blob: d97538c304980d7e7ce7eb74004b74c50b3a0356 [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 Shanka00857522017-07-06 18:28:14 -070024import android.os.SystemClock;
Sudheer Shankab8ad5942017-08-08 12:16:09 -070025import android.util.IntArray;
Sudheer Shanka9b735c52017-05-09 18:26:18 -070026import android.util.Slog;
27import android.util.SparseArray;
Sudheer Shanka00857522017-07-06 18:28:14 -070028import android.util.TimeUtils;
Sudheer Shanka9b735c52017-05-09 18:26:18 -070029
30import com.android.internal.annotations.VisibleForTesting;
31
32import java.io.BufferedReader;
33import java.io.FileReader;
34import java.io.IOException;
35
36/**
37 * Reads /proc/uid_time_in_state which has the format:
38 *
39 * uid: [freq1] [freq2] [freq3] ...
40 * [uid1]: [time in freq1] [time in freq2] [time in freq3] ...
41 * [uid2]: [time in freq1] [time in freq2] [time in freq3] ...
42 * ...
43 *
44 * This provides the times a UID's processes spent executing at each different cpu frequency.
45 * The file contains a monotonically increasing count of time for a single boot. This class
46 * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
47 * delta.
48 */
49public class KernelUidCpuFreqTimeReader {
Sudheer Shanka6b1396e2017-08-28 18:10:18 -070050 private static final boolean DEBUG = false;
Sudheer Shanka9b735c52017-05-09 18:26:18 -070051 private static final String TAG = "KernelUidCpuFreqTimeReader";
Sudheer Shankaa5bca092018-01-25 10:41:43 -080052 static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
Sudheer Shanka9b735c52017-05-09 18:26:18 -070053
54 public interface Callback {
Sudheer Shanka9b735c52017-05-09 18:26:18 -070055 void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs);
56 }
57
58 private long[] mCpuFreqs;
59 private int mCpuFreqsCount;
Sudheer Shanka00857522017-07-06 18:28:14 -070060 private long mLastTimeReadMs;
61 private long mNowTimeMs;
Sudheer Shanka9b735c52017-05-09 18:26:18 -070062
63 private SparseArray<long[]> mLastUidCpuFreqTimeMs = new SparseArray<>();
64
Sudheer Shankaacd7f2c2017-06-01 17:39:40 -070065 // We check the existence of proc file a few times (just in case it is not ready yet when we
66 // start reading) and if it is not available, we simply ignore further read requests.
67 private static final int TOTAL_READ_ERROR_COUNT = 5;
68 private int mReadErrorCounter;
Sudheer Shankab8ad5942017-08-08 12:16:09 -070069 private boolean mPerClusterTimesAvailable;
Sudheer Shankab2f83c12017-11-13 19:25:01 -080070 private boolean mAllUidTimesAvailable = true;
Sudheer Shankab8ad5942017-08-08 12:16:09 -070071
72 public boolean perClusterTimesAvailable() {
73 return mPerClusterTimesAvailable;
74 }
75
Sudheer Shankab2f83c12017-11-13 19:25:01 -080076 public boolean allUidTimesAvailable() {
77 return mAllUidTimesAvailable;
78 }
79
80 public SparseArray<long[]> getAllUidCpuFreqTimeMs() {
81 return mLastUidCpuFreqTimeMs;
82 }
83
Sudheer Shankab8ad5942017-08-08 12:16:09 -070084 public long[] readFreqs(@NonNull PowerProfile powerProfile) {
85 checkNotNull(powerProfile);
86
87 if (mCpuFreqs != null) {
88 // No need to read cpu freqs more than once.
89 return mCpuFreqs;
90 }
Sudheer Shankab2f83c12017-11-13 19:25:01 -080091 if (!mAllUidTimesAvailable) {
Sudheer Shankab8ad5942017-08-08 12:16:09 -070092 return null;
93 }
Jeff Sharkey89182982017-11-01 19:02:56 -060094 final int oldMask = StrictMode.allowThreadDiskReadsMask();
Sudheer Shankab8ad5942017-08-08 12:16:09 -070095 try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
Sudheer Shankab8ad5942017-08-08 12:16:09 -070096 return readFreqs(reader, powerProfile);
97 } catch (IOException e) {
Sudheer Shankab2f83c12017-11-13 19:25:01 -080098 if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
99 mAllUidTimesAvailable = false;
100 }
Sudheer Shankab8ad5942017-08-08 12:16:09 -0700101 Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
102 return null;
Jeff Sharkey89182982017-11-01 19:02:56 -0600103 } finally {
104 StrictMode.setThreadPolicyMask(oldMask);
Sudheer Shankab8ad5942017-08-08 12:16:09 -0700105 }
106 }
107
108 @VisibleForTesting
109 public long[] readFreqs(BufferedReader reader, PowerProfile powerProfile)
110 throws IOException {
111 final String line = reader.readLine();
112 if (line == null) {
113 return null;
114 }
115 return readCpuFreqs(line, powerProfile);
116 }
Sudheer Shankaacd7f2c2017-06-01 17:39:40 -0700117
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700118 public void readDelta(@Nullable Callback callback) {
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800119 if (mCpuFreqs == null) {
Sudheer Shankaacd7f2c2017-06-01 17:39:40 -0700120 return;
121 }
Jeff Sharkey89182982017-11-01 19:02:56 -0600122 final int oldMask = StrictMode.allowThreadDiskReadsMask();
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700123 try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
Sudheer Shanka00857522017-07-06 18:28:14 -0700124 mNowTimeMs = SystemClock.elapsedRealtime();
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700125 readDelta(reader, callback);
Sudheer Shanka00857522017-07-06 18:28:14 -0700126 mLastTimeReadMs = mNowTimeMs;
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700127 } catch (IOException e) {
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700128 Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
Jeff Sharkey89182982017-11-01 19:02:56 -0600129 } finally {
130 StrictMode.setThreadPolicyMask(oldMask);
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700131 }
132 }
133
Sudheer Shanka6d8dcec2017-06-01 12:09:03 -0700134 public void removeUid(int uid) {
135 mLastUidCpuFreqTimeMs.delete(uid);
136 }
137
Suprabh Shuklae6e723d2017-06-14 16:14:43 -0700138 public void removeUidsInRange(int startUid, int endUid) {
139 if (endUid < startUid) {
140 return;
141 }
142 mLastUidCpuFreqTimeMs.put(startUid, null);
143 mLastUidCpuFreqTimeMs.put(endUid, null);
144 final int firstIndex = mLastUidCpuFreqTimeMs.indexOfKey(startUid);
145 final int lastIndex = mLastUidCpuFreqTimeMs.indexOfKey(endUid);
146 mLastUidCpuFreqTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
147 }
148
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700149 @VisibleForTesting
150 public void readDelta(BufferedReader reader, @Nullable Callback callback) throws IOException {
151 String line = reader.readLine();
152 if (line == null) {
153 return;
154 }
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700155 while ((line = reader.readLine()) != null) {
156 final int index = line.indexOf(' ');
157 final int uid = Integer.parseInt(line.substring(0, index - 1), 10);
158 readTimesForUid(uid, line.substring(index + 1, line.length()), callback);
159 }
160 }
161
162 private void readTimesForUid(int uid, String line, Callback callback) {
163 long[] uidTimeMs = mLastUidCpuFreqTimeMs.get(uid);
164 if (uidTimeMs == null) {
165 uidTimeMs = new long[mCpuFreqsCount];
166 mLastUidCpuFreqTimeMs.put(uid, uidTimeMs);
167 }
168 final String[] timesStr = line.split(" ");
169 final int size = timesStr.length;
170 if (size != uidTimeMs.length) {
171 Slog.e(TAG, "No. of readings don't match cpu freqs, readings: " + size
172 + " cpuFreqsCount: " + uidTimeMs.length);
173 return;
174 }
175 final long[] deltaUidTimeMs = new long[size];
Sudheer Shanka00857522017-07-06 18:28:14 -0700176 final long[] curUidTimeMs = new long[size];
Sudheer Shanka97d15bf2017-06-19 19:19:08 -0700177 boolean notify = false;
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700178 for (int i = 0; i < size; ++i) {
179 // Times read will be in units of 10ms
180 final long totalTimeMs = Long.parseLong(timesStr[i], 10) * 10;
181 deltaUidTimeMs[i] = totalTimeMs - uidTimeMs[i];
Sudheer Shanka00857522017-07-06 18:28:14 -0700182 // If there is malformed data for any uid, then we just log about it and ignore
183 // the data for that uid.
184 if (deltaUidTimeMs[i] < 0 || totalTimeMs < 0) {
Sudheer Shanka6b1396e2017-08-28 18:10:18 -0700185 if (DEBUG) {
186 final StringBuilder sb = new StringBuilder("Malformed cpu freq data for UID=")
187 .append(uid).append("\n");
188 sb.append("data=").append("(").append(uidTimeMs[i]).append(",")
189 .append(totalTimeMs).append(")").append("\n");
190 sb.append("times=").append("(");
191 TimeUtils.formatDuration(mLastTimeReadMs, sb);
192 sb.append(",");
193 TimeUtils.formatDuration(mNowTimeMs, sb);
194 sb.append(")");
195 Slog.e(TAG, sb.toString());
196 }
Sudheer Shanka00857522017-07-06 18:28:14 -0700197 return;
198 }
199 curUidTimeMs[i] = totalTimeMs;
Sudheer Shanka97d15bf2017-06-19 19:19:08 -0700200 notify = notify || (deltaUidTimeMs[i] > 0);
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700201 }
Sudheer Shanka00857522017-07-06 18:28:14 -0700202 if (notify) {
203 System.arraycopy(curUidTimeMs, 0, uidTimeMs, 0, size);
204 if (callback != null) {
205 callback.onUidCpuFreqTime(uid, deltaUidTimeMs);
206 }
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700207 }
208 }
209
Sudheer Shankab8ad5942017-08-08 12:16:09 -0700210 private long[] readCpuFreqs(String line, PowerProfile powerProfile) {
211 final String[] freqStr = line.split(" ");
212 // First item would be "uid: " which needs to be ignored.
213 mCpuFreqsCount = freqStr.length - 1;
214 mCpuFreqs = new long[mCpuFreqsCount];
215 for (int i = 0; i < mCpuFreqsCount; ++i) {
216 mCpuFreqs[i] = Long.parseLong(freqStr[i + 1], 10);
217 }
218
219 // Check if the freqs in the proc file correspond to per-cluster freqs.
220 final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs();
221 final int numClusters = powerProfile.getNumCpuClusters();
222 if (numClusterFreqs.size() == numClusters) {
223 mPerClusterTimesAvailable = true;
224 for (int i = 0; i < numClusters; ++i) {
225 if (numClusterFreqs.get(i) != powerProfile.getNumSpeedStepsInCpuCluster(i)) {
226 mPerClusterTimesAvailable = false;
227 break;
228 }
229 }
230 } else {
231 mPerClusterTimesAvailable = false;
232 }
233 Slog.i(TAG, "mPerClusterTimesAvailable=" + mPerClusterTimesAvailable);
234
235 return mCpuFreqs;
236 }
237
238 /**
239 * Extracts no. of cpu clusters and no. of freqs in each of these clusters from the freqs
240 * read from the proc file.
241 *
242 * We need to assume that freqs in each cluster are strictly increasing.
243 * For e.g. if the freqs read from proc file are: 12, 34, 15, 45, 12, 15, 52. Then it means
244 * there are 3 clusters: (12, 34), (15, 45), (12, 15, 52)
245 *
246 * @return an IntArray filled with no. of freqs in each cluster.
247 */
248 private IntArray extractClusterInfoFromProcFileFreqs() {
249 final IntArray numClusterFreqs = new IntArray();
250 int freqsFound = 0;
251 for (int i = 0; i < mCpuFreqsCount; ++i) {
252 freqsFound++;
253 if (i + 1 == mCpuFreqsCount || mCpuFreqs[i + 1] <= mCpuFreqs[i]) {
254 numClusterFreqs.add(freqsFound);
255 freqsFound = 0;
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700256 }
257 }
Sudheer Shankab8ad5942017-08-08 12:16:09 -0700258 return numClusterFreqs;
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700259 }
260}