blob: 13cc6e6ea83185789229dea2c866d301e0abf8d5 [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;
Sudheer Shanka00857522017-07-06 18:28:14 -070023import android.os.SystemClock;
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;
Sudheer Shanka00857522017-07-06 18:28:14 -070027import android.util.TimeUtils;
Sudheer Shanka9b735c52017-05-09 18:26:18 -070028
29import com.android.internal.annotations.VisibleForTesting;
30
31import java.io.BufferedReader;
32import java.io.FileReader;
33import java.io.IOException;
34
35/**
36 * Reads /proc/uid_time_in_state which has the format:
37 *
38 * uid: [freq1] [freq2] [freq3] ...
39 * [uid1]: [time in freq1] [time in freq2] [time in freq3] ...
40 * [uid2]: [time in freq1] [time in freq2] [time in freq3] ...
41 * ...
42 *
43 * This provides the times a UID's processes spent executing at each different cpu frequency.
44 * The file contains a monotonically increasing count of time for a single boot. This class
45 * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
46 * delta.
47 */
48public class KernelUidCpuFreqTimeReader {
49 private static final String TAG = "KernelUidCpuFreqTimeReader";
50 private static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
51
52 public interface Callback {
Sudheer Shanka9b735c52017-05-09 18:26:18 -070053 void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs);
54 }
55
56 private long[] mCpuFreqs;
57 private int mCpuFreqsCount;
Sudheer Shanka00857522017-07-06 18:28:14 -070058 private long mLastTimeReadMs;
59 private long mNowTimeMs;
Sudheer Shanka9b735c52017-05-09 18:26:18 -070060
61 private SparseArray<long[]> mLastUidCpuFreqTimeMs = new SparseArray<>();
62
Sudheer Shankaacd7f2c2017-06-01 17:39:40 -070063 // We check the existence of proc file a few times (just in case it is not ready yet when we
64 // start reading) and if it is not available, we simply ignore further read requests.
65 private static final int TOTAL_READ_ERROR_COUNT = 5;
66 private int mReadErrorCounter;
67 private boolean mProcFileAvailable;
Sudheer Shankab8ad5942017-08-08 12:16:09 -070068 private boolean mPerClusterTimesAvailable;
69
70 public boolean perClusterTimesAvailable() {
71 return mPerClusterTimesAvailable;
72 }
73
74 public long[] readFreqs(@NonNull PowerProfile powerProfile) {
75 checkNotNull(powerProfile);
76
77 if (mCpuFreqs != null) {
78 // No need to read cpu freqs more than once.
79 return mCpuFreqs;
80 }
81 if (!mProcFileAvailable && mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
82 return null;
83 }
84 try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
85 mProcFileAvailable = true;
86 return readFreqs(reader, powerProfile);
87 } catch (IOException e) {
88 mReadErrorCounter++;
89 Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
90 return null;
91 }
92 }
93
94 @VisibleForTesting
95 public long[] readFreqs(BufferedReader reader, PowerProfile powerProfile)
96 throws IOException {
97 final String line = reader.readLine();
98 if (line == null) {
99 return null;
100 }
101 return readCpuFreqs(line, powerProfile);
102 }
Sudheer Shankaacd7f2c2017-06-01 17:39:40 -0700103
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700104 public void readDelta(@Nullable Callback callback) {
Sudheer Shankab8ad5942017-08-08 12:16:09 -0700105 if (!mProcFileAvailable) {
Sudheer Shankaacd7f2c2017-06-01 17:39:40 -0700106 return;
107 }
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700108 try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
Sudheer Shanka00857522017-07-06 18:28:14 -0700109 mNowTimeMs = SystemClock.elapsedRealtime();
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700110 readDelta(reader, callback);
Sudheer Shanka00857522017-07-06 18:28:14 -0700111 mLastTimeReadMs = mNowTimeMs;
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700112 } catch (IOException e) {
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700113 Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
114 }
115 }
116
Sudheer Shanka6d8dcec2017-06-01 12:09:03 -0700117 public void removeUid(int uid) {
118 mLastUidCpuFreqTimeMs.delete(uid);
119 }
120
Suprabh Shuklae6e723d2017-06-14 16:14:43 -0700121 public void removeUidsInRange(int startUid, int endUid) {
122 if (endUid < startUid) {
123 return;
124 }
125 mLastUidCpuFreqTimeMs.put(startUid, null);
126 mLastUidCpuFreqTimeMs.put(endUid, null);
127 final int firstIndex = mLastUidCpuFreqTimeMs.indexOfKey(startUid);
128 final int lastIndex = mLastUidCpuFreqTimeMs.indexOfKey(endUid);
129 mLastUidCpuFreqTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
130 }
131
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700132 @VisibleForTesting
133 public void readDelta(BufferedReader reader, @Nullable Callback callback) throws IOException {
134 String line = reader.readLine();
135 if (line == null) {
136 return;
137 }
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700138 while ((line = reader.readLine()) != null) {
139 final int index = line.indexOf(' ');
140 final int uid = Integer.parseInt(line.substring(0, index - 1), 10);
141 readTimesForUid(uid, line.substring(index + 1, line.length()), callback);
142 }
143 }
144
145 private void readTimesForUid(int uid, String line, Callback callback) {
146 long[] uidTimeMs = mLastUidCpuFreqTimeMs.get(uid);
147 if (uidTimeMs == null) {
148 uidTimeMs = new long[mCpuFreqsCount];
149 mLastUidCpuFreqTimeMs.put(uid, uidTimeMs);
150 }
151 final String[] timesStr = line.split(" ");
152 final int size = timesStr.length;
153 if (size != uidTimeMs.length) {
154 Slog.e(TAG, "No. of readings don't match cpu freqs, readings: " + size
155 + " cpuFreqsCount: " + uidTimeMs.length);
156 return;
157 }
158 final long[] deltaUidTimeMs = new long[size];
Sudheer Shanka00857522017-07-06 18:28:14 -0700159 final long[] curUidTimeMs = new long[size];
Sudheer Shanka97d15bf2017-06-19 19:19:08 -0700160 boolean notify = false;
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700161 for (int i = 0; i < size; ++i) {
162 // Times read will be in units of 10ms
163 final long totalTimeMs = Long.parseLong(timesStr[i], 10) * 10;
164 deltaUidTimeMs[i] = totalTimeMs - uidTimeMs[i];
Sudheer Shanka00857522017-07-06 18:28:14 -0700165 // If there is malformed data for any uid, then we just log about it and ignore
166 // the data for that uid.
167 if (deltaUidTimeMs[i] < 0 || totalTimeMs < 0) {
168 final StringBuilder sb = new StringBuilder("Malformed cpu freq data for UID=")
169 .append(uid).append("\n");
170 sb.append("data=").append("(").append(uidTimeMs[i]).append(",")
171 .append(totalTimeMs).append(")").append("\n");
Sudheer Shanka40c43f12017-08-08 15:44:09 -0700172 sb.append("times=").append("(");
173 TimeUtils.formatDuration(mLastTimeReadMs, sb); sb.append(",");
174 TimeUtils.formatDuration(mNowTimeMs, sb); sb.append(")");
Sudheer Shanka8cde9302017-08-23 11:29:23 -0700175 Slog.e(TAG, sb.toString());
Sudheer Shanka00857522017-07-06 18:28:14 -0700176 return;
177 }
178 curUidTimeMs[i] = totalTimeMs;
Sudheer Shanka97d15bf2017-06-19 19:19:08 -0700179 notify = notify || (deltaUidTimeMs[i] > 0);
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700180 }
Sudheer Shanka00857522017-07-06 18:28:14 -0700181 if (notify) {
182 System.arraycopy(curUidTimeMs, 0, uidTimeMs, 0, size);
183 if (callback != null) {
184 callback.onUidCpuFreqTime(uid, deltaUidTimeMs);
185 }
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700186 }
187 }
188
Sudheer Shankab8ad5942017-08-08 12:16:09 -0700189 private long[] readCpuFreqs(String line, PowerProfile powerProfile) {
190 final String[] freqStr = line.split(" ");
191 // First item would be "uid: " which needs to be ignored.
192 mCpuFreqsCount = freqStr.length - 1;
193 mCpuFreqs = new long[mCpuFreqsCount];
194 for (int i = 0; i < mCpuFreqsCount; ++i) {
195 mCpuFreqs[i] = Long.parseLong(freqStr[i + 1], 10);
196 }
197
198 // Check if the freqs in the proc file correspond to per-cluster freqs.
199 final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs();
200 final int numClusters = powerProfile.getNumCpuClusters();
201 if (numClusterFreqs.size() == numClusters) {
202 mPerClusterTimesAvailable = true;
203 for (int i = 0; i < numClusters; ++i) {
204 if (numClusterFreqs.get(i) != powerProfile.getNumSpeedStepsInCpuCluster(i)) {
205 mPerClusterTimesAvailable = false;
206 break;
207 }
208 }
209 } else {
210 mPerClusterTimesAvailable = false;
211 }
212 Slog.i(TAG, "mPerClusterTimesAvailable=" + mPerClusterTimesAvailable);
213
214 return mCpuFreqs;
215 }
216
217 /**
218 * Extracts no. of cpu clusters and no. of freqs in each of these clusters from the freqs
219 * read from the proc file.
220 *
221 * We need to assume that freqs in each cluster are strictly increasing.
222 * For e.g. if the freqs read from proc file are: 12, 34, 15, 45, 12, 15, 52. Then it means
223 * there are 3 clusters: (12, 34), (15, 45), (12, 15, 52)
224 *
225 * @return an IntArray filled with no. of freqs in each cluster.
226 */
227 private IntArray extractClusterInfoFromProcFileFreqs() {
228 final IntArray numClusterFreqs = new IntArray();
229 int freqsFound = 0;
230 for (int i = 0; i < mCpuFreqsCount; ++i) {
231 freqsFound++;
232 if (i + 1 == mCpuFreqsCount || mCpuFreqs[i + 1] <= mCpuFreqs[i]) {
233 numClusterFreqs.add(freqsFound);
234 freqsFound = 0;
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700235 }
236 }
Sudheer Shankab8ad5942017-08-08 12:16:09 -0700237 return numClusterFreqs;
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700238 }
239}