KernelUidCpuFreqTimeReader.java revision b2f83c16d6669456b237970327ebb9c92a4789f0
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 private 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