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