Add CPU and memory load monitoring to SystemMonitor.
- Gets CPU load from /proc/loadavg, calculates average load per
processor core
- Gets memory usage percentage from ActivityManager.getMemoryInfo()
- Sends out SystemMonitorEvent with CPU and memory usage levels set
Bug: 192009106
Test: CarServiceUnitTest:SystemMonitorUnitTest
Change-Id: I4f3ae059ca2ae1d070f3e5df6be50c52682c8780
diff --git a/service/src/com/android/car/telemetry/systemmonitor/SystemMonitor.java b/service/src/com/android/car/telemetry/systemmonitor/SystemMonitor.java
index 65bc45b..e9b1cb4 100644
--- a/service/src/com/android/car/telemetry/systemmonitor/SystemMonitor.java
+++ b/service/src/com/android/car/telemetry/systemmonitor/SystemMonitor.java
@@ -16,13 +16,49 @@
package com.android.car.telemetry.systemmonitor;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityManager.MemoryInfo;
+import android.content.Context;
+import android.os.Handler;
+import android.util.Slog;
+
+import com.android.car.CarLog;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+
/**
* SystemMonitor monitors system states and report to listeners when there are
* important changes.
*/
public class SystemMonitor {
- private SystemMonitorCallback mCallback;
+ private static final int NUM_LOADAVG_VALS = 3;
+ private static final float HI_CPU_LOAD_PER_CORE_BASE_LEVEL = 1.0f;
+ private static final float MED_CPU_LOAD_PER_CORE_BASE_LEVEL = 0.5f;
+ private static final float HI_MEM_LOAD_BASE_LEVEL = 0.95f;
+ private static final float MED_MEM_LOAD_BASE_LEVEL = 0.80f;
+ private static final String LOADAVG_PATH = "/proc/loadavg";
+
+ private static final int POLL_INTERVAL_MILLIS = 60000;
+
+ private final Handler mWorkerHandler;
+
+ private final Object mLock = new Object();
+
+ private final Context mContext;
+ private final ActivityManager mActivityManager;
+ private final String mLoadavgPath;
+
+ @GuardedBy("mLock")
+ @Nullable private SystemMonitorCallback mCallback;
+ @GuardedBy("mLock")
+ private boolean mSystemMonitorRunning = false;
+
/**
* Interface for receiving notifications about system monitor changes.
@@ -37,11 +73,180 @@
}
/**
- * Sets the callback to notify of system state changes.
+ * Creates a SystemMonitor instance set with default loadavg path.
+ *
+ * @param context the context this is running in.
+ * @param workerHandler a handler for running monitoring jobs.
+ * @return SystemMonitor instance.
+ */
+ public static SystemMonitor create(Context context, Handler workerHandler) {
+ return new SystemMonitor(context, workerHandler, LOADAVG_PATH);
+ }
+
+ @VisibleForTesting
+ SystemMonitor(Context context, Handler workerHandler, String loadavgPath) {
+ mContext = context;
+ mWorkerHandler = workerHandler;
+ mActivityManager = (ActivityManager)
+ mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ mLoadavgPath = loadavgPath;
+ }
+
+ /**
+ * Sets the {@link SystemMonitorCallback} to notify of system state changes.
*
* @param callback the callback to nofify state changes on.
*/
public void setSystemMonitorCallback(SystemMonitorCallback callback) {
- mCallback = callback;
+ synchronized (mLock) {
+ mCallback = callback;
+ if (!mWorkerHandler.hasCallbacks(this::getSystemLoadRepeated)) {
+ startSystemLoadMonitoring();
+ }
+ }
+ }
+
+ /**
+ * Unsets the {@link SystemMonitorCallback}.
+ */
+ public void unsetSystemMonitorCallback() {
+ synchronized (mLock) {
+ stopSystemLoadMonitoringLocked();
+ mCallback = null;
+ }
+ }
+
+ /**
+ * Gets the loadavg data from /proc/loadavg, getting the first 3 averages,
+ * which are 1-min, 5-min and 15-min moving averages respectively.
+ *
+ * Requires Selinux permissions 'open', 'read, 'getattr' to proc_loadavg,
+ * which is set in Car/car_product/sepolicy/private/carservice_app.te.
+ *
+ * @return the {@link CpuLoadavg}.
+ */
+ @VisibleForTesting
+ @Nullable
+ CpuLoadavg getCpuLoad() {
+ try (BufferedReader reader = new BufferedReader(new FileReader(mLoadavgPath))) {
+ String line = reader.readLine();
+ String[] vals = line.split("\\s+", NUM_LOADAVG_VALS + 1);
+ if (vals.length < NUM_LOADAVG_VALS) {
+ Slog.w(CarLog.TAG_TELEMETRY, "Loadavg wrong format");
+ return null;
+ }
+ CpuLoadavg cpuLoadavg = new CpuLoadavg();
+ cpuLoadavg.mOneMinuteVal = Float.parseFloat(vals[0]);
+ cpuLoadavg.mFiveMinutesVal = Float.parseFloat(vals[1]);
+ cpuLoadavg.mFifteenMinutesVal = Float.parseFloat(vals[2]);
+ return cpuLoadavg;
+ } catch (IOException | NumberFormatException ex) {
+ Slog.w(CarLog.TAG_TELEMETRY, "Failed to read loadavg file.", ex);
+ return null;
+ }
+ }
+
+ /**
+ * Gets the {@link ActivityManager.MemoryInfo} for system memory pressure.
+ *
+ * Of the MemoryInfo fields, we will only be using availMem and totalMem,
+ * since lowMemory and threshold are likely deprecated.
+ *
+ * @return {@link MemoryInfo} for the system.
+ */
+ private MemoryInfo getMemoryLoad() {
+ MemoryInfo mi = new ActivityManager.MemoryInfo();
+ mActivityManager.getMemoryInfo(mi);
+ return mi;
+ }
+
+ /**
+ * Sets the CPU usage level for a {@link SystemMonitorEvent}.
+ *
+ * @param event the {@link SystemMonitorEvent}.
+ * @param cpuLoadPerCore the CPU load average per CPU core.
+ */
+ @VisibleForTesting
+ void setEventCpuUsageLevel(SystemMonitorEvent event, double cpuLoadPerCore) {
+ if (cpuLoadPerCore > HI_CPU_LOAD_PER_CORE_BASE_LEVEL) {
+ event.setCpuUsageLevel(SystemMonitorEvent.USAGE_LEVEL_HI);
+ } else if (cpuLoadPerCore > MED_CPU_LOAD_PER_CORE_BASE_LEVEL
+ && cpuLoadPerCore <= HI_CPU_LOAD_PER_CORE_BASE_LEVEL) {
+ event.setCpuUsageLevel(SystemMonitorEvent.USAGE_LEVEL_MED);
+ } else {
+ event.setCpuUsageLevel(SystemMonitorEvent.USAGE_LEVEL_LOW);
+ }
+ }
+
+ /**
+ * Sets the memory usage level for a {@link SystemMonitorEvent}.
+ *
+ * @param event the {@link SystemMonitorEvent}.
+ * @param memLoadRatio ratio of used memory to total memory.
+ */
+ @VisibleForTesting
+ void setEventMemUsageLevel(SystemMonitorEvent event, double memLoadRatio) {
+ if (memLoadRatio > HI_MEM_LOAD_BASE_LEVEL) {
+ event.setMemoryUsageLevel(SystemMonitorEvent.USAGE_LEVEL_HI);
+ } else if (memLoadRatio > MED_MEM_LOAD_BASE_LEVEL
+ && memLoadRatio <= HI_MEM_LOAD_BASE_LEVEL) {
+ event.setMemoryUsageLevel(SystemMonitorEvent.USAGE_LEVEL_MED);
+ } else {
+ event.setMemoryUsageLevel(SystemMonitorEvent.USAGE_LEVEL_LOW);
+ }
+ }
+
+ /**
+ * The Runnable to repeatedly getting system load data with some interval.
+ */
+ private void getSystemLoadRepeated() {
+ synchronized (mLock) {
+ try {
+ CpuLoadavg cpuLoadAvg = getCpuLoad();
+ if (cpuLoadAvg == null) {
+ return;
+ }
+ int numProcessors = Runtime.getRuntime().availableProcessors();
+
+ MemoryInfo memInfo = getMemoryLoad();
+
+ SystemMonitorEvent event = new SystemMonitorEvent();
+ setEventCpuUsageLevel(event, cpuLoadAvg.mOneMinuteVal / numProcessors);
+ setEventMemUsageLevel(event, 1 - memInfo.availMem / memInfo.totalMem);
+
+ mCallback.onSystemMonitorEvent(event);
+ } finally {
+ if (mSystemMonitorRunning) {
+ mWorkerHandler.postDelayed(this::getSystemLoadRepeated, POLL_INTERVAL_MILLIS);
+ }
+ }
+ }
+ }
+
+ /**
+ * Starts system load monitoring.
+ */
+ private void startSystemLoadMonitoring() {
+ synchronized (mLock) {
+ mWorkerHandler.post(this::getSystemLoadRepeated);
+ mSystemMonitorRunning = true;
+ }
+ }
+
+ /**
+ * Stops system load monitoring.
+ */
+ @GuardedBy("mLock")
+ private void stopSystemLoadMonitoringLocked() {
+ synchronized (mLock) {
+ mWorkerHandler.removeCallbacks(this::getSystemLoadRepeated);
+ mSystemMonitorRunning = false;
+ }
+ }
+
+ static final class CpuLoadavg {
+ float mOneMinuteVal;
+ float mFiveMinutesVal;
+ float mFifteenMinutesVal;
}
}