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 package com.android.internal.os; 17 18 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; 19 import static com.android.internal.os.KernelUidCpuFreqTimeReader.UID_TIMES_PROC_FILE; 20 21 import android.annotation.NonNull; 22 import android.util.Slog; 23 import android.util.SparseArray; 24 25 import com.android.internal.annotations.GuardedBy; 26 import com.android.internal.annotations.VisibleForTesting; 27 28 import java.io.IOException; 29 import java.nio.ByteBuffer; 30 import java.nio.ByteOrder; 31 import java.nio.file.Files; 32 import java.nio.file.Paths; 33 import java.util.Arrays; 34 35 @VisibleForTesting(visibility = PACKAGE) 36 public class KernelSingleUidTimeReader { 37 private final String TAG = KernelUidCpuFreqTimeReader.class.getName(); 38 private final boolean DBG = false; 39 40 private final String PROC_FILE_DIR = "/proc/uid/"; 41 private final String PROC_FILE_NAME = "/time_in_state"; 42 43 @VisibleForTesting 44 public static final int TOTAL_READ_ERROR_COUNT = 5; 45 46 @GuardedBy("this") 47 private final int mCpuFreqsCount; 48 49 @GuardedBy("this") 50 private SparseArray<long[]> mLastUidCpuTimeMs = new SparseArray<>(); 51 52 @GuardedBy("this") 53 private int mReadErrorCounter; 54 @GuardedBy("this") 55 private boolean mSingleUidCpuTimesAvailable = true; 56 @GuardedBy("this") 57 private boolean mHasStaleData; 58 // We use the freq count obtained from /proc/uid_time_in_state to decide how many longs 59 // to read from each /proc/uid/<uid>/time_in_state. On the first read, verify if this is 60 // correct and if not, set {@link #mSingleUidCpuTimesAvailable} to false. This flag will 61 // indicate whether we checked for validity or not. 62 @GuardedBy("this") 63 private boolean mCpuFreqsCountVerified; 64 65 private final Injector mInjector; 66 KernelSingleUidTimeReader(int cpuFreqsCount)67 KernelSingleUidTimeReader(int cpuFreqsCount) { 68 this(cpuFreqsCount, new Injector()); 69 } 70 KernelSingleUidTimeReader(int cpuFreqsCount, Injector injector)71 public KernelSingleUidTimeReader(int cpuFreqsCount, Injector injector) { 72 mInjector = injector; 73 mCpuFreqsCount = cpuFreqsCount; 74 if (mCpuFreqsCount == 0) { 75 mSingleUidCpuTimesAvailable = false; 76 } 77 } 78 singleUidCpuTimesAvailable()79 public boolean singleUidCpuTimesAvailable() { 80 return mSingleUidCpuTimesAvailable; 81 } 82 readDeltaMs(int uid)83 public long[] readDeltaMs(int uid) { 84 synchronized (this) { 85 if (!mSingleUidCpuTimesAvailable) { 86 return null; 87 } 88 // Read total cpu times from the proc file. 89 final String procFile = new StringBuilder(PROC_FILE_DIR) 90 .append(uid) 91 .append(PROC_FILE_NAME).toString(); 92 final long[] cpuTimesMs; 93 try { 94 final byte[] data = mInjector.readData(procFile); 95 if (!mCpuFreqsCountVerified) { 96 verifyCpuFreqsCount(data.length, procFile); 97 } 98 final ByteBuffer buffer = ByteBuffer.wrap(data); 99 buffer.order(ByteOrder.nativeOrder()); 100 cpuTimesMs = readCpuTimesFromByteBuffer(buffer); 101 } catch (Exception e) { 102 if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) { 103 mSingleUidCpuTimesAvailable = false; 104 } 105 if (DBG) Slog.e(TAG, "Some error occured while reading " + procFile, e); 106 return null; 107 } 108 109 return computeDelta(uid, cpuTimesMs); 110 } 111 } 112 verifyCpuFreqsCount(int numBytes, String procFile)113 private void verifyCpuFreqsCount(int numBytes, String procFile) { 114 final int actualCount = (numBytes / Long.BYTES); 115 if (mCpuFreqsCount != actualCount) { 116 mSingleUidCpuTimesAvailable = false; 117 throw new IllegalStateException("Freq count didn't match," 118 + "count from " + UID_TIMES_PROC_FILE + "=" + mCpuFreqsCount + ", but" 119 + "count from " + procFile + "=" + actualCount); 120 } 121 mCpuFreqsCountVerified = true; 122 } 123 readCpuTimesFromByteBuffer(ByteBuffer buffer)124 private long[] readCpuTimesFromByteBuffer(ByteBuffer buffer) { 125 final long[] cpuTimesMs; 126 cpuTimesMs = new long[mCpuFreqsCount]; 127 for (int i = 0; i < mCpuFreqsCount; ++i) { 128 // Times read will be in units of 10ms 129 cpuTimesMs[i] = buffer.getLong() * 10; 130 } 131 return cpuTimesMs; 132 } 133 134 /** 135 * Compute and return cpu times delta of an uid using previously read cpu times and 136 * {@param latestCpuTimesMs}. 137 * 138 * @return delta of cpu times if at least one of the cpu time at a freq is +ve, otherwise null. 139 */ computeDelta(int uid, @NonNull long[] latestCpuTimesMs)140 public long[] computeDelta(int uid, @NonNull long[] latestCpuTimesMs) { 141 synchronized (this) { 142 if (!mSingleUidCpuTimesAvailable) { 143 return null; 144 } 145 // Subtract the last read cpu times to get deltas. 146 final long[] lastCpuTimesMs = mLastUidCpuTimeMs.get(uid); 147 final long[] deltaTimesMs = getDeltaLocked(lastCpuTimesMs, latestCpuTimesMs); 148 if (deltaTimesMs == null) { 149 if (DBG) Slog.e(TAG, "Malformed data read for uid=" + uid 150 + "; last=" + Arrays.toString(lastCpuTimesMs) 151 + "; latest=" + Arrays.toString(latestCpuTimesMs)); 152 return null; 153 } 154 // If all elements are zero, return null to avoid unnecessary work on the caller side. 155 boolean hasNonZero = false; 156 for (int i = deltaTimesMs.length - 1; i >= 0; --i) { 157 if (deltaTimesMs[i] > 0) { 158 hasNonZero = true; 159 break; 160 } 161 } 162 if (hasNonZero) { 163 mLastUidCpuTimeMs.put(uid, latestCpuTimesMs); 164 return deltaTimesMs; 165 } else { 166 return null; 167 } 168 } 169 } 170 171 /** 172 * Returns null if the latest cpu times are not valid**, otherwise delta of 173 * {@param latestCpuTimesMs} and {@param lastCpuTimesMs}. 174 * 175 * **latest cpu times are considered valid if all the cpu times are +ve and 176 * greater than or equal to previously read cpu times. 177 */ 178 @GuardedBy("this") 179 @VisibleForTesting(visibility = PACKAGE) getDeltaLocked(long[] lastCpuTimesMs, @NonNull long[] latestCpuTimesMs)180 public long[] getDeltaLocked(long[] lastCpuTimesMs, @NonNull long[] latestCpuTimesMs) { 181 for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) { 182 if (latestCpuTimesMs[i] < 0) { 183 return null; 184 } 185 } 186 if (lastCpuTimesMs == null) { 187 return latestCpuTimesMs; 188 } 189 final long[] deltaTimesMs = new long[latestCpuTimesMs.length]; 190 for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) { 191 deltaTimesMs[i] = latestCpuTimesMs[i] - lastCpuTimesMs[i]; 192 if (deltaTimesMs[i] < 0) { 193 return null; 194 } 195 } 196 return deltaTimesMs; 197 } 198 markDataAsStale(boolean hasStaleData)199 public void markDataAsStale(boolean hasStaleData) { 200 synchronized (this) { 201 mHasStaleData = hasStaleData; 202 } 203 } 204 hasStaleData()205 public boolean hasStaleData() { 206 synchronized (this) { 207 return mHasStaleData; 208 } 209 } 210 setAllUidsCpuTimesMs(SparseArray<long[]> allUidsCpuTimesMs)211 public void setAllUidsCpuTimesMs(SparseArray<long[]> allUidsCpuTimesMs) { 212 synchronized (this) { 213 mLastUidCpuTimeMs.clear(); 214 for (int i = allUidsCpuTimesMs.size() - 1; i >= 0; --i) { 215 final long[] cpuTimesMs = allUidsCpuTimesMs.valueAt(i); 216 if (cpuTimesMs != null) { 217 mLastUidCpuTimeMs.put(allUidsCpuTimesMs.keyAt(i), cpuTimesMs.clone()); 218 } 219 } 220 } 221 } 222 removeUid(int uid)223 public void removeUid(int uid) { 224 synchronized (this) { 225 mLastUidCpuTimeMs.delete(uid); 226 } 227 } 228 removeUidsInRange(int startUid, int endUid)229 public void removeUidsInRange(int startUid, int endUid) { 230 if (endUid < startUid) { 231 return; 232 } 233 synchronized (this) { 234 mLastUidCpuTimeMs.put(startUid, null); 235 mLastUidCpuTimeMs.put(endUid, null); 236 final int startIdx = mLastUidCpuTimeMs.indexOfKey(startUid); 237 final int endIdx = mLastUidCpuTimeMs.indexOfKey(endUid); 238 mLastUidCpuTimeMs.removeAtRange(startIdx, endIdx - startIdx + 1); 239 } 240 } 241 242 @VisibleForTesting 243 public static class Injector { readData(String procFile)244 public byte[] readData(String procFile) throws IOException { 245 return Files.readAllBytes(Paths.get(procFile)); 246 } 247 } 248 249 @VisibleForTesting getLastUidCpuTimeMs()250 public SparseArray<long[]> getLastUidCpuTimeMs() { 251 return mLastUidCpuTimeMs; 252 } 253 254 @VisibleForTesting setSingleUidCpuTimesAvailable(boolean singleUidCpuTimesAvailable)255 public void setSingleUidCpuTimesAvailable(boolean singleUidCpuTimesAvailable) { 256 mSingleUidCpuTimesAvailable = singleUidCpuTimesAvailable; 257 } 258 }