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 import static com.android.internal.os.KernelUidCpuFreqTimeReader.UID_TIMES_PROC_FILE;
20 
21 import android.annotation.NonNull;
22 import android.util.Slog;
23 import android.util.SparseArray;
24 
25 import com.android.internal.annotations.GuardedBy;
26 import com.android.internal.annotations.VisibleForTesting;
27 
28 import java.io.IOException;
29 import java.nio.ByteBuffer;
30 import java.nio.ByteOrder;
31 import java.nio.file.Files;
32 import java.nio.file.Paths;
33 import java.util.Arrays;
34 
35 @VisibleForTesting(visibility = PACKAGE)
36 public 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")
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 mHasStaleData;
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 
KernelSingleUidTimeReader(int cpuFreqsCount)67     KernelSingleUidTimeReader(int cpuFreqsCount) {
68         this(cpuFreqsCount, new Injector());
69     }
70 
KernelSingleUidTimeReader(int cpuFreqsCount, Injector injector)71     public KernelSingleUidTimeReader(int cpuFreqsCount, Injector injector) {
72         mInjector = injector;
73         mCpuFreqsCount = cpuFreqsCount;
74         if (mCpuFreqsCount == 0) {
75             mSingleUidCpuTimesAvailable = false;
76         }
77     }
78 
singleUidCpuTimesAvailable()79     public boolean singleUidCpuTimesAvailable() {
80         return mSingleUidCpuTimesAvailable;
81     }
82 
readDeltaMs(int uid)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();
92             final long[] cpuTimesMs;
93             try {
94                 final byte[] data = mInjector.readData(procFile);
95                 if (!mCpuFreqsCountVerified) {
96                     verifyCpuFreqsCount(data.length, procFile);
97                 }
98                 final ByteBuffer buffer = ByteBuffer.wrap(data);
99                 buffer.order(ByteOrder.nativeOrder());
100                 cpuTimesMs = readCpuTimesFromByteBuffer(buffer);
101             } 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 
verifyCpuFreqsCount(int numBytes, String procFile)113     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 
readCpuTimesFromByteBuffer(ByteBuffer buffer)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 
134     /**
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      */
computeDelta(int uid, @NonNull long[] latestCpuTimesMs)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)
getDeltaLocked(long[] lastCpuTimesMs, @NonNull long[] latestCpuTimesMs)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 
markDataAsStale(boolean hasStaleData)199     public void markDataAsStale(boolean hasStaleData) {
200         synchronized (this) {
201             mHasStaleData = hasStaleData;
202         }
203     }
204 
hasStaleData()205     public boolean hasStaleData() {
206         synchronized (this) {
207             return mHasStaleData;
208         }
209     }
210 
setAllUidsCpuTimesMs(SparseArray<long[]> allUidsCpuTimesMs)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 
removeUid(int uid)223     public void removeUid(int uid) {
224         synchronized (this) {
225             mLastUidCpuTimeMs.delete(uid);
226         }
227     }
228 
removeUidsInRange(int startUid, int endUid)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 {
readData(String procFile)244         public byte[] readData(String procFile) throws IOException {
245             return Files.readAllBytes(Paths.get(procFile));
246         }
247     }
248 
249     @VisibleForTesting
getLastUidCpuTimeMs()250     public SparseArray<long[]> getLastUidCpuTimeMs() {
251         return mLastUidCpuTimeMs;
252     }
253 
254     @VisibleForTesting
setSingleUidCpuTimesAvailable(boolean singleUidCpuTimesAvailable)255     public void setSingleUidCpuTimesAvailable(boolean singleUidCpuTimesAvailable) {
256         mSingleUidCpuTimesAvailable = singleUidCpuTimesAvailable;
257     }
258 }