blob: e85e29543d237610f1e2b98eca6d3419cd023686 [file] [log] [blame]
Sudheer Shanka9b735c52017-05-09 18:26:18 -07001/*
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;
Sudheer Shanka00857522017-07-06 18:28:14 -070020import android.os.SystemClock;
Sudheer Shanka9b735c52017-05-09 18:26:18 -070021import android.util.Slog;
22import android.util.SparseArray;
Sudheer Shanka00857522017-07-06 18:28:14 -070023import android.util.TimeUtils;
Sudheer Shanka9b735c52017-05-09 18:26:18 -070024
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 String TAG = "KernelUidCpuFreqTimeReader";
46 private static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state";
47
48 public interface Callback {
49 void onCpuFreqs(long[] cpuFreqs);
50 void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs);
51 }
52
53 private long[] mCpuFreqs;
54 private int mCpuFreqsCount;
Sudheer Shanka00857522017-07-06 18:28:14 -070055 private long mLastTimeReadMs;
56 private long mNowTimeMs;
Sudheer Shanka9b735c52017-05-09 18:26:18 -070057
58 private SparseArray<long[]> mLastUidCpuFreqTimeMs = new SparseArray<>();
59
Sudheer Shankaacd7f2c2017-06-01 17:39:40 -070060 // We check the existence of proc file a few times (just in case it is not ready yet when we
61 // start reading) and if it is not available, we simply ignore further read requests.
62 private static final int TOTAL_READ_ERROR_COUNT = 5;
63 private int mReadErrorCounter;
64 private boolean mProcFileAvailable;
65
Sudheer Shanka9b735c52017-05-09 18:26:18 -070066 public void readDelta(@Nullable Callback callback) {
Sudheer Shankaacd7f2c2017-06-01 17:39:40 -070067 if (!mProcFileAvailable && mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
68 return;
69 }
Sudheer Shanka9b735c52017-05-09 18:26:18 -070070 try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
Sudheer Shanka00857522017-07-06 18:28:14 -070071 mNowTimeMs = SystemClock.elapsedRealtime();
Sudheer Shanka9b735c52017-05-09 18:26:18 -070072 readDelta(reader, callback);
Sudheer Shanka00857522017-07-06 18:28:14 -070073 mLastTimeReadMs = mNowTimeMs;
Sudheer Shankaacd7f2c2017-06-01 17:39:40 -070074 mProcFileAvailable = true;
Sudheer Shanka9b735c52017-05-09 18:26:18 -070075 } catch (IOException e) {
Sudheer Shankaacd7f2c2017-06-01 17:39:40 -070076 mReadErrorCounter++;
Sudheer Shanka9b735c52017-05-09 18:26:18 -070077 Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
78 }
79 }
80
Sudheer Shanka6d8dcec2017-06-01 12:09:03 -070081 public void removeUid(int uid) {
82 mLastUidCpuFreqTimeMs.delete(uid);
83 }
84
Suprabh Shuklae6e723d2017-06-14 16:14:43 -070085 public void removeUidsInRange(int startUid, int endUid) {
86 if (endUid < startUid) {
87 return;
88 }
89 mLastUidCpuFreqTimeMs.put(startUid, null);
90 mLastUidCpuFreqTimeMs.put(endUid, null);
91 final int firstIndex = mLastUidCpuFreqTimeMs.indexOfKey(startUid);
92 final int lastIndex = mLastUidCpuFreqTimeMs.indexOfKey(endUid);
93 mLastUidCpuFreqTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1);
94 }
95
Sudheer Shanka9b735c52017-05-09 18:26:18 -070096 @VisibleForTesting
97 public void readDelta(BufferedReader reader, @Nullable Callback callback) throws IOException {
98 String line = reader.readLine();
99 if (line == null) {
100 return;
101 }
102 readCpuFreqs(line, callback);
103 while ((line = reader.readLine()) != null) {
104 final int index = line.indexOf(' ');
105 final int uid = Integer.parseInt(line.substring(0, index - 1), 10);
106 readTimesForUid(uid, line.substring(index + 1, line.length()), callback);
107 }
108 }
109
110 private void readTimesForUid(int uid, String line, Callback callback) {
111 long[] uidTimeMs = mLastUidCpuFreqTimeMs.get(uid);
112 if (uidTimeMs == null) {
113 uidTimeMs = new long[mCpuFreqsCount];
114 mLastUidCpuFreqTimeMs.put(uid, uidTimeMs);
115 }
116 final String[] timesStr = line.split(" ");
117 final int size = timesStr.length;
118 if (size != uidTimeMs.length) {
119 Slog.e(TAG, "No. of readings don't match cpu freqs, readings: " + size
120 + " cpuFreqsCount: " + uidTimeMs.length);
121 return;
122 }
123 final long[] deltaUidTimeMs = new long[size];
Sudheer Shanka00857522017-07-06 18:28:14 -0700124 final long[] curUidTimeMs = new long[size];
Sudheer Shanka97d15bf2017-06-19 19:19:08 -0700125 boolean notify = false;
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700126 for (int i = 0; i < size; ++i) {
127 // Times read will be in units of 10ms
128 final long totalTimeMs = Long.parseLong(timesStr[i], 10) * 10;
129 deltaUidTimeMs[i] = totalTimeMs - uidTimeMs[i];
Sudheer Shanka00857522017-07-06 18:28:14 -0700130 // If there is malformed data for any uid, then we just log about it and ignore
131 // the data for that uid.
132 if (deltaUidTimeMs[i] < 0 || totalTimeMs < 0) {
133 final StringBuilder sb = new StringBuilder("Malformed cpu freq data for UID=")
134 .append(uid).append("\n");
135 sb.append("data=").append("(").append(uidTimeMs[i]).append(",")
136 .append(totalTimeMs).append(")").append("\n");
Sudheer Shanka40c43f12017-08-08 15:44:09 -0700137 sb.append("times=").append("(");
138 TimeUtils.formatDuration(mLastTimeReadMs, sb); sb.append(",");
139 TimeUtils.formatDuration(mNowTimeMs, sb); sb.append(")");
Sudheer Shanka8cde9302017-08-23 11:29:23 -0700140 Slog.e(TAG, sb.toString());
Sudheer Shanka00857522017-07-06 18:28:14 -0700141 return;
142 }
143 curUidTimeMs[i] = totalTimeMs;
Sudheer Shanka97d15bf2017-06-19 19:19:08 -0700144 notify = notify || (deltaUidTimeMs[i] > 0);
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700145 }
Sudheer Shanka00857522017-07-06 18:28:14 -0700146 if (notify) {
147 System.arraycopy(curUidTimeMs, 0, uidTimeMs, 0, size);
148 if (callback != null) {
149 callback.onUidCpuFreqTime(uid, deltaUidTimeMs);
150 }
Sudheer Shanka9b735c52017-05-09 18:26:18 -0700151 }
152 }
153
154 private void readCpuFreqs(String line, Callback callback) {
155 if (mCpuFreqs == null) {
156 final String[] freqStr = line.split(" ");
157 // First item would be "uid:" which needs to be ignored
158 mCpuFreqsCount = freqStr.length - 1;
159 mCpuFreqs = new long[mCpuFreqsCount];
160 for (int i = 0; i < mCpuFreqsCount; ++i) {
161 mCpuFreqs[i] = Long.parseLong(freqStr[i + 1], 10);
162 }
163 }
164 if (callback != null) {
165 callback.onCpuFreqs(mCpuFreqs);
166 }
167 }
168}