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