KernelUidCpuFreqTimeReader.java revision 6b1396e8feef1f923a01daa56aa58ea3fffdcb82
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 android.annotation.Nullable;
20import android.os.SystemClock;
21import android.util.Slog;
22import android.util.SparseArray;
23import android.util.TimeUtils;
24
25import com.android.internal.annotations.VisibleForTesting;
26
27import java.io.BufferedReader;
28import java.io.FileReader;
29import java.io.IOException;
30
31/**
32 * Reads /proc/uid_time_in_state which has the format:
33 *
34 * uid: [freq1] [freq2] [freq3] ...
35 * [uid1]: [time in freq1] [time in freq2] [time in freq3] ...
36 * [uid2]: [time in freq1] [time in freq2] [time in freq3] ...
37 * ...
38 *
39 * This provides the times a UID's processes spent executing at each different cpu frequency.
40 * The file contains a monotonically increasing count of time for a single boot. This class
41 * maintains the previous results of a call to {@link #readDelta} in order to provide a proper
42 * delta.
43 */
44public class KernelUidCpuFreqTimeReader {
45    private static final boolean DEBUG = false;
46    private static final String TAG = "KernelUidCpuFreqTimeReader";
47    private static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
48
49    public interface Callback {
50        void onCpuFreqs(long[] cpuFreqs);
51        void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs);
52    }
53
54    private long[] mCpuFreqs;
55    private int mCpuFreqsCount;
56    private long mLastTimeReadMs;
57    private long mNowTimeMs;
58
59    private SparseArray<long[]> mLastUidCpuFreqTimeMs = new SparseArray<>();
60
61    // We check the existence of proc file a few times (just in case it is not ready yet when we
62    // start reading) and if it is not available, we simply ignore further read requests.
63    private static final int TOTAL_READ_ERROR_COUNT = 5;
64    private int mReadErrorCounter;
65    private boolean mProcFileAvailable;
66
67    public void readDelta(@Nullable Callback callback) {
68        if (!mProcFileAvailable && mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
69            return;
70        }
71        try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
72            mNowTimeMs = SystemClock.elapsedRealtime();
73            readDelta(reader, callback);
74            mLastTimeReadMs = mNowTimeMs;
75            mProcFileAvailable = true;
76        } catch (IOException e) {
77            mReadErrorCounter++;
78            Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
79        }
80    }
81
82    public void removeUid(int uid) {
83        mLastUidCpuFreqTimeMs.delete(uid);
84    }
85
86    public void removeUidsInRange(int startUid, int endUid) {
87        if (endUid < startUid) {
88            return;
89        }
90        mLastUidCpuFreqTimeMs.put(startUid, null);
91        mLastUidCpuFreqTimeMs.put(endUid, null);
92        final int firstIndex = mLastUidCpuFreqTimeMs.indexOfKey(startUid);
93        final int lastIndex = mLastUidCpuFreqTimeMs.indexOfKey(endUid);
94        mLastUidCpuFreqTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
95    }
96
97    @VisibleForTesting
98    public void readDelta(BufferedReader reader, @Nullable Callback callback) throws IOException {
99        String line = reader.readLine();
100        if (line == null) {
101            return;
102        }
103        readCpuFreqs(line, callback);
104        while ((line = reader.readLine()) != null) {
105            final int index = line.indexOf(' ');
106            final int uid = Integer.parseInt(line.substring(0, index - 1), 10);
107            readTimesForUid(uid, line.substring(index + 1, line.length()), callback);
108        }
109    }
110
111    private void readTimesForUid(int uid, String line, Callback callback) {
112        long[] uidTimeMs = mLastUidCpuFreqTimeMs.get(uid);
113        if (uidTimeMs == null) {
114            uidTimeMs = new long[mCpuFreqsCount];
115            mLastUidCpuFreqTimeMs.put(uid, uidTimeMs);
116        }
117        final String[] timesStr = line.split(" ");
118        final int size = timesStr.length;
119        if (size != uidTimeMs.length) {
120            Slog.e(TAG, "No. of readings don't match cpu freqs, readings: " + size
121                    + " cpuFreqsCount: " + uidTimeMs.length);
122            return;
123        }
124        final long[] deltaUidTimeMs = new long[size];
125        final long[] curUidTimeMs = new long[size];
126        boolean notify = false;
127        for (int i = 0; i < size; ++i) {
128            // Times read will be in units of 10ms
129            final long totalTimeMs = Long.parseLong(timesStr[i], 10) * 10;
130            deltaUidTimeMs[i] = totalTimeMs - uidTimeMs[i];
131            // If there is malformed data for any uid, then we just log about it and ignore
132            // the data for that uid.
133            if (deltaUidTimeMs[i] < 0 || totalTimeMs < 0) {
134                if (DEBUG) {
135                    final StringBuilder sb = new StringBuilder("Malformed cpu freq data for UID=")
136                            .append(uid).append("\n");
137                    sb.append("data=").append("(").append(uidTimeMs[i]).append(",")
138                            .append(totalTimeMs).append(")").append("\n");
139                    sb.append("times=").append("(");
140                    TimeUtils.formatDuration(mLastTimeReadMs, sb);
141                    sb.append(",");
142                    TimeUtils.formatDuration(mNowTimeMs, sb);
143                    sb.append(")");
144                    Slog.e(TAG, sb.toString());
145                }
146                return;
147            }
148            curUidTimeMs[i] = totalTimeMs;
149            notify = notify || (deltaUidTimeMs[i] > 0);
150        }
151        if (notify) {
152            System.arraycopy(curUidTimeMs, 0, uidTimeMs, 0, size);
153            if (callback != null) {
154                callback.onUidCpuFreqTime(uid, deltaUidTimeMs);
155            }
156        }
157    }
158
159    private void readCpuFreqs(String line, Callback callback) {
160        if (mCpuFreqs == null) {
161            final String[] freqStr = line.split(" ");
162            // First item would be "uid:" which needs to be ignored
163            mCpuFreqsCount = freqStr.length - 1;
164            mCpuFreqs = new long[mCpuFreqsCount];
165            for (int i = 0; i < mCpuFreqsCount; ++i) {
166                mCpuFreqs[i] = Long.parseLong(freqStr[i + 1], 10);
167            }
168        }
169        if (callback != null) {
170            callback.onCpuFreqs(mCpuFreqs);
171        }
172    }
173}
174