KernelUidCpuFreqTimeReader.java revision 2ab014426647bbc8960fdb4dadfe480b9806676e
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
17package com.android.internal.os;
18
19import static com.android.internal.util.Preconditions.checkNotNull;
20
21import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.os.StrictMode;
24import android.os.SystemClock;
25import android.util.IntArray;
26import android.util.Slog;
27import android.util.SparseArray;
28import android.util.TimeUtils;
29
30import com.android.internal.annotations.VisibleForTesting;
31
32import java.io.BufferedReader;
33import java.io.FileReader;
34import java.io.IOException;
35import java.nio.ByteBuffer;
36import java.nio.IntBuffer;
37
38/**
39 * Reads /proc/uid_time_in_state which has the format:
40 *
41 * uid: [freq1] [freq2] [freq3] ...
42 * [uid1]: [time in freq1] [time in freq2] [time in freq3] ...
43 * [uid2]: [time in freq1] [time in freq2] [time in freq3] ...
44 * ...
45 *
46 * Binary variation reads /proc/uid_cpupower/time_in_state in the following format:
47 * [n, uid0, time0a, time0b, ..., time0n,
48 * uid1, time1a, time1b, ..., time1n,
49 * uid2, time2a, time2b, ..., time2n, etc.]
50 * where n is the total number of frequencies.
51 *
52 * This provides the times a UID's processes spent executing at each different cpu frequency.
53 * The file contains a monotonically increasing count of time for a single boot. This class
54 * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
55 * delta.
56 *
57 * This class uses a throttler to reject any {@link #readDelta} call within
58 * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader},
59 * which has a shorter throttle interval and returns cached result from last read when the request
60 * is throttled.
61 *
62 * This class is NOT thread-safe and NOT designed to be accessed by more than one caller (due to
63 * the nature of {@link #readDelta(Callback)}).
64 */
65public class KernelUidCpuFreqTimeReader {
66    private static final boolean DEBUG = false;
67    private static final String TAG = "KernelUidCpuFreqTimeReader";
68    static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
69    // Throttle interval in milliseconds
70    private static final long DEFAULT_THROTTLE_INTERVAL = 10_000L;
71
72    public interface Callback {
73        void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs);
74    }
75
76    private long[] mCpuFreqs;
77    private long[] mCurTimes; // Reuse to prevent GC.
78    private long[] mDeltaTimes; // Reuse to prevent GC.
79    private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL;
80    private int mCpuFreqsCount;
81    private long mLastTimeReadMs = Long.MIN_VALUE;
82    private long mNowTimeMs;
83    private boolean mReadBinary = true;
84    private final KernelCpuProcReader mProcReader;
85
86    private SparseArray<long[]> mLastUidCpuFreqTimeMs = new SparseArray<>();
87
88    // We check the existence of proc file a few times (just in case it is not ready yet when we
89    // start reading) and if it is not available, we simply ignore further read requests.
90    private static final int TOTAL_READ_ERROR_COUNT = 5;
91    private int mReadErrorCounter;
92    private boolean mPerClusterTimesAvailable;
93    private boolean mAllUidTimesAvailable = true;
94
95    public KernelUidCpuFreqTimeReader() {
96        mProcReader = KernelCpuProcReader.getFreqTimeReaderInstance();
97    }
98
99    @VisibleForTesting
100    public KernelUidCpuFreqTimeReader(KernelCpuProcReader procReader) {
101        mProcReader = procReader;
102    }
103
104    public boolean perClusterTimesAvailable() {
105        return mPerClusterTimesAvailable;
106    }
107
108    public boolean allUidTimesAvailable() {
109        return mAllUidTimesAvailable;
110    }
111
112    public SparseArray<long[]> getAllUidCpuFreqTimeMs() {
113        return mLastUidCpuFreqTimeMs;
114    }
115
116    public long[] readFreqs(@NonNull PowerProfile powerProfile) {
117        checkNotNull(powerProfile);
118        if (mCpuFreqs != null) {
119            // No need to read cpu freqs more than once.
120            return mCpuFreqs;
121        }
122        if (!mAllUidTimesAvailable) {
123            return null;
124        }
125        final int oldMask = StrictMode.allowThreadDiskReadsMask();
126        try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
127            return readFreqs(reader, powerProfile);
128        } catch (IOException e) {
129            if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
130                mAllUidTimesAvailable = false;
131            }
132            Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
133            return null;
134        } finally {
135            StrictMode.setThreadPolicyMask(oldMask);
136        }
137    }
138
139    @VisibleForTesting
140    public long[] readFreqs(BufferedReader reader, PowerProfile powerProfile)
141            throws IOException {
142        final String line = reader.readLine();
143        if (line == null) {
144            return null;
145        }
146        return readCpuFreqs(line, powerProfile);
147    }
148
149    public void setReadBinary(boolean readBinary) {
150        mReadBinary = readBinary;
151    }
152
153    public void setThrottleInterval(long throttleInterval) {
154        if (throttleInterval >= 0) {
155            mThrottleInterval = throttleInterval;
156        }
157    }
158
159    public void readDelta(@Nullable Callback callback) {
160        if (mCpuFreqs == null) {
161            return;
162        }
163        if (SystemClock.elapsedRealtime() < mLastTimeReadMs + mThrottleInterval) {
164            Slog.w(TAG, "Throttle");
165            return;
166        }
167        mNowTimeMs = SystemClock.elapsedRealtime();
168        if (mReadBinary) {
169            readDeltaBinary(callback);
170        } else {
171            readDeltaString(callback);
172        }
173        mLastTimeReadMs = mNowTimeMs;
174    }
175
176    private void readDeltaString(@Nullable Callback callback) {
177        final int oldMask = StrictMode.allowThreadDiskReadsMask();
178        try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
179            readDelta(reader, callback);
180        } catch (IOException e) {
181            Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
182        } finally {
183            StrictMode.setThreadPolicyMask(oldMask);
184        }
185    }
186
187    @VisibleForTesting
188    public void readDeltaBinary(@Nullable Callback callback) {
189        synchronized (mProcReader) {
190            ByteBuffer bytes = mProcReader.readBytes();
191            if (bytes == null || bytes.remaining() <= 4) {
192                // Error already logged in mProcReader.
193                return;
194            }
195            if ((bytes.remaining() & 3) != 0) {
196                Slog.wtf(TAG, "Cannot parse cluster time proc bytes to int: " + bytes.remaining());
197                return;
198            }
199            IntBuffer buf = bytes.asIntBuffer();
200            final int freqs = buf.get();
201            if (freqs != mCpuFreqsCount) {
202                Slog.wtf(TAG, "Cpu freqs expect " + mCpuFreqsCount + " , got " + freqs);
203                return;
204            }
205            if (buf.remaining() % (freqs + 1) != 0) {
206                Slog.wtf(TAG, "Freq time format error: " + buf.remaining() + " / " + (freqs + 1));
207                return;
208            }
209            int numUids = buf.remaining() / (freqs + 1);
210            for (int i = 0; i < numUids; i++) {
211                int uid = buf.get();
212                long[] lastTimes = mLastUidCpuFreqTimeMs.get(uid);
213                if (lastTimes == null) {
214                    lastTimes = new long[mCpuFreqsCount];
215                    mLastUidCpuFreqTimeMs.put(uid, lastTimes);
216                }
217                boolean notify = false;
218                boolean corrupted = false;
219                for (int j = 0; j < freqs; j++) {
220                    mCurTimes[j] = (long) buf.get() * 10; // Unit is 10ms.
221                    mDeltaTimes[j] = mCurTimes[j] - lastTimes[j];
222                    if (mCurTimes[j] < 0 || mDeltaTimes[j] < 0) {
223                        Slog.e(TAG, "Unexpected data from freq time proc: " + mCurTimes[j]);
224                        corrupted = true;
225                    }
226                    notify |= mDeltaTimes[j] > 0;
227                }
228                if (notify && !corrupted) {
229                    System.arraycopy(mCurTimes, 0, lastTimes, 0, freqs);
230                    if (callback != null) {
231                        callback.onUidCpuFreqTime(uid, mDeltaTimes);
232                    }
233                }
234            }
235            // Slog.i(TAG, "Read uids: "+numUids);
236        }
237    }
238
239    public void removeUid(int uid) {
240        mLastUidCpuFreqTimeMs.delete(uid);
241    }
242
243    public void removeUidsInRange(int startUid, int endUid) {
244        if (endUid < startUid) {
245            return;
246        }
247        mLastUidCpuFreqTimeMs.put(startUid, null);
248        mLastUidCpuFreqTimeMs.put(endUid, null);
249        final int firstIndex = mLastUidCpuFreqTimeMs.indexOfKey(startUid);
250        final int lastIndex = mLastUidCpuFreqTimeMs.indexOfKey(endUid);
251        mLastUidCpuFreqTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
252    }
253
254    @VisibleForTesting
255    public void readDelta(BufferedReader reader, @Nullable Callback callback) throws IOException {
256        String line = reader.readLine();
257        if (line == null) {
258            return;
259        }
260        while ((line = reader.readLine()) != null) {
261            final int index = line.indexOf(' ');
262            final int uid = Integer.parseInt(line.substring(0, index - 1), 10);
263            readTimesForUid(uid, line.substring(index + 1, line.length()), callback);
264        }
265    }
266
267    private void readTimesForUid(int uid, String line, Callback callback) {
268        long[] uidTimeMs = mLastUidCpuFreqTimeMs.get(uid);
269        if (uidTimeMs == null) {
270            uidTimeMs = new long[mCpuFreqsCount];
271            mLastUidCpuFreqTimeMs.put(uid, uidTimeMs);
272        }
273        final String[] timesStr = line.split(" ");
274        final int size = timesStr.length;
275        if (size != uidTimeMs.length) {
276            Slog.e(TAG, "No. of readings don't match cpu freqs, readings: " + size
277                    + " cpuFreqsCount: " + uidTimeMs.length);
278            return;
279        }
280        final long[] deltaUidTimeMs = new long[size];
281        final long[] curUidTimeMs = new long[size];
282        boolean notify = false;
283        for (int i = 0; i < size; ++i) {
284            // Times read will be in units of 10ms
285            final long totalTimeMs = Long.parseLong(timesStr[i], 10) * 10;
286            deltaUidTimeMs[i] = totalTimeMs - uidTimeMs[i];
287            // If there is malformed data for any uid, then we just log about it and ignore
288            // the data for that uid.
289            if (deltaUidTimeMs[i] < 0 || totalTimeMs < 0) {
290                if (DEBUG) {
291                    final StringBuilder sb = new StringBuilder("Malformed cpu freq data for UID=")
292                            .append(uid).append("\n");
293                    sb.append("data=").append("(").append(uidTimeMs[i]).append(",")
294                            .append(totalTimeMs).append(")").append("\n");
295                    sb.append("times=").append("(");
296                    TimeUtils.formatDuration(mLastTimeReadMs, sb);
297                    sb.append(",");
298                    TimeUtils.formatDuration(mNowTimeMs, sb);
299                    sb.append(")");
300                    Slog.e(TAG, sb.toString());
301                }
302                return;
303            }
304            curUidTimeMs[i] = totalTimeMs;
305            notify = notify || (deltaUidTimeMs[i] > 0);
306        }
307        if (notify) {
308            System.arraycopy(curUidTimeMs, 0, uidTimeMs, 0, size);
309            if (callback != null) {
310                callback.onUidCpuFreqTime(uid, deltaUidTimeMs);
311            }
312        }
313    }
314
315    private long[] readCpuFreqs(String line, PowerProfile powerProfile) {
316        final String[] freqStr = line.split(" ");
317        // First item would be "uid: " which needs to be ignored.
318        mCpuFreqsCount = freqStr.length - 1;
319        mCpuFreqs = new long[mCpuFreqsCount];
320        mCurTimes = new long[mCpuFreqsCount];
321        mDeltaTimes = new long[mCpuFreqsCount];
322        for (int i = 0; i < mCpuFreqsCount; ++i) {
323            mCpuFreqs[i] = Long.parseLong(freqStr[i + 1], 10);
324        }
325
326        // Check if the freqs in the proc file correspond to per-cluster freqs.
327        final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs();
328        final int numClusters = powerProfile.getNumCpuClusters();
329        if (numClusterFreqs.size() == numClusters) {
330            mPerClusterTimesAvailable = true;
331            for (int i = 0; i < numClusters; ++i) {
332                if (numClusterFreqs.get(i) != powerProfile.getNumSpeedStepsInCpuCluster(i)) {
333                    mPerClusterTimesAvailable = false;
334                    break;
335                }
336            }
337        } else {
338            mPerClusterTimesAvailable = false;
339        }
340        Slog.i(TAG, "mPerClusterTimesAvailable=" + mPerClusterTimesAvailable);
341
342        return mCpuFreqs;
343    }
344
345    /**
346     * Extracts no. of cpu clusters and no. of freqs in each of these clusters from the freqs
347     * read from the proc file.
348     *
349     * We need to assume that freqs in each cluster are strictly increasing.
350     * For e.g. if the freqs read from proc file are: 12, 34, 15, 45, 12, 15, 52. Then it means
351     * there are 3 clusters: (12, 34), (15, 45), (12, 15, 52)
352     *
353     * @return an IntArray filled with no. of freqs in each cluster.
354     */
355    private IntArray extractClusterInfoFromProcFileFreqs() {
356        final IntArray numClusterFreqs = new IntArray();
357        int freqsFound = 0;
358        for (int i = 0; i < mCpuFreqsCount; ++i) {
359            freqsFound++;
360            if (i + 1 == mCpuFreqsCount || mCpuFreqs[i + 1] <= mCpuFreqs[i]) {
361                numClusterFreqs.add(freqsFound);
362                freqsFound = 0;
363            }
364        }
365        return numClusterFreqs;
366    }
367}
368