blob: 42839171dc53533ccf191e663650e32a9fbbbab8 [file] [log] [blame]
Sudheer Shankab2f83c12017-11-13 19:25:01 -08001/*
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 */
16package com.android.internal.os;
17
18import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
Sudheer Shankaa5bca092018-01-25 10:41:43 -080019import static com.android.internal.os.KernelUidCpuFreqTimeReader.UID_TIMES_PROC_FILE;
Sudheer Shankab2f83c12017-11-13 19:25:01 -080020
21import android.annotation.NonNull;
22import android.util.Slog;
23import android.util.SparseArray;
24
25import com.android.internal.annotations.GuardedBy;
26import com.android.internal.annotations.VisibleForTesting;
27
28import java.io.IOException;
29import java.nio.ByteBuffer;
30import java.nio.ByteOrder;
31import java.nio.file.Files;
32import java.nio.file.Paths;
33import java.util.Arrays;
34
35@VisibleForTesting(visibility = PACKAGE)
36public class KernelSingleUidTimeReader {
37 private final String TAG = KernelUidCpuFreqTimeReader.class.getName();
38 private final boolean DBG = false;
39
40 private final String PROC_FILE_DIR = "/proc/uid/";
41 private final String PROC_FILE_NAME = "/time_in_state";
42
43 @VisibleForTesting
44 public static final int TOTAL_READ_ERROR_COUNT = 5;
45
46 @GuardedBy("this")
47 private final int mCpuFreqsCount;
48
49 @GuardedBy("this")
Sudheer Shanka5c19b892018-01-05 17:25:46 -080050 private SparseArray<long[]> mLastUidCpuTimeMs = new SparseArray<>();
Sudheer Shankab2f83c12017-11-13 19:25:01 -080051
52 @GuardedBy("this")
53 private int mReadErrorCounter;
54 @GuardedBy("this")
55 private boolean mSingleUidCpuTimesAvailable = true;
Sudheer Shanka5c19b892018-01-05 17:25:46 -080056 @GuardedBy("this")
57 private boolean mHasStaleData;
Sudheer Shankaa5bca092018-01-25 10:41:43 -080058 // We use the freq count obtained from /proc/uid_time_in_state to decide how many longs
59 // to read from each /proc/uid/<uid>/time_in_state. On the first read, verify if this is
60 // correct and if not, set {@link #mSingleUidCpuTimesAvailable} to false. This flag will
61 // indicate whether we checked for validity or not.
62 @GuardedBy("this")
63 private boolean mCpuFreqsCountVerified;
Sudheer Shankab2f83c12017-11-13 19:25:01 -080064
65 private final Injector mInjector;
66
67 KernelSingleUidTimeReader(int cpuFreqsCount) {
68 this(cpuFreqsCount, new Injector());
69 }
70
71 public KernelSingleUidTimeReader(int cpuFreqsCount, Injector injector) {
72 mInjector = injector;
73 mCpuFreqsCount = cpuFreqsCount;
74 if (mCpuFreqsCount == 0) {
75 mSingleUidCpuTimesAvailable = false;
76 }
77 }
78
79 public boolean singleUidCpuTimesAvailable() {
80 return mSingleUidCpuTimesAvailable;
81 }
82
83 public long[] readDeltaMs(int uid) {
84 synchronized (this) {
85 if (!mSingleUidCpuTimesAvailable) {
86 return null;
87 }
88 // Read total cpu times from the proc file.
89 final String procFile = new StringBuilder(PROC_FILE_DIR)
90 .append(uid)
91 .append(PROC_FILE_NAME).toString();
Sudheer Shankaa5bca092018-01-25 10:41:43 -080092 final long[] cpuTimesMs;
Sudheer Shankab2f83c12017-11-13 19:25:01 -080093 try {
94 final byte[] data = mInjector.readData(procFile);
Sudheer Shankaa5bca092018-01-25 10:41:43 -080095 if (!mCpuFreqsCountVerified) {
96 verifyCpuFreqsCount(data.length, procFile);
97 }
Sudheer Shankab2f83c12017-11-13 19:25:01 -080098 final ByteBuffer buffer = ByteBuffer.wrap(data);
99 buffer.order(ByteOrder.nativeOrder());
Sudheer Shankaa5bca092018-01-25 10:41:43 -0800100 cpuTimesMs = readCpuTimesFromByteBuffer(buffer);
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800101 } catch (Exception e) {
102 if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
103 mSingleUidCpuTimesAvailable = false;
104 }
105 if (DBG) Slog.e(TAG, "Some error occured while reading " + procFile, e);
106 return null;
107 }
108
109 return computeDelta(uid, cpuTimesMs);
110 }
111 }
112
Sudheer Shankaa5bca092018-01-25 10:41:43 -0800113 private void verifyCpuFreqsCount(int numBytes, String procFile) {
114 final int actualCount = (numBytes / Long.BYTES);
115 if (mCpuFreqsCount != actualCount) {
116 mSingleUidCpuTimesAvailable = false;
117 throw new IllegalStateException("Freq count didn't match,"
118 + "count from " + UID_TIMES_PROC_FILE + "=" + mCpuFreqsCount + ", but"
119 + "count from " + procFile + "=" + actualCount);
120 }
121 mCpuFreqsCountVerified = true;
122 }
123
124 private long[] readCpuTimesFromByteBuffer(ByteBuffer buffer) {
125 final long[] cpuTimesMs;
126 cpuTimesMs = new long[mCpuFreqsCount];
127 for (int i = 0; i < mCpuFreqsCount; ++i) {
128 // Times read will be in units of 10ms
129 cpuTimesMs[i] = buffer.getLong() * 10;
130 }
131 return cpuTimesMs;
132 }
133
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800134 /**
135 * Compute and return cpu times delta of an uid using previously read cpu times and
136 * {@param latestCpuTimesMs}.
137 *
138 * @return delta of cpu times if at least one of the cpu time at a freq is +ve, otherwise null.
139 */
140 public long[] computeDelta(int uid, @NonNull long[] latestCpuTimesMs) {
141 synchronized (this) {
142 if (!mSingleUidCpuTimesAvailable) {
143 return null;
144 }
145 // Subtract the last read cpu times to get deltas.
146 final long[] lastCpuTimesMs = mLastUidCpuTimeMs.get(uid);
147 final long[] deltaTimesMs = getDeltaLocked(lastCpuTimesMs, latestCpuTimesMs);
148 if (deltaTimesMs == null) {
149 if (DBG) Slog.e(TAG, "Malformed data read for uid=" + uid
150 + "; last=" + Arrays.toString(lastCpuTimesMs)
151 + "; latest=" + Arrays.toString(latestCpuTimesMs));
152 return null;
153 }
154 // If all elements are zero, return null to avoid unnecessary work on the caller side.
155 boolean hasNonZero = false;
156 for (int i = deltaTimesMs.length - 1; i >= 0; --i) {
157 if (deltaTimesMs[i] > 0) {
158 hasNonZero = true;
159 break;
160 }
161 }
162 if (hasNonZero) {
163 mLastUidCpuTimeMs.put(uid, latestCpuTimesMs);
164 return deltaTimesMs;
165 } else {
166 return null;
167 }
168 }
169 }
170
171 /**
172 * Returns null if the latest cpu times are not valid**, otherwise delta of
173 * {@param latestCpuTimesMs} and {@param lastCpuTimesMs}.
174 *
175 * **latest cpu times are considered valid if all the cpu times are +ve and
176 * greater than or equal to previously read cpu times.
177 */
178 @GuardedBy("this")
179 @VisibleForTesting(visibility = PACKAGE)
180 public long[] getDeltaLocked(long[] lastCpuTimesMs, @NonNull long[] latestCpuTimesMs) {
181 for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
182 if (latestCpuTimesMs[i] < 0) {
183 return null;
184 }
185 }
186 if (lastCpuTimesMs == null) {
187 return latestCpuTimesMs;
188 }
189 final long[] deltaTimesMs = new long[latestCpuTimesMs.length];
190 for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
191 deltaTimesMs[i] = latestCpuTimesMs[i] - lastCpuTimesMs[i];
192 if (deltaTimesMs[i] < 0) {
193 return null;
194 }
195 }
196 return deltaTimesMs;
197 }
198
Sudheer Shanka5c19b892018-01-05 17:25:46 -0800199 public void markDataAsStale(boolean hasStaleData) {
200 synchronized (this) {
201 mHasStaleData = hasStaleData;
202 }
203 }
204
205 public boolean hasStaleData() {
206 synchronized (this) {
207 return mHasStaleData;
208 }
209 }
210
211 public void setAllUidsCpuTimesMs(SparseArray<long[]> allUidsCpuTimesMs) {
212 synchronized (this) {
213 mLastUidCpuTimeMs.clear();
214 for (int i = allUidsCpuTimesMs.size() - 1; i >= 0; --i) {
215 final long[] cpuTimesMs = allUidsCpuTimesMs.valueAt(i);
216 if (cpuTimesMs != null) {
217 mLastUidCpuTimeMs.put(allUidsCpuTimesMs.keyAt(i), cpuTimesMs.clone());
218 }
219 }
220 }
221 }
222
Sudheer Shankab2f83c12017-11-13 19:25:01 -0800223 public void removeUid(int uid) {
224 synchronized (this) {
225 mLastUidCpuTimeMs.delete(uid);
226 }
227 }
228
229 public void removeUidsInRange(int startUid, int endUid) {
230 if (endUid < startUid) {
231 return;
232 }
233 synchronized (this) {
234 mLastUidCpuTimeMs.put(startUid, null);
235 mLastUidCpuTimeMs.put(endUid, null);
236 final int startIdx = mLastUidCpuTimeMs.indexOfKey(startUid);
237 final int endIdx = mLastUidCpuTimeMs.indexOfKey(endUid);
238 mLastUidCpuTimeMs.removeAtRange(startIdx, endIdx - startIdx + 1);
239 }
240 }
241
242 @VisibleForTesting
243 public static class Injector {
244 public byte[] readData(String procFile) throws IOException {
245 return Files.readAllBytes(Paths.get(procFile));
246 }
247 }
248
249 @VisibleForTesting
250 public SparseArray<long[]> getLastUidCpuTimeMs() {
251 return mLastUidCpuTimeMs;
252 }
253
254 @VisibleForTesting
255 public void setSingleUidCpuTimesAvailable(boolean singleUidCpuTimesAvailable) {
256 mSingleUidCpuTimesAvailable = singleUidCpuTimesAvailable;
257 }
258}