1 /* 2 * Copyright (C) 2018 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 17 package com.android.internal.os; 18 19 import android.os.StrictMode; 20 import android.util.Slog; 21 22 import java.io.BufferedReader; 23 import java.io.FileNotFoundException; 24 import java.io.IOException; 25 import java.nio.CharBuffer; 26 import java.nio.file.Files; 27 import java.nio.file.NoSuchFileException; 28 import java.nio.file.Path; 29 import java.nio.file.Paths; 30 import java.util.Arrays; 31 import java.util.concurrent.locks.ReentrantReadWriteLock; 32 33 /** 34 * Reads human-readable cpu time proc files. 35 * 36 * It is implemented as singletons for built-in kernel proc files. Get___Instance() method will 37 * return corresponding reader instance. In order to prevent frequent GC, it reuses the same char[] 38 * to store data read from proc files. 39 * 40 * A KernelCpuProcStringReader instance keeps an error counter. When the number of read errors 41 * within that instance accumulates to 5, this instance will reject all further read requests. 42 * 43 * Data fetched within last 500ms is considered fresh, since the reading lifecycle can take up to 44 * 100ms. KernelCpuProcStringReader always tries to use cache if it is fresh and valid, but it can 45 * be disabled through a parameter. 46 * 47 * A KernelCpuProcReader instance is thread-safe. It acquires a write lock when reading the proc 48 * file, releases it right after, then acquires a read lock before returning a ProcFileIterator. 49 * Caller is responsible for closing ProcFileIterator (also auto-closable) after reading, otherwise 50 * deadlock will occur. 51 */ 52 public class KernelCpuProcStringReader { 53 private static final String TAG = KernelCpuProcStringReader.class.getSimpleName(); 54 private static final int ERROR_THRESHOLD = 5; 55 // Data read within the last 500ms is considered fresh. 56 private static final long FRESHNESS = 500L; 57 private static final int MAX_BUFFER_SIZE = 1024 * 1024; 58 59 private static final String PROC_UID_FREQ_TIME = "/proc/uid_time_in_state"; 60 private static final String PROC_UID_ACTIVE_TIME = "/proc/uid_concurrent_active_time"; 61 private static final String PROC_UID_CLUSTER_TIME = "/proc/uid_concurrent_policy_time"; 62 private static final String PROC_UID_USER_SYS_TIME = "/proc/uid_cputime/show_uid_stat"; 63 64 private static final KernelCpuProcStringReader FREQ_TIME_READER = 65 new KernelCpuProcStringReader(PROC_UID_FREQ_TIME); 66 private static final KernelCpuProcStringReader ACTIVE_TIME_READER = 67 new KernelCpuProcStringReader(PROC_UID_ACTIVE_TIME); 68 private static final KernelCpuProcStringReader CLUSTER_TIME_READER = 69 new KernelCpuProcStringReader(PROC_UID_CLUSTER_TIME); 70 private static final KernelCpuProcStringReader USER_SYS_TIME_READER = 71 new KernelCpuProcStringReader(PROC_UID_USER_SYS_TIME); 72 getFreqTimeReaderInstance()73 static KernelCpuProcStringReader getFreqTimeReaderInstance() { 74 return FREQ_TIME_READER; 75 } 76 getActiveTimeReaderInstance()77 static KernelCpuProcStringReader getActiveTimeReaderInstance() { 78 return ACTIVE_TIME_READER; 79 } 80 getClusterTimeReaderInstance()81 static KernelCpuProcStringReader getClusterTimeReaderInstance() { 82 return CLUSTER_TIME_READER; 83 } 84 getUserSysTimeReaderInstance()85 static KernelCpuProcStringReader getUserSysTimeReaderInstance() { 86 return USER_SYS_TIME_READER; 87 } 88 89 private int mErrors = 0; 90 private final Path mFile; 91 private final Clock mClock; 92 private char[] mBuf; 93 private int mSize; 94 private long mLastReadTime = 0; 95 private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock(); 96 private final ReentrantReadWriteLock.ReadLock mReadLock = mLock.readLock(); 97 private final ReentrantReadWriteLock.WriteLock mWriteLock = mLock.writeLock(); 98 KernelCpuProcStringReader(String file)99 public KernelCpuProcStringReader(String file) { 100 this(file, Clock.SYSTEM_CLOCK); 101 } 102 KernelCpuProcStringReader(String file, Clock clock)103 public KernelCpuProcStringReader(String file, Clock clock) { 104 mFile = Paths.get(file); 105 mClock = clock; 106 } 107 108 /** 109 * @see #open(boolean) Default behavior is trying to use cache. 110 */ open()111 public ProcFileIterator open() { 112 return open(false); 113 } 114 115 /** 116 * Opens the proc file and buffers all its content, which can be traversed through a 117 * ProcFileIterator. 118 * 119 * This method will tolerate at most 5 errors. After that, it will always return null. This is 120 * to save resources and to prevent log spam. 121 * 122 * This method is thread-safe. It first checks if there are other threads holding read/write 123 * lock. If there are, it assumes data is fresh and reuses the data. 124 * 125 * A read lock is automatically acquired when a valid ProcFileIterator is returned. Caller MUST 126 * call {@link ProcFileIterator#close()} when it is done to release the lock. 127 * 128 * @param ignoreCache If true, ignores the cache and refreshes the data anyway. 129 * @return A {@link ProcFileIterator} to iterate through the file content, or null if there is 130 * error. 131 */ open(boolean ignoreCache)132 public ProcFileIterator open(boolean ignoreCache) { 133 if (mErrors >= ERROR_THRESHOLD) { 134 return null; 135 } 136 137 if (ignoreCache) { 138 mWriteLock.lock(); 139 } else { 140 mReadLock.lock(); 141 if (dataValid()) { 142 return new ProcFileIterator(mSize); 143 } 144 mReadLock.unlock(); 145 mWriteLock.lock(); 146 if (dataValid()) { 147 // Recheck because another thread might have written data just before we did. 148 mReadLock.lock(); 149 mWriteLock.unlock(); 150 return new ProcFileIterator(mSize); 151 } 152 } 153 154 // At this point, write lock is held and data is invalid. 155 int total = 0; 156 int curr; 157 mSize = 0; 158 final int oldMask = StrictMode.allowThreadDiskReadsMask(); 159 try (BufferedReader r = Files.newBufferedReader(mFile)) { 160 if (mBuf == null) { 161 mBuf = new char[1024]; 162 } 163 while ((curr = r.read(mBuf, total, mBuf.length - total)) >= 0) { 164 total += curr; 165 if (total == mBuf.length) { 166 // Hit the limit. Resize buffer. 167 if (mBuf.length == MAX_BUFFER_SIZE) { 168 mErrors++; 169 Slog.e(TAG, "Proc file too large: " + mFile); 170 return null; 171 } 172 mBuf = Arrays.copyOf(mBuf, Math.min(mBuf.length << 1, MAX_BUFFER_SIZE)); 173 } 174 } 175 mSize = total; 176 mLastReadTime = mClock.elapsedRealtime(); 177 // ReentrantReadWriteLock allows lock downgrading. 178 mReadLock.lock(); 179 return new ProcFileIterator(total); 180 } catch (FileNotFoundException | NoSuchFileException e) { 181 mErrors++; 182 Slog.w(TAG, "File not found. It's normal if not implemented: " + mFile); 183 } catch (IOException e) { 184 mErrors++; 185 Slog.e(TAG, "Error reading " + mFile, e); 186 } finally { 187 StrictMode.setThreadPolicyMask(oldMask); 188 mWriteLock.unlock(); 189 } 190 return null; 191 } 192 dataValid()193 private boolean dataValid() { 194 return mSize > 0 && (mClock.elapsedRealtime() - mLastReadTime < FRESHNESS); 195 } 196 197 /** 198 * An autoCloseable iterator to iterate through a string proc file line by line. User must call 199 * close() when finish using to prevent deadlock. 200 */ 201 public class ProcFileIterator implements AutoCloseable { 202 private final int mSize; 203 private int mPos; 204 ProcFileIterator(int size)205 public ProcFileIterator(int size) { 206 mSize = size; 207 } 208 209 /** @return Whether there are more lines in the iterator. */ hasNextLine()210 public boolean hasNextLine() { 211 return mPos < mSize; 212 } 213 214 /** 215 * Fetches the next line. Note that all subsequent return values share the same char[] 216 * under the hood. 217 * 218 * @return A {@link java.nio.CharBuffer} containing the next line without the new line 219 * symbol. 220 */ nextLine()221 public CharBuffer nextLine() { 222 if (mPos >= mSize) { 223 return null; 224 } 225 int i = mPos; 226 // Move i to the next new line symbol, which is always '\n' in Android. 227 while (i < mSize && mBuf[i] != '\n') { 228 i++; 229 } 230 int start = mPos; 231 mPos = i + 1; 232 return CharBuffer.wrap(mBuf, start, i - start); 233 } 234 235 /** Total size of the proc file in chars. */ size()236 public int size() { 237 return mSize; 238 } 239 240 /** Must call close at the end to release the read lock! Or use try-with-resources. */ close()241 public void close() { 242 mReadLock.unlock(); 243 } 244 245 246 } 247 248 /** 249 * Converts all numbers in the CharBuffer into longs, and puts into the given long[]. 250 * 251 * Space and colon are treated as delimiters. All other chars are not allowed. All numbers 252 * are non-negative. To avoid GC, caller should try to use the same array for all calls. 253 * 254 * This method also resets the given buffer to the original position before return so that 255 * it can be read again. 256 * 257 * @param buf The char buffer to be converted. 258 * @param array An array to store the parsed numbers. 259 * @return The number of elements written to the given array. -1 if buf is null, -2 if buf 260 * contains invalid char, -3 if any number overflows. 261 */ asLongs(CharBuffer buf, long[] array)262 public static int asLongs(CharBuffer buf, long[] array) { 263 if (buf == null) { 264 return -1; 265 } 266 final int initialPos = buf.position(); 267 int count = 0; 268 long num = -1; 269 char c; 270 271 while (buf.remaining() > 0 && count < array.length) { 272 c = buf.get(); 273 if (!(isNumber(c) || c == ' ' || c == ':')) { 274 buf.position(initialPos); 275 return -2; 276 } 277 if (num < 0) { 278 if (isNumber(c)) { 279 num = c - '0'; 280 } 281 } else { 282 if (isNumber(c)) { 283 num = num * 10 + c - '0'; 284 if (num < 0) { 285 buf.position(initialPos); 286 return -3; 287 } 288 } else { 289 array[count++] = num; 290 num = -1; 291 } 292 } 293 } 294 if (num >= 0) { 295 array[count++] = num; 296 } 297 buf.position(initialPos); 298 return count; 299 } 300 isNumber(char c)301 private static boolean isNumber(char c) { 302 return c >= '0' && c <= '9'; 303 } 304 } 305