Add CpuMonitor to Android ApprtcDemo
R=magjed@webrtc.org, perkj@webrtc.org Review URL: https://webrtc-codereview.appspot.com/38169004 Cr-Commit-Position: refs/heads/master@{#8444} git-svn-id: http://webrtc.googlecode.com/svn/trunk@8444 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
		| @@ -59,6 +59,7 @@ public class CallFragment extends Fragment { | ||||
|   private boolean displayHud; | ||||
|   private volatile boolean isRunning; | ||||
|   private TextView hudView; | ||||
|   private final CpuMonitor cpuMonitor = new CpuMonitor(); | ||||
|  | ||||
|   /** | ||||
|    * Call control interface for container activity. | ||||
| @@ -224,6 +225,15 @@ public class CallFragment extends Fragment { | ||||
|           .append(actualBitrate) | ||||
|           .append("\n"); | ||||
|     } | ||||
|  | ||||
|     if (cpuMonitor.sampleCpuUtilization()) { | ||||
|       stat.append("CPU%: ") | ||||
|           .append(cpuMonitor.getCpuCurrent()) | ||||
|           .append("/") | ||||
|           .append(cpuMonitor.getCpuAvg3()) | ||||
|           .append("/") | ||||
|           .append(cpuMonitor.getCpuAvgAll()); | ||||
|     } | ||||
|     encoderStatView.setText(stat.toString()); | ||||
|     hudView.setText(bweBuilder.toString() + hudView.getText()); | ||||
|   } | ||||
|   | ||||
							
								
								
									
										313
									
								
								talk/examples/android/src/org/appspot/apprtc/CpuMonitor.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										313
									
								
								talk/examples/android/src/org/appspot/apprtc/CpuMonitor.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,313 @@ | ||||
| /* | ||||
|  * libjingle | ||||
|  * Copyright 2015 Google Inc. | ||||
|  * | ||||
|  * Redistribution and use in source and binary forms, with or without | ||||
|  * modification, are permitted provided that the following conditions are met: | ||||
|  * | ||||
|  *  1. Redistributions of source code must retain the above copyright notice, | ||||
|  *     this list of conditions and the following disclaimer. | ||||
|  *  2. Redistributions in binary form must reproduce the above copyright notice, | ||||
|  *     this list of conditions and the following disclaimer in the documentation | ||||
|  *     and/or other materials provided with the distribution. | ||||
|  *  3. The name of the author may not be used to endorse or promote products | ||||
|  *     derived from this software without specific prior written permission. | ||||
|  * | ||||
|  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED | ||||
|  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | ||||
|  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO | ||||
|  * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
|  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | ||||
|  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; | ||||
|  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, | ||||
|  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR | ||||
|  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF | ||||
|  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
|  */ | ||||
|  | ||||
| package org.appspot.apprtc; | ||||
|  | ||||
| import android.util.Log; | ||||
|  | ||||
| import java.io.BufferedReader; | ||||
| import java.io.FileNotFoundException; | ||||
| import java.io.FileReader; | ||||
| import java.io.IOException; | ||||
| import java.util.InputMismatchException; | ||||
| import java.util.Scanner; | ||||
|  | ||||
| /** | ||||
|  * Simple CPU monitor.  The caller creates a CpuMonitor object which can then | ||||
|  * be used via sampleCpuUtilization() to collect the percentual use of the | ||||
|  * cumulative CPU capacity for all CPUs running at their nominal frequency.  3 | ||||
|  * values are generated: (1) getCpuCurrent() returns the use since the last | ||||
|  * sampleCpuUtilization(), (2) getCpuAvg3() returns the use since 3 prior | ||||
|  * calls, and (3) getCpuAvgAll() returns the use over all SAMPLE_SAVE_NUMBER | ||||
|  * calls. | ||||
|  * | ||||
|  * <p>CPUs in Android are often "offline", and while this of course means 0 Hz | ||||
|  * as current frequency, in this state we cannot even get their nominal | ||||
|  * frequency.  We therefore tread carefully, and allow any CPU to be missing. | ||||
|  * Missing CPUs are assumed to have the same nominal frequency as any close | ||||
|  * lower-numbered CPU, but as soon as it is online, we'll get their proper | ||||
|  * frequency and remember it.  (Since CPU 0 in practice always seem to be | ||||
|  * online, this unidirectional frequency inheritance should be no problem in | ||||
|  * practice.) | ||||
|  * | ||||
|  * <p>Caveats: | ||||
|  *   o No provision made for zany "turbo" mode, common in the x86 world. | ||||
|  *   o No provision made for ARM big.LITTLE; if CPU n can switch behind our | ||||
|  *     back, we might get incorrect estimates. | ||||
|  *   o This is not thread-safe.  To call asynchronously, create different | ||||
|  *     CpuMonitor objects. | ||||
|  * | ||||
|  * <p>If we can gather enough info to generate a sensible result, | ||||
|  * sampleCpuUtilization returns true.  It is designed to never through an | ||||
|  * exception. | ||||
|  * | ||||
|  * <p>sampleCpuUtilization should not be called too often in its present form, | ||||
|  * since then deltas would be small and the percent values would fluctuate and | ||||
|  * be unreadable. If it is desirable to call it more often than say once per | ||||
|  * second, one would need to increase SAMPLE_SAVE_NUMBER and probably use | ||||
|  * Queue<Integer> to avoid copying overhead. | ||||
|  * | ||||
|  * <p>Known problems: | ||||
|  *   1. Nexus 7 devices running Kitkat have a kernel which often output an | ||||
|  *      incorrect 'idle' field in /proc/stat.  The value is close to twice the | ||||
|  *      correct value, and then returns to back to correct reading.  Both when | ||||
|  *      jumping up and back down we might create faulty CPU load readings. | ||||
|  */ | ||||
|  | ||||
| class CpuMonitor { | ||||
|   private static final int SAMPLE_SAVE_NUMBER = 10;  // Assumed to be >= 3. | ||||
|   private int[] percentVec = new int[SAMPLE_SAVE_NUMBER]; | ||||
|   private int sum3 = 0; | ||||
|   private int sum10 = 0; | ||||
|   private static final String TAG = "CpuMonitor"; | ||||
|   private long[] cpuFreq; | ||||
|   private int cpusPresent; | ||||
|   private double lastPercentFreq = -1; | ||||
|   private int cpuCurrent; | ||||
|   private int cpuAvg3; | ||||
|   private int cpuAvgAll; | ||||
|   private boolean initialized = false; | ||||
|   private String[] maxPath; | ||||
|   private String[] curPath; | ||||
|   ProcStat lastProcStat; | ||||
|  | ||||
|   private class ProcStat { | ||||
|     final long runTime; | ||||
|     final long idleTime; | ||||
|  | ||||
|     ProcStat(long aRunTime, long aIdleTime) { | ||||
|       runTime = aRunTime; | ||||
|       idleTime = aIdleTime; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   private void init() { | ||||
|     try { | ||||
|       FileReader fin = new FileReader("/sys/devices/system/cpu/present"); | ||||
|       try { | ||||
|         BufferedReader rdr = new BufferedReader(fin); | ||||
|         Scanner scanner = new Scanner(rdr).useDelimiter("[-\n]"); | ||||
|         scanner.nextInt();  // Skip leading number 0. | ||||
|         cpusPresent = 1 + scanner.nextInt(); | ||||
|       } catch (InputMismatchException e) { | ||||
|         Log.e(TAG, "Cannot do CPU stats due to /sys/devices/system/cpu/present parsing problem"); | ||||
|       } finally { | ||||
|         fin.close(); | ||||
|       } | ||||
|     } catch (FileNotFoundException e) { | ||||
|       Log.e(TAG, "Cannot do CPU stats since /sys/devices/system/cpu/present is missing"); | ||||
|     } catch (IOException e) { | ||||
|       Log.e(TAG, "Error closing file"); | ||||
|     } | ||||
|  | ||||
|     cpuFreq = new long [cpusPresent]; | ||||
|     maxPath = new String [cpusPresent]; | ||||
|     curPath = new String [cpusPresent]; | ||||
|     for (int i = 0; i < cpusPresent; i++) { | ||||
|       cpuFreq[i] = 0;  // Frequency "not yet determined". | ||||
|       maxPath[i] = "/sys/devices/system/cpu/cpu" + i + "/cpufreq/cpuinfo_max_freq"; | ||||
|       curPath[i] = "/sys/devices/system/cpu/cpu" + i + "/cpufreq/scaling_cur_freq"; | ||||
|     } | ||||
|  | ||||
|     lastProcStat = new ProcStat(0, 0); | ||||
|  | ||||
|     initialized = true; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Re-measure CPU use.  Call this method at an interval of around 1/s. | ||||
|    * This method returns true on success.  The fields | ||||
|    * cpuCurrent, cpuAvg3, and cpuAvgAll are updated on success, and represents: | ||||
|    * cpuCurrent: The CPU use since the last sampleCpuUtilization call. | ||||
|    * cpuAvg3: The average CPU over the last 3 calls. | ||||
|    * cpuAvgAll: The average CPU over the last SAMPLE_SAVE_NUMBER calls. | ||||
|    */ | ||||
|   public boolean sampleCpuUtilization() { | ||||
|     long lastSeenMaxFreq = 0; | ||||
|     long cpufreqCurSum = 0; | ||||
|     long cpufreqMaxSum = 0; | ||||
|  | ||||
|     if (!initialized) { | ||||
|       init(); | ||||
|     } | ||||
|  | ||||
|     for (int i = 0; i < cpusPresent; i++) { | ||||
|       /* | ||||
|        * For each CPU, attempt to first read its max frequency, then its | ||||
|        * current frequency.  Once as the max frequency for a CPU is found, | ||||
|        * save it in cpuFreq[]. | ||||
|        */ | ||||
|  | ||||
|       if (cpuFreq[i] == 0) { | ||||
|         // We have never found this CPU's max frequency.  Attempt to read it. | ||||
|         long cpufreqMax = readFreqFromFile(maxPath[i]); | ||||
|         if (cpufreqMax > 0) { | ||||
|           lastSeenMaxFreq = cpufreqMax; | ||||
|           cpuFreq[i] = cpufreqMax; | ||||
|           maxPath[i] = null;  // Kill path to free its memory. | ||||
|         } | ||||
|       } else { | ||||
|         lastSeenMaxFreq = cpuFreq[i];  // A valid, previously read value. | ||||
|       } | ||||
|  | ||||
|       long cpufreqCur = readFreqFromFile(curPath[i]); | ||||
|       cpufreqCurSum += cpufreqCur; | ||||
|  | ||||
|       /* Here, lastSeenMaxFreq might come from | ||||
|        * 1. cpuFreq[i], or | ||||
|        * 2. a previous iteration, or | ||||
|        * 3. a newly read value, or | ||||
|        * 4. hypothetically from the pre-loop dummy. | ||||
|        */ | ||||
|       cpufreqMaxSum += lastSeenMaxFreq; | ||||
|     } | ||||
|  | ||||
|     if (cpufreqMaxSum == 0) { | ||||
|       Log.e(TAG, "Could not read max frequency for any CPU"); | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|      * Since the cycle counts are for the period between the last invocation | ||||
|      * and this present one, we average the percentual CPU frequencies between | ||||
|      * now and the beginning of the measurement period.  This is significantly | ||||
|      * incorrect only if the frequencies have peeked or dropped in between the | ||||
|      * invocations. | ||||
|      */ | ||||
|     double newPercentFreq = 100.0 * cpufreqCurSum / cpufreqMaxSum; | ||||
|     double percentFreq; | ||||
|     if (lastPercentFreq > 0) { | ||||
|       percentFreq = (lastPercentFreq + newPercentFreq) * 0.5; | ||||
|     } else { | ||||
|       percentFreq = newPercentFreq; | ||||
|     } | ||||
|     lastPercentFreq = newPercentFreq; | ||||
|  | ||||
|     ProcStat procStat = readIdleAndRunTime(); | ||||
|     if (procStat == null) { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     long diffRunTime = procStat.runTime - lastProcStat.runTime; | ||||
|     long diffIdleTime = procStat.idleTime - lastProcStat.idleTime; | ||||
|  | ||||
|     // Save new measurements for next round's deltas. | ||||
|     lastProcStat = procStat; | ||||
|  | ||||
|     long allTime = diffRunTime + diffIdleTime; | ||||
|     int percent = allTime == 0 ? 0 : (int) Math.round(percentFreq * diffRunTime / allTime); | ||||
|     percent = Math.max(0, Math.min(percent, 100)); | ||||
|  | ||||
|     // Subtract old relevant measurement, add newest. | ||||
|     sum3 += percent - percentVec[2]; | ||||
|     // Subtract oldest measurement, add newest. | ||||
|     sum10 += percent - percentVec[SAMPLE_SAVE_NUMBER - 1]; | ||||
|  | ||||
|     // Rotate saved percent values, save new measurement in vacated spot. | ||||
|     for (int i = SAMPLE_SAVE_NUMBER - 1; i > 0; i--) { | ||||
|       percentVec[i] = percentVec[i - 1]; | ||||
|     } | ||||
|     percentVec[0] = percent; | ||||
|  | ||||
|     cpuCurrent = percent; | ||||
|     cpuAvg3 = sum3 / 3; | ||||
|     cpuAvgAll = sum10 / SAMPLE_SAVE_NUMBER; | ||||
|  | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   public int getCpuCurrent() { | ||||
|     return cpuCurrent; | ||||
|   } | ||||
|  | ||||
|   public int getCpuAvg3() { | ||||
|     return cpuAvg3; | ||||
|   } | ||||
|  | ||||
|   public int getCpuAvgAll() { | ||||
|     return cpuAvgAll; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Read a single integer value from the named file.  Return the read value | ||||
|    * or if an error occurs return 0. | ||||
|    */ | ||||
|   private long readFreqFromFile(String fileName) { | ||||
|     long number = 0; | ||||
|     try { | ||||
|       FileReader fin = new FileReader(fileName); | ||||
|       try { | ||||
|         BufferedReader rdr = new BufferedReader(fin); | ||||
|         Scanner scannerC = new Scanner(rdr); | ||||
|         number = scannerC.nextLong(); | ||||
|       } catch (InputMismatchException e) { | ||||
|         // CPU presumably got offline just after we opened file. | ||||
|       } finally { | ||||
|         fin.close(); | ||||
|       } | ||||
|     } catch (FileNotFoundException e) { | ||||
|       // CPU is offline, not an error. | ||||
|     } catch (IOException e) { | ||||
|       Log.e(TAG, "Error closing file"); | ||||
|     } | ||||
|     return number; | ||||
|   } | ||||
|  | ||||
|   /* | ||||
|    * Read the current utilization of all CPUs using the cumulative first line | ||||
|    * of /proc/stat. | ||||
|    */ | ||||
|   private ProcStat readIdleAndRunTime() { | ||||
|     long runTime = 0; | ||||
|     long idleTime = 0; | ||||
|     try { | ||||
|       FileReader fin = new FileReader("/proc/stat"); | ||||
|       try { | ||||
|         BufferedReader rdr = new BufferedReader(fin); | ||||
|         Scanner scanner = new Scanner(rdr); | ||||
|         scanner.next(); | ||||
|         long user = scanner.nextLong(); | ||||
|         long nice = scanner.nextLong(); | ||||
|         long sys = scanner.nextLong(); | ||||
|         runTime = user + nice + sys; | ||||
|         idleTime = scanner.nextLong(); | ||||
|       } catch (InputMismatchException e) { | ||||
|         Log.e(TAG, "Problems parsing /proc/stat"); | ||||
|         return null; | ||||
|       } finally { | ||||
|         fin.close(); | ||||
|       } | ||||
|     } catch (FileNotFoundException e) { | ||||
|       Log.e(TAG, "Cannot open /proc/stat for reading"); | ||||
|       return null; | ||||
|     } catch (IOException e) { | ||||
|       Log.e(TAG, "Problems reading /proc/stat"); | ||||
|       return null; | ||||
|     } | ||||
|     return new ProcStat(runTime, idleTime); | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 torbjorng@webrtc.org
					torbjorng@webrtc.org