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