blob: b3aec0c2a56108f7fd474912aabee1fad9611866 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.os;
import android.os.StrictMode;
import android.os.SystemClock;
import android.util.Slog;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.CharBuffer;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Reads human-readable cpu time proc files.
*
* It is implemented as singletons for built-in kernel proc files. Get___Instance() method will
* return corresponding reader instance. In order to prevent frequent GC, it reuses the same char[]
* to store data read from proc files.
*
* A KernelCpuProcStringReader instance keeps an error counter. When the number of read errors
* within that instance accumulates to 5, this instance will reject all further read requests.
*
* Data fetched within last 500ms is considered fresh, since the reading lifecycle can take up to
* 100ms. KernelCpuProcStringReader always tries to use cache if it is fresh and valid, but it can
* be disabled through a parameter.
*
* A KernelCpuProcReader instance is thread-safe. It acquires a write lock when reading the proc
* file, releases it right after, then acquires a read lock before returning a ProcFileIterator.
* Caller is responsible for closing ProcFileIterator (also auto-closable) after reading, otherwise
* deadlock will occur.
*/
public class KernelCpuProcStringReader {
private static final String TAG = KernelCpuProcStringReader.class.getSimpleName();
private static final int ERROR_THRESHOLD = 5;
// Data read within the last 500ms is considered fresh.
private static final long FRESHNESS = 500L;
private static final int MAX_BUFFER_SIZE = 1024 * 1024;
private static final String PROC_UID_FREQ_TIME = "/proc/uid_time_in_state";
private static final String PROC_UID_ACTIVE_TIME = "/proc/uid_concurrent_active_time";
private static final String PROC_UID_CLUSTER_TIME = "/proc/uid_concurrent_policy_time";
private static final String PROC_UID_USER_SYS_TIME = "/proc/uid_cputime/show_uid_stat";
private static final KernelCpuProcStringReader FREQ_TIME_READER =
new KernelCpuProcStringReader(PROC_UID_FREQ_TIME);
private static final KernelCpuProcStringReader ACTIVE_TIME_READER =
new KernelCpuProcStringReader(PROC_UID_ACTIVE_TIME);
private static final KernelCpuProcStringReader CLUSTER_TIME_READER =
new KernelCpuProcStringReader(PROC_UID_CLUSTER_TIME);
private static final KernelCpuProcStringReader USER_SYS_TIME_READER =
new KernelCpuProcStringReader(PROC_UID_USER_SYS_TIME);
static KernelCpuProcStringReader getFreqTimeReaderInstance() {
return FREQ_TIME_READER;
}
static KernelCpuProcStringReader getActiveTimeReaderInstance() {
return ACTIVE_TIME_READER;
}
static KernelCpuProcStringReader getClusterTimeReaderInstance() {
return CLUSTER_TIME_READER;
}
static KernelCpuProcStringReader getUserSysTimeReaderInstance() {
return USER_SYS_TIME_READER;
}
private int mErrors = 0;
private final Path mFile;
private char[] mBuf;
private int mSize;
private long mLastReadTime = 0;
private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
private final ReentrantReadWriteLock.ReadLock mReadLock = mLock.readLock();
private final ReentrantReadWriteLock.WriteLock mWriteLock = mLock.writeLock();
public KernelCpuProcStringReader(String file) {
mFile = Paths.get(file);
}
/**
* @see #open(boolean) Default behavior is trying to use cache.
*/
public ProcFileIterator open() {
return open(false);
}
/**
* Opens the proc file and buffers all its content, which can be traversed through a
* ProcFileIterator.
*
* This method will tolerate at most 5 errors. After that, it will always return null. This is
* to save resources and to prevent log spam.
*
* This method is thread-safe. It first checks if there are other threads holding read/write
* lock. If there are, it assumes data is fresh and reuses the data.
*
* A read lock is automatically acquired when a valid ProcFileIterator is returned. Caller MUST
* call {@link ProcFileIterator#close()} when it is done to release the lock.
*
* @param ignoreCache If true, ignores the cache and refreshes the data anyway.
* @return A {@link ProcFileIterator} to iterate through the file content, or null if there is
* error.
*/
public ProcFileIterator open(boolean ignoreCache) {
if (mErrors >= ERROR_THRESHOLD) {
return null;
}
if (ignoreCache) {
mWriteLock.lock();
} else {
mReadLock.lock();
if (dataValid()) {
return new ProcFileIterator(mSize);
}
mReadLock.unlock();
mWriteLock.lock();
if (dataValid()) {
// Recheck because another thread might have written data just before we did.
mReadLock.lock();
mWriteLock.unlock();
return new ProcFileIterator(mSize);
}
}
// At this point, write lock is held and data is invalid.
int total = 0;
int curr;
mSize = 0;
final int oldMask = StrictMode.allowThreadDiskReadsMask();
try (BufferedReader r = Files.newBufferedReader(mFile)) {
if (mBuf == null) {
mBuf = new char[1024];
}
while ((curr = r.read(mBuf, total, mBuf.length - total)) >= 0) {
total += curr;
if (total == mBuf.length) {
// Hit the limit. Resize buffer.
if (mBuf.length == MAX_BUFFER_SIZE) {
mErrors++;
Slog.e(TAG, "Proc file too large: " + mFile);
return null;
}
mBuf = Arrays.copyOf(mBuf, Math.min(mBuf.length << 1, MAX_BUFFER_SIZE));
}
}
mSize = total;
mLastReadTime = SystemClock.elapsedRealtime();
// ReentrantReadWriteLock allows lock downgrading.
mReadLock.lock();
return new ProcFileIterator(total);
} catch (FileNotFoundException | NoSuchFileException e) {
mErrors++;
Slog.w(TAG, "File not found. It's normal if not implemented: " + mFile);
} catch (IOException e) {
mErrors++;
Slog.e(TAG, "Error reading " + mFile, e);
} finally {
StrictMode.setThreadPolicyMask(oldMask);
mWriteLock.unlock();
}
return null;
}
private boolean dataValid() {
return mSize > 0 && (SystemClock.elapsedRealtime() - mLastReadTime < FRESHNESS);
}
/**
* An autoCloseable iterator to iterate through a string proc file line by line. User must call
* close() when finish using to prevent deadlock.
*/
public class ProcFileIterator implements AutoCloseable {
private final int mSize;
private int mPos;
public ProcFileIterator(int size) {
mSize = size;
}
/** @return Whether there are more lines in the iterator. */
public boolean hasNextLine() {
return mPos < mSize;
}
/**
* Fetches the next line. Note that all subsequent return values share the same char[]
* under the hood.
*
* @return A {@link java.nio.CharBuffer} containing the next line without the new line
* symbol.
*/
public CharBuffer nextLine() {
if (mPos >= mSize) {
return null;
}
int i = mPos;
// Move i to the next new line symbol, which is always '\n' in Android.
while (i < mSize && mBuf[i] != '\n') {
i++;
}
int start = mPos;
mPos = i + 1;
return CharBuffer.wrap(mBuf, start, i - start);
}
/** Total size of the proc file in chars. */
public int size() {
return mSize;
}
/** Must call close at the end to release the read lock! Or use try-with-resources. */
public void close() {
mReadLock.unlock();
}
}
/**
* Converts all numbers in the CharBuffer into longs, and puts into the given long[].
*
* Space and colon are treated as delimiters. All other chars are not allowed. All numbers
* are non-negative. To avoid GC, caller should try to use the same array for all calls.
*
* This method also resets the given buffer to the original position before return so that
* it can be read again.
*
* @param buf The char buffer to be converted.
* @param array An array to store the parsed numbers.
* @return The number of elements written to the given array. -1 if buf is null, -2 if buf
* contains invalid char, -3 if any number overflows.
*/
public static int asLongs(CharBuffer buf, long[] array) {
if (buf == null) {
return -1;
}
final int initialPos = buf.position();
int count = 0;
long num = -1;
char c;
while (buf.remaining() > 0 && count < array.length) {
c = buf.get();
if (!(isNumber(c) || c == ' ' || c == ':')) {
buf.position(initialPos);
return -2;
}
if (num < 0) {
if (isNumber(c)) {
num = c - '0';
}
} else {
if (isNumber(c)) {
num = num * 10 + c - '0';
if (num < 0) {
buf.position(initialPos);
return -3;
}
} else {
array[count++] = num;
num = -1;
}
}
}
if (num >= 0) {
array[count++] = num;
}
buf.position(initialPos);
return count;
}
private static boolean isNumber(char c) {
return c >= '0' && c <= '9';
}
}