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 20 import android.annotation.NonNull; 21 import android.util.Slog; 22 import android.util.SparseArray; 23 24 import com.android.internal.annotations.GuardedBy; 25 import com.android.internal.annotations.VisibleForTesting; 26 27 import dalvik.annotation.optimization.CriticalNative; 28 29 import java.io.IOException; 30 import java.nio.ByteBuffer; 31 import java.nio.ByteOrder; 32 import java.nio.file.Files; 33 import java.nio.file.Paths; 34 import java.util.Arrays; 35 36 public class KernelSingleUidTimeReader { 37 private static final String TAG = KernelSingleUidTimeReader.class.getName(); 38 private static final boolean DBG = false; 39 40 private static final String PROC_FILE_DIR = "/proc/uid/"; 41 private static final String PROC_FILE_NAME = "/time_in_state"; 42 private static final String UID_TIMES_PROC_FILE = "/proc/uid_time_in_state"; 43 44 @VisibleForTesting 45 public static final int TOTAL_READ_ERROR_COUNT = 5; 46 47 @GuardedBy("this") 48 private final int mCpuFreqsCount; 49 50 @GuardedBy("this") 51 private SparseArray<long[]> mLastUidCpuTimeMs = new SparseArray<>(); 52 53 @GuardedBy("this") 54 private int mReadErrorCounter; 55 @GuardedBy("this") 56 private boolean mSingleUidCpuTimesAvailable = true; 57 @GuardedBy("this") 58 private boolean mBpfTimesAvailable = true; 59 // We use the freq count obtained from /proc/uid_time_in_state to decide how many longs 60 // to read from each /proc/uid/<uid>/time_in_state. On the first read, verify if this is 61 // correct and if not, set {@link #mSingleUidCpuTimesAvailable} to false. This flag will 62 // indicate whether we checked for validity or not. 63 @GuardedBy("this") 64 private boolean mCpuFreqsCountVerified; 65 66 private final Injector mInjector; 67 canReadBpfTimes()68 private static final native boolean canReadBpfTimes(); 69 KernelSingleUidTimeReader(int cpuFreqsCount)70 public KernelSingleUidTimeReader(int cpuFreqsCount) { 71 this(cpuFreqsCount, new Injector()); 72 } 73 KernelSingleUidTimeReader(int cpuFreqsCount, Injector injector)74 public KernelSingleUidTimeReader(int cpuFreqsCount, Injector injector) { 75 mInjector = injector; 76 mCpuFreqsCount = cpuFreqsCount; 77 if (mCpuFreqsCount == 0) { 78 mSingleUidCpuTimesAvailable = false; 79 } 80 } 81 singleUidCpuTimesAvailable()82 public boolean singleUidCpuTimesAvailable() { 83 return mSingleUidCpuTimesAvailable; 84 } 85 readDeltaMs(int uid)86 public long[] readDeltaMs(int uid) { 87 synchronized (this) { 88 if (!mSingleUidCpuTimesAvailable) { 89 return null; 90 } 91 if (mBpfTimesAvailable) { 92 final long[] cpuTimesMs = mInjector.readBpfData(uid); 93 if (cpuTimesMs.length == 0) { 94 mBpfTimesAvailable = false; 95 } else if (!mCpuFreqsCountVerified && cpuTimesMs.length != mCpuFreqsCount) { 96 mSingleUidCpuTimesAvailable = false; 97 return null; 98 } else { 99 mCpuFreqsCountVerified = true; 100 return computeDelta(uid, cpuTimesMs); 101 } 102 } 103 // Read total cpu times from the proc file. 104 final String procFile = new StringBuilder(PROC_FILE_DIR) 105 .append(uid) 106 .append(PROC_FILE_NAME).toString(); 107 final long[] cpuTimesMs; 108 try { 109 final byte[] data = mInjector.readData(procFile); 110 if (!mCpuFreqsCountVerified) { 111 verifyCpuFreqsCount(data.length, procFile); 112 } 113 final ByteBuffer buffer = ByteBuffer.wrap(data); 114 buffer.order(ByteOrder.nativeOrder()); 115 cpuTimesMs = readCpuTimesFromByteBuffer(buffer); 116 } catch (Exception e) { 117 if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) { 118 mSingleUidCpuTimesAvailable = false; 119 } 120 if (DBG) Slog.e(TAG, "Some error occured while reading " + procFile, e); 121 return null; 122 } 123 124 return computeDelta(uid, cpuTimesMs); 125 } 126 } 127 verifyCpuFreqsCount(int numBytes, String procFile)128 private void verifyCpuFreqsCount(int numBytes, String procFile) { 129 final int actualCount = (numBytes / Long.BYTES); 130 if (mCpuFreqsCount != actualCount) { 131 mSingleUidCpuTimesAvailable = false; 132 throw new IllegalStateException("Freq count didn't match," 133 + "count from " + UID_TIMES_PROC_FILE + "=" + mCpuFreqsCount + ", but" 134 + "count from " + procFile + "=" + actualCount); 135 } 136 mCpuFreqsCountVerified = true; 137 } 138 readCpuTimesFromByteBuffer(ByteBuffer buffer)139 private long[] readCpuTimesFromByteBuffer(ByteBuffer buffer) { 140 final long[] cpuTimesMs; 141 cpuTimesMs = new long[mCpuFreqsCount]; 142 for (int i = 0; i < mCpuFreqsCount; ++i) { 143 // Times read will be in units of 10ms 144 cpuTimesMs[i] = buffer.getLong() * 10; 145 } 146 return cpuTimesMs; 147 } 148 149 /** 150 * Compute and return cpu times delta of an uid using previously read cpu times and 151 * {@param latestCpuTimesMs}. 152 * 153 * @return delta of cpu times if at least one of the cpu time at a freq is +ve, otherwise null. 154 */ computeDelta(int uid, @NonNull long[] latestCpuTimesMs)155 public long[] computeDelta(int uid, @NonNull long[] latestCpuTimesMs) { 156 synchronized (this) { 157 if (!mSingleUidCpuTimesAvailable) { 158 return null; 159 } 160 // Subtract the last read cpu times to get deltas. 161 final long[] lastCpuTimesMs = mLastUidCpuTimeMs.get(uid); 162 final long[] deltaTimesMs = getDeltaLocked(lastCpuTimesMs, latestCpuTimesMs); 163 if (deltaTimesMs == null) { 164 if (DBG) Slog.e(TAG, "Malformed data read for uid=" + uid 165 + "; last=" + Arrays.toString(lastCpuTimesMs) 166 + "; latest=" + Arrays.toString(latestCpuTimesMs)); 167 return null; 168 } 169 // If all elements are zero, return null to avoid unnecessary work on the caller side. 170 boolean hasNonZero = false; 171 for (int i = deltaTimesMs.length - 1; i >= 0; --i) { 172 if (deltaTimesMs[i] > 0) { 173 hasNonZero = true; 174 break; 175 } 176 } 177 if (hasNonZero) { 178 mLastUidCpuTimeMs.put(uid, latestCpuTimesMs); 179 return deltaTimesMs; 180 } else { 181 return null; 182 } 183 } 184 } 185 186 /** 187 * Returns null if the latest cpu times are not valid**, otherwise delta of 188 * {@param latestCpuTimesMs} and {@param lastCpuTimesMs}. 189 * 190 * **latest cpu times are considered valid if all the cpu times are +ve and 191 * greater than or equal to previously read cpu times. 192 */ 193 @GuardedBy("this") 194 @VisibleForTesting(visibility = PACKAGE) getDeltaLocked(long[] lastCpuTimesMs, @NonNull long[] latestCpuTimesMs)195 public long[] getDeltaLocked(long[] lastCpuTimesMs, @NonNull long[] latestCpuTimesMs) { 196 for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) { 197 if (latestCpuTimesMs[i] < 0) { 198 return null; 199 } 200 } 201 if (lastCpuTimesMs == null) { 202 return latestCpuTimesMs; 203 } 204 final long[] deltaTimesMs = new long[latestCpuTimesMs.length]; 205 for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) { 206 deltaTimesMs[i] = latestCpuTimesMs[i] - lastCpuTimesMs[i]; 207 if (deltaTimesMs[i] < 0) { 208 return null; 209 } 210 } 211 return deltaTimesMs; 212 } 213 setAllUidsCpuTimesMs(SparseArray<long[]> allUidsCpuTimesMs)214 public void setAllUidsCpuTimesMs(SparseArray<long[]> allUidsCpuTimesMs) { 215 synchronized (this) { 216 mLastUidCpuTimeMs.clear(); 217 for (int i = allUidsCpuTimesMs.size() - 1; i >= 0; --i) { 218 final long[] cpuTimesMs = allUidsCpuTimesMs.valueAt(i); 219 if (cpuTimesMs != null) { 220 mLastUidCpuTimeMs.put(allUidsCpuTimesMs.keyAt(i), cpuTimesMs.clone()); 221 } 222 } 223 } 224 } 225 removeUid(int uid)226 public void removeUid(int uid) { 227 synchronized (this) { 228 mLastUidCpuTimeMs.delete(uid); 229 } 230 } 231 removeUidsInRange(int startUid, int endUid)232 public void removeUidsInRange(int startUid, int endUid) { 233 if (endUid < startUid) { 234 return; 235 } 236 synchronized (this) { 237 mLastUidCpuTimeMs.put(startUid, null); 238 mLastUidCpuTimeMs.put(endUid, null); 239 final int startIdx = mLastUidCpuTimeMs.indexOfKey(startUid); 240 final int endIdx = mLastUidCpuTimeMs.indexOfKey(endUid); 241 mLastUidCpuTimeMs.removeAtRange(startIdx, endIdx - startIdx + 1); 242 } 243 } 244 245 /** 246 * Retrieves CPU time-in-state data for the specified UID and adds the accumulated 247 * delta to the supplied counter. 248 */ addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs)249 public void addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs) { 250 mInjector.addDelta(uid, counter, timestampMs, null); 251 } 252 253 /** 254 * Same as {@link #addDelta(int, LongArrayMultiStateCounter, long)}, also returning 255 * the delta in the supplied array container. 256 */ addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs, LongArrayMultiStateCounter.LongArrayContainer deltaContainer)257 public void addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs, 258 LongArrayMultiStateCounter.LongArrayContainer deltaContainer) { 259 mInjector.addDelta(uid, counter, timestampMs, deltaContainer); 260 } 261 262 @VisibleForTesting 263 public static class Injector { readData(String procFile)264 public byte[] readData(String procFile) throws IOException { 265 return Files.readAllBytes(Paths.get(procFile)); 266 } 267 readBpfData(int uid)268 public native long[] readBpfData(int uid); 269 270 /** 271 * Reads CPU time-in-state data for the specified UID and adds the delta since the 272 * previous call to the current state stats in the LongArrayMultiStateCounter. 273 * 274 * The delta is also returned via the optional deltaOut parameter. 275 */ addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs, LongArrayMultiStateCounter.LongArrayContainer deltaOut)276 public boolean addDelta(int uid, LongArrayMultiStateCounter counter, long timestampMs, 277 LongArrayMultiStateCounter.LongArrayContainer deltaOut) { 278 return addDeltaFromBpf(uid, counter.mNativeObject, timestampMs, 279 deltaOut != null ? deltaOut.mNativeObject : 0); 280 } 281 282 @CriticalNative addDeltaFromBpf(int uid, long longArrayMultiStateCounterNativePointer, long timestampMs, long longArrayContainerNativePointer)283 private static native boolean addDeltaFromBpf(int uid, 284 long longArrayMultiStateCounterNativePointer, long timestampMs, 285 long longArrayContainerNativePointer); 286 287 /** 288 * Used for testing. 289 * 290 * Takes mock cpu-time-in-frequency data and uses it the same way eBPF data would be used. 291 */ addDeltaForTest(int uid, LongArrayMultiStateCounter counter, long timestampMs, long[][] timeInFreqDataNanos, LongArrayMultiStateCounter.LongArrayContainer deltaOut)292 public boolean addDeltaForTest(int uid, LongArrayMultiStateCounter counter, 293 long timestampMs, long[][] timeInFreqDataNanos, 294 LongArrayMultiStateCounter.LongArrayContainer deltaOut) { 295 return addDeltaForTest(uid, counter.mNativeObject, timestampMs, timeInFreqDataNanos, 296 deltaOut != null ? deltaOut.mNativeObject : 0); 297 } 298 addDeltaForTest(int uid, long longArrayMultiStateCounterNativePointer, long timestampMs, long[][] timeInFreqDataNanos, long longArrayContainerNativePointer)299 private static native boolean addDeltaForTest(int uid, 300 long longArrayMultiStateCounterNativePointer, long timestampMs, 301 long[][] timeInFreqDataNanos, long longArrayContainerNativePointer); 302 } 303 304 @VisibleForTesting getLastUidCpuTimeMs()305 public SparseArray<long[]> getLastUidCpuTimeMs() { 306 return mLastUidCpuTimeMs; 307 } 308 309 @VisibleForTesting setSingleUidCpuTimesAvailable(boolean singleUidCpuTimesAvailable)310 public void setSingleUidCpuTimesAvailable(boolean singleUidCpuTimesAvailable) { 311 mSingleUidCpuTimesAvailable = singleUidCpuTimesAvailable; 312 } 313 }