1/*
2 * Copyright (C) 2017 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
17package com.android.car.storagemonitoring;
18
19import android.car.storagemonitoring.IoStatsEntry;
20import android.car.storagemonitoring.UidIoRecord;
21import android.test.suitebuilder.annotation.MediumTest;
22import android.util.SparseArray;
23import com.android.car.procfsinspector.ProcessInfo;
24import com.android.car.systeminterface.SystemStateInterface;
25import java.time.Duration;
26import java.util.ArrayList;
27import java.util.Collections;
28import java.util.List;
29import java.util.stream.Collectors;
30import junit.framework.TestCase;
31
32/**
33 * Tests IoStatsTracker functionality.
34 */
35@MediumTest
36public class IoStatsTrackerTest extends TestCase {
37    private static final int SAMPLE_WINDOW_MS = 1000;
38    private static final List<IoStatsEntry> EMPTY = Collections.emptyList();
39    private static final String TAG = IoStatsTrackerTest.class.getSimpleName();
40
41    public void testNewUsersAppear() throws Exception {
42        final MockSystemStateInterface mockSystemStateInterface = new MockSystemStateInterface();
43        IoStatsTracker ioStatsTracker = new IoStatsTracker(EMPTY,
44            SAMPLE_WINDOW_MS, mockSystemStateInterface);
45
46        assertEquals(0, ioStatsTracker.getCurrentSample().size());
47        assertEquals(0, ioStatsTracker.getTotal().size());
48
49        UserActivity user0 = new UserActivity(0);
50        user0.foreground_rchar = 50;
51        user0.background_wchar = 10;
52
53        UserActivity user1 = new UserActivity(1);
54        user1.foreground_rchar = 30;
55        user1.background_wchar = 50;
56
57        UidIoRecord process0 = user0.updateSystemState(mockSystemStateInterface);
58        UidIoRecord process1 = user1.updateSystemState(mockSystemStateInterface);
59
60        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
61
62        assertEquals(2, ioStatsTracker.getCurrentSample().size());
63        assertEquals(2, ioStatsTracker.getTotal().size());
64
65        assertTrue(ioStatsTracker.getCurrentSample().get(0).representsSameMetrics(process0));
66        assertTrue(ioStatsTracker.getCurrentSample().get(1).representsSameMetrics(process1));
67
68        assertTrue(ioStatsTracker.getTotal().get(0).representsSameMetrics(process0));
69        assertTrue(ioStatsTracker.getTotal().get(1).representsSameMetrics(process1));
70    }
71
72    public void testUserMetricsChange() throws Exception {
73        final MockSystemStateInterface mockSystemStateInterface = new MockSystemStateInterface();
74        IoStatsTracker ioStatsTracker = new IoStatsTracker(EMPTY,
75            SAMPLE_WINDOW_MS, mockSystemStateInterface);
76
77        UserActivity user0 = new UserActivity(0);
78        user0.foreground_rchar = 50;
79        user0.background_wchar = 10;
80
81        user0.updateSystemState(mockSystemStateInterface);
82        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
83
84        user0.foreground_rchar = 60;
85        user0.foreground_wchar = 10;
86        UidIoRecord process0 = user0.updateSystemState(mockSystemStateInterface);
87        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
88
89        assertEquals(1, ioStatsTracker.getCurrentSample().size());
90        assertEquals(1, ioStatsTracker.getTotal().size());
91
92        IoStatsEntry sample0 = ioStatsTracker.getCurrentSample().get(0);
93        IoStatsEntry total0 = ioStatsTracker.getTotal().get(0);
94
95        assertNotNull(sample0);
96        assertNotNull(total0);
97
98        assertTrue(total0.representsSameMetrics(process0));
99
100        assertEquals(10, sample0.foreground.bytesRead);
101        assertEquals(10, sample0.foreground.bytesWritten);
102        assertEquals(0, sample0.background.bytesWritten);
103    }
104
105    public void testUpdateNoIoProcessActive() throws Exception {
106        final MockSystemStateInterface mockSystemStateInterface = new MockSystemStateInterface();
107        IoStatsTracker ioStatsTracker = new IoStatsTracker(EMPTY,
108            SAMPLE_WINDOW_MS, mockSystemStateInterface);
109
110        UserActivity user0 = new UserActivity(0);
111        user0.foreground_rchar = 50;
112        user0.background_wchar = 10;
113
114        user0.updateSystemState(mockSystemStateInterface);
115        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
116
117        user0.spawnProcess();
118        user0.updateSystemState(mockSystemStateInterface);
119        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
120
121        assertEquals(1, ioStatsTracker.getCurrentSample().size());
122        assertEquals(1, ioStatsTracker.getTotal().size());
123
124        IoStatsEntry sample0 = ioStatsTracker.getCurrentSample().get(0);
125        IoStatsEntry total0 = ioStatsTracker.getTotal().get(0);
126
127        assertEquals(2 * SAMPLE_WINDOW_MS, sample0.runtimeMillis);
128        assertEquals(2 * SAMPLE_WINDOW_MS, total0.runtimeMillis);
129
130        assertEquals(0, sample0.foreground.bytesRead);
131        assertEquals(0, sample0.background.bytesWritten);
132    }
133
134    public void testUpdateNoIoProcessInactive() throws Exception {
135        final MockSystemStateInterface mockSystemStateInterface = new MockSystemStateInterface();
136        IoStatsTracker ioStatsTracker = new IoStatsTracker(EMPTY,
137            SAMPLE_WINDOW_MS, mockSystemStateInterface);
138
139        UserActivity user0 = new UserActivity(0);
140        user0.foreground_rchar = 50;
141        user0.background_wchar = 10;
142
143        user0.updateSystemState(mockSystemStateInterface);
144        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
145
146        user0.killProcess();
147        UidIoRecord record0 = user0.updateSystemState(mockSystemStateInterface);
148        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
149
150        assertEquals(0, ioStatsTracker.getCurrentSample().size());
151        assertEquals(1, ioStatsTracker.getTotal().size());
152
153        IoStatsEntry total0 = ioStatsTracker.getTotal().get(0);
154        assertEquals(SAMPLE_WINDOW_MS, total0.runtimeMillis);
155        assertTrue(total0.representsSameMetrics(record0));
156    }
157
158    public void testUpdateIoHappens() throws Exception {
159        final MockSystemStateInterface mockSystemStateInterface = new MockSystemStateInterface();
160        IoStatsTracker ioStatsTracker = new IoStatsTracker(EMPTY,
161            SAMPLE_WINDOW_MS, mockSystemStateInterface);
162
163        UserActivity user0 = new UserActivity(0);
164        user0.foreground_rchar = 50;
165        user0.background_wchar = 10;
166
167        user0.updateSystemState(mockSystemStateInterface);
168        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
169
170        user0.foreground_rchar = 60;
171        UidIoRecord record0 = user0.updateSystemState(mockSystemStateInterface);
172        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
173
174        assertEquals(1, ioStatsTracker.getCurrentSample().size());
175        assertEquals(1, ioStatsTracker.getTotal().size());
176
177        IoStatsEntry sample0 = ioStatsTracker.getCurrentSample().get(0);
178        IoStatsEntry total0 = ioStatsTracker.getTotal().get(0);
179
180        assertTrue(total0.representsSameMetrics(record0));
181        assertEquals(2 * SAMPLE_WINDOW_MS, total0.runtimeMillis);
182        assertEquals(2 * SAMPLE_WINDOW_MS, sample0.runtimeMillis);
183        assertEquals(10, sample0.foreground.bytesRead);
184        assertEquals(0, sample0.background.bytesWritten);
185    }
186
187    public void testUpdateGoAwayComeBackProcess() throws Exception {
188        final MockSystemStateInterface mockSystemStateInterface = new MockSystemStateInterface();
189        IoStatsTracker ioStatsTracker = new IoStatsTracker(EMPTY,
190            SAMPLE_WINDOW_MS, mockSystemStateInterface);
191
192        UserActivity user0 = new UserActivity(0);
193        user0.foreground_rchar = 50;
194        user0.background_wchar = 10;
195
196        user0.updateSystemState(mockSystemStateInterface);
197        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
198
199        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
200
201        assertEquals(0, ioStatsTracker.getCurrentSample().size());
202        assertEquals(1, ioStatsTracker.getTotal().size());
203
204        user0.spawnProcess();
205        user0.updateSystemState(mockSystemStateInterface);
206        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
207
208        assertEquals(1, ioStatsTracker.getCurrentSample().size());
209        IoStatsEntry sample0 = ioStatsTracker.getCurrentSample().get(0);
210        assertEquals(2 * SAMPLE_WINDOW_MS, sample0.runtimeMillis);
211    }
212
213    public void testUpdateGoAwayComeBackIo() throws Exception {
214        final MockSystemStateInterface mockSystemStateInterface = new MockSystemStateInterface();
215        IoStatsTracker ioStatsTracker = new IoStatsTracker(EMPTY,
216            SAMPLE_WINDOW_MS, mockSystemStateInterface);
217
218        UserActivity user0 = new UserActivity(0);
219        user0.foreground_rchar = 50;
220        user0.background_wchar = 10;
221
222        user0.updateSystemState(mockSystemStateInterface);
223        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
224
225        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
226
227        assertEquals(0, ioStatsTracker.getCurrentSample().size());
228        assertEquals(1, ioStatsTracker.getTotal().size());
229
230        user0.foreground_fsync = 1;
231
232        user0.updateSystemState(mockSystemStateInterface);
233        ioStatsTracker.update(mockSystemStateInterface.mIoRecords);
234
235        assertEquals(1, ioStatsTracker.getCurrentSample().size());
236
237        IoStatsEntry sample0 = ioStatsTracker.getCurrentSample().get(0);
238        assertEquals(2 * SAMPLE_WINDOW_MS, sample0.runtimeMillis);
239        assertEquals(1, sample0.foreground.fsyncCalls);
240    }
241
242    private static final class UserActivity {
243        private final int mUid;
244        private boolean mHasProcess;
245
246        private long foreground_rchar;
247        private long foreground_wchar;
248        private long foreground_read_bytes;
249        private long foreground_write_bytes;
250        private long foreground_fsync;
251
252        private long background_rchar;
253        private long background_wchar;
254        private long background_read_bytes;
255        private long background_write_bytes;
256        private long background_fsync;
257
258        UserActivity(int uid) {
259            mUid = uid;
260        }
261
262        void spawnProcess() {
263            mHasProcess = true;
264        }
265        void killProcess() {
266            mHasProcess = false;
267        }
268
269        UidIoRecord updateSystemState(MockSystemStateInterface systemState) {
270            UidIoRecord uidIoRecord = new UidIoRecord(mUid,
271                foreground_rchar,
272                foreground_wchar,
273                foreground_read_bytes,
274                foreground_write_bytes,
275                foreground_fsync,
276                background_rchar,
277                background_wchar,
278                background_read_bytes,
279                background_write_bytes,
280                background_fsync);
281
282            systemState.addIoRecord(uidIoRecord);
283            if (mHasProcess) {
284                systemState.addProcess(new ProcessInfo(1, mUid));
285            } else {
286                systemState.removeUserProcesses(mUid);
287            }
288
289            return uidIoRecord;
290        }
291    }
292
293    private final class MockSystemStateInterface implements SystemStateInterface {
294        private final List<ProcessInfo> mProcesses = new ArrayList<>();
295        private final SparseArray<UidIoRecord> mIoRecords = new SparseArray<>();
296
297        @Override
298        public void shutdown() {
299        }
300
301        @Override
302        public boolean enterDeepSleep(int wakeupTimeSec) {
303            return true;
304        }
305
306        @Override
307        public void scheduleActionForBootCompleted(Runnable action, Duration delay) {
308        }
309
310        @Override
311        public boolean isWakeupCausedByTimer() {
312            return false;
313        }
314
315        @Override
316        public boolean isSystemSupportingDeepSleep() {
317            return false;
318        }
319
320        @Override
321        public synchronized List<ProcessInfo> getRunningProcesses() {
322            return mProcesses;
323        }
324
325        synchronized void addProcess(ProcessInfo processInfo) {
326            mProcesses.add(processInfo);
327        }
328
329        synchronized void removeUserProcesses(int uid) {
330            mProcesses.removeAll(
331                    mProcesses.stream().filter(pi -> pi.uid == uid).collect(Collectors.toList()));
332        }
333
334        synchronized void addIoRecord(UidIoRecord record) {
335            mIoRecords.put(record.uid, record);
336        }
337
338        synchronized void clear() {
339            mProcesses.clear();
340            mIoRecords.clear();
341        }
342    }
343}
344