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 java.io.BufferedReader;
24 import java.io.FileNotFoundException;
25 import java.io.IOException;
26 import java.nio.CharBuffer;
27 import java.nio.file.Files;
28 import java.nio.file.NoSuchFileException;
29 import java.nio.file.Path;
30 import java.nio.file.Paths;
31 import java.util.Arrays;
32 import java.util.concurrent.locks.ReentrantReadWriteLock;
33 
34 /**
35  * Reads human-readable cpu time proc files.
36  *
37  * It is implemented as singletons for built-in kernel proc files. Get___Instance() method will
38  * return corresponding reader instance. In order to prevent frequent GC, it reuses the same char[]
39  * to store data read from proc files.
40  *
41  * A KernelCpuProcStringReader instance keeps an error counter. When the number of read errors
42  * within that instance accumulates to 5, this instance will reject all further read requests.
43  *
44  * Data fetched within last 500ms is considered fresh, since the reading lifecycle can take up to
45  * 100ms. KernelCpuProcStringReader always tries to use cache if it is fresh and valid, but it can
46  * be disabled through a parameter.
47  *
48  * A KernelCpuProcReader instance is thread-safe. It acquires a write lock when reading the proc
49  * file, releases it right after, then acquires a read lock before returning a ProcFileIterator.
50  * Caller is responsible for closing ProcFileIterator (also auto-closable) after reading, otherwise
51  * deadlock will occur.
52  */
53 public class KernelCpuProcStringReader {
54     private static final String TAG = KernelCpuProcStringReader.class.getSimpleName();
55     private static final int ERROR_THRESHOLD = 5;
56     // Data read within the last 500ms is considered fresh.
57     private static final long FRESHNESS = 500L;
58     private static final int MAX_BUFFER_SIZE = 1024 * 1024;
59 
60     private static final String PROC_UID_FREQ_TIME = "/proc/uid_time_in_state";
61     private static final String PROC_UID_ACTIVE_TIME = "/proc/uid_concurrent_active_time";
62     private static final String PROC_UID_CLUSTER_TIME = "/proc/uid_concurrent_policy_time";
63     private static final String PROC_UID_USER_SYS_TIME = "/proc/uid_cputime/show_uid_stat";
64 
65     private static final KernelCpuProcStringReader FREQ_TIME_READER =
66             new KernelCpuProcStringReader(PROC_UID_FREQ_TIME);
67     private static final KernelCpuProcStringReader ACTIVE_TIME_READER =
68             new KernelCpuProcStringReader(PROC_UID_ACTIVE_TIME);
69     private static final KernelCpuProcStringReader CLUSTER_TIME_READER =
70             new KernelCpuProcStringReader(PROC_UID_CLUSTER_TIME);
71     private static final KernelCpuProcStringReader USER_SYS_TIME_READER =
72             new KernelCpuProcStringReader(PROC_UID_USER_SYS_TIME);
73 
getFreqTimeReaderInstance()74     static KernelCpuProcStringReader getFreqTimeReaderInstance() {
75         return FREQ_TIME_READER;
76     }
77 
getActiveTimeReaderInstance()78     static KernelCpuProcStringReader getActiveTimeReaderInstance() {
79         return ACTIVE_TIME_READER;
80     }
81 
getClusterTimeReaderInstance()82     static KernelCpuProcStringReader getClusterTimeReaderInstance() {
83         return CLUSTER_TIME_READER;
84     }
85 
getUserSysTimeReaderInstance()86     static KernelCpuProcStringReader getUserSysTimeReaderInstance() {
87         return USER_SYS_TIME_READER;
88     }
89 
90     private int mErrors = 0;
91     private final Path mFile;
92     private char[] mBuf;
93     private int mSize;
94     private long mLastReadTime = 0;
95     private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
96     private final ReentrantReadWriteLock.ReadLock mReadLock = mLock.readLock();
97     private final ReentrantReadWriteLock.WriteLock mWriteLock = mLock.writeLock();
98 
KernelCpuProcStringReader(String file)99     public KernelCpuProcStringReader(String file) {
100         mFile = Paths.get(file);
101     }
102 
103     /**
104      * @see #open(boolean) Default behavior is trying to use cache.
105      */
open()106     public ProcFileIterator open() {
107         return open(false);
108     }
109 
110     /**
111      * Opens the proc file and buffers all its content, which can be traversed through a
112      * ProcFileIterator.
113      *
114      * This method will tolerate at most 5 errors. After that, it will always return null. This is
115      * to save resources and to prevent log spam.
116      *
117      * This method is thread-safe. It first checks if there are other threads holding read/write
118      * lock. If there are, it assumes data is fresh and reuses the data.
119      *
120      * A read lock is automatically acquired when a valid ProcFileIterator is returned. Caller MUST
121      * call {@link ProcFileIterator#close()} when it is done to release the lock.
122      *
123      * @param ignoreCache If true, ignores the cache and refreshes the data anyway.
124      * @return A {@link ProcFileIterator} to iterate through the file content, or null if there is
125      * error.
126      */
open(boolean ignoreCache)127     public ProcFileIterator open(boolean ignoreCache) {
128         if (mErrors >= ERROR_THRESHOLD) {
129             return null;
130         }
131 
132         if (ignoreCache) {
133             mWriteLock.lock();
134         } else {
135             mReadLock.lock();
136             if (dataValid()) {
137                 return new ProcFileIterator(mSize);
138             }
139             mReadLock.unlock();
140             mWriteLock.lock();
141             if (dataValid()) {
142                 // Recheck because another thread might have written data just before we did.
143                 mReadLock.lock();
144                 mWriteLock.unlock();
145                 return new ProcFileIterator(mSize);
146             }
147         }
148 
149         // At this point, write lock is held and data is invalid.
150         int total = 0;
151         int curr;
152         mSize = 0;
153         final int oldMask = StrictMode.allowThreadDiskReadsMask();
154         try (BufferedReader r = Files.newBufferedReader(mFile)) {
155             if (mBuf == null) {
156                 mBuf = new char[1024];
157             }
158             while ((curr = r.read(mBuf, total, mBuf.length - total)) >= 0) {
159                 total += curr;
160                 if (total == mBuf.length) {
161                     // Hit the limit. Resize buffer.
162                     if (mBuf.length == MAX_BUFFER_SIZE) {
163                         mErrors++;
164                         Slog.e(TAG, "Proc file too large: " + mFile);
165                         return null;
166                     }
167                     mBuf = Arrays.copyOf(mBuf, Math.min(mBuf.length << 1, MAX_BUFFER_SIZE));
168                 }
169             }
170             mSize = total;
171             mLastReadTime = SystemClock.elapsedRealtime();
172             // ReentrantReadWriteLock allows lock downgrading.
173             mReadLock.lock();
174             return new ProcFileIterator(total);
175         } catch (FileNotFoundException | NoSuchFileException e) {
176             mErrors++;
177             Slog.w(TAG, "File not found. It's normal if not implemented: " + mFile);
178         } catch (IOException e) {
179             mErrors++;
180             Slog.e(TAG, "Error reading " + mFile, e);
181         } finally {
182             StrictMode.setThreadPolicyMask(oldMask);
183             mWriteLock.unlock();
184         }
185         return null;
186     }
187 
dataValid()188     private boolean dataValid() {
189         return mSize > 0 && (SystemClock.elapsedRealtime() - mLastReadTime < FRESHNESS);
190     }
191 
192     /**
193      * An autoCloseable iterator to iterate through a string proc file line by line. User must call
194      * close() when finish using to prevent deadlock.
195      */
196     public class ProcFileIterator implements AutoCloseable {
197         private final int mSize;
198         private int mPos;
199 
ProcFileIterator(int size)200         public ProcFileIterator(int size) {
201             mSize = size;
202         }
203 
204         /** @return Whether there are more lines in the iterator. */
hasNextLine()205         public boolean hasNextLine() {
206             return mPos < mSize;
207         }
208 
209         /**
210          * Fetches the next line. Note that all subsequent return values share the same char[]
211          * under the hood.
212          *
213          * @return A {@link java.nio.CharBuffer} containing the next line without the new line
214          * symbol.
215          */
nextLine()216         public CharBuffer nextLine() {
217             if (mPos >= mSize) {
218                 return null;
219             }
220             int i = mPos;
221             // Move i to the next new line symbol, which is always '\n' in Android.
222             while (i < mSize && mBuf[i] != '\n') {
223                 i++;
224             }
225             int start = mPos;
226             mPos = i + 1;
227             return CharBuffer.wrap(mBuf, start, i - start);
228         }
229 
230         /** Total size of the proc file in chars. */
size()231         public int size() {
232             return mSize;
233         }
234 
235         /** Must call close at the end to release the read lock! Or use try-with-resources. */
close()236         public void close() {
237             mReadLock.unlock();
238         }
239 
240 
241     }
242 
243     /**
244      * Converts all numbers in the CharBuffer into longs, and puts into the given long[].
245      *
246      * Space and colon are treated as delimiters. All other chars are not allowed. All numbers
247      * are non-negative. To avoid GC, caller should try to use the same array for all calls.
248      *
249      * This method also resets the given buffer to the original position before return so that
250      * it can be read again.
251      *
252      * @param buf   The char buffer to be converted.
253      * @param array An array to store the parsed numbers.
254      * @return The number of elements written to the given array. -1 if buf is null, -2 if buf
255      * contains invalid char, -3 if any number overflows.
256      */
asLongs(CharBuffer buf, long[] array)257     public static int asLongs(CharBuffer buf, long[] array) {
258         if (buf == null) {
259             return -1;
260         }
261         final int initialPos = buf.position();
262         int count = 0;
263         long num = -1;
264         char c;
265 
266         while (buf.remaining() > 0 && count < array.length) {
267             c = buf.get();
268             if (!(isNumber(c) || c == ' ' || c == ':')) {
269                 buf.position(initialPos);
270                 return -2;
271             }
272             if (num < 0) {
273                 if (isNumber(c)) {
274                     num = c - '0';
275                 }
276             } else {
277                 if (isNumber(c)) {
278                     num = num * 10 + c - '0';
279                     if (num < 0) {
280                         buf.position(initialPos);
281                         return -3;
282                     }
283                 } else {
284                     array[count++] = num;
285                     num = -1;
286                 }
287             }
288         }
289         if (num >= 0) {
290             array[count++] = num;
291         }
292         buf.position(initialPos);
293         return count;
294     }
295 
isNumber(char c)296     private static boolean isNumber(char c) {
297         return c >= '0' && c <= '9';
298     }
299 }
300