1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package com.android.internal.util; 16 17 import static android.os.Trace.TRACE_TAG_APP; 18 19 import android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.content.Context; 23 import android.os.Build; 24 import android.os.SystemClock; 25 import android.os.Trace; 26 import android.provider.DeviceConfig; 27 import android.text.TextUtils; 28 import android.util.EventLog; 29 import android.util.Log; 30 import android.util.SparseArray; 31 32 import com.android.internal.annotations.GuardedBy; 33 import com.android.internal.logging.EventLogTags; 34 import com.android.internal.os.BackgroundThread; 35 36 import java.lang.annotation.Retention; 37 import java.lang.annotation.RetentionPolicy; 38 import java.util.concurrent.ThreadLocalRandom; 39 import java.util.concurrent.TimeUnit; 40 41 /** 42 * Class to track various latencies in SystemUI. It then writes the latency to statsd and also 43 * outputs it to logcat so these latencies can be captured by tests and then used for dashboards. 44 * <p> 45 * This is currently only in Keyguard so it can be shared between SystemUI and Keyguard, but 46 * eventually we'd want to merge these two packages together so Keyguard can use common classes 47 * that are shared with SystemUI. 48 */ 49 public class LatencyTracker { 50 private static final String TAG = "LatencyTracker"; 51 private static final String SETTINGS_ENABLED_KEY = "enabled"; 52 private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval"; 53 private static final boolean DEBUG = false; 54 /** Default to being enabled on debug builds. */ 55 private static final boolean DEFAULT_ENABLED = Build.IS_DEBUGGABLE; 56 /** Default to collecting data for 1/5 of all actions (randomly sampled). */ 57 private static final int DEFAULT_SAMPLING_INTERVAL = 5; 58 59 /** 60 * Time it takes until the first frame of the notification panel to be displayed while expanding 61 */ 62 public static final int ACTION_EXPAND_PANEL = 0; 63 64 /** 65 * Time it takes until the first frame of recents is drawn after invoking it with the button. 66 */ 67 public static final int ACTION_TOGGLE_RECENTS = 1; 68 69 /** 70 * Time between we get a fingerprint acquired signal until we start with the unlock animation 71 */ 72 public static final int ACTION_FINGERPRINT_WAKE_AND_UNLOCK = 2; 73 74 /** 75 * Time it takes to check PIN/Pattern/Password. 76 */ 77 public static final int ACTION_CHECK_CREDENTIAL = 3; 78 79 /** 80 * Time it takes to check fully PIN/Pattern/Password, i.e. that's the time spent including the 81 * actions to unlock a user. 82 */ 83 public static final int ACTION_CHECK_CREDENTIAL_UNLOCKED = 4; 84 85 /** 86 * Time it takes to turn on the screen. 87 */ 88 public static final int ACTION_TURN_ON_SCREEN = 5; 89 90 /** 91 * Time it takes to rotate the screen. 92 */ 93 public static final int ACTION_ROTATE_SCREEN = 6; 94 95 /* 96 * Time between we get a face acquired signal until we start with the unlock animation 97 */ 98 public static final int ACTION_FACE_WAKE_AND_UNLOCK = 7; 99 100 /** 101 * Time between the swipe-up gesture and window drawn of recents activity. 102 */ 103 public static final int ACTION_START_RECENTS_ANIMATION = 8; 104 105 /** 106 * Time it takes the sensor to detect rotation. 107 */ 108 public static final int ACTION_ROTATE_SCREEN_SENSOR = 9; 109 110 /** 111 * Time it takes to for the camera based algorithm to rotate the screen. 112 */ 113 public static final int ACTION_ROTATE_SCREEN_CAMERA_CHECK = 10; 114 115 /** 116 * Time it takes to start unlock animation . 117 */ 118 public static final int ACTION_LOCKSCREEN_UNLOCK = 11; 119 120 /** 121 * Time it takes to switch users. 122 */ 123 public static final int ACTION_USER_SWITCH = 12; 124 125 private static final int[] ACTIONS_ALL = { 126 ACTION_EXPAND_PANEL, 127 ACTION_TOGGLE_RECENTS, 128 ACTION_FINGERPRINT_WAKE_AND_UNLOCK, 129 ACTION_CHECK_CREDENTIAL, 130 ACTION_CHECK_CREDENTIAL_UNLOCKED, 131 ACTION_TURN_ON_SCREEN, 132 ACTION_ROTATE_SCREEN, 133 ACTION_FACE_WAKE_AND_UNLOCK, 134 ACTION_START_RECENTS_ANIMATION, 135 ACTION_ROTATE_SCREEN_SENSOR, 136 ACTION_ROTATE_SCREEN_CAMERA_CHECK, 137 ACTION_LOCKSCREEN_UNLOCK, 138 ACTION_USER_SWITCH 139 }; 140 141 /** @hide */ 142 @IntDef({ 143 ACTION_EXPAND_PANEL, 144 ACTION_TOGGLE_RECENTS, 145 ACTION_FINGERPRINT_WAKE_AND_UNLOCK, 146 ACTION_CHECK_CREDENTIAL, 147 ACTION_CHECK_CREDENTIAL_UNLOCKED, 148 ACTION_TURN_ON_SCREEN, 149 ACTION_ROTATE_SCREEN, 150 ACTION_FACE_WAKE_AND_UNLOCK, 151 ACTION_START_RECENTS_ANIMATION, 152 ACTION_ROTATE_SCREEN_SENSOR, 153 ACTION_ROTATE_SCREEN_CAMERA_CHECK, 154 ACTION_LOCKSCREEN_UNLOCK, 155 ACTION_USER_SWITCH 156 }) 157 @Retention(RetentionPolicy.SOURCE) 158 public @interface Action { 159 } 160 161 private static final int[] STATSD_ACTION = new int[]{ 162 FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_EXPAND_PANEL, 163 FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_TOGGLE_RECENTS, 164 FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FINGERPRINT_WAKE_AND_UNLOCK, 165 FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL, 166 FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_CHECK_CREDENTIAL_UNLOCKED, 167 FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_TURN_ON_SCREEN, 168 FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN, 169 FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_FACE_WAKE_AND_UNLOCK, 170 FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_START_RECENTS_ANIMATION, 171 FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_SENSOR, 172 FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_ROTATE_SCREEN_CAMERA_CHECK, 173 FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_LOCKSCREEN_UNLOCK, 174 FrameworkStatsLog.UIACTION_LATENCY_REPORTED__ACTION__ACTION_USER_SWITCH 175 }; 176 177 private static LatencyTracker sLatencyTracker; 178 179 private final Object mLock = new Object(); 180 @GuardedBy("mLock") 181 private final SparseArray<Session> mSessions = new SparseArray<>(); 182 @GuardedBy("mLock") 183 private final int[] mTraceThresholdPerAction = new int[ACTIONS_ALL.length]; 184 @GuardedBy("mLock") 185 private int mSamplingInterval; 186 @GuardedBy("mLock") 187 private boolean mEnabled; 188 getInstance(Context context)189 public static LatencyTracker getInstance(Context context) { 190 if (sLatencyTracker == null) { 191 synchronized (LatencyTracker.class) { 192 if (sLatencyTracker == null) { 193 sLatencyTracker = new LatencyTracker(); 194 } 195 } 196 } 197 return sLatencyTracker; 198 } 199 LatencyTracker()200 private LatencyTracker() { 201 mEnabled = DEFAULT_ENABLED; 202 mSamplingInterval = DEFAULT_SAMPLING_INTERVAL; 203 204 // Post initialization to the background in case we're running on the main thread. 205 BackgroundThread.getHandler().post(() -> this.updateProperties( 206 DeviceConfig.getProperties(DeviceConfig.NAMESPACE_LATENCY_TRACKER))); 207 DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_LATENCY_TRACKER, 208 BackgroundThread.getExecutor(), this::updateProperties); 209 } 210 updateProperties(DeviceConfig.Properties properties)211 private void updateProperties(DeviceConfig.Properties properties) { 212 synchronized (mLock) { 213 mSamplingInterval = properties.getInt(SETTINGS_SAMPLING_INTERVAL_KEY, 214 DEFAULT_SAMPLING_INTERVAL); 215 mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED); 216 for (int action : ACTIONS_ALL) { 217 mTraceThresholdPerAction[action] = 218 properties.getInt(getNameOfAction(STATSD_ACTION[action]), -1); 219 } 220 } 221 } 222 223 /** 224 * A helper method to translate action type to name. 225 * 226 * @param atomsProtoAction the action type defined in AtomsProto.java 227 * @return the name of the action 228 */ getNameOfAction(int atomsProtoAction)229 public static String getNameOfAction(int atomsProtoAction) { 230 // Defined in AtomsProto.java 231 switch (atomsProtoAction) { 232 case 0: 233 return "UNKNOWN"; 234 case 1: 235 return "ACTION_EXPAND_PANEL"; 236 case 2: 237 return "ACTION_TOGGLE_RECENTS"; 238 case 3: 239 return "ACTION_FINGERPRINT_WAKE_AND_UNLOCK"; 240 case 4: 241 return "ACTION_CHECK_CREDENTIAL"; 242 case 5: 243 return "ACTION_CHECK_CREDENTIAL_UNLOCKED"; 244 case 6: 245 return "ACTION_TURN_ON_SCREEN"; 246 case 7: 247 return "ACTION_ROTATE_SCREEN"; 248 case 8: 249 return "ACTION_FACE_WAKE_AND_UNLOCK"; 250 case 9: 251 return "ACTION_START_RECENTS_ANIMATION"; 252 case 10: 253 return "ACTION_ROTATE_SCREEN_CAMERA_CHECK"; 254 case 11: 255 return "ACTION_ROTATE_SCREEN_SENSOR"; 256 case 12: 257 return "ACTION_LOCKSCREEN_UNLOCK"; 258 case 13: 259 return "ACTION_USER_SWITCH"; 260 default: 261 throw new IllegalArgumentException("Invalid action"); 262 } 263 } 264 getTraceNameOfAction(@ction int action, String tag)265 private static String getTraceNameOfAction(@Action int action, String tag) { 266 if (TextUtils.isEmpty(tag)) { 267 return "L<" + getNameOfAction(STATSD_ACTION[action]) + ">"; 268 } else { 269 return "L<" + getNameOfAction(STATSD_ACTION[action]) + "::" + tag + ">"; 270 } 271 } 272 getTraceTriggerNameForAction(@ction int action)273 private static String getTraceTriggerNameForAction(@Action int action) { 274 return "com.android.telemetry.latency-tracker-" + getNameOfAction(STATSD_ACTION[action]); 275 } 276 isEnabled(Context ctx)277 public static boolean isEnabled(Context ctx) { 278 return getInstance(ctx).isEnabled(); 279 } 280 isEnabled()281 public boolean isEnabled() { 282 synchronized (mLock) { 283 return mEnabled; 284 } 285 } 286 287 /** 288 * Notifies that an action is starting. <s>This needs to be called from the main thread.</s> 289 * 290 * @param action The action to start. One of the ACTION_* values. 291 */ onActionStart(@ction int action)292 public void onActionStart(@Action int action) { 293 onActionStart(action, null); 294 } 295 296 /** 297 * Notifies that an action is starting. <s>This needs to be called from the main thread.</s> 298 * 299 * @param action The action to start. One of the ACTION_* values. 300 * @param tag The brief description of the action. 301 */ onActionStart(@ction int action, String tag)302 public void onActionStart(@Action int action, String tag) { 303 synchronized (mLock) { 304 if (!isEnabled()) { 305 return; 306 } 307 // skip if the action is already instrumenting. 308 if (mSessions.get(action) != null) { 309 return; 310 } 311 Session session = new Session(action, tag); 312 session.begin(() -> onActionCancel(action)); 313 mSessions.put(action, session); 314 315 if (DEBUG) { 316 Log.d(TAG, "onActionStart: " + session.name() + ", start=" + session.mStartRtc); 317 } 318 } 319 } 320 321 /** 322 * Notifies that an action has ended. <s>This needs to be called from the main thread.</s> 323 * 324 * @param action The action to end. One of the ACTION_* values. 325 */ onActionEnd(@ction int action)326 public void onActionEnd(@Action int action) { 327 synchronized (mLock) { 328 if (!isEnabled()) { 329 return; 330 } 331 Session session = mSessions.get(action); 332 if (session == null) { 333 return; 334 } 335 session.end(); 336 mSessions.delete(action); 337 logAction(action, session.duration()); 338 339 if (DEBUG) { 340 Log.d(TAG, "onActionEnd:" + session.name() + ", duration=" + session.duration()); 341 } 342 } 343 } 344 345 /** 346 * Notifies that an action has canceled. <s>This needs to be called from the main thread.</s> 347 * 348 * @param action The action to cancel. One of the ACTION_* values. 349 * @hide 350 */ onActionCancel(@ction int action)351 public void onActionCancel(@Action int action) { 352 synchronized (mLock) { 353 Session session = mSessions.get(action); 354 if (session == null) { 355 return; 356 } 357 session.cancel(); 358 mSessions.delete(action); 359 360 if (DEBUG) { 361 Log.d(TAG, "onActionCancel: " + session.name()); 362 } 363 } 364 } 365 366 /** 367 * Logs an action that has started and ended. This needs to be called from the main thread. 368 * 369 * @param action The action to end. One of the ACTION_* values. 370 * @param duration The duration of the action in ms. 371 */ logAction(@ction int action, int duration)372 public void logAction(@Action int action, int duration) { 373 boolean shouldSample; 374 int traceThreshold; 375 synchronized (mLock) { 376 shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0; 377 traceThreshold = mTraceThresholdPerAction[action]; 378 } 379 380 if (traceThreshold > 0 && duration >= traceThreshold) { 381 PerfettoTrigger.trigger(getTraceTriggerNameForAction(action)); 382 } 383 384 logActionDeprecated(action, duration, shouldSample); 385 } 386 387 /** 388 * Logs an action that has started and ended. This needs to be called from the main thread. 389 * 390 * @param action The action to end. One of the ACTION_* values. 391 * @param duration The duration of the action in ms. 392 * @param writeToStatsLog Whether to write the measured latency to FrameworkStatsLog. 393 */ logActionDeprecated( @ction int action, int duration, boolean writeToStatsLog)394 public static void logActionDeprecated( 395 @Action int action, int duration, boolean writeToStatsLog) { 396 Log.i(TAG, getNameOfAction(STATSD_ACTION[action]) + " latency=" + duration); 397 EventLog.writeEvent(EventLogTags.SYSUI_LATENCY, action, duration); 398 399 if (writeToStatsLog) { 400 FrameworkStatsLog.write( 401 FrameworkStatsLog.UI_ACTION_LATENCY_REPORTED, STATSD_ACTION[action], duration); 402 } 403 } 404 405 static class Session { 406 @Action 407 private final int mAction; 408 private final String mTag; 409 private final String mName; 410 private Runnable mTimeoutRunnable; 411 private long mStartRtc = -1; 412 private long mEndRtc = -1; 413 Session(@ction int action, @Nullable String tag)414 Session(@Action int action, @Nullable String tag) { 415 mAction = action; 416 mTag = tag; 417 mName = TextUtils.isEmpty(mTag) 418 ? getNameOfAction(STATSD_ACTION[mAction]) 419 : getNameOfAction(STATSD_ACTION[mAction]) + "::" + mTag; 420 } 421 name()422 String name() { 423 return mName; 424 } 425 traceName()426 String traceName() { 427 return getTraceNameOfAction(mAction, mTag); 428 } 429 begin(@onNull Runnable timeoutAction)430 void begin(@NonNull Runnable timeoutAction) { 431 mStartRtc = SystemClock.elapsedRealtime(); 432 Trace.asyncTraceBegin(TRACE_TAG_APP, traceName(), 0); 433 434 // start counting timeout. 435 mTimeoutRunnable = timeoutAction; 436 BackgroundThread.getHandler() 437 .postDelayed(mTimeoutRunnable, TimeUnit.SECONDS.toMillis(15)); 438 } 439 end()440 void end() { 441 mEndRtc = SystemClock.elapsedRealtime(); 442 Trace.asyncTraceEnd(TRACE_TAG_APP, traceName(), 0); 443 BackgroundThread.getHandler().removeCallbacks(mTimeoutRunnable); 444 mTimeoutRunnable = null; 445 } 446 cancel()447 void cancel() { 448 Trace.asyncTraceEnd(TRACE_TAG_APP, traceName(), 0); 449 BackgroundThread.getHandler().removeCallbacks(mTimeoutRunnable); 450 mTimeoutRunnable = null; 451 } 452 duration()453 int duration() { 454 return (int) (mEndRtc - mStartRtc); 455 } 456 } 457 } 458