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