• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 package com.android.internal.os;
17 
18 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
19 
20 import android.annotation.NonNull;
21 import android.util.Slog;
22 import android.util.SparseArray;
23 
24 import com.android.internal.annotations.GuardedBy;
25 import com.android.internal.annotations.VisibleForTesting;
26 
27 import dalvik.annotation.optimization.CriticalNative;
28 
29 import java.io.IOException;
30 import java.nio.ByteBuffer;
31 import java.nio.ByteOrder;
32 import java.nio.file.Files;
33 import java.nio.file.Paths;
34 import java.util.Arrays;
35 
36 @VisibleForTesting(visibility = PACKAGE)
37 public class KernelSingleUidTimeReader {
38     private static final String TAG = KernelSingleUidTimeReader.class.getName();
39     private static final boolean DBG = false;
40 
41     private static final String PROC_FILE_DIR = "/proc/uid/";
42     private static final String PROC_FILE_NAME = "/time_in_state";
43     private static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
44 
45     @VisibleForTesting
46     public static final int TOTAL_READ_ERROR_COUNT = 5;
47 
48     @GuardedBy("this")
49     private final int mCpuFreqsCount;
50 
51     @GuardedBy("this")
52     private SparseArray<long[]> mLastUidCpuTimeMs = new SparseArray<>();
53 
54     @GuardedBy("this")
55     private int mReadErrorCounter;
56     @GuardedBy("this")
57     private boolean mSingleUidCpuTimesAvailable = true;
58     @GuardedBy("this")
59     private boolean mBpfTimesAvailable = true;
60     // We use the freq count obtained from /proc/uid_time_in_state to decide how many longs
61     // to read from each /proc/uid/<uid>/time_in_state. On the first read, verify if this is
62     // correct and if not, set {@link #mSingleUidCpuTimesAvailable} to false. This flag will
63     // indicate whether we checked for validity or not.
64     @GuardedBy("this")
65     private boolean mCpuFreqsCountVerified;
66 
67     private final Injector mInjector;
68 
canReadBpfTimes()69     private static final native boolean canReadBpfTimes();
70 
KernelSingleUidTimeReader(int cpuFreqsCount)71     KernelSingleUidTimeReader(int cpuFreqsCount) {
72         this(cpuFreqsCount, new Injector());
73     }
74 
KernelSingleUidTimeReader(int cpuFreqsCount, Injector injector)75     public KernelSingleUidTimeReader(int cpuFreqsCount, Injector injector) {
76         mInjector = injector;
77         mCpuFreqsCount = cpuFreqsCount;
78         if (mCpuFreqsCount == 0) {
79             mSingleUidCpuTimesAvailable = false;
80         }
81     }
82 
singleUidCpuTimesAvailable()83     public boolean singleUidCpuTimesAvailable() {
84         return mSingleUidCpuTimesAvailable;
85     }
86 
readDeltaMs(int uid)87     public long[] readDeltaMs(int uid) {
88         synchronized (this) {
89             if (!mSingleUidCpuTimesAvailable) {
90                 return null;
91             }
92             if (mBpfTimesAvailable) {
93                 final long[] cpuTimesMs = mInjector.readBpfData(uid);
94                 if (cpuTimesMs.length == 0) {
95                     mBpfTimesAvailable = false;
96                 } else if (!mCpuFreqsCountVerified && cpuTimesMs.length != mCpuFreqsCount) {
97                     mSingleUidCpuTimesAvailable = false;
98                     return null;
99                 } else {
100                     mCpuFreqsCountVerified = true;
101                     return computeDelta(uid, cpuTimesMs);
102                 }
103             }
104             // Read total cpu times from the proc file.
105             final String procFile = new StringBuilder(PROC_FILE_DIR)
106                     .append(uid)
107                     .append(PROC_FILE_NAME).toString();
108             final long[] cpuTimesMs;
109             try {
110                 final byte[] data = mInjector.readData(procFile);
111                 if (!mCpuFreqsCountVerified) {
112                     verifyCpuFreqsCount(data.length, procFile);
113                 }
114                 final ByteBuffer buffer = ByteBuffer.wrap(data);
115                 buffer.order(ByteOrder.nativeOrder());
116                 cpuTimesMs = readCpuTimesFromByteBuffer(buffer);
117             } catch (Exception e) {
118                 if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
119                     mSingleUidCpuTimesAvailable = false;
120                 }
121                 if (DBG) Slog.e(TAG, "Some error occured while reading " + procFile, e);
122                 return null;
123             }
124 
125             return computeDelta(uid, cpuTimesMs);
126         }
127     }
128 
verifyCpuFreqsCount(int numBytes, String procFile)129     private void verifyCpuFreqsCount(int numBytes, String procFile) {
130         final int actualCount = (numBytes / Long.BYTES);
131         if (mCpuFreqsCount != actualCount) {
132             mSingleUidCpuTimesAvailable = false;
133             throw new IllegalStateException("Freq count didn't match,"
134                     + "count from " + UID_TIMES_PROC_FILE + "=" + mCpuFreqsCount + ", but"
135                     + "count from " + procFile + "=" + actualCount);
136         }
137         mCpuFreqsCountVerified = true;
138     }
139 
readCpuTimesFromByteBuffer(ByteBuffer buffer)140     private long[] readCpuTimesFromByteBuffer(ByteBuffer buffer) {
141         final long[] cpuTimesMs;
142         cpuTimesMs = new long[mCpuFreqsCount];
143         for (int i = 0; i < mCpuFreqsCount; ++i) {
144             // Times read will be in units of 10ms
145             cpuTimesMs[i] = buffer.getLong() * 10;
146         }
147         return cpuTimesMs;
148     }
149 
150     /**
151      * Compute and return cpu times delta of an uid using previously read cpu times and
152      * {@param latestCpuTimesMs}.
153      *
154      * @return delta of cpu times if at least one of the cpu time at a freq is +ve, otherwise null.
155      */
computeDelta(int uid, @NonNull long[] latestCpuTimesMs)156     public long[] computeDelta(int uid, @NonNull long[] latestCpuTimesMs) {
157         synchronized (this) {
158             if (!mSingleUidCpuTimesAvailable) {
159                 return null;
160             }
161             // Subtract the last read cpu times to get deltas.
162             final long[] lastCpuTimesMs = mLastUidCpuTimeMs.get(uid);
163             final long[] deltaTimesMs = getDeltaLocked(lastCpuTimesMs, latestCpuTimesMs);
164             if (deltaTimesMs == null) {
165                 if (DBG) Slog.e(TAG, "Malformed data read for uid=" + uid
166                         + "; last=" + Arrays.toString(lastCpuTimesMs)
167                         + "; latest=" + Arrays.toString(latestCpuTimesMs));
168                 return null;
169             }
170             // If all elements are zero, return null to avoid unnecessary work on the caller side.
171             boolean hasNonZero = false;
172             for (int i = deltaTimesMs.length - 1; i >= 0; --i) {
173                 if (deltaTimesMs[i] > 0) {
174                     hasNonZero = true;
175                     break;
176                 }
177             }
178             if (hasNonZero) {
179                 mLastUidCpuTimeMs.put(uid, latestCpuTimesMs);
180                 return deltaTimesMs;
181             } else {
182                 return null;
183             }
184         }
185     }
186 
187     /**
188      * Returns null if the latest cpu times are not valid**, otherwise delta of
189      * {@param latestCpuTimesMs} and {@param lastCpuTimesMs}.
190      *
191      * **latest cpu times are considered valid if all the cpu times are +ve and
192      * greater than or equal to previously read cpu times.
193      */
194     @GuardedBy("this")
195     @VisibleForTesting(visibility = PACKAGE)
getDeltaLocked(long[] lastCpuTimesMs, @NonNull long[] latestCpuTimesMs)196     public long[] getDeltaLocked(long[] lastCpuTimesMs, @NonNull long[] latestCpuTimesMs) {
197         for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
198             if (latestCpuTimesMs[i] < 0) {
199                 return null;
200             }
201         }
202         if (lastCpuTimesMs == null) {
203             return latestCpuTimesMs;
204         }
205         final long[] deltaTimesMs = new long[latestCpuTimesMs.length];
206         for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
207             deltaTimesMs[i] = latestCpuTimesMs[i] - lastCpuTimesMs[i];
208             if (deltaTimesMs[i] < 0) {
209                 return null;
210             }
211         }
212         return deltaTimesMs;
213     }
214 
setAllUidsCpuTimesMs(SparseArray<long[]> allUidsCpuTimesMs)215     public void setAllUidsCpuTimesMs(SparseArray<long[]> allUidsCpuTimesMs) {
216         synchronized (this) {
217             mLastUidCpuTimeMs.clear();
218             for (int i = allUidsCpuTimesMs.size() - 1; i >= 0; --i) {
219                 final long[] cpuTimesMs = allUidsCpuTimesMs.valueAt(i);
220                 if (cpuTimesMs != null) {
221                     mLastUidCpuTimeMs.put(allUidsCpuTimesMs.keyAt(i), cpuTimesMs.clone());
222                 }
223             }
224         }
225     }
226 
removeUid(int uid)227     public void removeUid(int uid) {
228         synchronized (this) {
229             mLastUidCpuTimeMs.delete(uid);
230         }
231     }
232 
removeUidsInRange(int startUid, int endUid)233     public void removeUidsInRange(int startUid, int endUid) {
234         if (endUid < startUid) {
235             return;
236         }
237         synchronized (this) {
238             mLastUidCpuTimeMs.put(startUid, null);
239             mLastUidCpuTimeMs.put(endUid, null);
240             final int startIdx = mLastUidCpuTimeMs.indexOfKey(startUid);
241             final int endIdx = mLastUidCpuTimeMs.indexOfKey(endUid);
242             mLastUidCpuTimeMs.removeAtRange(startIdx, endIdx - startIdx + 1);
243         }
244     }
245 
246     /**
247      * Retrieves CPU time-in-state data for the specified UID and adds the accumulated
248      * delta to the supplied counter.
249      */
addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs)250     public void addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs) {
251         mInjector.addDelta(uid, counter, timestampMs, null);
252     }
253 
254     /**
255      * Same as {@link #addDelta(int, LongArrayMultiStateCounter, long)}, also returning
256      * the delta in the supplied array container.
257      */
addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs, LongArrayMultiStateCounter.LongArrayContainer deltaContainer)258     public void addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs,
259             LongArrayMultiStateCounter.LongArrayContainer deltaContainer) {
260         mInjector.addDelta(uid, counter, timestampMs, deltaContainer);
261     }
262 
263     @VisibleForTesting
264     public static class Injector {
readData(String procFile)265         public byte[] readData(String procFile) throws IOException {
266             return Files.readAllBytes(Paths.get(procFile));
267         }
268 
readBpfData(int uid)269         public native long[] readBpfData(int uid);
270 
271         /**
272          * Reads CPU time-in-state data for the specified UID and adds the delta since the
273          * previous call to the current state stats in the LongArrayMultiStateCounter.
274          *
275          * The delta is also returned via the optional deltaOut parameter.
276          */
addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs, LongArrayMultiStateCounter.LongArrayContainer deltaOut)277         public boolean addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs,
278                 LongArrayMultiStateCounter.LongArrayContainer deltaOut) {
279             return addDeltaFromBpf(uid, counter.mNativeObject, timestampMs,
280                     deltaOut != null ? deltaOut.mNativeObject : 0);
281         }
282 
283         @CriticalNative
addDeltaFromBpf(int uid, long longArrayMultiStateCounterNativePointer, long timestampMs, long longArrayContainerNativePointer)284         private static native boolean addDeltaFromBpf(int uid,
285                 long longArrayMultiStateCounterNativePointer, long timestampMs,
286                 long longArrayContainerNativePointer);
287 
288         /**
289          * Used for testing.
290          *
291          * Takes mock cpu-time-in-frequency data and uses it the same way eBPF data would be used.
292          */
addDeltaForTest(int uid, LongArrayMultiStateCounter counter, long timestampMs, long[][] timeInFreqDataNanos, LongArrayMultiStateCounter.LongArrayContainer deltaOut)293         public boolean addDeltaForTest(int uid, LongArrayMultiStateCounter counter,
294                 long timestampMs, long[][] timeInFreqDataNanos,
295                 LongArrayMultiStateCounter.LongArrayContainer deltaOut) {
296             return addDeltaForTest(uid, counter.mNativeObject, timestampMs, timeInFreqDataNanos,
297                     deltaOut != null ? deltaOut.mNativeObject : 0);
298         }
299 
addDeltaForTest(int uid, long longArrayMultiStateCounterNativePointer, long timestampMs, long[][] timeInFreqDataNanos, long longArrayContainerNativePointer)300         private static native boolean addDeltaForTest(int uid,
301                 long longArrayMultiStateCounterNativePointer, long timestampMs,
302                 long[][] timeInFreqDataNanos, long longArrayContainerNativePointer);
303     }
304 
305     @VisibleForTesting
getLastUidCpuTimeMs()306     public SparseArray<long[]> getLastUidCpuTimeMs() {
307         return mLastUidCpuTimeMs;
308     }
309 
310     @VisibleForTesting
setSingleUidCpuTimesAvailable(boolean singleUidCpuTimesAvailable)311     public void setSingleUidCpuTimesAvailable(boolean singleUidCpuTimesAvailable) {
312         mSingleUidCpuTimesAvailable = singleUidCpuTimesAvailable;
313     }
314 }