• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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