KernelUidCpuFreqTimeReader.java revision b8ad594300b26c89a1b4b0ec4957b49600e58c08
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.SystemClock;
24import android.util.IntArray;
25import android.util.Slog;
26import android.util.SparseArray;
27import android.util.TimeUtils;
28
29import com.android.internal.annotations.VisibleForTesting;
30
31import java.io.BufferedReader;
32import java.io.FileReader;
33import java.io.IOException;
34
35/**
36 * Reads /proc/uid_time_in_state which has the format:
37 *
38 * uid: [freq1] [freq2] [freq3] ...
39 * [uid1]: [time in freq1] [time in freq2] [time in freq3] ...
40 * [uid2]: [time in freq1] [time in freq2] [time in freq3] ...
41 * ...
42 *
43 * This provides the times a UID's processes spent executing at each different cpu frequency.
44 * The file contains a monotonically increasing count of time for a single boot. This class
45 * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
46 * delta.
47 */
48public class KernelUidCpuFreqTimeReader {
49    private static final String TAG = "KernelUidCpuFreqTimeReader";
50    private static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
51
52    public interface Callback {
53        void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs);
54    }
55
56    private long[] mCpuFreqs;
57    private int mCpuFreqsCount;
58    private long mLastTimeReadMs;
59    private long mNowTimeMs;
60
61    private SparseArray<long[]> mLastUidCpuFreqTimeMs = new SparseArray<>();
62
63    // We check the existence of proc file a few times (just in case it is not ready yet when we
64    // start reading) and if it is not available, we simply ignore further read requests.
65    private static final int TOTAL_READ_ERROR_COUNT = 5;
66    private int mReadErrorCounter;
67    private boolean mProcFileAvailable;
68    private boolean mPerClusterTimesAvailable;
69
70    public boolean perClusterTimesAvailable() {
71        return mPerClusterTimesAvailable;
72    }
73
74    public long[] readFreqs(@NonNull PowerProfile powerProfile) {
75        checkNotNull(powerProfile);
76
77        if (mCpuFreqs != null) {
78            // No need to read cpu freqs more than once.
79            return mCpuFreqs;
80        }
81        if (!mProcFileAvailable && mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
82            return null;
83        }
84        try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
85            mProcFileAvailable = true;
86            return readFreqs(reader, powerProfile);
87        } catch (IOException e) {
88            mReadErrorCounter++;
89            Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
90            return null;
91        }
92    }
93
94    @VisibleForTesting
95    public long[] readFreqs(BufferedReader reader, PowerProfile powerProfile)
96            throws IOException {
97        final String line = reader.readLine();
98        if (line == null) {
99            return null;
100        }
101        return readCpuFreqs(line, powerProfile);
102    }
103
104    public void readDelta(@Nullable Callback callback) {
105        if (!mProcFileAvailable) {
106            return;
107        }
108        try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
109            mNowTimeMs = SystemClock.elapsedRealtime();
110            readDelta(reader, callback);
111            mLastTimeReadMs = mNowTimeMs;
112        } catch (IOException e) {
113            Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
114        }
115    }
116
117    public void removeUid(int uid) {
118        mLastUidCpuFreqTimeMs.delete(uid);
119    }
120
121    public void removeUidsInRange(int startUid, int endUid) {
122        if (endUid < startUid) {
123            return;
124        }
125        mLastUidCpuFreqTimeMs.put(startUid, null);
126        mLastUidCpuFreqTimeMs.put(endUid, null);
127        final int firstIndex = mLastUidCpuFreqTimeMs.indexOfKey(startUid);
128        final int lastIndex = mLastUidCpuFreqTimeMs.indexOfKey(endUid);
129        mLastUidCpuFreqTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
130    }
131
132    @VisibleForTesting
133    public void readDelta(BufferedReader reader, @Nullable Callback callback) throws IOException {
134        String line = reader.readLine();
135        if (line == null) {
136            return;
137        }
138        while ((line = reader.readLine()) != null) {
139            final int index = line.indexOf(' ');
140            final int uid = Integer.parseInt(line.substring(0, index - 1), 10);
141            readTimesForUid(uid, line.substring(index + 1, line.length()), callback);
142        }
143    }
144
145    private void readTimesForUid(int uid, String line, Callback callback) {
146        long[] uidTimeMs = mLastUidCpuFreqTimeMs.get(uid);
147        if (uidTimeMs == null) {
148            uidTimeMs = new long[mCpuFreqsCount];
149            mLastUidCpuFreqTimeMs.put(uid, uidTimeMs);
150        }
151        final String[] timesStr = line.split(" ");
152        final int size = timesStr.length;
153        if (size != uidTimeMs.length) {
154            Slog.e(TAG, "No. of readings don't match cpu freqs, readings: " + size
155                    + " cpuFreqsCount: " + uidTimeMs.length);
156            return;
157        }
158        final long[] deltaUidTimeMs = new long[size];
159        final long[] curUidTimeMs = new long[size];
160        boolean notify = false;
161        for (int i = 0; i < size; ++i) {
162            // Times read will be in units of 10ms
163            final long totalTimeMs = Long.parseLong(timesStr[i], 10) * 10;
164            deltaUidTimeMs[i] = totalTimeMs - uidTimeMs[i];
165            // If there is malformed data for any uid, then we just log about it and ignore
166            // the data for that uid.
167            if (deltaUidTimeMs[i] < 0 || totalTimeMs < 0) {
168                final StringBuilder sb = new StringBuilder("Malformed cpu freq data for UID=")
169                        .append(uid).append("\n");
170                sb.append("data=").append("(").append(uidTimeMs[i]).append(",")
171                        .append(totalTimeMs).append(")").append("\n");
172                sb.append("times=").append("(");
173                TimeUtils.formatDuration(mLastTimeReadMs, sb); sb.append(",");
174                TimeUtils.formatDuration(mNowTimeMs, sb); sb.append(")");
175                Slog.wtf(TAG, sb.toString());
176                return;
177            }
178            curUidTimeMs[i] = totalTimeMs;
179            notify = notify || (deltaUidTimeMs[i] > 0);
180        }
181        if (notify) {
182            System.arraycopy(curUidTimeMs, 0, uidTimeMs, 0, size);
183            if (callback != null) {
184                callback.onUidCpuFreqTime(uid, deltaUidTimeMs);
185            }
186        }
187    }
188
189    private long[] readCpuFreqs(String line, PowerProfile powerProfile) {
190        final String[] freqStr = line.split(" ");
191        // First item would be "uid: " which needs to be ignored.
192        mCpuFreqsCount = freqStr.length - 1;
193        mCpuFreqs = new long[mCpuFreqsCount];
194        for (int i = 0; i < mCpuFreqsCount; ++i) {
195            mCpuFreqs[i] = Long.parseLong(freqStr[i + 1], 10);
196        }
197
198        // Check if the freqs in the proc file correspond to per-cluster freqs.
199        final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs();
200        final int numClusters = powerProfile.getNumCpuClusters();
201        if (numClusterFreqs.size() == numClusters) {
202            mPerClusterTimesAvailable = true;
203            for (int i = 0; i < numClusters; ++i) {
204                if (numClusterFreqs.get(i) != powerProfile.getNumSpeedStepsInCpuCluster(i)) {
205                    mPerClusterTimesAvailable = false;
206                    break;
207                }
208            }
209        } else {
210            mPerClusterTimesAvailable = false;
211        }
212        Slog.i(TAG, "mPerClusterTimesAvailable=" + mPerClusterTimesAvailable);
213
214        return mCpuFreqs;
215    }
216
217    /**
218     * Extracts no. of cpu clusters and no. of freqs in each of these clusters from the freqs
219     * read from the proc file.
220     *
221     * We need to assume that freqs in each cluster are strictly increasing.
222     * For e.g. if the freqs read from proc file are: 12, 34, 15, 45, 12, 15, 52. Then it means
223     * there are 3 clusters: (12, 34), (15, 45), (12, 15, 52)
224     *
225     * @return an IntArray filled with no. of freqs in each cluster.
226     */
227    private IntArray extractClusterInfoFromProcFileFreqs() {
228        final IntArray numClusterFreqs = new IntArray();
229        int freqsFound = 0;
230        for (int i = 0; i < mCpuFreqsCount; ++i) {
231            freqsFound++;
232            if (i + 1 == mCpuFreqsCount || mCpuFreqs[i + 1] <= mCpuFreqs[i]) {
233                numClusterFreqs.add(freqsFound);
234                freqsFound = 0;
235            }
236        }
237        return numClusterFreqs;
238    }
239}
240