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