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