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