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 com.android.internal.annotations.VisibleForTesting; 24 25 import java.io.FileNotFoundException; 26 import java.io.IOException; 27 import java.nio.ByteBuffer; 28 import java.nio.ByteOrder; 29 import java.nio.channels.FileChannel; 30 import java.nio.file.NoSuchFileException; 31 import java.nio.file.Path; 32 import java.nio.file.Paths; 33 import java.nio.file.StandardOpenOption; 34 35 /** 36 * Reads cpu time proc files with throttling (adjustable interval). 37 * 38 * KernelCpuProcReader is implemented as singletons for built-in kernel proc files. Get___Instance() 39 * method will return corresponding reader instance. In order to prevent frequent GC, 40 * KernelCpuProcReader reuses a {@link ByteBuffer} to store data read from proc files. 41 * 42 * A KernelCpuProcReader instance keeps an error counter. When the number of read errors within that 43 * instance accumulates to 5, this instance will reject all further read requests. 44 * 45 * Each KernelCpuProcReader instance also has a throttler. Throttle interval can be adjusted via 46 * {@link #setThrottleInterval(long)} method. Default throttle interval is 3000ms. If current 47 * timestamp based on {@link SystemClock#elapsedRealtime()} is less than throttle interval from 48 * the last read timestamp, {@link #readBytes()} will return previous result. 49 * 50 * A KernelCpuProcReader instance is thread-unsafe. Caller needs to hold a lock on this object while 51 * accessing its instance methods or digesting the return values. 52 */ 53 public class KernelCpuProcReader { 54 private static final String TAG = "KernelCpuProcReader"; 55 private static final int ERROR_THRESHOLD = 5; 56 // Throttle interval in milliseconds 57 private static final long DEFAULT_THROTTLE_INTERVAL = 3000L; 58 private static final int INITIAL_BUFFER_SIZE = 8 * 1024; 59 private static final int MAX_BUFFER_SIZE = 1024 * 1024; 60 private static final String PROC_UID_FREQ_TIME = "/proc/uid_cpupower/time_in_state"; 61 private static final String PROC_UID_ACTIVE_TIME = "/proc/uid_cpupower/concurrent_active_time"; 62 private static final String PROC_UID_CLUSTER_TIME = "/proc/uid_cpupower/concurrent_policy_time"; 63 64 private static final KernelCpuProcReader mFreqTimeReader = new KernelCpuProcReader( 65 PROC_UID_FREQ_TIME); 66 private static final KernelCpuProcReader mActiveTimeReader = new KernelCpuProcReader( 67 PROC_UID_ACTIVE_TIME); 68 private static final KernelCpuProcReader mClusterTimeReader = new KernelCpuProcReader( 69 PROC_UID_CLUSTER_TIME); 70 getFreqTimeReaderInstance()71 public static KernelCpuProcReader getFreqTimeReaderInstance() { 72 return mFreqTimeReader; 73 } 74 getActiveTimeReaderInstance()75 public static KernelCpuProcReader getActiveTimeReaderInstance() { 76 return mActiveTimeReader; 77 } 78 getClusterTimeReaderInstance()79 public static KernelCpuProcReader getClusterTimeReaderInstance() { 80 return mClusterTimeReader; 81 } 82 83 private int mErrors; 84 private long mThrottleInterval = DEFAULT_THROTTLE_INTERVAL; 85 private long mLastReadTime = Long.MIN_VALUE; 86 private final Path mProc; 87 private ByteBuffer mBuffer; 88 89 @VisibleForTesting KernelCpuProcReader(String procFile)90 public KernelCpuProcReader(String procFile) { 91 mProc = Paths.get(procFile); 92 mBuffer = ByteBuffer.allocateDirect(INITIAL_BUFFER_SIZE); 93 mBuffer.clear(); 94 } 95 96 /** 97 * Reads all bytes from the corresponding proc file. 98 * 99 * If elapsed time since last call to this method is less than the throttle interval, it will 100 * return previous result. When IOException accumulates to 5, it will always return null. This 101 * method is thread-unsafe, so is the return value. Caller needs to hold a lock on this 102 * object while calling this method and digesting its return value. 103 * 104 * @return a {@link ByteBuffer} containing all bytes from the proc file. 105 */ readBytes()106 public ByteBuffer readBytes() { 107 if (mErrors >= ERROR_THRESHOLD) { 108 return null; 109 } 110 if (SystemClock.elapsedRealtime() < mLastReadTime + mThrottleInterval) { 111 if (mBuffer.limit() > 0 && mBuffer.limit() < mBuffer.capacity()) { 112 // mBuffer has data. 113 return mBuffer.asReadOnlyBuffer().order(ByteOrder.nativeOrder()); 114 } 115 return null; 116 } 117 mLastReadTime = SystemClock.elapsedRealtime(); 118 mBuffer.clear(); 119 final int oldMask = StrictMode.allowThreadDiskReadsMask(); 120 try (FileChannel fc = FileChannel.open(mProc, StandardOpenOption.READ)) { 121 while (fc.read(mBuffer) == mBuffer.capacity()) { 122 if (!resize()) { 123 mErrors++; 124 Slog.e(TAG, "Proc file is too large: " + mProc); 125 return null; 126 } 127 fc.position(0); 128 } 129 } catch (NoSuchFileException | FileNotFoundException e) { 130 // Happens when the kernel does not provide this file. Not a big issue. Just log it. 131 mErrors++; 132 Slog.w(TAG, "File not exist: " + mProc); 133 return null; 134 } catch (IOException e) { 135 mErrors++; 136 Slog.e(TAG, "Error reading: " + mProc, e); 137 return null; 138 } finally { 139 StrictMode.setThreadPolicyMask(oldMask); 140 } 141 mBuffer.flip(); 142 return mBuffer.asReadOnlyBuffer().order(ByteOrder.nativeOrder()); 143 } 144 145 /** 146 * Sets the throttle interval. Set to 0 will disable throttling. Thread-unsafe, holding a lock 147 * on this object is recommended. 148 * 149 * @param throttleInterval throttle interval in milliseconds 150 */ setThrottleInterval(long throttleInterval)151 public void setThrottleInterval(long throttleInterval) { 152 if (throttleInterval >= 0) { 153 mThrottleInterval = throttleInterval; 154 } 155 } 156 resize()157 private boolean resize() { 158 if (mBuffer.capacity() >= MAX_BUFFER_SIZE) { 159 return false; 160 } 161 int newSize = Math.min(mBuffer.capacity() << 1, MAX_BUFFER_SIZE); 162 // Slog.i(TAG, "Resize buffer " + mBuffer.capacity() + " => " + newSize); 163 mBuffer = ByteBuffer.allocateDirect(newSize); 164 return true; 165 } 166 } 167