KernelUidCpuFreqTimeReader.java revision b8ad594300b26c89a1b4b0ec4957b49600e58c08
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.SystemClock; 24import android.util.IntArray; 25import android.util.Slog; 26import android.util.SparseArray; 27import android.util.TimeUtils; 28 29import com.android.internal.annotations.VisibleForTesting; 30 31import java.io.BufferedReader; 32import java.io.FileReader; 33import java.io.IOException; 34 35/** 36 * Reads /proc/uid_time_in_state which has the format: 37 * 38 * uid: [freq1] [freq2] [freq3] ... 39 * [uid1]: [time in freq1] [time in freq2] [time in freq3] ... 40 * [uid2]: [time in freq1] [time in freq2] [time in freq3] ... 41 * ... 42 * 43 * This provides the times a UID's processes spent executing at each different cpu frequency. 44 * The file contains a monotonically increasing count of time for a single boot. This class 45 * maintains the previous results of a call to {@link #readDelta} in order to provide a proper 46 * delta. 47 */ 48public class KernelUidCpuFreqTimeReader { 49 private static final String TAG = "KernelUidCpuFreqTimeReader"; 50 private static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state"; 51 52 public interface Callback { 53 void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs); 54 } 55 56 private long[] mCpuFreqs; 57 private int mCpuFreqsCount; 58 private long mLastTimeReadMs; 59 private long mNowTimeMs; 60 61 private SparseArray<long[]> mLastUidCpuFreqTimeMs = new SparseArray<>(); 62 63 // We check the existence of proc file a few times (just in case it is not ready yet when we 64 // start reading) and if it is not available, we simply ignore further read requests. 65 private static final int TOTAL_READ_ERROR_COUNT = 5; 66 private int mReadErrorCounter; 67 private boolean mProcFileAvailable; 68 private boolean mPerClusterTimesAvailable; 69 70 public boolean perClusterTimesAvailable() { 71 return mPerClusterTimesAvailable; 72 } 73 74 public long[] readFreqs(@NonNull PowerProfile powerProfile) { 75 checkNotNull(powerProfile); 76 77 if (mCpuFreqs != null) { 78 // No need to read cpu freqs more than once. 79 return mCpuFreqs; 80 } 81 if (!mProcFileAvailable && mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) { 82 return null; 83 } 84 try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) { 85 mProcFileAvailable = true; 86 return readFreqs(reader, powerProfile); 87 } catch (IOException e) { 88 mReadErrorCounter++; 89 Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e); 90 return null; 91 } 92 } 93 94 @VisibleForTesting 95 public long[] readFreqs(BufferedReader reader, PowerProfile powerProfile) 96 throws IOException { 97 final String line = reader.readLine(); 98 if (line == null) { 99 return null; 100 } 101 return readCpuFreqs(line, powerProfile); 102 } 103 104 public void readDelta(@Nullable Callback callback) { 105 if (!mProcFileAvailable) { 106 return; 107 } 108 try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) { 109 mNowTimeMs = SystemClock.elapsedRealtime(); 110 readDelta(reader, callback); 111 mLastTimeReadMs = mNowTimeMs; 112 } catch (IOException e) { 113 Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e); 114 } 115 } 116 117 public void removeUid(int uid) { 118 mLastUidCpuFreqTimeMs.delete(uid); 119 } 120 121 public void removeUidsInRange(int startUid, int endUid) { 122 if (endUid < startUid) { 123 return; 124 } 125 mLastUidCpuFreqTimeMs.put(startUid, null); 126 mLastUidCpuFreqTimeMs.put(endUid, null); 127 final int firstIndex = mLastUidCpuFreqTimeMs.indexOfKey(startUid); 128 final int lastIndex = mLastUidCpuFreqTimeMs.indexOfKey(endUid); 129 mLastUidCpuFreqTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1); 130 } 131 132 @VisibleForTesting 133 public void readDelta(BufferedReader reader, @Nullable Callback callback) throws IOException { 134 String line = reader.readLine(); 135 if (line == null) { 136 return; 137 } 138 while ((line = reader.readLine()) != null) { 139 final int index = line.indexOf(' '); 140 final int uid = Integer.parseInt(line.substring(0, index - 1), 10); 141 readTimesForUid(uid, line.substring(index + 1, line.length()), callback); 142 } 143 } 144 145 private void readTimesForUid(int uid, String line, Callback callback) { 146 long[] uidTimeMs = mLastUidCpuFreqTimeMs.get(uid); 147 if (uidTimeMs == null) { 148 uidTimeMs = new long[mCpuFreqsCount]; 149 mLastUidCpuFreqTimeMs.put(uid, uidTimeMs); 150 } 151 final String[] timesStr = line.split(" "); 152 final int size = timesStr.length; 153 if (size != uidTimeMs.length) { 154 Slog.e(TAG, "No. of readings don't match cpu freqs, readings: " + size 155 + " cpuFreqsCount: " + uidTimeMs.length); 156 return; 157 } 158 final long[] deltaUidTimeMs = new long[size]; 159 final long[] curUidTimeMs = new long[size]; 160 boolean notify = false; 161 for (int i = 0; i < size; ++i) { 162 // Times read will be in units of 10ms 163 final long totalTimeMs = Long.parseLong(timesStr[i], 10) * 10; 164 deltaUidTimeMs[i] = totalTimeMs - uidTimeMs[i]; 165 // If there is malformed data for any uid, then we just log about it and ignore 166 // the data for that uid. 167 if (deltaUidTimeMs[i] < 0 || totalTimeMs < 0) { 168 final StringBuilder sb = new StringBuilder("Malformed cpu freq data for UID=") 169 .append(uid).append("\n"); 170 sb.append("data=").append("(").append(uidTimeMs[i]).append(",") 171 .append(totalTimeMs).append(")").append("\n"); 172 sb.append("times=").append("("); 173 TimeUtils.formatDuration(mLastTimeReadMs, sb); sb.append(","); 174 TimeUtils.formatDuration(mNowTimeMs, sb); sb.append(")"); 175 Slog.wtf(TAG, sb.toString()); 176 return; 177 } 178 curUidTimeMs[i] = totalTimeMs; 179 notify = notify || (deltaUidTimeMs[i] > 0); 180 } 181 if (notify) { 182 System.arraycopy(curUidTimeMs, 0, uidTimeMs, 0, size); 183 if (callback != null) { 184 callback.onUidCpuFreqTime(uid, deltaUidTimeMs); 185 } 186 } 187 } 188 189 private long[] readCpuFreqs(String line, PowerProfile powerProfile) { 190 final String[] freqStr = line.split(" "); 191 // First item would be "uid: " which needs to be ignored. 192 mCpuFreqsCount = freqStr.length - 1; 193 mCpuFreqs = new long[mCpuFreqsCount]; 194 for (int i = 0; i < mCpuFreqsCount; ++i) { 195 mCpuFreqs[i] = Long.parseLong(freqStr[i + 1], 10); 196 } 197 198 // Check if the freqs in the proc file correspond to per-cluster freqs. 199 final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs(); 200 final int numClusters = powerProfile.getNumCpuClusters(); 201 if (numClusterFreqs.size() == numClusters) { 202 mPerClusterTimesAvailable = true; 203 for (int i = 0; i < numClusters; ++i) { 204 if (numClusterFreqs.get(i) != powerProfile.getNumSpeedStepsInCpuCluster(i)) { 205 mPerClusterTimesAvailable = false; 206 break; 207 } 208 } 209 } else { 210 mPerClusterTimesAvailable = false; 211 } 212 Slog.i(TAG, "mPerClusterTimesAvailable=" + mPerClusterTimesAvailable); 213 214 return mCpuFreqs; 215 } 216 217 /** 218 * Extracts no. of cpu clusters and no. of freqs in each of these clusters from the freqs 219 * read from the proc file. 220 * 221 * We need to assume that freqs in each cluster are strictly increasing. 222 * For e.g. if the freqs read from proc file are: 12, 34, 15, 45, 12, 15, 52. Then it means 223 * there are 3 clusters: (12, 34), (15, 45), (12, 15, 52) 224 * 225 * @return an IntArray filled with no. of freqs in each cluster. 226 */ 227 private IntArray extractClusterInfoFromProcFileFreqs() { 228 final IntArray numClusterFreqs = new IntArray(); 229 int freqsFound = 0; 230 for (int i = 0; i < mCpuFreqsCount; ++i) { 231 freqsFound++; 232 if (i + 1 == mCpuFreqsCount || mCpuFreqs[i + 1] <= mCpuFreqs[i]) { 233 numClusterFreqs.add(freqsFound); 234 freqsFound = 0; 235 } 236 } 237 return numClusterFreqs; 238 } 239} 240