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