KernelUidCpuFreqTimeReader.java revision 2ab014426647bbc8960fdb4dadfe480b9806676e
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; 35import java.nio.ByteBuffer; 36import java.nio.IntBuffer; 37 38/** 39 * Reads /proc/uid_time_in_state which has the format: 40 * 41 * uid: [freq1] [freq2] [freq3] ... 42 * [uid1]: [time in freq1] [time in freq2] [time in freq3] ... 43 * [uid2]: [time in freq1] [time in freq2] [time in freq3] ... 44 * ... 45 * 46 * Binary variation reads /proc/uid_cpupower/time_in_state in the following format: 47 * [n, uid0, time0a, time0b, ..., time0n, 48 * uid1, time1a, time1b, ..., time1n, 49 * uid2, time2a, time2b, ..., time2n, etc.] 50 * where n is the total number of frequencies. 51 * 52 * This provides the times a UID's processes spent executing at each different cpu frequency. 53 * The file contains a monotonically increasing count of time for a single boot. This class 54 * maintains the previous results of a call to {@link #readDelta} in order to provide a proper 55 * delta. 56 * 57 * This class uses a throttler to reject any {@link #readDelta} call within 58 * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader}, 59 * which has a shorter throttle interval and returns cached result from last read when the request 60 * is throttled. 61 * 62 * This class is NOT thread-safe and NOT designed to be accessed by more than one caller (due to 63 * the nature of {@link #readDelta(Callback)}). 64 */ 65public class KernelUidCpuFreqTimeReader { 66 private static final boolean DEBUG = false; 67 private static final String TAG = "KernelUidCpuFreqTimeReader"; 68 static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state"; 69 // Throttle interval in milliseconds 70 private static final long DEFAULT_THROTTLE_INTERVAL = 10_000L; 71 72 public interface Callback { 73 void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs); 74 } 75 76 private long[] mCpuFreqs; 77 private long[] mCurTimes; // Reuse to prevent GC. 78 private long[] mDeltaTimes; // Reuse to prevent GC. 79 private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL; 80 private int mCpuFreqsCount; 81 private long mLastTimeReadMs = Long.MIN_VALUE; 82 private long mNowTimeMs; 83 private boolean mReadBinary = true; 84 private final KernelCpuProcReader mProcReader; 85 86 private SparseArray<long[]> mLastUidCpuFreqTimeMs = new SparseArray<>(); 87 88 // We check the existence of proc file a few times (just in case it is not ready yet when we 89 // start reading) and if it is not available, we simply ignore further read requests. 90 private static final int TOTAL_READ_ERROR_COUNT = 5; 91 private int mReadErrorCounter; 92 private boolean mPerClusterTimesAvailable; 93 private boolean mAllUidTimesAvailable = true; 94 95 public KernelUidCpuFreqTimeReader() { 96 mProcReader = KernelCpuProcReader.getFreqTimeReaderInstance(); 97 } 98 99 @VisibleForTesting 100 public KernelUidCpuFreqTimeReader(KernelCpuProcReader procReader) { 101 mProcReader = procReader; 102 } 103 104 public boolean perClusterTimesAvailable() { 105 return mPerClusterTimesAvailable; 106 } 107 108 public boolean allUidTimesAvailable() { 109 return mAllUidTimesAvailable; 110 } 111 112 public SparseArray<long[]> getAllUidCpuFreqTimeMs() { 113 return mLastUidCpuFreqTimeMs; 114 } 115 116 public long[] readFreqs(@NonNull PowerProfile powerProfile) { 117 checkNotNull(powerProfile); 118 if (mCpuFreqs != null) { 119 // No need to read cpu freqs more than once. 120 return mCpuFreqs; 121 } 122 if (!mAllUidTimesAvailable) { 123 return null; 124 } 125 final int oldMask = StrictMode.allowThreadDiskReadsMask(); 126 try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) { 127 return readFreqs(reader, powerProfile); 128 } catch (IOException e) { 129 if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) { 130 mAllUidTimesAvailable = false; 131 } 132 Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e); 133 return null; 134 } finally { 135 StrictMode.setThreadPolicyMask(oldMask); 136 } 137 } 138 139 @VisibleForTesting 140 public long[] readFreqs(BufferedReader reader, PowerProfile powerProfile) 141 throws IOException { 142 final String line = reader.readLine(); 143 if (line == null) { 144 return null; 145 } 146 return readCpuFreqs(line, powerProfile); 147 } 148 149 public void setReadBinary(boolean readBinary) { 150 mReadBinary = readBinary; 151 } 152 153 public void setThrottleInterval(long throttleInterval) { 154 if (throttleInterval >= 0) { 155 mThrottleInterval = throttleInterval; 156 } 157 } 158 159 public void readDelta(@Nullable Callback callback) { 160 if (mCpuFreqs == null) { 161 return; 162 } 163 if (SystemClock.elapsedRealtime() < mLastTimeReadMs + mThrottleInterval) { 164 Slog.w(TAG, "Throttle"); 165 return; 166 } 167 mNowTimeMs = SystemClock.elapsedRealtime(); 168 if (mReadBinary) { 169 readDeltaBinary(callback); 170 } else { 171 readDeltaString(callback); 172 } 173 mLastTimeReadMs = mNowTimeMs; 174 } 175 176 private void readDeltaString(@Nullable Callback callback) { 177 final int oldMask = StrictMode.allowThreadDiskReadsMask(); 178 try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) { 179 readDelta(reader, callback); 180 } catch (IOException e) { 181 Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e); 182 } finally { 183 StrictMode.setThreadPolicyMask(oldMask); 184 } 185 } 186 187 @VisibleForTesting 188 public void readDeltaBinary(@Nullable Callback callback) { 189 synchronized (mProcReader) { 190 ByteBuffer bytes = mProcReader.readBytes(); 191 if (bytes == null || bytes.remaining() <= 4) { 192 // Error already logged in mProcReader. 193 return; 194 } 195 if ((bytes.remaining() & 3) != 0) { 196 Slog.wtf(TAG, "Cannot parse cluster time proc bytes to int: " + bytes.remaining()); 197 return; 198 } 199 IntBuffer buf = bytes.asIntBuffer(); 200 final int freqs = buf.get(); 201 if (freqs != mCpuFreqsCount) { 202 Slog.wtf(TAG, "Cpu freqs expect " + mCpuFreqsCount + " , got " + freqs); 203 return; 204 } 205 if (buf.remaining() % (freqs + 1) != 0) { 206 Slog.wtf(TAG, "Freq time format error: " + buf.remaining() + " / " + (freqs + 1)); 207 return; 208 } 209 int numUids = buf.remaining() / (freqs + 1); 210 for (int i = 0; i < numUids; i++) { 211 int uid = buf.get(); 212 long[] lastTimes = mLastUidCpuFreqTimeMs.get(uid); 213 if (lastTimes == null) { 214 lastTimes = new long[mCpuFreqsCount]; 215 mLastUidCpuFreqTimeMs.put(uid, lastTimes); 216 } 217 boolean notify = false; 218 boolean corrupted = false; 219 for (int j = 0; j < freqs; j++) { 220 mCurTimes[j] = (long) buf.get() * 10; // Unit is 10ms. 221 mDeltaTimes[j] = mCurTimes[j] - lastTimes[j]; 222 if (mCurTimes[j] < 0 || mDeltaTimes[j] < 0) { 223 Slog.e(TAG, "Unexpected data from freq time proc: " + mCurTimes[j]); 224 corrupted = true; 225 } 226 notify |= mDeltaTimes[j] > 0; 227 } 228 if (notify && !corrupted) { 229 System.arraycopy(mCurTimes, 0, lastTimes, 0, freqs); 230 if (callback != null) { 231 callback.onUidCpuFreqTime(uid, mDeltaTimes); 232 } 233 } 234 } 235 // Slog.i(TAG, "Read uids: "+numUids); 236 } 237 } 238 239 public void removeUid(int uid) { 240 mLastUidCpuFreqTimeMs.delete(uid); 241 } 242 243 public void removeUidsInRange(int startUid, int endUid) { 244 if (endUid < startUid) { 245 return; 246 } 247 mLastUidCpuFreqTimeMs.put(startUid, null); 248 mLastUidCpuFreqTimeMs.put(endUid, null); 249 final int firstIndex = mLastUidCpuFreqTimeMs.indexOfKey(startUid); 250 final int lastIndex = mLastUidCpuFreqTimeMs.indexOfKey(endUid); 251 mLastUidCpuFreqTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1); 252 } 253 254 @VisibleForTesting 255 public void readDelta(BufferedReader reader, @Nullable Callback callback) throws IOException { 256 String line = reader.readLine(); 257 if (line == null) { 258 return; 259 } 260 while ((line = reader.readLine()) != null) { 261 final int index = line.indexOf(' '); 262 final int uid = Integer.parseInt(line.substring(0, index - 1), 10); 263 readTimesForUid(uid, line.substring(index + 1, line.length()), callback); 264 } 265 } 266 267 private void readTimesForUid(int uid, String line, Callback callback) { 268 long[] uidTimeMs = mLastUidCpuFreqTimeMs.get(uid); 269 if (uidTimeMs == null) { 270 uidTimeMs = new long[mCpuFreqsCount]; 271 mLastUidCpuFreqTimeMs.put(uid, uidTimeMs); 272 } 273 final String[] timesStr = line.split(" "); 274 final int size = timesStr.length; 275 if (size != uidTimeMs.length) { 276 Slog.e(TAG, "No. of readings don't match cpu freqs, readings: " + size 277 + " cpuFreqsCount: " + uidTimeMs.length); 278 return; 279 } 280 final long[] deltaUidTimeMs = new long[size]; 281 final long[] curUidTimeMs = new long[size]; 282 boolean notify = false; 283 for (int i = 0; i < size; ++i) { 284 // Times read will be in units of 10ms 285 final long totalTimeMs = Long.parseLong(timesStr[i], 10) * 10; 286 deltaUidTimeMs[i] = totalTimeMs - uidTimeMs[i]; 287 // If there is malformed data for any uid, then we just log about it and ignore 288 // the data for that uid. 289 if (deltaUidTimeMs[i] < 0 || totalTimeMs < 0) { 290 if (DEBUG) { 291 final StringBuilder sb = new StringBuilder("Malformed cpu freq data for UID=") 292 .append(uid).append("\n"); 293 sb.append("data=").append("(").append(uidTimeMs[i]).append(",") 294 .append(totalTimeMs).append(")").append("\n"); 295 sb.append("times=").append("("); 296 TimeUtils.formatDuration(mLastTimeReadMs, sb); 297 sb.append(","); 298 TimeUtils.formatDuration(mNowTimeMs, sb); 299 sb.append(")"); 300 Slog.e(TAG, sb.toString()); 301 } 302 return; 303 } 304 curUidTimeMs[i] = totalTimeMs; 305 notify = notify || (deltaUidTimeMs[i] > 0); 306 } 307 if (notify) { 308 System.arraycopy(curUidTimeMs, 0, uidTimeMs, 0, size); 309 if (callback != null) { 310 callback.onUidCpuFreqTime(uid, deltaUidTimeMs); 311 } 312 } 313 } 314 315 private long[] readCpuFreqs(String line, PowerProfile powerProfile) { 316 final String[] freqStr = line.split(" "); 317 // First item would be "uid: " which needs to be ignored. 318 mCpuFreqsCount = freqStr.length - 1; 319 mCpuFreqs = new long[mCpuFreqsCount]; 320 mCurTimes = new long[mCpuFreqsCount]; 321 mDeltaTimes = new long[mCpuFreqsCount]; 322 for (int i = 0; i < mCpuFreqsCount; ++i) { 323 mCpuFreqs[i] = Long.parseLong(freqStr[i + 1], 10); 324 } 325 326 // Check if the freqs in the proc file correspond to per-cluster freqs. 327 final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs(); 328 final int numClusters = powerProfile.getNumCpuClusters(); 329 if (numClusterFreqs.size() == numClusters) { 330 mPerClusterTimesAvailable = true; 331 for (int i = 0; i < numClusters; ++i) { 332 if (numClusterFreqs.get(i) != powerProfile.getNumSpeedStepsInCpuCluster(i)) { 333 mPerClusterTimesAvailable = false; 334 break; 335 } 336 } 337 } else { 338 mPerClusterTimesAvailable = false; 339 } 340 Slog.i(TAG, "mPerClusterTimesAvailable=" + mPerClusterTimesAvailable); 341 342 return mCpuFreqs; 343 } 344 345 /** 346 * Extracts no. of cpu clusters and no. of freqs in each of these clusters from the freqs 347 * read from the proc file. 348 * 349 * We need to assume that freqs in each cluster are strictly increasing. 350 * For e.g. if the freqs read from proc file are: 12, 34, 15, 45, 12, 15, 52. Then it means 351 * there are 3 clusters: (12, 34), (15, 45), (12, 15, 52) 352 * 353 * @return an IntArray filled with no. of freqs in each cluster. 354 */ 355 private IntArray extractClusterInfoFromProcFileFreqs() { 356 final IntArray numClusterFreqs = new IntArray(); 357 int freqsFound = 0; 358 for (int i = 0; i < mCpuFreqsCount; ++i) { 359 freqsFound++; 360 if (i + 1 == mCpuFreqsCount || mCpuFreqs[i + 1] <= mCpuFreqs[i]) { 361 numClusterFreqs.add(freqsFound); 362 freqsFound = 0; 363 } 364 } 365 return numClusterFreqs; 366 } 367} 368