305 lines
11 KiB
Java
305 lines
11 KiB
Java
/*
|
|
* 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.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 final Clock mClock;
|
|
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) {
|
|
this(file, Clock.SYSTEM_CLOCK);
|
|
}
|
|
|
|
public KernelCpuProcStringReader(String file, Clock clock) {
|
|
mFile = Paths.get(file);
|
|
mClock = clock;
|
|
}
|
|
|
|
/**
|
|
* @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 = mClock.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 && (mClock.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';
|
|
}
|
|
}
|