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