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 
17 package com.android.internal.os;
18 
19 import static com.android.internal.util.Preconditions.checkNotNull;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.os.StrictMode;
24 import android.util.IntArray;
25 import android.util.Slog;
26 import android.util.SparseArray;
27 
28 import com.android.internal.annotations.VisibleForTesting;
29 
30 import java.io.BufferedReader;
31 import java.io.FileReader;
32 import java.io.IOException;
33 import java.nio.ByteBuffer;
34 import java.nio.IntBuffer;
35 import java.util.function.Consumer;
36 
37 /**
38  * Reads /proc/uid_time_in_state which has the format:
39  *
40  * uid: [freq1] [freq2] [freq3] ...
41  * [uid1]: [time in freq1] [time in freq2] [time in freq3] ...
42  * [uid2]: [time in freq1] [time in freq2] [time in freq3] ...
43  * ...
44  *
45  * Binary variation reads /proc/uid_cpupower/time_in_state in the following format:
46  * [n, uid0, time0a, time0b, ..., time0n,
47  * uid1, time1a, time1b, ..., time1n,
48  * uid2, time2a, time2b, ..., time2n, etc.]
49  * where n is the total number of frequencies.
50  *
51  * This provides the times a UID's processes spent executing at each different cpu frequency.
52  * The file contains a monotonically increasing count of time for a single boot. This class
53  * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
54  * delta.
55  *
56  * This class uses a throttler to reject any {@link #readDelta} call within
57  * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader},
58  * which has a shorter throttle interval and returns cached result from last read when the request
59  * is throttled.
60  *
61  * This class is NOT thread-safe and NOT designed to be accessed by more than one caller since each
62  * caller has its own view of delta.
63  */
64 public class KernelUidCpuFreqTimeReader extends
65         KernelUidCpuTimeReaderBase<KernelUidCpuFreqTimeReader.Callback> {
66     private static final String TAG = KernelUidCpuFreqTimeReader.class.getSimpleName();
67     static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
68 
69     public interface Callback extends KernelUidCpuTimeReaderBase.Callback {
onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs)70         void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs);
71     }
72 
73     private long[] mCpuFreqs;
74     private long[] mCurTimes; // Reuse to prevent GC.
75     private long[] mDeltaTimes; // Reuse to prevent GC.
76     private int mCpuFreqsCount;
77     private final KernelCpuProcReader mProcReader;
78 
79     private SparseArray<long[]> mLastUidCpuFreqTimeMs = new SparseArray<>();
80 
81     // We check the existence of proc file a few times (just in case it is not ready yet when we
82     // start reading) and if it is not available, we simply ignore further read requests.
83     private static final int TOTAL_READ_ERROR_COUNT = 5;
84     private int mReadErrorCounter;
85     private boolean mPerClusterTimesAvailable;
86     private boolean mAllUidTimesAvailable = true;
87 
KernelUidCpuFreqTimeReader()88     public KernelUidCpuFreqTimeReader() {
89         mProcReader = KernelCpuProcReader.getFreqTimeReaderInstance();
90     }
91 
92     @VisibleForTesting
KernelUidCpuFreqTimeReader(KernelCpuProcReader procReader)93     public KernelUidCpuFreqTimeReader(KernelCpuProcReader procReader) {
94         mProcReader = procReader;
95     }
96 
perClusterTimesAvailable()97     public boolean perClusterTimesAvailable() {
98         return mPerClusterTimesAvailable;
99     }
100 
allUidTimesAvailable()101     public boolean allUidTimesAvailable() {
102         return mAllUidTimesAvailable;
103     }
104 
getAllUidCpuFreqTimeMs()105     public SparseArray<long[]> getAllUidCpuFreqTimeMs() {
106         return mLastUidCpuFreqTimeMs;
107     }
108 
readFreqs(@onNull PowerProfile powerProfile)109     public long[] readFreqs(@NonNull PowerProfile powerProfile) {
110         checkNotNull(powerProfile);
111         if (mCpuFreqs != null) {
112             // No need to read cpu freqs more than once.
113             return mCpuFreqs;
114         }
115         if (!mAllUidTimesAvailable) {
116             return null;
117         }
118         final int oldMask = StrictMode.allowThreadDiskReadsMask();
119         try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
120             return readFreqs(reader, powerProfile);
121         } catch (IOException e) {
122             if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
123                 mAllUidTimesAvailable = false;
124             }
125             Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
126             return null;
127         } finally {
128             StrictMode.setThreadPolicyMask(oldMask);
129         }
130     }
131 
132     @VisibleForTesting
readFreqs(BufferedReader reader, PowerProfile powerProfile)133     public long[] readFreqs(BufferedReader reader, PowerProfile powerProfile)
134             throws IOException {
135         final String line = reader.readLine();
136         if (line == null) {
137             return null;
138         }
139         final String[] freqStr = line.split(" ");
140         // First item would be "uid: " which needs to be ignored.
141         mCpuFreqsCount = freqStr.length - 1;
142         mCpuFreqs = new long[mCpuFreqsCount];
143         mCurTimes = new long[mCpuFreqsCount];
144         mDeltaTimes = new long[mCpuFreqsCount];
145         for (int i = 0; i < mCpuFreqsCount; ++i) {
146             mCpuFreqs[i] = Long.parseLong(freqStr[i + 1], 10);
147         }
148 
149         // Check if the freqs in the proc file correspond to per-cluster freqs.
150         final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs();
151         final int numClusters = powerProfile.getNumCpuClusters();
152         if (numClusterFreqs.size() == numClusters) {
153             mPerClusterTimesAvailable = true;
154             for (int i = 0; i < numClusters; ++i) {
155                 if (numClusterFreqs.get(i) != powerProfile.getNumSpeedStepsInCpuCluster(i)) {
156                     mPerClusterTimesAvailable = false;
157                     break;
158                 }
159             }
160         } else {
161             mPerClusterTimesAvailable = false;
162         }
163         Slog.i(TAG, "mPerClusterTimesAvailable=" + mPerClusterTimesAvailable);
164         return mCpuFreqs;
165     }
166 
167     @Override
168     @VisibleForTesting
readDeltaImpl(@ullable Callback callback)169     public void readDeltaImpl(@Nullable Callback callback) {
170         if (mCpuFreqs == null) {
171             return;
172         }
173         readImpl((buf) -> {
174             int uid = buf.get();
175             long[] lastTimes = mLastUidCpuFreqTimeMs.get(uid);
176             if (lastTimes == null) {
177                 lastTimes = new long[mCpuFreqsCount];
178                 mLastUidCpuFreqTimeMs.put(uid, lastTimes);
179             }
180             if (!getFreqTimeForUid(buf, mCurTimes)) {
181                 return;
182             }
183             boolean notify = false;
184             boolean valid = true;
185             for (int i = 0; i < mCpuFreqsCount; i++) {
186                 mDeltaTimes[i] = mCurTimes[i] - lastTimes[i];
187                 if (mDeltaTimes[i] < 0) {
188                     Slog.e(TAG, "Negative delta from freq time proc: " + mDeltaTimes[i]);
189                     valid = false;
190                 }
191                 notify |= mDeltaTimes[i] > 0;
192             }
193             if (notify && valid) {
194                 System.arraycopy(mCurTimes, 0, lastTimes, 0, mCpuFreqsCount);
195                 if (callback != null) {
196                     callback.onUidCpuFreqTime(uid, mDeltaTimes);
197                 }
198             }
199         });
200     }
201 
readAbsolute(Callback callback)202     public void readAbsolute(Callback callback) {
203         readImpl((buf) -> {
204             int uid = buf.get();
205             if (getFreqTimeForUid(buf, mCurTimes)) {
206                 callback.onUidCpuFreqTime(uid, mCurTimes);
207             }
208         });
209     }
210 
getFreqTimeForUid(IntBuffer buffer, long[] freqTime)211     private boolean getFreqTimeForUid(IntBuffer buffer, long[] freqTime) {
212         boolean valid = true;
213         for (int i = 0; i < mCpuFreqsCount; i++) {
214             freqTime[i] = (long) buffer.get() * 10; // Unit is 10ms.
215             if (freqTime[i] < 0) {
216                 Slog.e(TAG, "Negative time from freq time proc: " + freqTime[i]);
217                 valid = false;
218             }
219         }
220         return valid;
221     }
222 
223     /**
224      * readImpl accepts a callback to process the uid entry. readDeltaImpl needs to store the last
225      * seen results while processing the buffer, while readAbsolute returns the absolute value read
226      * from the buffer without storing. So readImpl contains the common logic of the two, leaving
227      * the difference to a processUid function.
228      *
229      * @param processUid the callback function to process the uid entry in the buffer.
230      */
readImpl(Consumer<IntBuffer> processUid)231     private void readImpl(Consumer<IntBuffer> processUid) {
232         synchronized (mProcReader) {
233             ByteBuffer bytes = mProcReader.readBytes();
234             if (bytes == null || bytes.remaining() <= 4) {
235                 // Error already logged in mProcReader.
236                 return;
237             }
238             if ((bytes.remaining() & 3) != 0) {
239                 Slog.wtf(TAG, "Cannot parse freq time proc bytes to int: " + bytes.remaining());
240                 return;
241             }
242             IntBuffer buf = bytes.asIntBuffer();
243             final int freqs = buf.get();
244             if (freqs != mCpuFreqsCount) {
245                 Slog.wtf(TAG, "Cpu freqs expect " + mCpuFreqsCount + " , got " + freqs);
246                 return;
247             }
248             if (buf.remaining() % (freqs + 1) != 0) {
249                 Slog.wtf(TAG, "Freq time format error: " + buf.remaining() + " / " + (freqs + 1));
250                 return;
251             }
252             int numUids = buf.remaining() / (freqs + 1);
253             for (int i = 0; i < numUids; i++) {
254                 processUid.accept(buf);
255             }
256             if (DEBUG) {
257                 Slog.d(TAG, "Read uids: #" + numUids);
258             }
259         }
260     }
261 
removeUid(int uid)262     public void removeUid(int uid) {
263         mLastUidCpuFreqTimeMs.delete(uid);
264     }
265 
removeUidsInRange(int startUid, int endUid)266     public void removeUidsInRange(int startUid, int endUid) {
267         mLastUidCpuFreqTimeMs.put(startUid, null);
268         mLastUidCpuFreqTimeMs.put(endUid, null);
269         final int firstIndex = mLastUidCpuFreqTimeMs.indexOfKey(startUid);
270         final int lastIndex = mLastUidCpuFreqTimeMs.indexOfKey(endUid);
271         mLastUidCpuFreqTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
272     }
273 
274     /**
275      * Extracts no. of cpu clusters and no. of freqs in each of these clusters from the freqs
276      * read from the proc file.
277      *
278      * We need to assume that freqs in each cluster are strictly increasing.
279      * For e.g. if the freqs read from proc file are: 12, 34, 15, 45, 12, 15, 52. Then it means
280      * there are 3 clusters: (12, 34), (15, 45), (12, 15, 52)
281      *
282      * @return an IntArray filled with no. of freqs in each cluster.
283      */
extractClusterInfoFromProcFileFreqs()284     private IntArray extractClusterInfoFromProcFileFreqs() {
285         final IntArray numClusterFreqs = new IntArray();
286         int freqsFound = 0;
287         for (int i = 0; i < mCpuFreqsCount; ++i) {
288             freqsFound++;
289             if (i + 1 == mCpuFreqsCount || mCpuFreqs[i + 1] <= mCpuFreqs[i]) {
290                 numClusterFreqs.add(freqsFound);
291                 freqsFound = 0;
292             }
293         }
294         return numClusterFreqs;
295     }
296 }
297