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