| /* |
| * 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'; |
| } |
| } |