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