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 java.io.IOException;
28 import java.nio.ByteBuffer;
29 import java.nio.ByteOrder;
30 import java.nio.file.Files;
31 import java.nio.file.Paths;
32 import java.util.Arrays;
33 
34 @VisibleForTesting(visibility = PACKAGE)
35 public class KernelSingleUidTimeReader {
36     private static final String TAG = KernelSingleUidTimeReader.class.getName();
37     private static final boolean DBG = false;
38 
39     private static final String PROC_FILE_DIR = "/proc/uid/";
40     private static final String PROC_FILE_NAME = "/time_in_state";
41     private static final String UID_TIMES_PROC_FILE = "/proc/uid_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")
50     private SparseArray<long[]> mLastUidCpuTimeMs = new SparseArray<>();
51 
52     @GuardedBy("this")
53     private int mReadErrorCounter;
54     @GuardedBy("this")
55     private boolean mSingleUidCpuTimesAvailable = true;
56     @GuardedBy("this")
57     private boolean mBpfTimesAvailable = true;
58     // 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;
64 
65     private final Injector mInjector;
66 
canReadBpfTimes()67     private static final native boolean canReadBpfTimes();
68 
KernelSingleUidTimeReader(int cpuFreqsCount)69     KernelSingleUidTimeReader(int cpuFreqsCount) {
70         this(cpuFreqsCount, new Injector());
71     }
72 
KernelSingleUidTimeReader(int cpuFreqsCount, Injector injector)73     public KernelSingleUidTimeReader(int cpuFreqsCount, Injector injector) {
74         mInjector = injector;
75         mCpuFreqsCount = cpuFreqsCount;
76         if (mCpuFreqsCount == 0) {
77             mSingleUidCpuTimesAvailable = false;
78         }
79     }
80 
singleUidCpuTimesAvailable()81     public boolean singleUidCpuTimesAvailable() {
82         return mSingleUidCpuTimesAvailable;
83     }
84 
readDeltaMs(int uid)85     public long[] readDeltaMs(int uid) {
86         synchronized (this) {
87             if (!mSingleUidCpuTimesAvailable) {
88                 return null;
89             }
90             if (mBpfTimesAvailable) {
91                 final long[] cpuTimesMs = mInjector.readBpfData(uid);
92                 if (cpuTimesMs.length == 0) {
93                     mBpfTimesAvailable = false;
94                 } else if (!mCpuFreqsCountVerified && cpuTimesMs.length != mCpuFreqsCount) {
95                     mSingleUidCpuTimesAvailable = false;
96                     return null;
97                 } else {
98                     mCpuFreqsCountVerified = true;
99                     return computeDelta(uid, cpuTimesMs);
100                 }
101             }
102             // Read total cpu times from the proc file.
103             final String procFile = new StringBuilder(PROC_FILE_DIR)
104                     .append(uid)
105                     .append(PROC_FILE_NAME).toString();
106             final long[] cpuTimesMs;
107             try {
108                 final byte[] data = mInjector.readData(procFile);
109                 if (!mCpuFreqsCountVerified) {
110                     verifyCpuFreqsCount(data.length, procFile);
111                 }
112                 final ByteBuffer buffer = ByteBuffer.wrap(data);
113                 buffer.order(ByteOrder.nativeOrder());
114                 cpuTimesMs = readCpuTimesFromByteBuffer(buffer);
115             } catch (Exception e) {
116                 if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
117                     mSingleUidCpuTimesAvailable = false;
118                 }
119                 if (DBG) Slog.e(TAG, "Some error occured while reading " + procFile, e);
120                 return null;
121             }
122 
123             return computeDelta(uid, cpuTimesMs);
124         }
125     }
126 
verifyCpuFreqsCount(int numBytes, String procFile)127     private void verifyCpuFreqsCount(int numBytes, String procFile) {
128         final int actualCount = (numBytes / Long.BYTES);
129         if (mCpuFreqsCount != actualCount) {
130             mSingleUidCpuTimesAvailable = false;
131             throw new IllegalStateException("Freq count didn't match,"
132                     + "count from " + UID_TIMES_PROC_FILE + "=" + mCpuFreqsCount + ", but"
133                     + "count from " + procFile + "=" + actualCount);
134         }
135         mCpuFreqsCountVerified = true;
136     }
137 
readCpuTimesFromByteBuffer(ByteBuffer buffer)138     private long[] readCpuTimesFromByteBuffer(ByteBuffer buffer) {
139         final long[] cpuTimesMs;
140         cpuTimesMs = new long[mCpuFreqsCount];
141         for (int i = 0; i < mCpuFreqsCount; ++i) {
142             // Times read will be in units of 10ms
143             cpuTimesMs[i] = buffer.getLong() * 10;
144         }
145         return cpuTimesMs;
146     }
147 
148     /**
149      * Compute and return cpu times delta of an uid using previously read cpu times and
150      * {@param latestCpuTimesMs}.
151      *
152      * @return delta of cpu times if at least one of the cpu time at a freq is +ve, otherwise null.
153      */
computeDelta(int uid, @NonNull long[] latestCpuTimesMs)154     public long[] computeDelta(int uid, @NonNull long[] latestCpuTimesMs) {
155         synchronized (this) {
156             if (!mSingleUidCpuTimesAvailable) {
157                 return null;
158             }
159             // Subtract the last read cpu times to get deltas.
160             final long[] lastCpuTimesMs = mLastUidCpuTimeMs.get(uid);
161             final long[] deltaTimesMs = getDeltaLocked(lastCpuTimesMs, latestCpuTimesMs);
162             if (deltaTimesMs == null) {
163                 if (DBG) Slog.e(TAG, "Malformed data read for uid=" + uid
164                         + "; last=" + Arrays.toString(lastCpuTimesMs)
165                         + "; latest=" + Arrays.toString(latestCpuTimesMs));
166                 return null;
167             }
168             // If all elements are zero, return null to avoid unnecessary work on the caller side.
169             boolean hasNonZero = false;
170             for (int i = deltaTimesMs.length - 1; i >= 0; --i) {
171                 if (deltaTimesMs[i] > 0) {
172                     hasNonZero = true;
173                     break;
174                 }
175             }
176             if (hasNonZero) {
177                 mLastUidCpuTimeMs.put(uid, latestCpuTimesMs);
178                 return deltaTimesMs;
179             } else {
180                 return null;
181             }
182         }
183     }
184 
185     /**
186      * Returns null if the latest cpu times are not valid**, otherwise delta of
187      * {@param latestCpuTimesMs} and {@param lastCpuTimesMs}.
188      *
189      * **latest cpu times are considered valid if all the cpu times are +ve and
190      * greater than or equal to previously read cpu times.
191      */
192     @GuardedBy("this")
193     @VisibleForTesting(visibility = PACKAGE)
getDeltaLocked(long[] lastCpuTimesMs, @NonNull long[] latestCpuTimesMs)194     public long[] getDeltaLocked(long[] lastCpuTimesMs, @NonNull long[] latestCpuTimesMs) {
195         for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
196             if (latestCpuTimesMs[i] < 0) {
197                 return null;
198             }
199         }
200         if (lastCpuTimesMs == null) {
201             return latestCpuTimesMs;
202         }
203         final long[] deltaTimesMs = new long[latestCpuTimesMs.length];
204         for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
205             deltaTimesMs[i] = latestCpuTimesMs[i] - lastCpuTimesMs[i];
206             if (deltaTimesMs[i] < 0) {
207                 return null;
208             }
209         }
210         return deltaTimesMs;
211     }
212 
setAllUidsCpuTimesMs(SparseArray<long[]> allUidsCpuTimesMs)213     public void setAllUidsCpuTimesMs(SparseArray<long[]> allUidsCpuTimesMs) {
214         synchronized (this) {
215             mLastUidCpuTimeMs.clear();
216             for (int i = allUidsCpuTimesMs.size() - 1; i >= 0; --i) {
217                 final long[] cpuTimesMs = allUidsCpuTimesMs.valueAt(i);
218                 if (cpuTimesMs != null) {
219                     mLastUidCpuTimeMs.put(allUidsCpuTimesMs.keyAt(i), cpuTimesMs.clone());
220                 }
221             }
222         }
223     }
224 
removeUid(int uid)225     public void removeUid(int uid) {
226         synchronized (this) {
227             mLastUidCpuTimeMs.delete(uid);
228         }
229     }
230 
removeUidsInRange(int startUid, int endUid)231     public void removeUidsInRange(int startUid, int endUid) {
232         if (endUid < startUid) {
233             return;
234         }
235         synchronized (this) {
236             mLastUidCpuTimeMs.put(startUid, null);
237             mLastUidCpuTimeMs.put(endUid, null);
238             final int startIdx = mLastUidCpuTimeMs.indexOfKey(startUid);
239             final int endIdx = mLastUidCpuTimeMs.indexOfKey(endUid);
240             mLastUidCpuTimeMs.removeAtRange(startIdx, endIdx - startIdx + 1);
241         }
242     }
243 
244     @VisibleForTesting
245     public static class Injector {
readData(String procFile)246         public byte[] readData(String procFile) throws IOException {
247             return Files.readAllBytes(Paths.get(procFile));
248         }
249 
readBpfData(int uid)250         public native long[] readBpfData(int uid);
251     }
252 
253     @VisibleForTesting
getLastUidCpuTimeMs()254     public SparseArray<long[]> getLastUidCpuTimeMs() {
255         return mLastUidCpuTimeMs;
256     }
257 
258     @VisibleForTesting
setSingleUidCpuTimesAvailable(boolean singleUidCpuTimesAvailable)259     public void setSingleUidCpuTimesAvailable(boolean singleUidCpuTimesAvailable) {
260         mSingleUidCpuTimesAvailable = singleUidCpuTimesAvailable;
261     }
262 }