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 static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.Mockito.any;
22 import static org.mockito.Mockito.atLeastOnce;
23 import static org.mockito.Mockito.doAnswer;
24 import static org.mockito.Mockito.times;
25 import static org.mockito.Mockito.verify;
26 import static org.mockito.Mockito.when;
27 
28 import android.app.ActivityManager;
29 import android.app.ActivityManager.MemoryInfo;
30 import android.os.Handler;
31 
32 import org.junit.Before;
33 import org.junit.Rule;
34 import org.junit.Test;
35 import org.junit.rules.TemporaryFolder;
36 import org.junit.runner.RunWith;
37 import org.mockito.ArgumentCaptor;
38 import org.mockito.Captor;
39 import org.mockito.Mock;
40 import org.mockito.junit.MockitoJUnitRunner;
41 
42 import java.io.File;
43 import java.io.FileWriter;
44 import java.io.IOException;
45 
46 @RunWith(MockitoJUnitRunner.class)
47 public class SystemMonitorTest {
48 
49     @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
50 
51     private static final String TEST_LOADAVG = "0.2 3.4 2.2 123/1452 21348";
52     private static final String TEST_LOADAVG_BAD_FORMAT = "1.2 3.4";
53     private static final String TEST_LOADAVG_NOT_FLOAT = "1.2 abc 2.1 12/231 2";
54 
55     @Mock private Handler mMockHandler; // it promptly executes the runnable in the same thread
56     @Mock private ActivityManager mMockActivityManager;
57     @Mock private SystemMonitor.SystemMonitorCallback mMockCallback;
58 
59     @Captor ArgumentCaptor<Runnable> mRunnableCaptor;
60     @Captor ArgumentCaptor<SystemMonitorEvent> mEventCaptor;
61 
62     @Before
setup()63     public void setup() {
64         when(mMockHandler.post(any(Runnable.class))).thenAnswer(i -> {
65             Runnable runnable = i.getArgument(0);
66             runnable.run();
67             return true;
68         });
69     }
70 
71     @Test
testSetEventCpuUsageLevel_setsCorrectUsageLevelForHighUsage()72     public void testSetEventCpuUsageLevel_setsCorrectUsageLevelForHighUsage() {
73         SystemMonitor systemMonitor = SystemMonitor.create(mMockActivityManager, mMockHandler);
74         SystemMonitorEvent event = new SystemMonitorEvent();
75 
76         systemMonitor.setEventCpuUsageLevel(event, /* cpuLoadPerCore= */ 1.5);
77 
78         assertThat(event.getCpuUsageLevel())
79             .isEqualTo(SystemMonitorEvent.USAGE_LEVEL_HI);
80     }
81 
82     @Test
testSetEventCpuUsageLevel_setsCorrectUsageLevelForMedUsage()83     public void testSetEventCpuUsageLevel_setsCorrectUsageLevelForMedUsage() {
84         SystemMonitor systemMonitor = SystemMonitor.create(mMockActivityManager, mMockHandler);
85         SystemMonitorEvent event = new SystemMonitorEvent();
86 
87         systemMonitor.setEventCpuUsageLevel(event, /* cpuLoadPerCore= */ 0.6);
88 
89         assertThat(event.getCpuUsageLevel())
90             .isEqualTo(SystemMonitorEvent.USAGE_LEVEL_MED);
91     }
92 
93     @Test
testSetEventCpuUsageLevel_setsCorrectUsageLevelForLowUsage()94     public void testSetEventCpuUsageLevel_setsCorrectUsageLevelForLowUsage() {
95         SystemMonitor systemMonitor = SystemMonitor.create(mMockActivityManager, mMockHandler);
96         SystemMonitorEvent event = new SystemMonitorEvent();
97 
98         systemMonitor.setEventCpuUsageLevel(event, /* cpuLoadPerCore= */ 0.5);
99 
100         assertThat(event.getCpuUsageLevel())
101             .isEqualTo(SystemMonitorEvent.USAGE_LEVEL_LOW);
102     }
103 
104     @Test
testSetEventMemUsageLevel_setsCorrectUsageLevelForHighUsage()105     public void testSetEventMemUsageLevel_setsCorrectUsageLevelForHighUsage() {
106         SystemMonitor systemMonitor = SystemMonitor.create(mMockActivityManager, mMockHandler);
107         SystemMonitorEvent event = new SystemMonitorEvent();
108 
109         systemMonitor.setEventMemUsageLevel(event, /* memLoadRatio= */ 0.98);
110 
111         assertThat(event.getMemoryUsageLevel())
112             .isEqualTo(SystemMonitorEvent.USAGE_LEVEL_HI);
113     }
114 
115     @Test
testSetEventMemUsageLevel_setsCorrectUsageLevelForMedUsage()116     public void testSetEventMemUsageLevel_setsCorrectUsageLevelForMedUsage() {
117         SystemMonitor systemMonitor = SystemMonitor.create(mMockActivityManager, mMockHandler);
118         SystemMonitorEvent event = new SystemMonitorEvent();
119 
120         systemMonitor.setEventMemUsageLevel(event, /* memLoadRatio= */ 0.85);
121 
122         assertThat(event.getMemoryUsageLevel())
123             .isEqualTo(SystemMonitorEvent.USAGE_LEVEL_MED);
124     }
125 
126     @Test
testSetEventMemUsageLevel_setsCorrectUsageLevelForLowUsage()127     public void testSetEventMemUsageLevel_setsCorrectUsageLevelForLowUsage() {
128         SystemMonitor systemMonitor = SystemMonitor.create(mMockActivityManager, mMockHandler);
129         SystemMonitorEvent event = new SystemMonitorEvent();
130 
131         systemMonitor.setEventMemUsageLevel(event, /* memLoadRatio= */ 0.80);
132 
133         assertThat(event.getMemoryUsageLevel())
134             .isEqualTo(SystemMonitorEvent.USAGE_LEVEL_LOW);
135     }
136 
137     @Test
testSetCallback_whenMemUsageLow_shouldInvokeCallback()138     public void testSetCallback_whenMemUsageLow_shouldInvokeCallback() throws IOException {
139         doAnswer(i -> {
140             MemoryInfo mi = i.getArgument(0); // memory usage is at 50%
141             mi.availMem = 5_000_000L;
142             mi.totalMem = 10_000_000;
143             return null;
144         }).when(mMockActivityManager).getMemoryInfo(any(MemoryInfo.class));
145         SystemMonitor systemMonitor = new SystemMonitor(
146                 mMockActivityManager, mMockHandler, writeTempFile(TEST_LOADAVG));
147 
148         systemMonitor.setSystemMonitorCallback(mMockCallback);
149 
150         verify(mMockCallback, atLeastOnce()).onSystemMonitorEvent(mEventCaptor.capture());
151         SystemMonitorEvent event = mEventCaptor.getValue();
152         // from TEST_LOADAVG, cpu load = 0.2 / numProcessors, CPU usage should be low
153         assertThat(event.getCpuUsageLevel()).isEqualTo(SystemMonitorEvent.USAGE_LEVEL_LOW);
154         // 1 - 5_000_000 / 10_000_000 = 0.5, memory usage should be low
155         assertThat(event.getMemoryUsageLevel()).isEqualTo(SystemMonitorEvent.USAGE_LEVEL_LOW);
156     }
157 
158     @Test
testSetCallback_whenMemUsageHigh_shouldInvokeCallback()159     public void testSetCallback_whenMemUsageHigh_shouldInvokeCallback() throws IOException {
160         doAnswer(i -> {
161             MemoryInfo mi = i.getArgument(0); // memory usage is at 95%
162             mi.availMem = 500_000L;
163             mi.totalMem = 10_000_000L;
164             return null;
165         }).when(mMockActivityManager).getMemoryInfo(any(MemoryInfo.class));
166         SystemMonitor systemMonitor = new SystemMonitor(
167                 mMockActivityManager, mMockHandler, writeTempFile(TEST_LOADAVG));
168 
169         systemMonitor.setSystemMonitorCallback(mMockCallback);
170 
171         verify(mMockCallback, atLeastOnce()).onSystemMonitorEvent(mEventCaptor.capture());
172         SystemMonitorEvent event = mEventCaptor.getValue();
173         // 1 - 500_000 / 10_000_000 = 0.95, memory usage should be high
174         assertThat(event.getMemoryUsageLevel()).isEqualTo(SystemMonitorEvent.USAGE_LEVEL_HI);
175     }
176 
177     @Test
testWhenLoadavgIsBadFormat_getCpuLoadReturnsNull()178     public void testWhenLoadavgIsBadFormat_getCpuLoadReturnsNull() throws IOException {
179         SystemMonitor systemMonitor = new SystemMonitor(
180                 mMockActivityManager, mMockHandler, writeTempFile(TEST_LOADAVG_BAD_FORMAT));
181 
182         assertThat(systemMonitor.getCpuLoad()).isNull();
183     }
184 
185     @Test
testWhenLoadavgIsNotFloatParsable_getCpuLoadReturnsNull()186     public void testWhenLoadavgIsNotFloatParsable_getCpuLoadReturnsNull() throws IOException {
187         SystemMonitor systemMonitor = new SystemMonitor(
188                 mMockActivityManager, mMockHandler, writeTempFile(TEST_LOADAVG_NOT_FLOAT));
189 
190         assertThat(systemMonitor.getCpuLoad()).isNull();
191     }
192 
193     @Test
testWhenUnsetCallback_sameCallbackFromSetCallbackIsRemoved()194     public void testWhenUnsetCallback_sameCallbackFromSetCallbackIsRemoved() throws IOException {
195         SystemMonitor systemMonitor = new SystemMonitor(
196                 mMockActivityManager, mMockHandler, writeTempFile(TEST_LOADAVG));
197 
198         systemMonitor.setSystemMonitorCallback(mMockCallback);
199         systemMonitor.unsetSystemMonitorCallback();
200 
201         verify(mMockHandler, times(1)).post(mRunnableCaptor.capture());
202         Runnable setRunnable = mRunnableCaptor.getValue();
203         verify(mMockHandler, times(1)).removeCallbacks(mRunnableCaptor.capture());
204         Runnable unsetRunnable = mRunnableCaptor.getValue();
205         assertThat(setRunnable).isEqualTo(unsetRunnable);
206     }
207 
208     /**
209      * Creates and writes to the temp file, returns its path.
210      */
writeTempFile(String content)211     private String writeTempFile(String content) throws IOException {
212         File tempFile = temporaryFolder.newFile();
213         try (FileWriter fw = new FileWriter(tempFile)) {
214             fw.write(content);
215         }
216         return tempFile.getAbsolutePath();
217     }
218 }
219