1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.car.telemetry.systemmonitor;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.ActivityManager;
22 import android.app.ActivityManager.MemoryInfo;
23 import android.car.builtin.util.Slogf;
24 import android.os.Handler;
25 
26 import com.android.car.CarLog;
27 import com.android.internal.annotations.VisibleForTesting;
28 
29 import java.io.BufferedReader;
30 import java.io.FileReader;
31 import java.io.IOException;
32 
33 /**
34  * SystemMonitor monitors system states and report to listeners when there are
35  * important changes.
36  * All methods in this class should be invoked from the telemetry thread.
37  */
38 public class SystemMonitor {
39 
40     private static final int NUM_LOADAVG_VALS = 3;
41     private static final float HI_CPU_LOAD_PER_CORE_BASE_LEVEL = 1.0f;
42     private static final float MED_CPU_LOAD_PER_CORE_BASE_LEVEL = 0.5f;
43     private static final float HI_MEM_LOAD_BASE_LEVEL = 0.95f;
44     private static final float MED_MEM_LOAD_BASE_LEVEL = 0.80f;
45     private static final String LOADAVG_PATH = "/proc/loadavg";
46 
47     private static final int POLL_INTERVAL_MILLIS = 60000;
48 
49     private final Handler mTelemetryHandler;
50 
51     private final ActivityManager mActivityManager;
52     private final String mLoadavgPath;
53     private final Runnable mSystemLoadRunnable = this::getSystemLoadRepeated;
54 
55     @Nullable private SystemMonitorCallback mCallback;
56     private boolean mSystemMonitorRunning = false;
57 
58     /**
59      * Interface for receiving notifications about system monitor changes.
60      */
61     public interface SystemMonitorCallback {
62         /**
63          * Listens to system monitor event.
64          *
65          * @param event the system monitor event.
66          */
onSystemMonitorEvent(@onNull SystemMonitorEvent event)67         void onSystemMonitorEvent(@NonNull SystemMonitorEvent event);
68     }
69 
70     /**
71      * Creates a SystemMonitor instance set with default loadavg path.
72      *
73      * @param activityManager Activity manager this is running in.
74      * @param workerHandler a handler for running monitoring jobs.
75      * @return SystemMonitor instance.
76      */
create( @onNull ActivityManager activityManager, @NonNull Handler workerHandler)77     public static SystemMonitor create(
78             @NonNull ActivityManager activityManager, @NonNull Handler workerHandler) {
79         return new SystemMonitor(activityManager, workerHandler, LOADAVG_PATH);
80     }
81 
82     @VisibleForTesting
SystemMonitor( @onNull ActivityManager activityManager, @NonNull Handler telemetryHandler, @NonNull String loadavgPath)83     SystemMonitor(
84             @NonNull ActivityManager activityManager,
85             @NonNull Handler telemetryHandler,
86             @NonNull String loadavgPath) {
87         mTelemetryHandler = telemetryHandler;
88         mActivityManager = activityManager;
89         mLoadavgPath = loadavgPath;
90     }
91 
92     /**
93      * Sets the {@link SystemMonitorCallback} to notify of system state changes.
94      *
95      * @param callback the callback to nofify state changes on.
96      */
setSystemMonitorCallback(@onNull SystemMonitorCallback callback)97     public void setSystemMonitorCallback(@NonNull SystemMonitorCallback callback) {
98         mCallback = callback;
99         if (!mSystemMonitorRunning) {
100             startSystemLoadMonitoring();
101         }
102     }
103 
104     /**
105      * Unsets the {@link SystemMonitorCallback}.
106      */
unsetSystemMonitorCallback()107     public void unsetSystemMonitorCallback() {
108         mTelemetryHandler.removeCallbacks(mSystemLoadRunnable);
109         mSystemMonitorRunning = false;
110         mCallback = null;
111     }
112 
113     /**
114      * Gets the loadavg data from /proc/loadavg, getting the first 3 averages,
115      * which are 1-min, 5-min and 15-min moving averages respectively.
116      *
117      * Requires Selinux permissions 'open', 'read, 'getattr' to proc_loadavg,
118      * which is set in Car/car_product/sepolicy/private/carservice_app.te.
119      *
120      * @return the {@link CpuLoadavg}.
121      */
122     @VisibleForTesting
123     @Nullable
getCpuLoad()124     CpuLoadavg getCpuLoad() {
125         try (BufferedReader reader = new BufferedReader(new FileReader(mLoadavgPath))) {
126             String line = reader.readLine();
127             String[] vals = line.split("\\s+", NUM_LOADAVG_VALS + 1);
128             if (vals.length < NUM_LOADAVG_VALS) {
129                 Slogf.w(CarLog.TAG_TELEMETRY, "Loadavg wrong format");
130                 return null;
131             }
132             CpuLoadavg cpuLoadavg = new CpuLoadavg();
133             cpuLoadavg.mOneMinuteVal = Float.parseFloat(vals[0]);
134             cpuLoadavg.mFiveMinutesVal = Float.parseFloat(vals[1]);
135             cpuLoadavg.mFifteenMinutesVal = Float.parseFloat(vals[2]);
136             return cpuLoadavg;
137         } catch (IOException | NumberFormatException ex) {
138             Slogf.w(CarLog.TAG_TELEMETRY, "Failed to read loadavg file.", ex);
139             return null;
140         }
141     }
142 
143     /**
144      * Gets the {@link ActivityManager.MemoryInfo} for system memory pressure.
145      *
146      * Of the MemoryInfo fields, we will only be using availMem and totalMem,
147      * since lowMemory and threshold are likely deprecated.
148      *
149      * @return {@link MemoryInfo} for the system.
150      */
151     @NonNull
getMemoryLoad()152     private MemoryInfo getMemoryLoad() {
153         MemoryInfo mi = new ActivityManager.MemoryInfo();
154         mActivityManager.getMemoryInfo(mi);
155         return mi;
156     }
157 
158     /**
159      * Sets the CPU usage level for a {@link SystemMonitorEvent}.
160      *
161      * @param event the {@link SystemMonitorEvent}.
162      * @param cpuLoadPerCore the CPU load average per CPU core.
163      */
164     @VisibleForTesting
setEventCpuUsageLevel(@onNull SystemMonitorEvent event, double cpuLoadPerCore)165     void setEventCpuUsageLevel(@NonNull SystemMonitorEvent event, double cpuLoadPerCore) {
166         if (cpuLoadPerCore > HI_CPU_LOAD_PER_CORE_BASE_LEVEL) {
167             event.setCpuUsageLevel(SystemMonitorEvent.USAGE_LEVEL_HI);
168         } else if (cpuLoadPerCore > MED_CPU_LOAD_PER_CORE_BASE_LEVEL
169                    && cpuLoadPerCore <= HI_CPU_LOAD_PER_CORE_BASE_LEVEL) {
170             event.setCpuUsageLevel(SystemMonitorEvent.USAGE_LEVEL_MED);
171         } else {
172             event.setCpuUsageLevel(SystemMonitorEvent.USAGE_LEVEL_LOW);
173         }
174     }
175 
176     /**
177      * Sets the memory usage level for a {@link SystemMonitorEvent}.
178      *
179      * @param event the {@link SystemMonitorEvent}.
180      * @param memLoadRatio ratio of used memory to total memory.
181      */
182     @VisibleForTesting
setEventMemUsageLevel(@onNull SystemMonitorEvent event, double memLoadRatio)183     void setEventMemUsageLevel(@NonNull SystemMonitorEvent event, double memLoadRatio) {
184         if (memLoadRatio > HI_MEM_LOAD_BASE_LEVEL) {
185             event.setMemoryUsageLevel(SystemMonitorEvent.USAGE_LEVEL_HI);
186         } else if (memLoadRatio > MED_MEM_LOAD_BASE_LEVEL
187                    && memLoadRatio <= HI_MEM_LOAD_BASE_LEVEL) {
188             event.setMemoryUsageLevel(SystemMonitorEvent.USAGE_LEVEL_MED);
189         } else {
190             event.setMemoryUsageLevel(SystemMonitorEvent.USAGE_LEVEL_LOW);
191         }
192     }
193 
194     /**
195      * The Runnable to repeatedly getting system load data with some interval.
196      */
getSystemLoadRepeated()197     private void getSystemLoadRepeated() {
198         try {
199             CpuLoadavg cpuLoadAvg = getCpuLoad();
200             if (cpuLoadAvg == null) {
201                 return;
202             }
203             int numProcessors = Runtime.getRuntime().availableProcessors();
204 
205             MemoryInfo memInfo = getMemoryLoad();
206 
207             SystemMonitorEvent event = new SystemMonitorEvent();
208             setEventCpuUsageLevel(event, cpuLoadAvg.mOneMinuteVal / numProcessors);
209             setEventMemUsageLevel(event, 1 - (double) memInfo.availMem / memInfo.totalMem);
210 
211             mCallback.onSystemMonitorEvent(event);
212         } finally {
213             if (mSystemMonitorRunning) {
214                 mTelemetryHandler.postDelayed(mSystemLoadRunnable, POLL_INTERVAL_MILLIS);
215             }
216         }
217     }
218 
219     /**
220      * Starts system load monitoring.
221      */
startSystemLoadMonitoring()222     private void startSystemLoadMonitoring() {
223         mTelemetryHandler.post(mSystemLoadRunnable);
224         mSystemMonitorRunning = true;
225     }
226 
227     static final class CpuLoadavg {
228         float mOneMinuteVal;
229         float mFiveMinutesVal;
230         float mFifteenMinutesVal;
231     }
232 }
233