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
diff --git a/talk/examples/android/src/org/appspot/apprtc/CallFragment.java b/talk/examples/android/src/org/appspot/apprtc/CallFragment.java
index e6b5a6f..dc7695c 100644
--- a/talk/examples/android/src/org/appspot/apprtc/CallFragment.java
+++ b/talk/examples/android/src/org/appspot/apprtc/CallFragment.java
@@ -59,6 +59,7 @@
   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 @@
           .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());
   }
diff --git a/talk/examples/android/src/org/appspot/apprtc/CpuMonitor.java b/talk/examples/android/src/org/appspot/apprtc/CpuMonitor.java
new file mode 100644
index 0000000..327d47e
--- /dev/null
+++ b/talk/examples/android/src/org/appspot/apprtc/CpuMonitor.java
@@ -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);
+  }
+}