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 17 package com.android.internal.os; 18 19 import static com.android.internal.util.Preconditions.checkNotNull; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.os.StrictMode; 24 import android.util.IntArray; 25 import android.util.Slog; 26 import android.util.SparseArray; 27 28 import com.android.internal.annotations.VisibleForTesting; 29 30 import java.io.BufferedReader; 31 import java.io.FileReader; 32 import java.io.IOException; 33 import java.nio.ByteBuffer; 34 import java.nio.IntBuffer; 35 import java.util.function.Consumer; 36 37 /** 38 * Reads /proc/uid_time_in_state which has the format: 39 * 40 * uid: [freq1] [freq2] [freq3] ... 41 * [uid1]: [time in freq1] [time in freq2] [time in freq3] ... 42 * [uid2]: [time in freq1] [time in freq2] [time in freq3] ... 43 * ... 44 * 45 * Binary variation reads /proc/uid_cpupower/time_in_state in the following format: 46 * [n, uid0, time0a, time0b, ..., time0n, 47 * uid1, time1a, time1b, ..., time1n, 48 * uid2, time2a, time2b, ..., time2n, etc.] 49 * where n is the total number of frequencies. 50 * 51 * This provides the times a UID's processes spent executing at each different cpu frequency. 52 * The file contains a monotonically increasing count of time for a single boot. This class 53 * maintains the previous results of a call to {@link #readDelta} in order to provide a proper 54 * delta. 55 * 56 * This class uses a throttler to reject any {@link #readDelta} call within 57 * {@link #mThrottleInterval}. This is different from the throttler in {@link KernelCpuProcReader}, 58 * which has a shorter throttle interval and returns cached result from last read when the request 59 * is throttled. 60 * 61 * This class is NOT thread-safe and NOT designed to be accessed by more than one caller since each 62 * caller has its own view of delta. 63 */ 64 public class KernelUidCpuFreqTimeReader extends 65 KernelUidCpuTimeReaderBase<KernelUidCpuFreqTimeReader.Callback> { 66 private static final String TAG = KernelUidCpuFreqTimeReader.class.getSimpleName(); 67 static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state"; 68 69 public interface Callback extends KernelUidCpuTimeReaderBase.Callback { onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs)70 void onUidCpuFreqTime(int uid, long[] cpuFreqTimeMs); 71 } 72 73 private long[] mCpuFreqs; 74 private long[] mCurTimes; // Reuse to prevent GC. 75 private long[] mDeltaTimes; // Reuse to prevent GC. 76 private int mCpuFreqsCount; 77 private final KernelCpuProcReader mProcReader; 78 79 private SparseArray<long[]> mLastUidCpuFreqTimeMs = new SparseArray<>(); 80 81 // We check the existence of proc file a few times (just in case it is not ready yet when we 82 // start reading) and if it is not available, we simply ignore further read requests. 83 private static final int TOTAL_READ_ERROR_COUNT = 5; 84 private int mReadErrorCounter; 85 private boolean mPerClusterTimesAvailable; 86 private boolean mAllUidTimesAvailable = true; 87 KernelUidCpuFreqTimeReader()88 public KernelUidCpuFreqTimeReader() { 89 mProcReader = KernelCpuProcReader.getFreqTimeReaderInstance(); 90 } 91 92 @VisibleForTesting KernelUidCpuFreqTimeReader(KernelCpuProcReader procReader)93 public KernelUidCpuFreqTimeReader(KernelCpuProcReader procReader) { 94 mProcReader = procReader; 95 } 96 perClusterTimesAvailable()97 public boolean perClusterTimesAvailable() { 98 return mPerClusterTimesAvailable; 99 } 100 allUidTimesAvailable()101 public boolean allUidTimesAvailable() { 102 return mAllUidTimesAvailable; 103 } 104 getAllUidCpuFreqTimeMs()105 public SparseArray<long[]> getAllUidCpuFreqTimeMs() { 106 return mLastUidCpuFreqTimeMs; 107 } 108 readFreqs(@onNull PowerProfile powerProfile)109 public long[] readFreqs(@NonNull PowerProfile powerProfile) { 110 checkNotNull(powerProfile); 111 if (mCpuFreqs != null) { 112 // No need to read cpu freqs more than once. 113 return mCpuFreqs; 114 } 115 if (!mAllUidTimesAvailable) { 116 return null; 117 } 118 final int oldMask = StrictMode.allowThreadDiskReadsMask(); 119 try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) { 120 return readFreqs(reader, powerProfile); 121 } catch (IOException e) { 122 if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) { 123 mAllUidTimesAvailable = false; 124 } 125 Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e); 126 return null; 127 } finally { 128 StrictMode.setThreadPolicyMask(oldMask); 129 } 130 } 131 132 @VisibleForTesting readFreqs(BufferedReader reader, PowerProfile powerProfile)133 public long[] readFreqs(BufferedReader reader, PowerProfile powerProfile) 134 throws IOException { 135 final String line = reader.readLine(); 136 if (line == null) { 137 return null; 138 } 139 final String[] freqStr = line.split(" "); 140 // First item would be "uid: " which needs to be ignored. 141 mCpuFreqsCount = freqStr.length - 1; 142 mCpuFreqs = new long[mCpuFreqsCount]; 143 mCurTimes = new long[mCpuFreqsCount]; 144 mDeltaTimes = new long[mCpuFreqsCount]; 145 for (int i = 0; i < mCpuFreqsCount; ++i) { 146 mCpuFreqs[i] = Long.parseLong(freqStr[i + 1], 10); 147 } 148 149 // Check if the freqs in the proc file correspond to per-cluster freqs. 150 final IntArray numClusterFreqs = extractClusterInfoFromProcFileFreqs(); 151 final int numClusters = powerProfile.getNumCpuClusters(); 152 if (numClusterFreqs.size() == numClusters) { 153 mPerClusterTimesAvailable = true; 154 for (int i = 0; i < numClusters; ++i) { 155 if (numClusterFreqs.get(i) != powerProfile.getNumSpeedStepsInCpuCluster(i)) { 156 mPerClusterTimesAvailable = false; 157 break; 158 } 159 } 160 } else { 161 mPerClusterTimesAvailable = false; 162 } 163 Slog.i(TAG, "mPerClusterTimesAvailable=" + mPerClusterTimesAvailable); 164 return mCpuFreqs; 165 } 166 167 @Override 168 @VisibleForTesting readDeltaImpl(@ullable Callback callback)169 public void readDeltaImpl(@Nullable Callback callback) { 170 if (mCpuFreqs == null) { 171 return; 172 } 173 readImpl((buf) -> { 174 int uid = buf.get(); 175 long[] lastTimes = mLastUidCpuFreqTimeMs.get(uid); 176 if (lastTimes == null) { 177 lastTimes = new long[mCpuFreqsCount]; 178 mLastUidCpuFreqTimeMs.put(uid, lastTimes); 179 } 180 if (!getFreqTimeForUid(buf, mCurTimes)) { 181 return; 182 } 183 boolean notify = false; 184 boolean valid = true; 185 for (int i = 0; i < mCpuFreqsCount; i++) { 186 mDeltaTimes[i] = mCurTimes[i] - lastTimes[i]; 187 if (mDeltaTimes[i] < 0) { 188 Slog.e(TAG, "Negative delta from freq time proc: " + mDeltaTimes[i]); 189 valid = false; 190 } 191 notify |= mDeltaTimes[i] > 0; 192 } 193 if (notify && valid) { 194 System.arraycopy(mCurTimes, 0, lastTimes, 0, mCpuFreqsCount); 195 if (callback != null) { 196 callback.onUidCpuFreqTime(uid, mDeltaTimes); 197 } 198 } 199 }); 200 } 201 readAbsolute(Callback callback)202 public void readAbsolute(Callback callback) { 203 readImpl((buf) -> { 204 int uid = buf.get(); 205 if (getFreqTimeForUid(buf, mCurTimes)) { 206 callback.onUidCpuFreqTime(uid, mCurTimes); 207 } 208 }); 209 } 210 getFreqTimeForUid(IntBuffer buffer, long[] freqTime)211 private boolean getFreqTimeForUid(IntBuffer buffer, long[] freqTime) { 212 boolean valid = true; 213 for (int i = 0; i < mCpuFreqsCount; i++) { 214 freqTime[i] = (long) buffer.get() * 10; // Unit is 10ms. 215 if (freqTime[i] < 0) { 216 Slog.e(TAG, "Negative time from freq time proc: " + freqTime[i]); 217 valid = false; 218 } 219 } 220 return valid; 221 } 222 223 /** 224 * readImpl accepts a callback to process the uid entry. readDeltaImpl needs to store the last 225 * seen results while processing the buffer, while readAbsolute returns the absolute value read 226 * from the buffer without storing. So readImpl contains the common logic of the two, leaving 227 * the difference to a processUid function. 228 * 229 * @param processUid the callback function to process the uid entry in the buffer. 230 */ readImpl(Consumer<IntBuffer> processUid)231 private void readImpl(Consumer<IntBuffer> processUid) { 232 synchronized (mProcReader) { 233 ByteBuffer bytes = mProcReader.readBytes(); 234 if (bytes == null || bytes.remaining() <= 4) { 235 // Error already logged in mProcReader. 236 return; 237 } 238 if ((bytes.remaining() & 3) != 0) { 239 Slog.wtf(TAG, "Cannot parse freq time proc bytes to int: " + bytes.remaining()); 240 return; 241 } 242 IntBuffer buf = bytes.asIntBuffer(); 243 final int freqs = buf.get(); 244 if (freqs != mCpuFreqsCount) { 245 Slog.wtf(TAG, "Cpu freqs expect " + mCpuFreqsCount + " , got " + freqs); 246 return; 247 } 248 if (buf.remaining() % (freqs + 1) != 0) { 249 Slog.wtf(TAG, "Freq time format error: " + buf.remaining() + " / " + (freqs + 1)); 250 return; 251 } 252 int numUids = buf.remaining() / (freqs + 1); 253 for (int i = 0; i < numUids; i++) { 254 processUid.accept(buf); 255 } 256 if (DEBUG) { 257 Slog.d(TAG, "Read uids: #" + numUids); 258 } 259 } 260 } 261 removeUid(int uid)262 public void removeUid(int uid) { 263 mLastUidCpuFreqTimeMs.delete(uid); 264 } 265 removeUidsInRange(int startUid, int endUid)266 public void removeUidsInRange(int startUid, int endUid) { 267 mLastUidCpuFreqTimeMs.put(startUid, null); 268 mLastUidCpuFreqTimeMs.put(endUid, null); 269 final int firstIndex = mLastUidCpuFreqTimeMs.indexOfKey(startUid); 270 final int lastIndex = mLastUidCpuFreqTimeMs.indexOfKey(endUid); 271 mLastUidCpuFreqTimeMs.removeAtRange(firstIndex, lastIndex - firstIndex + 1); 272 } 273 274 /** 275 * Extracts no. of cpu clusters and no. of freqs in each of these clusters from the freqs 276 * read from the proc file. 277 * 278 * We need to assume that freqs in each cluster are strictly increasing. 279 * For e.g. if the freqs read from proc file are: 12, 34, 15, 45, 12, 15, 52. Then it means 280 * there are 3 clusters: (12, 34), (15, 45), (12, 15, 52) 281 * 282 * @return an IntArray filled with no. of freqs in each cluster. 283 */ extractClusterInfoFromProcFileFreqs()284 private IntArray extractClusterInfoFromProcFileFreqs() { 285 final IntArray numClusterFreqs = new IntArray(); 286 int freqsFound = 0; 287 for (int i = 0; i < mCpuFreqsCount; ++i) { 288 freqsFound++; 289 if (i + 1 == mCpuFreqsCount || mCpuFreqs[i + 1] <= mCpuFreqs[i]) { 290 numClusterFreqs.add(freqsFound); 291 freqsFound = 0; 292 } 293 } 294 return numClusterFreqs; 295 } 296 } 297