Update CPU monitor to use moving averages.

And improve accuracy a little.

BUG=b/28560555
R=wzh@webrtc.org

Review URL: https://codereview.webrtc.org/1951663002 .

Cr-Commit-Position: refs/heads/master@{#12631}
This commit is contained in:
Alex Glaznev
2016-05-04 11:04:11 -07:00
parent 28a44564c9
commit ef00ec1d4e
2 changed files with 125 additions and 118 deletions

View File

@ -14,19 +14,14 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.os.BatteryManager; import android.os.BatteryManager;
import android.os.Environment;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.Log; import android.util.Log;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.text.SimpleDateFormat; import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Scanner; import java.util.Scanner;
import org.appspot.apprtc.util.LooperExecutor; import org.appspot.apprtc.util.LooperExecutor;
@ -75,35 +70,34 @@ import org.appspot.apprtc.util.LooperExecutor;
class CpuMonitor { class CpuMonitor {
private static final String TAG = "CpuMonitor"; private static final String TAG = "CpuMonitor";
private static final String DUMP_FILE = "cpu_log.txt"; private static final int MOVING_AVERAGE_SAMPLES = 10;
private static final int CPU_STAT_SAMPLE_PERIOD = 2000;
private static final int CPU_STAT_LOG_PERIOD = 6000; private static final int CPU_STAT_SAMPLE_PERIOD_MS = 1000;
private static final int CPU_STAT_LOG_PERIOD_MS = 5000;
private final Context appContext; private final Context appContext;
// User CPU usage at current frequency.
private final MovingAverage userCpuUsage;
// System CPU usage at current frequency.
private final MovingAverage systemCpuUsage;
// Total CPU usage relative to maximum frequency.
private final MovingAverage totalCpuUsage;
// CPU frequency in percentage from maximum.
private final MovingAverage frequencyScale;
private LooperExecutor executor; private LooperExecutor executor;
private long lastStatLogTimeMs; private long lastStatLogTimeMs;
private int iterations;
private double currentUserCpuUsage;
private double currentSystemCpuUsage;
private double currentTotalCpuUsage;
private double currentFrequencyScale = -1;
private double sumUserCpuUsage;
private double sumSystemCpuUsage;
private double sumFrequencyScale;
private double sumTotalCpuUsage;
private long[] cpuFreqMax; private long[] cpuFreqMax;
private int cpusPresent; private int cpusPresent;
private int actualCpusPresent; private int actualCpusPresent;
private boolean initialized = false; private boolean initialized;
private boolean cpuOveruse;
private String[] maxPath; private String[] maxPath;
private String[] curPath; private String[] curPath;
private double[] curFreqScales; private double[] curFreqScales;
private ProcStat lastProcStat; private ProcStat lastProcStat;
private static boolean dumpEnabled = false; private static class ProcStat {
private static FileOutputStream fileWriter;
private class ProcStat {
final long userTime; final long userTime;
final long systemTime; final long systemTime;
final long idleTime; final long idleTime;
@ -115,10 +109,55 @@ class CpuMonitor {
} }
} }
private static class MovingAverage {
private final int size;
private double sum;
private double currentValue;
private double circBuffer[];
private int circBufferIndex;
public MovingAverage(int size) {
if (size <= 0) {
throw new AssertionError("Size value in MovingAverage ctor should be positive.");
}
this.size = size;
circBuffer = new double[size];
}
public void reset() {
Arrays.fill(circBuffer, 0);
circBufferIndex = 0;
sum = 0;
currentValue = 0;
}
public void addValue(double value) {
sum -= circBuffer[circBufferIndex];
circBuffer[circBufferIndex++] = value;
currentValue = value;
sum += value;
if (circBufferIndex >= size) {
circBufferIndex = 0;
}
}
public double getCurrent() {
return currentValue;
}
public double getAverage() {
return sum / (double) size;
}
}
public CpuMonitor(Context context) { public CpuMonitor(Context context) {
Log.d(TAG, "CpuMonitor ctor."); Log.d(TAG, "CpuMonitor ctor.");
appContext = context.getApplicationContext(); appContext = context.getApplicationContext();
lastStatLogTimeMs = 0; userCpuUsage = new MovingAverage(MOVING_AVERAGE_SAMPLES);
systemCpuUsage = new MovingAverage(MOVING_AVERAGE_SAMPLES);
totalCpuUsage = new MovingAverage(MOVING_AVERAGE_SAMPLES);
frequencyScale = new MovingAverage(MOVING_AVERAGE_SAMPLES);
lastStatLogTimeMs = SystemClock.elapsedRealtime();
executor = new LooperExecutor(); executor = new LooperExecutor();
executor.requestStart(); executor.requestStart();
@ -149,67 +188,42 @@ class CpuMonitor {
} }
} }
public synchronized void reset() {
if (executor != null) {
Log.d(TAG, "reset");
resetStat();
cpuOveruse = false;
}
}
public synchronized int getCpuUsageCurrent() { public synchronized int getCpuUsageCurrent() {
return doubleToPercent(currentTotalCpuUsage); return doubleToPercent(userCpuUsage.getCurrent() + systemCpuUsage.getCurrent());
} }
public synchronized int getCpuUsageAverage() { public synchronized int getCpuUsageAverage() {
return sumDoubleToPercent(sumTotalCpuUsage, iterations); return doubleToPercent(userCpuUsage.getAverage() + systemCpuUsage.getAverage());
} }
public synchronized int getCpuFrequencyScaleCurrent() { public synchronized int getFrequencyScaleAverage() {
return doubleToPercent(currentFrequencyScale); return doubleToPercent(frequencyScale.getAverage());
} }
private void scheduleCpuUtilizationTask() { private void scheduleCpuUtilizationTask() {
executor.scheduleAtFixedRate(new Runnable() { executor.scheduleAtFixedRate(new Runnable() {
@Override @Override
public void run() { public void run() {
logCpuUtilization(); cpuUtilizationTask();
} }
}, CPU_STAT_SAMPLE_PERIOD); }, CPU_STAT_SAMPLE_PERIOD_MS);
} }
private void checkDump(String statString) { private void cpuUtilizationTask() {
if (!dumpEnabled) {
return;
}
if (fileWriter == null) {
Log.d(TAG, "Start log dump");
String fileName = Environment.getExternalStorageDirectory().getAbsolutePath()
+ File.separator + DUMP_FILE;
try {
fileWriter = new FileOutputStream(fileName, false /* append */);
} catch (FileNotFoundException e) {
Log.e(TAG, "Can not open file.", e);
dumpEnabled = false;
return;
}
}
Date date = Calendar.getInstance().getTime();
SimpleDateFormat df = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
String msg = df.format(date) + " " + TAG + ":" + statString + "\n";
try {
fileWriter.write(msg.getBytes());
} catch (IOException e) {
Log.e(TAG, "Can not write to file.", e);
dumpEnabled = false;
}
}
private void logCpuUtilization() {
boolean logStatistics = false;
if (SystemClock.elapsedRealtime() - lastStatLogTimeMs >= CPU_STAT_LOG_PERIOD) {
lastStatLogTimeMs = SystemClock.elapsedRealtime();
logStatistics = true;
}
boolean cpuMonitorAvailable = sampleCpuUtilization(); boolean cpuMonitorAvailable = sampleCpuUtilization();
if (logStatistics && cpuMonitorAvailable) { if (cpuMonitorAvailable
&& SystemClock.elapsedRealtime() - lastStatLogTimeMs >= CPU_STAT_LOG_PERIOD_MS) {
lastStatLogTimeMs = SystemClock.elapsedRealtime();
String statString = getStatString(); String statString = getStatString();
checkDump(statString);
Log.d(TAG, statString); Log.d(TAG, statString);
resetStat();
} }
} }
@ -251,11 +265,11 @@ class CpuMonitor {
} }
private synchronized void resetStat() { private synchronized void resetStat() {
sumUserCpuUsage = 0; userCpuUsage.reset();
sumSystemCpuUsage = 0; systemCpuUsage.reset();
sumFrequencyScale = 0; totalCpuUsage.reset();
sumTotalCpuUsage = 0; frequencyScale.reset();
iterations = 0; lastStatLogTimeMs = SystemClock.elapsedRealtime();
} }
private int getBatteryLevel() { private int getBatteryLevel() {
@ -305,6 +319,7 @@ class CpuMonitor {
// We have never found this CPU's max frequency. Attempt to read it. // We have never found this CPU's max frequency. Attempt to read it.
long cpufreqMax = readFreqFromFile(maxPath[i]); long cpufreqMax = readFreqFromFile(maxPath[i]);
if (cpufreqMax > 0) { if (cpufreqMax > 0) {
Log.d(TAG, "Core " + i + ". Max frequency: " + cpufreqMax);
lastSeenMaxFreq = cpufreqMax; lastSeenMaxFreq = cpufreqMax;
cpuFreqMax[i] = cpufreqMax; cpuFreqMax[i] = cpufreqMax;
maxPath[i] = null; // Kill path to free its memory. maxPath[i] = null; // Kill path to free its memory.
@ -347,12 +362,9 @@ class CpuMonitor {
* incorrect only if the frequencies have peeked or dropped in between the * incorrect only if the frequencies have peeked or dropped in between the
* invocations. * invocations.
*/ */
double newFrequencyScale = (double) cpuFreqCurSum / cpuFreqMaxSum; double currentFrequencyScale = cpuFreqCurSum / (double) cpuFreqMaxSum;
double frequencyScale; if (frequencyScale.getCurrent() > 0) {
if (currentFrequencyScale > 0) { currentFrequencyScale = (frequencyScale.getCurrent() + currentFrequencyScale) * 0.5;
frequencyScale = (currentFrequencyScale + newFrequencyScale) * 0.5;
} else {
frequencyScale = newFrequencyScale;
} }
ProcStat procStat = readProcStat(); ProcStat procStat = readProcStat();
@ -365,24 +377,23 @@ class CpuMonitor {
long diffIdleTime = procStat.idleTime - lastProcStat.idleTime; long diffIdleTime = procStat.idleTime - lastProcStat.idleTime;
long allTime = diffUserTime + diffSystemTime + diffIdleTime; long allTime = diffUserTime + diffSystemTime + diffIdleTime;
if (frequencyScale == 0 || allTime == 0) { if (currentFrequencyScale == 0 || allTime == 0) {
return false; return false;
} }
// Update statistics. // Update statistics.
currentFrequencyScale = frequencyScale; frequencyScale.addValue(currentFrequencyScale);
sumFrequencyScale += frequencyScale;
currentUserCpuUsage = (double) diffUserTime / allTime; double currentUserCpuUsage = diffUserTime / (double) allTime;
sumUserCpuUsage += currentUserCpuUsage; userCpuUsage.addValue(currentUserCpuUsage);
currentSystemCpuUsage = (double) diffSystemTime / allTime; double currentSystemCpuUsage = diffSystemTime / (double) allTime;
sumSystemCpuUsage += currentSystemCpuUsage; systemCpuUsage.addValue(currentSystemCpuUsage);
currentTotalCpuUsage = (currentUserCpuUsage + currentSystemCpuUsage) * currentFrequencyScale; double currentTotalCpuUsage =
sumTotalCpuUsage += currentTotalCpuUsage; (currentUserCpuUsage + currentSystemCpuUsage) * currentFrequencyScale;
totalCpuUsage.addValue(currentTotalCpuUsage);
iterations++;
// Save new measurements for next round's deltas. // Save new measurements for next round's deltas.
lastProcStat = procStat; lastProcStat = procStat;
@ -393,38 +404,30 @@ class CpuMonitor {
return (int) (d * 100 + 0.5); return (int) (d * 100 + 0.5);
} }
private int sumDoubleToPercent(double d, int iterations) { private synchronized String getStatString() {
if (iterations > 0) {
return (int) (d * 100.0 / (double) iterations + 0.5);
} else {
return 0;
}
}
private String getStatString() {
StringBuilder stat = new StringBuilder(); StringBuilder stat = new StringBuilder();
stat.append("CPU User: ") stat.append("CPU User: ")
.append(doubleToPercent(currentUserCpuUsage)).append("/") .append(doubleToPercent(userCpuUsage.getCurrent())).append("/")
.append(sumDoubleToPercent(sumUserCpuUsage, iterations)).append(" (") .append(doubleToPercent(userCpuUsage.getAverage()))
.append(doubleToPercent(currentUserCpuUsage * currentFrequencyScale)).append(")")
.append(". System: ") .append(". System: ")
.append(doubleToPercent(currentSystemCpuUsage)).append("/") .append(doubleToPercent(systemCpuUsage.getCurrent())).append("/")
.append(sumDoubleToPercent(sumSystemCpuUsage, iterations)).append(" (") .append(doubleToPercent(systemCpuUsage.getAverage()))
.append(doubleToPercent(currentSystemCpuUsage * currentFrequencyScale)).append(")") .append(". Freq: ")
.append(". CPU freq %: ") .append(doubleToPercent(frequencyScale.getCurrent())).append("/")
.append(doubleToPercent(currentFrequencyScale)).append("/") .append(doubleToPercent(frequencyScale.getAverage()))
.append(sumDoubleToPercent(sumFrequencyScale, iterations)) .append(". Total usage: ")
.append(". Total CPU usage: ") .append(doubleToPercent(totalCpuUsage.getCurrent())).append("/")
.append(doubleToPercent(currentTotalCpuUsage)).append("/") .append(doubleToPercent(totalCpuUsage.getAverage()))
.append(sumDoubleToPercent(sumTotalCpuUsage, iterations))
.append(". Cores: ") .append(". Cores: ")
.append(actualCpusPresent); .append(actualCpusPresent);
stat.append("( "); stat.append("( ");
for (int i = 0; i < cpusPresent; i++) { for (int i = 0; i < cpusPresent; i++) {
stat.append(doubleToPercent(curFreqScales[i])).append(" "); stat.append(doubleToPercent(curFreqScales[i])).append(" ");
} }
stat.append("). Battery %: ") stat.append("). Battery: ").append(getBatteryLevel());
.append(getBatteryLevel()); if (cpuOveruse) {
stat.append(". Overuse.");
}
return stat.toString(); return stat.toString();
} }
@ -468,13 +471,15 @@ class CpuMonitor {
BufferedReader rdr = new BufferedReader(fin); BufferedReader rdr = new BufferedReader(fin);
Scanner scanner = new Scanner(rdr); Scanner scanner = new Scanner(rdr);
scanner.next(); scanner.next();
userTime = scanner.nextLong(); userTime = scanner.nextLong(); // user
long nice = scanner.nextLong(); long nice = scanner.nextLong(); // nice
userTime += nice; userTime += nice;
systemTime = scanner.nextLong(); systemTime = scanner.nextLong(); // system
idleTime = scanner.nextLong(); idleTime = scanner.nextLong(); // idle
long ioWaitTime = scanner.nextLong(); long ioWaitTime = scanner.nextLong(); // iowait
userTime += ioWaitTime; userTime += ioWaitTime;
long irqTime = scanner.nextLong() + scanner.nextLong(); // irq + softirq
systemTime += irqTime;
scanner.close(); scanner.close();
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Problems parsing /proc/stat"); Log.e(TAG, "Problems parsing /proc/stat");

View File

@ -194,8 +194,10 @@ public class HudFragment extends Fragment {
} }
if (cpuMonitor != null) { if (cpuMonitor != null) {
encoderStat.append("CPU%: ").append(cpuMonitor.getCpuUsageCurrent()) encoderStat.append("CPU%: ")
.append(". Freq: ").append(cpuMonitor.getCpuFrequencyScaleCurrent()); .append(cpuMonitor.getCpuUsageCurrent()).append("/")
.append(cpuMonitor.getCpuUsageAverage())
.append(". Freq: ").append(cpuMonitor.getFrequencyScaleAverage());
} }
encoderStatView.setText(encoderStat.toString()); encoderStatView.setText(encoderStat.toString());
} }