blob: 5b46d0f29c20a4870a86c036bddd956a5a6a2865 [file] [log] [blame]
Sudheer Shanka9b735c52017-05-09 18:26:18 -07001/*
2 * Copyright (C) 2017 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
Sudheer Shankab8ad5942017-08-08 12:16:09 -070019import static com.android.internal.util.Preconditions.checkNotNull;
20
21import android.annotation.NonNull;
Sudheer Shanka9b735c52017-05-09 18:26:18 -070022import android.annotation.Nullable;
Jeff Sharkey89182982017-11-01 19:02:56 -060023import android.os.StrictMode;
Sudheer Shankab8ad5942017-08-08 12:16:09 -070024import android.util.IntArray;
Sudheer Shanka9b735c52017-05-09 18:26:18 -070025import android.util.Slog;
26import android.util.SparseArray;
27
28import com.android.internal.annotations.VisibleForTesting;
29
30import java.io.BufferedReader;
31import java.io.FileReader;
32import java.io.IOException;
Mike Ma2ab01442018-02-13 14:22:47 -080033import java.nio.ByteBuffer;
34import java.nio.IntBuffer;
Mike Ma234d1822018-03-13 18:53:21 -070035import java.util.function.Consumer;
Sudheer Shanka9b735c52017-05-09 18:26:18 -070036
37/**
38 * Reads /proc/uid_time_in_state which has the format:
39 *
40 * uid: [freq1] [freq2] [freq3] ...
41 * [uid1]: [time in freq1] [time in freq2] [time in freq3] ...
42 * [uid2]: [time in freq1] [time in freq2] [time in freq3] ...
43 * ...
44 *
Mike Ma2ab01442018-02-13 14:22:47 -080045 * Binary variation reads /proc/uid_cpupower/time_in_state in the following format:
46 * [n, uid0, time0a, time0b, ..., time0n,
47 * uid1, time1a, time1b, ..., time1n,
48 * uid2, time2a, time2b, ..., time2n, etc.]
49 * where n is the total number of frequencies.
50 *
Sudheer Shanka9b735c52017-05-09 18:26:18 -070051 * This provides the times a UID's processes spent executing at each different cpu frequency.
52 * The file contains a monotonically increasing count of time for a single boot. This class
53 * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
54 * delta.
Mike Ma2ab01442018-02-13 14:22:47 -080055 *
56 * This class uses a throttler to reject any {@link #readDelta} call within
57 * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader},
58 * which has a shorter throttle interval and returns cached result from last read when the request
59 * is throttled.
60 *
Mike Ma69d8b3e2018-02-23 14:51:26 -080061 * This class is NOT thread-safe and NOT designed to be accessed by more than one caller since each
62 * caller has its own view of delta.
Sudheer Shanka9b735c52017-05-09 18:26:18 -070063 */
Mike Ma69d8b3e2018-02-23 14:51:26 -080064public class KernelUidCpuFreqTimeReader extends
65 KernelUidCpuTimeReaderBase<KernelUidCpuFreqTimeReader.Callback> {
66 private static final String TAG = KernelUidCpuFreqTimeReader.class.getSimpleName();
Sudheer Shankaa5bca092018-01-25 10:41:43 -080067 static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
Sudheer Shanka9b735c52017-05-09 18:26:18 -070068
Mike Ma69d8b3e2018-02-23 14:51:26 -080069 public interface Callback extends KernelUidCpuTimeReaderBase.Callback {
Sudheer Shanka9b735c52017-05-09 18:26:18 -070070 void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs);
71 }
72
73 private long[] mCpuFreqs;
Mike Ma2ab01442018-02-13 14:22:47 -080074 private long[] mCurTimes; // Reuse to prevent GC.
75 private long[] mDeltaTimes; // Reuse to prevent GC.
Sudheer Shanka9b735c52017-05-09 18:26:18 -070076 private int mCpuFreqsCount;
Mike Ma2ab01442018-02-13 14:22:47 -080077 private final KernelCpuProcReader mProcReader;
Sudheer Shanka9b735c52017-05-09 18:26:18 -070078
79 private SparseArray<long[]> mLastUidCpuFreqTimeMs = new SparseArray<>();
80
Sudheer Shankaacd7f2c2017-06-01 17:39:40 -070081 // We check the existence of proc file a few times (just in case it is not ready yet when we
82 // start reading) and if it is not available, we simply ignore further read requests.
83 private static final int TOTAL_READ_ERROR_COUNT = 5;
84 private int mReadErrorCounter;
Sudheer Shankab8ad5942017-08-08 12:16:09 -070085 private boolean mPerClusterTimesAvailable;
Sudheer Shankab2f83c12017-11-13 19:25:01 -080086 private boolean mAllUidTimesAvailable = true;
Sudheer Shankab8ad5942017-08-08 12:16:09 -070087
Mike Ma2ab01442018-02-13 14:22:47 -080088 public KernelUidCpuFreqTimeReader() {
89 mProcReader = KernelCpuProcReader.getFreqTimeReaderInstance();
90 }
91
92 @VisibleForTesting
93 public KernelUidCpuFreqTimeReader(KernelCpuProcReader procReader) {
94 mProcReader = procReader;
95 }
96
Sudheer Shankab8ad5942017-08-08 12:16:09 -070097 public boolean perClusterTimesAvailable() {
98 return mPerClusterTimesAvailable;
99 }
100
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800101 public boolean allUidTimesAvailable() {
102 return mAllUidTimesAvailable;
103 }
104
105 public SparseArray<long[]> getAllUidCpuFreqTimeMs() {
106 return mLastUidCpuFreqTimeMs;
107 }
108
Sudheer Shankab8ad5942017-08-08 12:16:09 -0700109 public long[] readFreqs(@NonNull PowerProfile powerProfile) {
110 checkNotNull(powerProfile);
Sudheer Shankab8ad5942017-08-08 12:16:09 -0700111 if (mCpuFreqs != null) {
112 // No need to read cpu freqs more than once.
113 return mCpuFreqs;
114 }
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800115 if (!mAllUidTimesAvailable) {
Sudheer Shankab8ad5942017-08-08 12:16:09 -0700116 return null;
117 }
Jeff Sharkey89182982017-11-01 19:02:56 -0600118 final int oldMask = StrictMode.allowThreadDiskReadsMask();
Sudheer Shankab8ad5942017-08-08 12:16:09 -0700119 try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
Sudheer Shankab8ad5942017-08-08 12:16:09 -0700120 return readFreqs(reader, powerProfile);
121 } catch (IOException e) {
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800122 if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
123 mAllUidTimesAvailable = false;
124 }
Sudheer Shankab8ad5942017-08-08 12:16:09 -0700125 Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
126 return null;
Jeff Sharkey89182982017-11-01 19:02:56 -0600127 } finally {
128 StrictMode.setThreadPolicyMask(oldMask);
Sudheer Shankab8ad5942017-08-08 12:16:09 -0700129 }
130 }
131
132 @VisibleForTesting
133 public long[] readFreqs(BufferedReader reader, PowerProfile powerProfile)
134 throws IOException {
135 final String line = reader.readLine();
136 if (line == null) {
137 return null;
138 }
Sudheer Shankab8ad5942017-08-08 12:16:09 -0700139 final String[] freqStr = line.split(" ");
140 // First item would be "uid: " which needs to be ignored.
141 mCpuFreqsCount = freqStr.length - 1;
142 mCpuFreqs = new long[mCpuFreqsCount];
Mike Ma2ab01442018-02-13 14:22:47 -0800143 mCurTimes = new long[mCpuFreqsCount];
144 mDeltaTimes = new long[mCpuFreqsCount];
Sudheer Shankab8ad5942017-08-08 12:16:09 -0700145 for (int i = 0; i < mCpuFreqsCount; ++i) {
146 mCpuFreqs[i] = Long.parseLong(freqStr[i + 1], 10);
147 }
148
149 // Check if the freqs in the proc file correspond to per-cluster freqs.
150 final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs();
151 final int numClusters = powerProfile.getNumCpuClusters();
152 if (numClusterFreqs.size() == numClusters) {
153 mPerClusterTimesAvailable = true;
154 for (int i = 0; i < numClusters; ++i) {
155 if (numClusterFreqs.get(i) != powerProfile.getNumSpeedStepsInCpuCluster(i)) {
156 mPerClusterTimesAvailable = false;
157 break;
158 }
159 }
160 } else {
161 mPerClusterTimesAvailable = false;
162 }
163 Slog.i(TAG, "mPerClusterTimesAvailable=" + mPerClusterTimesAvailable);
Sudheer Shankab8ad5942017-08-08 12:16:09 -0700164 return mCpuFreqs;
165 }
166
Mike Ma234d1822018-03-13 18:53:21 -0700167 @Override
168 @VisibleForTesting
169 public void readDeltaImpl(@Nullable Callback callback) {
170 if (mCpuFreqs == null) {
171 return;
172 }
173 readImpl((buf) -> {
174 int uid = buf.get();
175 long[] lastTimes = mLastUidCpuFreqTimeMs.get(uid);
176 if (lastTimes == null) {
177 lastTimes = new long[mCpuFreqsCount];
178 mLastUidCpuFreqTimeMs.put(uid, lastTimes);
179 }
180 if (!getFreqTimeForUid(buf, mCurTimes)) {
181 return;
182 }
183 boolean notify = false;
184 boolean valid = true;
185 for (int i = 0; i < mCpuFreqsCount; i++) {
186 mDeltaTimes[i] = mCurTimes[i] - lastTimes[i];
187 if (mDeltaTimes[i] < 0) {
188 Slog.e(TAG, "Negative delta from freq time proc: " + mDeltaTimes[i]);
189 valid = false;
190 }
191 notify |= mDeltaTimes[i] > 0;
192 }
193 if (notify && valid) {
194 System.arraycopy(mCurTimes, 0, lastTimes, 0, mCpuFreqsCount);
195 if (callback != null) {
196 callback.onUidCpuFreqTime(uid, mDeltaTimes);
197 }
198 }
199 });
200 }
201
202 public void readAbsolute(Callback callback) {
203 readImpl((buf) -> {
204 int uid = buf.get();
205 if (getFreqTimeForUid(buf, mCurTimes)) {
206 callback.onUidCpuFreqTime(uid, mCurTimes);
207 }
208 });
209 }
210
211 private boolean getFreqTimeForUid(IntBuffer buffer, long[] freqTime) {
212 boolean valid = true;
213 for (int i = 0; i < mCpuFreqsCount; i++) {
214 freqTime[i] = (long) buffer.get() * 10; // Unit is 10ms.
215 if (freqTime[i] < 0) {
216 Slog.e(TAG, "Negative time from freq time proc: " + freqTime[i]);
217 valid = false;
218 }
219 }
220 return valid;
221 }
222
223 /**
224 * readImpl accepts a callback to process the uid entry. readDeltaImpl needs to store the last
225 * seen results while processing the buffer, while readAbsolute returns the absolute value read
226 * from the buffer without storing. So readImpl contains the common logic of the two, leaving
227 * the difference to a processUid function.
228 *
229 * @param processUid the callback function to process the uid entry in the buffer.
230 */
231 private void readImpl(Consumer<IntBuffer> processUid) {
232 synchronized (mProcReader) {
233 ByteBuffer bytes = mProcReader.readBytes();
234 if (bytes == null || bytes.remaining() <= 4) {
235 // Error already logged in mProcReader.
236 return;
237 }
238 if ((bytes.remaining() & 3) != 0) {
239 Slog.wtf(TAG, "Cannot parse freq time proc bytes to int: " + bytes.remaining());
240 return;
241 }
242 IntBuffer buf = bytes.asIntBuffer();
243 final int freqs = buf.get();
244 if (freqs != mCpuFreqsCount) {
245 Slog.wtf(TAG, "Cpu freqs expect " + mCpuFreqsCount + " , got " + freqs);
246 return;
247 }
248 if (buf.remaining() % (freqs + 1) != 0) {
249 Slog.wtf(TAG, "Freq time format error: " + buf.remaining() + " / " + (freqs + 1));
250 return;
251 }
252 int numUids = buf.remaining() / (freqs + 1);
253 for (int i = 0; i < numUids; i++) {
254 processUid.accept(buf);
255 }
256 if (DEBUG) {
257 Slog.d(TAG, "Read uids: #" + numUids);
258 }
259 }
260 }
261
262 public void removeUid(int uid) {
263 mLastUidCpuFreqTimeMs.delete(uid);
264 }
265
266 public void removeUidsInRange(int startUid, int endUid) {
267 mLastUidCpuFreqTimeMs.put(startUid, null);
268 mLastUidCpuFreqTimeMs.put(endUid, null);
269 final int firstIndex = mLastUidCpuFreqTimeMs.indexOfKey(startUid);
270 final int lastIndex = mLastUidCpuFreqTimeMs.indexOfKey(endUid);
271 mLastUidCpuFreqTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
272 }
273
Sudheer Shankab8ad5942017-08-08 12:16:09 -0700274 /**
275 * Extracts no. of cpu clusters and no. of freqs in each of these clusters from the freqs
276 * read from the proc file.
277 *
278 * We need to assume that freqs in each cluster are strictly increasing.
279 * For e.g. if the freqs read from proc file are: 12, 34, 15, 45, 12, 15, 52. Then it means
280 * there are 3 clusters: (12, 34), (15, 45), (12, 15, 52)
281 *
282 * @return an IntArray filled with no. of freqs in each cluster.
283 */
284 private IntArray extractClusterInfoFromProcFileFreqs() {
285 final IntArray numClusterFreqs = new IntArray();
286 int freqsFound = 0;
287 for (int i = 0; i < mCpuFreqsCount; ++i) {
288 freqsFound++;
289 if (i + 1 == mCpuFreqsCount || mCpuFreqs[i + 1] <= mCpuFreqs[i]) {
290 numClusterFreqs.add(freqsFound);
291 freqsFound = 0;
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700292 }
293 }
Sudheer Shankab8ad5942017-08-08 12:16:09 -0700294 return numClusterFreqs;
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700295 }
296}