1 /*
2  *  Copyright 2015 The WebRTC Project Authors. All rights reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 package org.appspot.apprtc;
12 
13 import android.util.Log;
14 
15 import java.io.BufferedReader;
16 import java.io.FileNotFoundException;
17 import java.io.FileReader;
18 import java.io.IOException;
19 import java.util.InputMismatchException;
20 import java.util.Scanner;
21 
22 /**
23  * Simple CPU monitor.  The caller creates a CpuMonitor object which can then
24  * be used via sampleCpuUtilization() to collect the percentual use of the
25  * cumulative CPU capacity for all CPUs running at their nominal frequency.  3
26  * values are generated: (1) getCpuCurrent() returns the use since the last
27  * sampleCpuUtilization(), (2) getCpuAvg3() returns the use since 3 prior
28  * calls, and (3) getCpuAvgAll() returns the use over all SAMPLE_SAVE_NUMBER
29  * calls.
30  *
31  * <p>CPUs in Android are often "offline", and while this of course means 0 Hz
32  * as current frequency, in this state we cannot even get their nominal
33  * frequency.  We therefore tread carefully, and allow any CPU to be missing.
34  * Missing CPUs are assumed to have the same nominal frequency as any close
35  * lower-numbered CPU, but as soon as it is online, we'll get their proper
36  * frequency and remember it.  (Since CPU 0 in practice always seem to be
37  * online, this unidirectional frequency inheritance should be no problem in
38  * practice.)
39  *
40  * <p>Caveats:
41  *   o No provision made for zany "turbo" mode, common in the x86 world.
42  *   o No provision made for ARM big.LITTLE; if CPU n can switch behind our
43  *     back, we might get incorrect estimates.
44  *   o This is not thread-safe.  To call asynchronously, create different
45  *     CpuMonitor objects.
46  *
47  * <p>If we can gather enough info to generate a sensible result,
48  * sampleCpuUtilization returns true.  It is designed to never through an
49  * exception.
50  *
51  * <p>sampleCpuUtilization should not be called too often in its present form,
52  * since then deltas would be small and the percent values would fluctuate and
53  * be unreadable. If it is desirable to call it more often than say once per
54  * second, one would need to increase SAMPLE_SAVE_NUMBER and probably use
55  * Queue<Integer> to avoid copying overhead.
56  *
57  * <p>Known problems:
58  *   1. Nexus 7 devices running Kitkat have a kernel which often output an
59  *      incorrect 'idle' field in /proc/stat.  The value is close to twice the
60  *      correct value, and then returns to back to correct reading.  Both when
61  *      jumping up and back down we might create faulty CPU load readings.
62  */
63 
64 class CpuMonitor {
65   private static final int SAMPLE_SAVE_NUMBER = 10;  // Assumed to be >= 3.
66   private int[] percentVec = new int[SAMPLE_SAVE_NUMBER];
67   private int sum3 = 0;
68   private int sum10 = 0;
69   private static final String TAG = "CpuMonitor";
70   private long[] cpuFreq;
71   private int cpusPresent;
72   private double lastPercentFreq = -1;
73   private int cpuCurrent;
74   private int cpuAvg3;
75   private int cpuAvgAll;
76   private boolean initialized = false;
77   private String[] maxPath;
78   private String[] curPath;
79   ProcStat lastProcStat;
80 
81   private class ProcStat {
82     final long runTime;
83     final long idleTime;
84 
ProcStat(long aRunTime, long aIdleTime)85     ProcStat(long aRunTime, long aIdleTime) {
86       runTime = aRunTime;
87       idleTime = aIdleTime;
88     }
89   }
90 
init()91   private void init() {
92     try {
93       FileReader fin = new FileReader("/sys/devices/system/cpu/present");
94       try {
95         BufferedReader rdr = new BufferedReader(fin);
96         Scanner scanner = new Scanner(rdr).useDelimiter("[-\n]");
97         scanner.nextInt();  // Skip leading number 0.
98         cpusPresent = 1 + scanner.nextInt();
99         scanner.close();
100       } catch (Exception e) {
101         Log.e(TAG, "Cannot do CPU stats due to /sys/devices/system/cpu/present parsing problem");
102       } finally {
103         fin.close();
104       }
105     } catch (FileNotFoundException e) {
106       Log.e(TAG, "Cannot do CPU stats since /sys/devices/system/cpu/present is missing");
107     } catch (IOException e) {
108       Log.e(TAG, "Error closing file");
109     }
110 
111     cpuFreq = new long [cpusPresent];
112     maxPath = new String [cpusPresent];
113     curPath = new String [cpusPresent];
114     for (int i = 0; i < cpusPresent; i++) {
115       cpuFreq[i] = 0;  // Frequency "not yet determined".
116       maxPath[i] = "/sys/devices/system/cpu/cpu" + i + "/cpufreq/cpuinfo_max_freq";
117       curPath[i] = "/sys/devices/system/cpu/cpu" + i + "/cpufreq/scaling_cur_freq";
118     }
119 
120     lastProcStat = new ProcStat(0, 0);
121 
122     initialized = true;
123   }
124 
125   /**
126    * Re-measure CPU use.  Call this method at an interval of around 1/s.
127    * This method returns true on success.  The fields
128    * cpuCurrent, cpuAvg3, and cpuAvgAll are updated on success, and represents:
129    * cpuCurrent: The CPU use since the last sampleCpuUtilization call.
130    * cpuAvg3: The average CPU over the last 3 calls.
131    * cpuAvgAll: The average CPU over the last SAMPLE_SAVE_NUMBER calls.
132    */
sampleCpuUtilization()133   public boolean sampleCpuUtilization() {
134     long lastSeenMaxFreq = 0;
135     long cpufreqCurSum = 0;
136     long cpufreqMaxSum = 0;
137 
138     if (!initialized) {
139       init();
140     }
141 
142     for (int i = 0; i < cpusPresent; i++) {
143       /*
144        * For each CPU, attempt to first read its max frequency, then its
145        * current frequency.  Once as the max frequency for a CPU is found,
146        * save it in cpuFreq[].
147        */
148 
149       if (cpuFreq[i] == 0) {
150         // We have never found this CPU's max frequency.  Attempt to read it.
151         long cpufreqMax = readFreqFromFile(maxPath[i]);
152         if (cpufreqMax > 0) {
153           lastSeenMaxFreq = cpufreqMax;
154           cpuFreq[i] = cpufreqMax;
155           maxPath[i] = null;  // Kill path to free its memory.
156         }
157       } else {
158         lastSeenMaxFreq = cpuFreq[i];  // A valid, previously read value.
159       }
160 
161       long cpufreqCur = readFreqFromFile(curPath[i]);
162       cpufreqCurSum += cpufreqCur;
163 
164       /* Here, lastSeenMaxFreq might come from
165        * 1. cpuFreq[i], or
166        * 2. a previous iteration, or
167        * 3. a newly read value, or
168        * 4. hypothetically from the pre-loop dummy.
169        */
170       cpufreqMaxSum += lastSeenMaxFreq;
171     }
172 
173     if (cpufreqMaxSum == 0) {
174       Log.e(TAG, "Could not read max frequency for any CPU");
175       return false;
176     }
177 
178     /*
179      * Since the cycle counts are for the period between the last invocation
180      * and this present one, we average the percentual CPU frequencies between
181      * now and the beginning of the measurement period.  This is significantly
182      * incorrect only if the frequencies have peeked or dropped in between the
183      * invocations.
184      */
185     double newPercentFreq = 100.0 * cpufreqCurSum / cpufreqMaxSum;
186     double percentFreq;
187     if (lastPercentFreq > 0) {
188       percentFreq = (lastPercentFreq + newPercentFreq) * 0.5;
189     } else {
190       percentFreq = newPercentFreq;
191     }
192     lastPercentFreq = newPercentFreq;
193 
194     ProcStat procStat = readIdleAndRunTime();
195     if (procStat == null) {
196       return false;
197     }
198 
199     long diffRunTime = procStat.runTime - lastProcStat.runTime;
200     long diffIdleTime = procStat.idleTime - lastProcStat.idleTime;
201 
202     // Save new measurements for next round's deltas.
203     lastProcStat = procStat;
204 
205     long allTime = diffRunTime + diffIdleTime;
206     int percent = allTime == 0 ? 0 : (int) Math.round(percentFreq * diffRunTime / allTime);
207     percent = Math.max(0, Math.min(percent, 100));
208 
209     // Subtract old relevant measurement, add newest.
210     sum3 += percent - percentVec[2];
211     // Subtract oldest measurement, add newest.
212     sum10 += percent - percentVec[SAMPLE_SAVE_NUMBER - 1];
213 
214     // Rotate saved percent values, save new measurement in vacated spot.
215     for (int i = SAMPLE_SAVE_NUMBER - 1; i > 0; i--) {
216       percentVec[i] = percentVec[i - 1];
217     }
218     percentVec[0] = percent;
219 
220     cpuCurrent = percent;
221     cpuAvg3 = sum3 / 3;
222     cpuAvgAll = sum10 / SAMPLE_SAVE_NUMBER;
223 
224     return true;
225   }
226 
getCpuCurrent()227   public int getCpuCurrent() {
228     return cpuCurrent;
229   }
230 
getCpuAvg3()231   public int getCpuAvg3() {
232     return cpuAvg3;
233   }
234 
getCpuAvgAll()235   public int getCpuAvgAll() {
236     return cpuAvgAll;
237   }
238 
239   /**
240    * Read a single integer value from the named file.  Return the read value
241    * or if an error occurs return 0.
242    */
readFreqFromFile(String fileName)243   private long readFreqFromFile(String fileName) {
244     long number = 0;
245     try {
246       FileReader fin = new FileReader(fileName);
247       try {
248         BufferedReader rdr = new BufferedReader(fin);
249         Scanner scannerC = new Scanner(rdr);
250         number = scannerC.nextLong();
251         scannerC.close();
252       } catch (Exception e) {
253         // CPU presumably got offline just after we opened file.
254       } finally {
255         fin.close();
256       }
257     } catch (FileNotFoundException e) {
258       // CPU is offline, not an error.
259     } catch (IOException e) {
260       Log.e(TAG, "Error closing file");
261     }
262     return number;
263   }
264 
265   /*
266    * Read the current utilization of all CPUs using the cumulative first line
267    * of /proc/stat.
268    */
readIdleAndRunTime()269   private ProcStat readIdleAndRunTime() {
270     long runTime = 0;
271     long idleTime = 0;
272     try {
273       FileReader fin = new FileReader("/proc/stat");
274       try {
275         BufferedReader rdr = new BufferedReader(fin);
276         Scanner scanner = new Scanner(rdr);
277         scanner.next();
278         long user = scanner.nextLong();
279         long nice = scanner.nextLong();
280         long sys = scanner.nextLong();
281         runTime = user + nice + sys;
282         idleTime = scanner.nextLong();
283         scanner.close();
284       } catch (Exception e) {
285         Log.e(TAG, "Problems parsing /proc/stat");
286         return null;
287       } finally {
288         fin.close();
289       }
290     } catch (FileNotFoundException e) {
291       Log.e(TAG, "Cannot open /proc/stat for reading");
292       return null;
293     } catch (IOException e) {
294       Log.e(TAG, "Problems reading /proc/stat");
295       return null;
296     }
297     return new ProcStat(runTime, idleTime);
298   }
299 }
300