blob: 396deb4d7a0a0f5964ea685ada551c09aa8f2819 [file] [log] [blame]
Mike Ma2ab01442018-02-13 14:22:47 -08001/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.os;
18
19import android.os.StrictMode;
20import android.os.SystemClock;
21import android.util.Slog;
22
23import com.android.internal.annotations.VisibleForTesting;
24
Mike Ma363ccf72018-03-01 14:50:02 -080025import java.io.FileNotFoundException;
Mike Ma2ab01442018-02-13 14:22:47 -080026import java.io.IOException;
27import java.nio.ByteBuffer;
28import java.nio.ByteOrder;
29import java.nio.channels.FileChannel;
Mike Ma363ccf72018-03-01 14:50:02 -080030import java.nio.file.NoSuchFileException;
Mike Ma2ab01442018-02-13 14:22:47 -080031import java.nio.file.Path;
32import java.nio.file.Paths;
33import java.nio.file.StandardOpenOption;
34
35/**
36 * Reads cpu time proc files with throttling (adjustable interval).
37 *
38 * KernelCpuProcReader is implemented as singletons for built-in kernel proc files. Get___Instance()
39 * method will return corresponding reader instance. In order to prevent frequent GC,
40 * KernelCpuProcReader reuses a {@link ByteBuffer} to store data read from proc files.
41 *
42 * A KernelCpuProcReader instance keeps an error counter. When the number of read errors within that
43 * instance accumulates to 5, this instance will reject all further read requests.
44 *
45 * Each KernelCpuProcReader instance also has a throttler. Throttle interval can be adjusted via
46 * {@link #setThrottleInterval(long)} method. Default throttle interval is 3000ms. If current
47 * timestamp based on {@link SystemClock#elapsedRealtime()} is less than throttle interval from
48 * the last read timestamp, {@link #readBytes()} will return previous result.
49 *
50 * A KernelCpuProcReader instance is thread-unsafe. Caller needs to hold a lock on this object while
51 * accessing its instance methods or digesting the return values.
52 */
53public class KernelCpuProcReader {
54 private static final String TAG = "KernelCpuProcReader";
55 private static final int ERROR_THRESHOLD = 5;
56 // Throttle interval in milliseconds
57 private static final long DEFAULT_THROTTLE_INTERVAL = 3000L;
58 private static final int INITIAL_BUFFER_SIZE = 8 * 1024;
59 private static final int MAX_BUFFER_SIZE = 1024 * 1024;
60 private static final String PROC_UID_FREQ_TIME = "/proc/uid_cpupower/time_in_state";
61 private static final String PROC_UID_ACTIVE_TIME = "/proc/uid_cpupower/concurrent_active_time";
62 private static final String PROC_UID_CLUSTER_TIME = "/proc/uid_cpupower/concurrent_policy_time";
63
64 private static final KernelCpuProcReader mFreqTimeReader = new KernelCpuProcReader(
65 PROC_UID_FREQ_TIME);
66 private static final KernelCpuProcReader mActiveTimeReader = new KernelCpuProcReader(
67 PROC_UID_ACTIVE_TIME);
68 private static final KernelCpuProcReader mClusterTimeReader = new KernelCpuProcReader(
69 PROC_UID_CLUSTER_TIME);
70
71 public static KernelCpuProcReader getFreqTimeReaderInstance() {
72 return mFreqTimeReader;
73 }
74
75 public static KernelCpuProcReader getActiveTimeReaderInstance() {
76 return mActiveTimeReader;
77 }
78
79 public static KernelCpuProcReader getClusterTimeReaderInstance() {
80 return mClusterTimeReader;
81 }
82
83 private int mErrors;
84 private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL;
85 private long mLastReadTime = Long.MIN_VALUE;
86 private final Path mProc;
87 private ByteBuffer mBuffer;
88
89 @VisibleForTesting
90 public KernelCpuProcReader(String procFile) {
91 mProc = Paths.get(procFile);
92 mBuffer = ByteBuffer.allocateDirect(INITIAL_BUFFER_SIZE);
93 mBuffer.clear();
94 }
95
96 /**
97 * Reads all bytes from the corresponding proc file.
98 *
99 * If elapsed time since last call to this method is less than the throttle interval, it will
100 * return previous result. When IOException accumulates to 5, it will always return null. This
101 * method is thread-unsafe, so is the return value. Caller needs to hold a lock on this
102 * object while calling this method and digesting its return value.
103 *
104 * @return a {@link ByteBuffer} containing all bytes from the proc file.
105 */
106 public ByteBuffer readBytes() {
107 if (mErrors >= ERROR_THRESHOLD) {
108 return null;
109 }
110 if (SystemClock.elapsedRealtime() < mLastReadTime + mThrottleInterval) {
111 if (mBuffer.limit() > 0 && mBuffer.limit() < mBuffer.capacity()) {
112 // mBuffer has data.
113 return mBuffer.asReadOnlyBuffer().order(ByteOrder.nativeOrder());
114 }
115 return null;
116 }
117 mLastReadTime = SystemClock.elapsedRealtime();
118 mBuffer.clear();
119 final int oldMask = StrictMode.allowThreadDiskReadsMask();
120 try (FileChannel fc = FileChannel.open(mProc, StandardOpenOption.READ)) {
121 while (fc.read(mBuffer) == mBuffer.capacity()) {
122 if (!resize()) {
123 mErrors++;
124 Slog.e(TAG, "Proc file is too large: " + mProc);
125 return null;
126 }
127 fc.position(0);
128 }
Mike Ma363ccf72018-03-01 14:50:02 -0800129 } catch (NoSuchFileException | FileNotFoundException e) {
130 // Happens when the kernel does not provide this file. Not a big issue. Just log it.
131 mErrors++;
132 Slog.w(TAG, "File not exist: " + mProc);
133 return null;
Mike Ma2ab01442018-02-13 14:22:47 -0800134 } catch (IOException e) {
135 mErrors++;
136 Slog.e(TAG, "Error reading: " + mProc, e);
137 return null;
138 } finally {
139 StrictMode.setThreadPolicyMask(oldMask);
140 }
141 mBuffer.flip();
142 return mBuffer.asReadOnlyBuffer().order(ByteOrder.nativeOrder());
143 }
144
145 /**
146 * Sets the throttle interval. Set to 0 will disable throttling. Thread-unsafe, holding a lock
147 * on this object is recommended.
148 *
149 * @param throttleInterval throttle interval in milliseconds
150 */
151 public void setThrottleInterval(long throttleInterval) {
152 if (throttleInterval >= 0) {
153 mThrottleInterval = throttleInterval;
154 }
155 }
156
157 private boolean resize() {
158 if (mBuffer.capacity() >= MAX_BUFFER_SIZE) {
159 return false;
160 }
161 int newSize = Math.min(mBuffer.capacity() << 1, MAX_BUFFER_SIZE);
162 // Slog.i(TAG, "Resize buffer " + mBuffer.capacity() + " => " + newSize);
163 mBuffer = ByteBuffer.allocateDirect(newSize);
164 return true;
165 }
166}