• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.internal.util;
18 
19 import static android.provider.DeviceConfig.NAMESPACE_LATENCY_TRACKER;
20 import static android.text.TextUtils.formatSimple;
21 
22 import static com.android.internal.util.FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED;
23 import static com.android.internal.util.LatencyTracker.STATSD_ACTION;
24 
25 import static com.google.common.truth.Truth.assertThat;
26 import static com.google.common.truth.Truth.assertWithMessage;
27 
28 import android.provider.DeviceConfig;
29 
30 import androidx.test.ext.junit.runners.AndroidJUnit4;
31 
32 import com.android.internal.util.LatencyTracker.ActionProperties;
33 
34 import com.google.common.truth.Expect;
35 
36 import org.junit.After;
37 import org.junit.Before;
38 import org.junit.Rule;
39 import org.junit.Test;
40 import org.junit.runner.RunWith;
41 
42 import java.lang.reflect.Field;
43 import java.lang.reflect.Modifier;
44 import java.util.Arrays;
45 import java.util.HashMap;
46 import java.util.HashSet;
47 import java.util.List;
48 import java.util.Map;
49 import java.util.stream.Collectors;
50 
51 @RunWith(AndroidJUnit4.class)
52 public class LatencyTrackerTest {
53     private static final String ENUM_NAME_PREFIX = "UIACTION_LATENCY_REPORTED__ACTION__";
54 
55     @Rule
56     public final Expect mExpect = Expect.create();
57 
58     // Fake is used because it tests the real logic of LatencyTracker, and it only fakes the
59     // outcomes (PerfettoTrigger and FrameworkStatsLog).
60     private FakeLatencyTracker mLatencyTracker;
61 
62     @Before
setUp()63     public void setUp() throws Exception {
64         mLatencyTracker = FakeLatencyTracker.create();
65     }
66 
67     @After
tearDown()68     public void tearDown() {
69         mLatencyTracker.stopListeningForLatencyTrackerConfigChanges();
70     }
71 
72     @Test
testCujsMapToEnumsCorrectly()73     public void testCujsMapToEnumsCorrectly() {
74         List<Field> actions = getAllActionFields();
75         Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields())
76                 .filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX)
77                         && Modifier.isStatic(f.getModifiers())
78                         && f.getType() == int.class)
79                 .collect(Collectors.toMap(this::getIntFieldChecked, Field::getName));
80 
81         assertThat(enumsMap.size() - 1).isEqualTo(actions.size());
82 
83         actions.forEach(f -> {
84             final int action = getIntFieldChecked(f);
85             final String actionName = f.getName();
86             final String expectedEnumName = formatSimple("%s%s", ENUM_NAME_PREFIX, actionName);
87             final int enumKey = STATSD_ACTION[action];
88             final String enumName = enumsMap.get(enumKey);
89             final String expectedActionName = LatencyTracker.getNameOfAction(enumKey);
90             mExpect
91                     .withMessage(formatSimple(
92                             "%s (%d) not matches %s (%d)", actionName, action, enumName, enumKey))
93                     .that(expectedEnumName.equals(enumName))
94                     .isTrue();
95             mExpect
96                     .withMessage(
97                             formatSimple("getNameOfAction(%d) not matches: %s, expected=%s",
98                                     enumKey, actionName, expectedActionName))
99                     .that(actionName.equals(expectedActionName))
100                     .isTrue();
101         });
102     }
103 
104     @Test
testCujTypeEnumCorrectlyDefined()105     public void testCujTypeEnumCorrectlyDefined() throws Exception {
106         List<Field> cujEnumFields = getAllActionFields();
107         HashSet<Integer> allValues = new HashSet<>();
108         for (Field field : cujEnumFields) {
109             int fieldValue = field.getInt(null);
110             assertWithMessage(
111                     "Field %s must have a mapping to a value in STATSD_ACTION",
112                     field.getName())
113                     .that(fieldValue < STATSD_ACTION.length)
114                     .isTrue();
115             assertWithMessage("All CujType values must be unique. Field %s repeats existing value.",
116                     field.getName())
117                     .that(allValues.add(fieldValue))
118                     .isTrue();
119         }
120     }
121 
122     @Test
123     public void testIsEnabled_trueWhenGlobalEnabled() throws Exception {
124         DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER,
125                 LatencyTracker.SETTINGS_ENABLED_KEY, "true", false);
126         mLatencyTracker.waitForGlobalEnabledState(true);
127         mLatencyTracker.waitForAllPropertiesEnableState(true);
128 
129         //noinspection deprecation
130         assertThat(mLatencyTracker.isEnabled()).isTrue();
131     }
132 
133     @Test
134     public void testIsEnabled_falseWhenGlobalDisabled() throws Exception {
135         DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER,
136                 LatencyTracker.SETTINGS_ENABLED_KEY, "false", false);
137         mLatencyTracker.waitForGlobalEnabledState(false);
138         mLatencyTracker.waitForAllPropertiesEnableState(false);
139 
140         //noinspection deprecation
141         assertThat(mLatencyTracker.isEnabled()).isFalse();
142     }
143 
144     @Test
145     public void testIsEnabledAction_useGlobalValueWhenActionEnableIsNotSet()
146             throws Exception {
147         // using a single test action, but this applies to all actions
148         int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
149         DeviceConfig.deleteProperty(NAMESPACE_LATENCY_TRACKER,
150                 "action_show_voice_interaction_enable");
151         mLatencyTracker.waitForAllPropertiesEnableState(false);
152         DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER,
153                 LatencyTracker.SETTINGS_ENABLED_KEY, "true", false);
154         mLatencyTracker.waitForGlobalEnabledState(true);
155         mLatencyTracker.waitForAllPropertiesEnableState(true);
156 
157         assertThat(mLatencyTracker.isEnabled(action)).isTrue();
158     }
159 
160     @Test
161     public void testIsEnabledAction_actionPropertyOverridesGlobalProperty()
162             throws Exception {
163         // using a single test action, but this applies to all actions
164         int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
165         DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER,
166                 LatencyTracker.SETTINGS_ENABLED_KEY, "false", false);
167         mLatencyTracker.waitForGlobalEnabledState(false);
168 
169         Map<String, String> deviceConfigProperties = new HashMap<>();
170         deviceConfigProperties.put("action_show_voice_interaction_enable", "true");
171         deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1");
172         deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "-1");
173         DeviceConfig.setProperties(
174                 new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER,
175                         deviceConfigProperties));
176 
177         mLatencyTracker.waitForMatchingActionProperties(
178                 new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */,
179                         -1 /* traceThreshold */));
180 
181         assertThat(mLatencyTracker.isEnabled(action)).isTrue();
182     }
183 
184     @Test
185     public void testLogsWhenEnabled() throws Exception {
186         // using a single test action, but this applies to all actions
187         int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
188         Map<String, String> deviceConfigProperties = new HashMap<>();
189         deviceConfigProperties.put("action_show_voice_interaction_enable", "true");
190         deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1");
191         deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "-1");
192         DeviceConfig.setProperties(
193                 new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER,
194                         deviceConfigProperties));
195         mLatencyTracker.waitForMatchingActionProperties(
196                 new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */,
197                         -1 /* traceThreshold */));
198 
199         mLatencyTracker.logAction(action, 1234);
200         assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).hasSize(1);
201         LatencyTracker.FrameworkStatsLogEvent frameworkStatsLog =
202                 mLatencyTracker.getEventsWrittenToFrameworkStats(action).get(0);
203         assertThat(frameworkStatsLog.logCode).isEqualTo(UI_ACTION_LATENCY_REPORTED);
204         assertThat(frameworkStatsLog.statsdAction).isEqualTo(STATSD_ACTION[action]);
205         assertThat(frameworkStatsLog.durationMillis).isEqualTo(1234);
206 
207         mLatencyTracker.clearEvents();
208 
209         mLatencyTracker.onActionStart(action);
210         mLatencyTracker.onActionEnd(action);
211         // assert that action was logged, but we cannot confirm duration logged
212         assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).hasSize(1);
213         frameworkStatsLog = mLatencyTracker.getEventsWrittenToFrameworkStats(action).get(0);
214         assertThat(frameworkStatsLog.logCode).isEqualTo(UI_ACTION_LATENCY_REPORTED);
215         assertThat(frameworkStatsLog.statsdAction).isEqualTo(STATSD_ACTION[action]);
216     }
217 
218     @Test
219     public void testDoesNotLogWhenDisabled() throws Exception {
220         // using a single test action, but this applies to all actions
221         int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
222         DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER, "action_show_voice_interaction_enable",
223                 "false", false);
224         mLatencyTracker.waitForActionEnabledState(action, false);
225         assertThat(mLatencyTracker.isEnabled(action)).isFalse();
226 
227         mLatencyTracker.logAction(action, 1234);
228         assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).isEmpty();
229 
230         mLatencyTracker.onActionStart(action);
231         mLatencyTracker.onActionEnd(action);
232         assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).isEmpty();
233     }
234 
235     @Test
236     public void testOnActionEndDoesNotLogWithoutOnActionStart()
237             throws Exception {
238         // using a single test action, but this applies to all actions
239         int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
240         DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER, "action_show_voice_interaction_enable",
241                 "true", false);
242         mLatencyTracker.waitForActionEnabledState(action, true);
243         assertThat(mLatencyTracker.isEnabled(action)).isTrue();
244 
245         mLatencyTracker.onActionEnd(action);
246         assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).isEmpty();
247     }
248 
249     @Test
250     public void testOnActionEndDoesNotLogWhenCanceled()
251             throws Exception {
252         // using a single test action, but this applies to all actions
253         int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
254         DeviceConfig.setProperty(NAMESPACE_LATENCY_TRACKER, "action_show_voice_interaction_enable",
255                 "true", false);
256         mLatencyTracker.waitForActionEnabledState(action, true);
257         assertThat(mLatencyTracker.isEnabled(action)).isTrue();
258 
259         mLatencyTracker.onActionStart(action);
260         mLatencyTracker.onActionCancel(action);
261         mLatencyTracker.onActionEnd(action);
262         assertThat(mLatencyTracker.getEventsWrittenToFrameworkStats(action)).isEmpty();
263     }
264 
265     @Test
266     public void testNeverTriggersPerfettoWhenThresholdNegative()
267             throws Exception {
268         // using a single test action, but this applies to all actions
269         int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
270         Map<String, String> deviceConfigProperties = new HashMap<>();
271         deviceConfigProperties.put("action_show_voice_interaction_enable", "true");
272         deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1");
273         deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "-1");
274         DeviceConfig.setProperties(
275                 new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER,
276                         deviceConfigProperties));
277         mLatencyTracker.waitForMatchingActionProperties(
278                 new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */,
279                         -1 /* traceThreshold */));
280 
281         mLatencyTracker.onActionStart(action);
282         mLatencyTracker.onActionEnd(action);
283         assertThat(mLatencyTracker.getTriggeredPerfettoTraceNames()).isEmpty();
284     }
285 
286     @Test
287     public void testNeverTriggersPerfettoWhenDisabled()
288             throws Exception {
289         // using a single test action, but this applies to all actions
290         int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
291         Map<String, String> deviceConfigProperties = new HashMap<>();
292         deviceConfigProperties.put("action_show_voice_interaction_enable", "false");
293         deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1");
294         deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "1");
295         DeviceConfig.setProperties(
296                 new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER,
297                         deviceConfigProperties));
298         mLatencyTracker.waitForMatchingActionProperties(
299                 new ActionProperties(action, false /* enabled */, 1 /* samplingInterval */,
300                         1 /* traceThreshold */));
301 
302         mLatencyTracker.onActionStart(action);
303         mLatencyTracker.onActionEnd(action);
304         assertThat(mLatencyTracker.getTriggeredPerfettoTraceNames()).isEmpty();
305     }
306 
307     @Test
308     public void testTriggersPerfettoWhenAboveThreshold()
309             throws Exception {
310         // using a single test action, but this applies to all actions
311         int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
312         Map<String, String> deviceConfigProperties = new HashMap<>();
313         deviceConfigProperties.put("action_show_voice_interaction_enable", "true");
314         deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1");
315         deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "1");
316         DeviceConfig.setProperties(
317                 new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER,
318                         deviceConfigProperties));
319         mLatencyTracker.waitForMatchingActionProperties(
320                 new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */,
321                         1 /* traceThreshold */));
322 
323         mLatencyTracker.onActionStart(action);
324         // We need to sleep here to ensure that the end call is past the set trace threshold (1ms)
325         Thread.sleep(5 /* millis */);
326         mLatencyTracker.onActionEnd(action);
327         assertThat(mLatencyTracker.getTriggeredPerfettoTraceNames()).hasSize(1);
328         assertThat(mLatencyTracker.getTriggeredPerfettoTraceNames().get(0)).isEqualTo(
329                 "com.android.telemetry.latency-tracker-ACTION_SHOW_VOICE_INTERACTION");
330     }
331 
332     @Test
333     public void testNeverTriggersPerfettoWhenBelowThreshold()
334             throws Exception {
335         // using a single test action, but this applies to all actions
336         int action = LatencyTracker.ACTION_SHOW_VOICE_INTERACTION;
337         Map<String, String> deviceConfigProperties = new HashMap<>();
338         deviceConfigProperties.put("action_show_voice_interaction_enable", "true");
339         deviceConfigProperties.put("action_show_voice_interaction_sample_interval", "1");
340         deviceConfigProperties.put("action_show_voice_interaction_trace_threshold", "1000");
341         DeviceConfig.setProperties(
342                 new DeviceConfig.Properties(NAMESPACE_LATENCY_TRACKER,
343                         deviceConfigProperties));
344         mLatencyTracker.waitForMatchingActionProperties(
345                 new ActionProperties(action, true /* enabled */, 1 /* samplingInterval */,
346                         1000 /* traceThreshold */));
347 
348         mLatencyTracker.onActionStart(action);
349         // No sleep here to ensure that end call comes before 1000ms threshold
350         mLatencyTracker.onActionEnd(action);
351         assertThat(mLatencyTracker.getTriggeredPerfettoTraceNames()).isEmpty();
352     }
353 
354     private List<Field> getAllActionFields() {
355         return Arrays.stream(LatencyTracker.class.getDeclaredFields()).filter(
356                 field -> field.getName().startsWith("ACTION_") && Modifier.isStatic(
357                         field.getModifiers()) && field.getType() == int.class).collect(
358                 Collectors.toList());
359     }
360 
getIntFieldChecked(Field field)361     private int getIntFieldChecked(Field field) {
362         try {
363             return field.getInt(null);
364         } catch (IllegalAccessException ex) {
365             throw new RuntimeException(ex);
366         }
367     }
368 }
369