1 /*
2  * Copyright (C) 2024 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 package com.android.server.power.stats;
17 
18 import static org.junit.Assert.assertEquals;
19 import static org.junit.Assert.assertFalse;
20 import static org.junit.Assert.assertTrue;
21 
22 import android.os.nano.OsProtoEnums;
23 
24 import androidx.test.filters.SmallTest;
25 import androidx.test.runner.AndroidJUnit4;
26 
27 import org.junit.Before;
28 import org.junit.Test;
29 import org.junit.runner.RunWith;
30 
31 import java.util.ArrayList;
32 
33 @RunWith(AndroidJUnit4.class)
34 @SmallTest
35 public class WakelockStatsFrameworkEventsTest {
36     private WakelockStatsFrameworkEvents mEvents;
37 
38     @Before
setup()39     public void setup() {
40         mEvents = new WakelockStatsFrameworkEvents();
41     }
42 
43     private static final int UID_1 = 1;
44     private static final int UID_2 = 2;
45 
46     private static final String TAG_1 = "TAG1";
47     private static final String TAG_2 = "TAG2";
48 
49     private static final int WAKELOCK_TYPE_1 = OsProtoEnums.PARTIAL_WAKE_LOCK;
50     private static final int WAKELOCK_TYPE_2 = OsProtoEnums.DOZE_WAKE_LOCK;
51 
52     private static final long TS_1 = 1000;
53     private static final long TS_2 = 2000;
54     private static final long TS_3 = 3000;
55     private static final long TS_4 = 4000;
56     private static final long TS_5 = 5000;
57 
58     // Mirrors com.android.os.framework.FrameworkWakelockInfo proto.
59     private static class WakelockInfo {
60         public int uid;
61         public String tag;
62         public int type;
63         public long uptimeMillis;
64         public long completedCount;
65 
WakelockInfo(int uid, String tag, int type, long uptimeMillis, long completedCount)66         WakelockInfo(int uid, String tag, int type, long uptimeMillis, long completedCount) {
67             this.uid = uid;
68             this.tag = tag;
69             this.type = type;
70             this.uptimeMillis = uptimeMillis;
71             this.completedCount = completedCount;
72         }
73     }
74 
75     // Assumes that mEvents is empty.
76     @SuppressWarnings("GuardedBy")
makeMetricsAlmostOverflow()77     private void makeMetricsAlmostOverflow() throws Exception {
78         for (int i = 0; i < mEvents.SUMMARY_THRESHOLD - 1; i++) {
79             String tag = "forceOverflow" + i;
80             mEvents.noteStartWakeLock(UID_1, tag, WAKELOCK_TYPE_1, TS_1);
81             mEvents.noteStopWakeLock(UID_1, tag, WAKELOCK_TYPE_1, TS_2);
82         }
83 
84         assertFalse("not overflow", mEvents.inOverflow());
85         ArrayList<WakelockInfo> info = pullResults(TS_4);
86         WakelockInfo notOverflowInfo =
87                 info.stream()
88                         .filter(i -> i.tag.equals(mEvents.OVERFLOW_TAG))
89                         .findFirst()
90                         .orElse(null);
91 
92         assertEquals("not overflow", notOverflowInfo, null);
93 
94         // Add one more to hit an overflow state.
95         String lastTag = "forceOverflowLast";
96         mEvents.noteStartWakeLock(UID_1, lastTag, WAKELOCK_TYPE_2, TS_1);
97         mEvents.noteStopWakeLock(UID_1, lastTag, WAKELOCK_TYPE_2, TS_2);
98 
99         assertTrue("overflow", mEvents.inOverflow());
100         info = pullResults(TS_4);
101 
102         WakelockInfo tag1Info =
103                 info.stream().filter(i -> i.tag.equals(lastTag)).findFirst().orElse(null);
104 
105         assertTrue("lastTag found", tag1Info != null);
106         assertEquals("uid", UID_1, tag1Info.uid);
107         assertEquals("tag", lastTag, tag1Info.tag);
108         assertEquals("type", WAKELOCK_TYPE_2, tag1Info.type);
109         assertEquals("duration", TS_2 - TS_1, tag1Info.uptimeMillis);
110         assertEquals("count", 1, tag1Info.completedCount);
111     }
112 
113     // Assumes that mEvents is empty.
114     @SuppressWarnings("GuardedBy")
makeMetricsAlmostHardCap()115     private void makeMetricsAlmostHardCap() throws Exception {
116         for (int i = 0; i < mEvents.MAX_WAKELOCK_DIMENSIONS - 1; i++) {
117             mEvents.noteStartWakeLock(i /* uid */, TAG_1, WAKELOCK_TYPE_1, TS_1);
118             mEvents.noteStopWakeLock(i /* uid */, TAG_1, WAKELOCK_TYPE_1, TS_2);
119         }
120 
121         assertFalse("not hard capped", mEvents.inHardCap());
122         ArrayList<WakelockInfo> info = pullResults(TS_4);
123         WakelockInfo notOverflowInfo =
124                 info.stream()
125                         .filter(i -> i.tag.equals(mEvents.HARD_CAP_TAG))
126                         .findFirst()
127                         .orElse(null);
128 
129         assertEquals("not overflow", notOverflowInfo, null);
130 
131         // Add one more to hit an hardcap state.
132         int hardCapUid = mEvents.MAX_WAKELOCK_DIMENSIONS;
133         mEvents.noteStartWakeLock(hardCapUid, TAG_2, WAKELOCK_TYPE_2, TS_1);
134         mEvents.noteStopWakeLock(hardCapUid, TAG_2, WAKELOCK_TYPE_2, TS_2);
135 
136         assertTrue("hard capped", mEvents.inHardCap());
137         info = pullResults(TS_4);
138 
139         WakelockInfo tag2Info =
140                 info.stream().filter(i -> i.uid == hardCapUid).findFirst().orElse(null);
141 
142         assertTrue("hardCapUid found", tag2Info != null);
143         assertEquals("uid", hardCapUid, tag2Info.uid);
144         assertEquals("tag", mEvents.OVERFLOW_TAG, tag2Info.tag);
145         assertEquals("type", mEvents.OVERFLOW_LEVEL, tag2Info.type);
146         assertEquals("duration", TS_2 - TS_1, tag2Info.uptimeMillis);
147         assertEquals("count", 1, tag2Info.completedCount);
148     }
149 
pullResults(long timestamp)150     private ArrayList<WakelockInfo> pullResults(long timestamp) {
151         ArrayList<WakelockInfo> results = new ArrayList<>();
152         WakelockStatsFrameworkEvents.EventLogger logger =
153                 new WakelockStatsFrameworkEvents.EventLogger() {
154                     public void logResult(
155                             int uid,
156                             String tag,
157                             int wakeLockLevel,
158                             long uptimeMillis,
159                             long completedCount) {
160                         WakelockInfo info =
161                                 new WakelockInfo(
162                                         uid, tag, wakeLockLevel, uptimeMillis, completedCount);
163                         results.add(info);
164                     }
165                 };
166         mEvents.pullFrameworkWakelockInfoAtoms(timestamp, logger);
167         return results;
168     }
169 
170     @Test
singleWakelock()171     public void singleWakelock() {
172         mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_1);
173         mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_2);
174 
175         ArrayList<WakelockInfo> info = pullResults(TS_3);
176 
177         assertEquals("size", 1, info.size());
178         assertEquals("uid", UID_1, info.get(0).uid);
179         assertEquals("tag", TAG_1, info.get(0).tag);
180         assertEquals("type", WAKELOCK_TYPE_1, info.get(0).type);
181         assertEquals("duration", TS_2 - TS_1, info.get(0).uptimeMillis);
182         assertEquals("count", 1, info.get(0).completedCount);
183     }
184 
185     @Test
wakelockOpen()186     public void wakelockOpen() throws Exception {
187         mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_1);
188 
189         ArrayList<WakelockInfo> info = pullResults(TS_3);
190 
191         assertEquals("size", 1, info.size());
192         assertEquals("uid", UID_1, info.get(0).uid);
193         assertEquals("tag", TAG_1, info.get(0).tag);
194         assertEquals("type", WAKELOCK_TYPE_1, info.get(0).type);
195         assertEquals("duration", TS_3 - TS_1, info.get(0).uptimeMillis);
196         assertEquals("count", 0, info.get(0).completedCount);
197     }
198 
199     @Test
wakelockOpenOverlap()200     public void wakelockOpenOverlap() throws Exception {
201         mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_1);
202         mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_2);
203         mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_3);
204 
205         ArrayList<WakelockInfo> info = pullResults(TS_4);
206 
207         assertEquals("size", 1, info.size());
208         assertEquals("uid", UID_1, info.get(0).uid);
209         assertEquals("tag", TAG_1, info.get(0).tag);
210         assertEquals("type", WAKELOCK_TYPE_1, info.get(0).type);
211         assertEquals("duration", TS_4 - TS_1, info.get(0).uptimeMillis);
212         assertEquals("count", 0, info.get(0).completedCount);
213     }
214 
215     @Test
testOverflow()216     public void testOverflow() throws Exception {
217         makeMetricsAlmostOverflow();
218 
219         // This one gets tagged as an overflow.
220         mEvents.noteStartWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2, TS_1);
221         mEvents.noteStopWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2, TS_2);
222 
223         ArrayList<WakelockInfo> info = pullResults(TS_4);
224         WakelockInfo overflowInfo =
225                 info.stream()
226                         .filter(i -> i.tag.equals(mEvents.OVERFLOW_TAG))
227                         .findFirst()
228                         .orElse(null);
229 
230         assertEquals("uid", UID_1, overflowInfo.uid);
231         assertEquals("type", mEvents.OVERFLOW_LEVEL, overflowInfo.type);
232         assertEquals("duration", TS_2 - TS_1, overflowInfo.uptimeMillis);
233         assertEquals("count", 1, overflowInfo.completedCount);
234     }
235 
236     @Test
testOverflowOpen()237     public void testOverflowOpen() throws Exception {
238         makeMetricsAlmostOverflow();
239 
240         // This is the open wakelock that overflows.
241         mEvents.noteStartWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2, TS_1);
242 
243         ArrayList<WakelockInfo> info = pullResults(TS_4);
244         WakelockInfo overflowInfo =
245                 info.stream()
246                         .filter(i -> i.tag.equals(mEvents.OVERFLOW_TAG))
247                         .findFirst()
248                         .orElse(null);
249 
250         assertEquals("uid", UID_1, overflowInfo.uid);
251         assertEquals("type", mEvents.OVERFLOW_LEVEL, overflowInfo.type);
252         assertEquals("duration", (TS_4 - TS_1), overflowInfo.uptimeMillis);
253         assertEquals("count", 0, overflowInfo.completedCount);
254     }
255 
256     @Test
testHardCap()257     public void testHardCap() throws Exception {
258         makeMetricsAlmostHardCap();
259 
260         // This one gets tagged as a hard cap.
261         mEvents.noteStartWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2, TS_1);
262         mEvents.noteStopWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2, TS_2);
263 
264         ArrayList<WakelockInfo> info = pullResults(TS_4);
265         WakelockInfo hardCapInfo =
266                 info.stream()
267                         .filter(i -> i.tag.equals(mEvents.HARD_CAP_TAG))
268                         .findFirst()
269                         .orElse(null);
270 
271         assertEquals("uid", mEvents.HARD_CAP_UID, hardCapInfo.uid);
272         assertEquals("type", mEvents.OVERFLOW_LEVEL, hardCapInfo.type);
273         assertEquals("duration", TS_2 - TS_1, hardCapInfo.uptimeMillis);
274         assertEquals("count", 1, hardCapInfo.completedCount);
275     }
276 
277     @Test
testHardCapOpen()278     public void testHardCapOpen() throws Exception {
279         makeMetricsAlmostHardCap();
280 
281         // This is the open wakelock that overflows.
282         mEvents.noteStartWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_2, TS_1);
283 
284         ArrayList<WakelockInfo> info = pullResults(TS_4);
285         WakelockInfo hardCapInfo =
286                 info.stream()
287                         .filter(i -> i.tag.equals(mEvents.HARD_CAP_TAG))
288                         .findFirst()
289                         .orElse(null);
290 
291         assertEquals("uid", mEvents.HARD_CAP_UID, hardCapInfo.uid);
292         assertEquals("type", mEvents.OVERFLOW_LEVEL, hardCapInfo.type);
293         assertEquals("duration", (TS_4 - TS_1), hardCapInfo.uptimeMillis);
294         assertEquals("count", 0, hardCapInfo.completedCount);
295     }
296 
297     @Test
overlappingWakelocks()298     public void overlappingWakelocks() throws Exception {
299         mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_1);
300         mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_2);
301         mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_3);
302         mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_4);
303 
304         ArrayList<WakelockInfo> info = pullResults(TS_5);
305 
306         assertEquals("size", 1, info.size());
307         assertEquals("uid", UID_1, info.get(0).uid);
308         assertEquals("tag", TAG_1, info.get(0).tag);
309         assertEquals("type", WAKELOCK_TYPE_1, info.get(0).type);
310         assertEquals("duration", TS_4 - TS_1, info.get(0).uptimeMillis);
311         assertEquals("count", 1, info.get(0).completedCount);
312     }
313 
314     @Test
diffUid()315     public void diffUid() throws Exception {
316         mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_1);
317         mEvents.noteStartWakeLock(UID_2, TAG_1, WAKELOCK_TYPE_1, TS_2);
318         mEvents.noteStopWakeLock(UID_2, TAG_1, WAKELOCK_TYPE_1, TS_3);
319         mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_4);
320 
321         ArrayList<WakelockInfo> info = pullResults(TS_5);
322         assertEquals("size", 2, info.size());
323 
324         WakelockInfo uid1Info = info.stream().filter(i -> i.uid == UID_1).findFirst().orElse(null);
325 
326         assertTrue("UID_1 found", uid1Info != null);
327         assertEquals("uid", UID_1, uid1Info.uid);
328         assertEquals("tag", TAG_1, uid1Info.tag);
329         assertEquals("type", WAKELOCK_TYPE_1, uid1Info.type);
330         assertEquals("duration", TS_4 - TS_1, uid1Info.uptimeMillis);
331         assertEquals("count", 1, uid1Info.completedCount);
332 
333         WakelockInfo uid2Info = info.stream().filter(i -> i.uid == UID_2).findFirst().orElse(null);
334         assertTrue("UID_2 found", uid2Info != null);
335         assertEquals("uid", UID_2, uid2Info.uid);
336         assertEquals("tag", TAG_1, uid2Info.tag);
337         assertEquals("type", WAKELOCK_TYPE_1, uid2Info.type);
338         assertEquals("duration", TS_3 - TS_2, uid2Info.uptimeMillis);
339         assertEquals("count", 1, uid2Info.completedCount);
340     }
341 
342     @Test
diffTag()343     public void diffTag() throws Exception {
344         mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_1);
345         mEvents.noteStartWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_1, TS_2);
346         mEvents.noteStopWakeLock(UID_1, TAG_2, WAKELOCK_TYPE_1, TS_3);
347         mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_4);
348 
349         ArrayList<WakelockInfo> info = pullResults(TS_5);
350         assertEquals("size", 2, info.size());
351 
352         WakelockInfo uid1Info =
353                 info.stream().filter(i -> i.tag.equals(TAG_1)).findFirst().orElse(null);
354 
355         assertTrue("TAG_1 found", uid1Info != null);
356         assertEquals("uid", UID_1, uid1Info.uid);
357         assertEquals("tag", TAG_1, uid1Info.tag);
358         assertEquals("type", WAKELOCK_TYPE_1, uid1Info.type);
359         assertEquals("duration", TS_4 - TS_1, uid1Info.uptimeMillis);
360         assertEquals("count", 1, uid1Info.completedCount);
361 
362         WakelockInfo uid2Info =
363                 info.stream().filter(i -> i.tag.equals(TAG_2)).findFirst().orElse(null);
364         assertTrue("TAG_2 found", uid2Info != null);
365         assertEquals("uid", UID_1, uid2Info.uid);
366         assertEquals("tag", TAG_2, uid2Info.tag);
367         assertEquals("type", WAKELOCK_TYPE_1, uid2Info.type);
368         assertEquals("duration", TS_3 - TS_2, uid2Info.uptimeMillis);
369         assertEquals("count", 1, uid2Info.completedCount);
370     }
371 
372     @Test
diffType()373     public void diffType() throws Exception {
374         mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_1);
375         mEvents.noteStartWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_2, TS_2);
376         mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_2, TS_3);
377         mEvents.noteStopWakeLock(UID_1, TAG_1, WAKELOCK_TYPE_1, TS_4);
378 
379         ArrayList<WakelockInfo> info = pullResults(TS_5);
380         assertEquals("size", 2, info.size());
381 
382         WakelockInfo uid1Info =
383                 info.stream().filter(i -> i.type == WAKELOCK_TYPE_1).findFirst().orElse(null);
384 
385         assertTrue("WAKELOCK_TYPE_1 found", uid1Info != null);
386         assertEquals("uid", UID_1, uid1Info.uid);
387         assertEquals("tag", TAG_1, uid1Info.tag);
388         assertEquals("type", WAKELOCK_TYPE_1, uid1Info.type);
389         assertEquals("duration", TS_4 - TS_1, uid1Info.uptimeMillis);
390         assertEquals("count", 1, uid1Info.completedCount);
391 
392         WakelockInfo uid2Info =
393                 info.stream().filter(i -> i.type == WAKELOCK_TYPE_2).findFirst().orElse(null);
394         assertTrue("WAKELOCK_TYPE_2 found", uid2Info != null);
395         assertEquals("uid", UID_1, uid2Info.uid);
396         assertEquals("tag", TAG_1, uid2Info.tag);
397         assertEquals("type", WAKELOCK_TYPE_2, uid2Info.type);
398         assertEquals("duration", TS_3 - TS_2, uid2Info.uptimeMillis);
399         assertEquals("count", 1, uid2Info.completedCount);
400     }
401 }
402