1 // Copyright 2023 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base.jank_tracker; 6 7 import android.os.Handler; 8 import android.os.HandlerThread; 9 10 import androidx.annotation.Nullable; 11 12 import java.util.HashMap; 13 import java.util.concurrent.atomic.AtomicBoolean; 14 15 /** 16 * This class receives requests to start and stop jank scenario tracking and runs them in a 17 * HandlerThread it creates. In addition it handles the recording of periodic jank metrics. 18 */ 19 public class JankReportingScheduler { 20 private static final long PERIODIC_METRIC_DELAY_MS = 5_000; 21 private final FrameMetricsStore mFrameMetricsStore; 22 // TODO(b/308551047): Fix/cleanup this member variable. We do query the map but we never add 23 // anything to it. 24 private final HashMap<Integer, JankReportingRunnable> mRunnableStore; 25 JankReportingScheduler(FrameMetricsStore frameMetricsStore)26 public JankReportingScheduler(FrameMetricsStore frameMetricsStore) { 27 mFrameMetricsStore = frameMetricsStore; 28 mRunnableStore = new HashMap<Integer, JankReportingRunnable>(); 29 } 30 31 private final Runnable mPeriodicMetricReporter = 32 new Runnable() { 33 @Override 34 public void run() { 35 // delay should never be null. 36 finishTrackingScenario(JankScenario.PERIODIC_REPORTING); 37 38 if (mIsPeriodicReporterLooping.get()) { 39 // We delay starting the next periodic reporting until the timeout has 40 // finished by taking the delay and +1 so that it will run in order (it 41 // was posted above). 42 startTrackingScenario(JankScenario.PERIODIC_REPORTING); 43 getOrCreateHandler() 44 .postDelayed(mPeriodicMetricReporter, PERIODIC_METRIC_DELAY_MS); 45 } 46 } 47 }; 48 49 @Nullable protected HandlerThread mHandlerThread; 50 @Nullable private Handler mHandler; 51 private final AtomicBoolean mIsPeriodicReporterLooping = new AtomicBoolean(false); 52 startTrackingScenario(@ankScenario int scenario)53 public void startTrackingScenario(@JankScenario int scenario) { 54 // We check to see if there was already a stop task queued at some point and attempt to 55 // cancel it. Regardless we send the startTracking runnable because we will ignore this 56 // start if the stop did get canceled and the stopTask already ran we'll start a new 57 // scenario. 58 JankReportingRunnable stopTask = mRunnableStore.get(scenario); 59 if (stopTask != null) { 60 getOrCreateHandler().removeCallbacks(stopTask); 61 mRunnableStore.remove(scenario); 62 } 63 getOrCreateHandler() 64 .post( 65 new JankReportingRunnable( 66 mFrameMetricsStore, 67 scenario, 68 /* isStartingTracking= */ true, 69 mHandler, 70 null)); 71 } 72 finishTrackingScenario(@ankScenario int scenario)73 public void finishTrackingScenario(@JankScenario int scenario) { 74 finishTrackingScenario(scenario, -1); 75 } 76 finishTrackingScenario(@ankScenario int scenario, long endScenarioTimeNs)77 public void finishTrackingScenario(@JankScenario int scenario, long endScenarioTimeNs) { 78 finishTrackingScenario(scenario, JankEndScenarioTime.endAt(endScenarioTimeNs)); 79 } 80 finishTrackingScenario( @ankScenario int scenario, JankEndScenarioTime endScenarioTime)81 public void finishTrackingScenario( 82 @JankScenario int scenario, JankEndScenarioTime endScenarioTime) { 83 // We store the stop task in case the delay is greater than zero and we start this scenario 84 // again. 85 JankReportingRunnable runnable = 86 mRunnableStore.getOrDefault( 87 scenario, 88 new JankReportingRunnable( 89 mFrameMetricsStore, 90 scenario, 91 /* isStartingTracking= */ false, 92 mHandler, 93 endScenarioTime)); 94 getOrCreateHandler().post(runnable); 95 } 96 getOrCreateHandler()97 public Handler getOrCreateHandler() { 98 if (mHandler == null) { 99 mHandlerThread = new HandlerThread("Jank-Tracker"); 100 mHandlerThread.start(); 101 mHandler = new Handler(mHandlerThread.getLooper()); 102 mHandler.post( 103 new Runnable() { 104 @Override 105 public void run() { 106 mFrameMetricsStore.initialize(); 107 } 108 }); 109 } 110 return mHandler; 111 } 112 startReportingPeriodicMetrics()113 public void startReportingPeriodicMetrics() { 114 // If mIsPeriodicReporterLooping was already true then there's no need to post another task. 115 if (mIsPeriodicReporterLooping.getAndSet(true)) { 116 return; 117 } 118 startTrackingScenario(JankScenario.PERIODIC_REPORTING); 119 getOrCreateHandler().postDelayed(mPeriodicMetricReporter, PERIODIC_METRIC_DELAY_MS); 120 } 121 stopReportingPeriodicMetrics()122 public void stopReportingPeriodicMetrics() { 123 // Disable mPeriodicMetricReporter looping, and return early if it was already disabled. 124 if (!mIsPeriodicReporterLooping.getAndSet(false)) { 125 return; 126 } 127 // Remove any existing mPeriodicMetricReporter delayed tasks. 128 getOrCreateHandler().removeCallbacks(mPeriodicMetricReporter); 129 // Run mPeriodicMetricReporter one last time immediately. 130 getOrCreateHandler().post(mPeriodicMetricReporter); 131 } 132 } 133