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;
     }
 }