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:
parent
7ac374abd7
commit
f906e55de1
@ -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);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user