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 9 import org.chromium.base.TraceEvent; 10 11 /** 12 * This runnable receives a FrameMetricsStore instance and starts/stops tracking a given scenario. 13 * When a scenario stops it takes its metrics and sends them to native to be recorded in UMA. 14 * This is executed by JankReportingScheduler on its own thread. 15 */ 16 class JankReportingRunnable implements Runnable { 17 private final FrameMetricsStore mMetricsStore; 18 private final @JankScenario int mScenario; 19 private final boolean mIsStartingTracking; 20 // This must be the same handler that this is running on. 21 private final Handler mHandler; 22 // If metrics should be collected based on the state (scrolling) specify a 23 // JankEndScenarioTime. 24 private final JankEndScenarioTime mJankEndScenarioTime; 25 26 // When a JankEndScenarioTime is specified we don't immediately collect the metrics but instead 27 // post a task (this runnable). However to keep code reuse the same between delay/no-delay we 28 // use the same runnable but don't post it when there is no delay. 29 private class FinalReportingRunnable implements Runnable { 30 @Override run()31 public void run() { 32 try (TraceEvent e = TraceEvent.scoped("ReportingCUJScenarioData", mScenario)) { 33 JankMetrics metrics; 34 if (mJankEndScenarioTime == null) { 35 metrics = mMetricsStore.stopTrackingScenario(mScenario); 36 } else { 37 // Since this is after the timeout we just unconditionally get the metrics. 38 metrics = 39 mMetricsStore.stopTrackingScenario( 40 mScenario, mJankEndScenarioTime.endScenarioTimeNs); 41 } 42 43 if (metrics == null || metrics.timestampsNs.length == 0) { 44 TraceEvent.instant("no metrics"); 45 return; 46 } 47 48 long startTime = metrics.timestampsNs[0] / 1000000; 49 long lastTime = metrics.timestampsNs[metrics.timestampsNs.length - 1] / 1000000; 50 long lastDuration = metrics.durationsNs[metrics.durationsNs.length - 1] / 1000000; 51 // The time that we have metrics covering is from the first VSYNC_TIMESTAMP 52 // (startTime) to the last frame has finished (lastTime + lastDuration). 53 long endTime = lastTime - startTime + lastDuration; 54 55 // Confirm that the current call context is valid. 56 // Debug builds will assert and fail; release builds will optimize this out. 57 JankMetricUMARecorderJni.get(); 58 // TODO(salg@): Cache metrics in case native takes >30s to initialize. 59 JankMetricUMARecorder.recordJankMetricsToUMA( 60 metrics, startTime, endTime, mScenario); 61 } 62 } 63 } 64 JankReportingRunnable( FrameMetricsStore metricsStore, @JankScenario int scenario, boolean isStartingTracking, Handler handler, JankEndScenarioTime endScenarioTime)65 JankReportingRunnable( 66 FrameMetricsStore metricsStore, 67 @JankScenario int scenario, 68 boolean isStartingTracking, 69 Handler handler, 70 JankEndScenarioTime endScenarioTime) { 71 mMetricsStore = metricsStore; 72 mScenario = scenario; 73 mIsStartingTracking = isStartingTracking; 74 mHandler = handler; 75 mJankEndScenarioTime = endScenarioTime; 76 } 77 78 @Override run()79 public void run() { 80 try (TraceEvent e = 81 TraceEvent.scoped( 82 "StartingOrStoppingJankScenario", 83 "StartingScenario:" 84 + Boolean.toString(mIsStartingTracking) 85 + ",Scenario:" 86 + Integer.toString(mScenario))) { 87 if (mIsStartingTracking) { 88 if (mMetricsStore == null) { 89 TraceEvent.instant("StartTrackingScenario metrics store null"); 90 return; 91 } 92 mMetricsStore.startTrackingScenario(mScenario); 93 return; 94 } 95 boolean dataIsReady = 96 mJankEndScenarioTime == null 97 || (mJankEndScenarioTime != null 98 && mMetricsStore.hasReceivedMetricsPast( 99 mJankEndScenarioTime.endScenarioTimeNs)); 100 101 if (dataIsReady) { 102 new FinalReportingRunnable().run(); 103 } else { 104 mHandler.postDelayed( 105 new FinalReportingRunnable(), mJankEndScenarioTime.timeoutDelayMs); 106 } 107 } 108 } 109 } 110