| /* |
| * 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 com.android.internal.annotations.VisibleForTesting; |
| |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| import java.nio.ByteBuffer; |
| import java.nio.ByteOrder; |
| import java.nio.channels.FileChannel; |
| import java.nio.file.NoSuchFileException; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.nio.file.StandardOpenOption; |
| |
| /** |
| * Reads cpu time proc files with throttling (adjustable interval). |
| * |
| * KernelCpuProcReader is implemented as singletons for built-in kernel proc files. Get___Instance() |
| * method will return corresponding reader instance. In order to prevent frequent GC, |
| * KernelCpuProcReader reuses a {@link ByteBuffer} to store data read from proc files. |
| * |
| * A KernelCpuProcReader 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. |
| * |
| * Each KernelCpuProcReader instance also has a throttler. Throttle interval can be adjusted via |
| * {@link #setThrottleInterval(long)} method. Default throttle interval is 3000ms. If current |
| * timestamp based on {@link SystemClock#elapsedRealtime()} is less than throttle interval from |
| * the last read timestamp, {@link #readBytes()} will return previous result. |
| * |
| * A KernelCpuProcReader instance is thread-unsafe. Caller needs to hold a lock on this object while |
| * accessing its instance methods or digesting the return values. |
| */ |
| public class KernelCpuProcReader { |
| private static final String TAG = "KernelCpuProcReader"; |
| private static final int ERROR_THRESHOLD = 5; |
| // Throttle interval in milliseconds |
| private static final long DEFAULT_THROTTLE_INTERVAL = 3000L; |
| private static final int INITIAL_BUFFER_SIZE = 8 * 1024; |
| private static final int MAX_BUFFER_SIZE = 1024 * 1024; |
| private static final String PROC_UID_FREQ_TIME = "/proc/uid_cpupower/time_in_state"; |
| private static final String PROC_UID_ACTIVE_TIME = "/proc/uid_cpupower/concurrent_active_time"; |
| private static final String PROC_UID_CLUSTER_TIME = "/proc/uid_cpupower/concurrent_policy_time"; |
| |
| private static final KernelCpuProcReader mFreqTimeReader = new KernelCpuProcReader( |
| PROC_UID_FREQ_TIME); |
| private static final KernelCpuProcReader mActiveTimeReader = new KernelCpuProcReader( |
| PROC_UID_ACTIVE_TIME); |
| private static final KernelCpuProcReader mClusterTimeReader = new KernelCpuProcReader( |
| PROC_UID_CLUSTER_TIME); |
| |
| public static KernelCpuProcReader getFreqTimeReaderInstance() { |
| return mFreqTimeReader; |
| } |
| |
| public static KernelCpuProcReader getActiveTimeReaderInstance() { |
| return mActiveTimeReader; |
| } |
| |
| public static KernelCpuProcReader getClusterTimeReaderInstance() { |
| return mClusterTimeReader; |
| } |
| |
| private int mErrors; |
| private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL; |
| private long mLastReadTime = Long.MIN_VALUE; |
| private final Path mProc; |
| private ByteBuffer mBuffer; |
| |
| @VisibleForTesting |
| public KernelCpuProcReader(String procFile) { |
| mProc = Paths.get(procFile); |
| mBuffer = ByteBuffer.allocateDirect(INITIAL_BUFFER_SIZE); |
| mBuffer.clear(); |
| } |
| |
| /** |
| * Reads all bytes from the corresponding proc file. |
| * |
| * If elapsed time since last call to this method is less than the throttle interval, it will |
| * return previous result. When IOException accumulates to 5, it will always return null. This |
| * method is thread-unsafe, so is the return value. Caller needs to hold a lock on this |
| * object while calling this method and digesting its return value. |
| * |
| * @return a {@link ByteBuffer} containing all bytes from the proc file. |
| */ |
| public ByteBuffer readBytes() { |
| if (mErrors >= ERROR_THRESHOLD) { |
| return null; |
| } |
| if (SystemClock.elapsedRealtime() < mLastReadTime + mThrottleInterval) { |
| if (mBuffer.limit() > 0 && mBuffer.limit() < mBuffer.capacity()) { |
| // mBuffer has data. |
| return mBuffer.asReadOnlyBuffer().order(ByteOrder.nativeOrder()); |
| } |
| return null; |
| } |
| mLastReadTime = SystemClock.elapsedRealtime(); |
| mBuffer.clear(); |
| final int oldMask = StrictMode.allowThreadDiskReadsMask(); |
| try (FileChannel fc = FileChannel.open(mProc, StandardOpenOption.READ)) { |
| while (fc.read(mBuffer) == mBuffer.capacity()) { |
| if (!resize()) { |
| mErrors++; |
| Slog.e(TAG, "Proc file is too large: " + mProc); |
| return null; |
| } |
| fc.position(0); |
| } |
| } catch (NoSuchFileException | FileNotFoundException e) { |
| // Happens when the kernel does not provide this file. Not a big issue. Just log it. |
| mErrors++; |
| Slog.w(TAG, "File not exist: " + mProc); |
| return null; |
| } catch (IOException e) { |
| mErrors++; |
| Slog.e(TAG, "Error reading: " + mProc, e); |
| return null; |
| } finally { |
| StrictMode.setThreadPolicyMask(oldMask); |
| } |
| mBuffer.flip(); |
| return mBuffer.asReadOnlyBuffer().order(ByteOrder.nativeOrder()); |
| } |
| |
| /** |
| * Sets the throttle interval. Set to 0 will disable throttling. Thread-unsafe, holding a lock |
| * on this object is recommended. |
| * |
| * @param throttleInterval throttle interval in milliseconds |
| */ |
| public void setThrottleInterval(long throttleInterval) { |
| if (throttleInterval >= 0) { |
| mThrottleInterval = throttleInterval; |
| } |
| } |
| |
| private boolean resize() { |
| if (mBuffer.capacity() >= MAX_BUFFER_SIZE) { |
| return false; |
| } |
| int newSize = Math.min(mBuffer.capacity() << 1, MAX_BUFFER_SIZE); |
| // Slog.i(TAG, "Resize buffer " + mBuffer.capacity() + " => " + newSize); |
| mBuffer = ByteBuffer.allocateDirect(newSize); |
| return true; |
| } |
| } |