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