| // Copyright 2023 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/android/jank_metric_uma_recorder.h" |
| |
| #include <cstdint> |
| |
| #include "base/android/jni_android.h" |
| #include "base/android/jni_array.h" |
| #include "base/base_jni/JankMetricUMARecorder_jni.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/base_tracing.h" |
| #include "jank_metric_uma_recorder.h" |
| |
| namespace base::android { |
| |
| namespace { |
| |
| // Histogram min, max and no. of buckets. |
| constexpr int kVsyncCountsMin = 1; |
| constexpr int kVsyncCountsMax = 50; |
| constexpr int kVsyncCountsBuckets = 25; |
| |
| enum class PerScrollHistogramType { |
| kPercentage = 0, |
| kMax = 1, |
| kSum = 2, |
| }; |
| |
| const char* GetPerScrollHistogramName(JankScenario scenario, |
| int num_frames, |
| PerScrollHistogramType type) { |
| #define HISTOGRAM_NAME(hist_scenario, hist_type, length) \ |
| "Android.FrameTimelineJank." #hist_scenario "." #hist_type \ |
| "." \ |
| "PerScroll." #length |
| if (scenario == JankScenario::WEBVIEW_SCROLLING) { |
| if (type == PerScrollHistogramType::kPercentage) { |
| if (num_frames <= 16) { |
| return HISTOGRAM_NAME(WebviewScrolling, DelayedFramesPercentage, Small); |
| } else if (num_frames <= 64) { |
| return HISTOGRAM_NAME(WebviewScrolling, DelayedFramesPercentage, |
| Medium); |
| } else { |
| return HISTOGRAM_NAME(WebviewScrolling, DelayedFramesPercentage, Large); |
| } |
| } else if (type == PerScrollHistogramType::kMax) { |
| if (num_frames <= 16) { |
| return HISTOGRAM_NAME(WebviewScrolling, MissedVsyncsMax, Small); |
| } else if (num_frames <= 64) { |
| return HISTOGRAM_NAME(WebviewScrolling, MissedVsyncsMax, Medium); |
| } else { |
| return HISTOGRAM_NAME(WebviewScrolling, MissedVsyncsMax, Large); |
| } |
| } else { |
| DCHECK_EQ(type, PerScrollHistogramType::kSum); |
| if (num_frames <= 16) { |
| return HISTOGRAM_NAME(WebviewScrolling, MissedVsyncsSum, Small); |
| } else if (num_frames <= 64) { |
| return HISTOGRAM_NAME(WebviewScrolling, MissedVsyncsSum, Medium); |
| } else { |
| return HISTOGRAM_NAME(WebviewScrolling, MissedVsyncsSum, Large); |
| } |
| } |
| } else { |
| DCHECK_EQ(scenario, JankScenario::FEED_SCROLLING); |
| if (type == PerScrollHistogramType::kPercentage) { |
| if (num_frames <= 16) { |
| return HISTOGRAM_NAME(FeedScrolling, DelayedFramesPercentage, Small); |
| } else if (num_frames <= 64) { |
| return HISTOGRAM_NAME(FeedScrolling, DelayedFramesPercentage, Medium); |
| } else { |
| return HISTOGRAM_NAME(FeedScrolling, DelayedFramesPercentage, Large); |
| } |
| } else if (type == PerScrollHistogramType::kMax) { |
| if (num_frames <= 16) { |
| return HISTOGRAM_NAME(FeedScrolling, MissedVsyncsMax, Small); |
| } else if (num_frames <= 64) { |
| return HISTOGRAM_NAME(FeedScrolling, MissedVsyncsMax, Medium); |
| } else { |
| return HISTOGRAM_NAME(FeedScrolling, MissedVsyncsMax, Large); |
| } |
| } else { |
| DCHECK_EQ(type, PerScrollHistogramType::kSum); |
| if (num_frames <= 16) { |
| return HISTOGRAM_NAME(FeedScrolling, MissedVsyncsSum, Small); |
| } else if (num_frames <= 64) { |
| return HISTOGRAM_NAME(FeedScrolling, MissedVsyncsSum, Medium); |
| } else { |
| return HISTOGRAM_NAME(FeedScrolling, MissedVsyncsSum, Large); |
| } |
| } |
| } |
| #undef HISTOGRAM_NAME |
| } |
| |
| // Emits trace event for all scenarios and per scroll histograms for webview and |
| // feed scrolling scenarios. |
| void EmitMetrics(JankScenario scenario, |
| int janky_frame_count, |
| int missed_vsyncs_max, |
| int missed_vsyncs_sum, |
| int num_presented_frames, |
| int64_t reporting_interval_start_time, |
| int64_t reporting_interval_duration) { |
| DCHECK_GT(num_presented_frames, 0); |
| int delayed_frames_percentage = |
| (100 * janky_frame_count) / num_presented_frames; |
| if (reporting_interval_start_time > 0) { |
| // The following code does nothing if base tracing is disabled. |
| [[maybe_unused]] int non_janky_frame_count = |
| num_presented_frames - janky_frame_count; |
| [[maybe_unused]] auto t = perfetto::Track(static_cast<uint64_t>( |
| reporting_interval_start_time + static_cast<int>(scenario))); |
| TRACE_EVENT_BEGIN( |
| "android_webview.timeline,android.ui.jank", |
| "JankMetricsReportingInterval", t, |
| base::TimeTicks::FromUptimeMillis(reporting_interval_start_time), |
| "janky_frames", janky_frame_count, "non_janky_frames", |
| non_janky_frame_count, "scenario", static_cast<int>(scenario), |
| "delayed_frames_percentage", delayed_frames_percentage, |
| "missed_vsyns_max", missed_vsyncs_max, "missed_vsyncs_sum", |
| missed_vsyncs_sum); |
| TRACE_EVENT_END( |
| "android_webview.timeline,android.ui.jank", t, |
| base::TimeTicks::FromUptimeMillis( |
| (reporting_interval_start_time + reporting_interval_duration))); |
| } |
| |
| if (scenario != JankScenario::WEBVIEW_SCROLLING && |
| scenario != JankScenario::FEED_SCROLLING) { |
| return; |
| } |
| base::UmaHistogramPercentage( |
| GetPerScrollHistogramName(scenario, num_presented_frames, |
| PerScrollHistogramType::kPercentage), |
| delayed_frames_percentage); |
| base::UmaHistogramCustomCounts( |
| GetPerScrollHistogramName(scenario, num_presented_frames, |
| PerScrollHistogramType::kMax), |
| missed_vsyncs_max, kVsyncCountsMin, kVsyncCountsMax, kVsyncCountsBuckets); |
| base::UmaHistogramCustomCounts( |
| GetPerScrollHistogramName(scenario, num_presented_frames, |
| PerScrollHistogramType::kSum), |
| missed_vsyncs_sum, kVsyncCountsMin, kVsyncCountsMax, kVsyncCountsBuckets); |
| } |
| |
| } // namespace |
| |
| const char* GetAndroidFrameTimelineJankHistogramName(JankScenario scenario) { |
| #define HISTOGRAM_NAME(x) "Android.FrameTimelineJank.FrameJankStatus." #x |
| switch (scenario) { |
| case JankScenario::PERIODIC_REPORTING: |
| return HISTOGRAM_NAME(Total); |
| case JankScenario::OMNIBOX_FOCUS: |
| return HISTOGRAM_NAME(OmniboxFocus); |
| case JankScenario::NEW_TAB_PAGE: |
| return HISTOGRAM_NAME(NewTabPage); |
| case JankScenario::STARTUP: |
| return HISTOGRAM_NAME(Startup); |
| case JankScenario::TAB_SWITCHER: |
| return HISTOGRAM_NAME(TabSwitcher); |
| case JankScenario::OPEN_LINK_IN_NEW_TAB: |
| return HISTOGRAM_NAME(OpenLinkInNewTab); |
| case JankScenario::START_SURFACE_HOMEPAGE: |
| return HISTOGRAM_NAME(StartSurfaceHomepage); |
| case JankScenario::START_SURFACE_TAB_SWITCHER: |
| return HISTOGRAM_NAME(StartSurfaceTabSwitcher); |
| case JankScenario::FEED_SCROLLING: |
| return HISTOGRAM_NAME(FeedScrolling); |
| case JankScenario::WEBVIEW_SCROLLING: |
| return HISTOGRAM_NAME(WebviewScrolling); |
| default: |
| return HISTOGRAM_NAME(UNKNOWN); |
| } |
| #undef HISTOGRAM_NAME |
| } |
| |
| const char* GetAndroidFrameTimelineDurationHistogramName( |
| JankScenario scenario) { |
| #define HISTOGRAM_NAME(x) "Android.FrameTimelineJank.Duration." #x |
| switch (scenario) { |
| case JankScenario::PERIODIC_REPORTING: |
| return HISTOGRAM_NAME(Total); |
| case JankScenario::OMNIBOX_FOCUS: |
| return HISTOGRAM_NAME(OmniboxFocus); |
| case JankScenario::NEW_TAB_PAGE: |
| return HISTOGRAM_NAME(NewTabPage); |
| case JankScenario::STARTUP: |
| return HISTOGRAM_NAME(Startup); |
| case JankScenario::TAB_SWITCHER: |
| return HISTOGRAM_NAME(TabSwitcher); |
| case JankScenario::OPEN_LINK_IN_NEW_TAB: |
| return HISTOGRAM_NAME(OpenLinkInNewTab); |
| case JankScenario::START_SURFACE_HOMEPAGE: |
| return HISTOGRAM_NAME(StartSurfaceHomepage); |
| case JankScenario::START_SURFACE_TAB_SWITCHER: |
| return HISTOGRAM_NAME(StartSurfaceTabSwitcher); |
| case JankScenario::FEED_SCROLLING: |
| return HISTOGRAM_NAME(FeedScrolling); |
| case JankScenario::WEBVIEW_SCROLLING: |
| return HISTOGRAM_NAME(WebviewScrolling); |
| default: |
| return HISTOGRAM_NAME(UNKNOWN); |
| } |
| #undef HISTOGRAM_NAME |
| } |
| |
| // This function is called from Java with JNI, it's declared in |
| // base/base_jni/JankMetricUMARecorder_jni.h which is an autogenerated |
| // header. The actual implementation is in RecordJankMetrics for simpler |
| // testing. |
| void JNI_JankMetricUMARecorder_RecordJankMetrics( |
| JNIEnv* env, |
| const base::android::JavaParamRef<jlongArray>& java_durations_ns, |
| const base::android::JavaParamRef<jintArray>& java_missed_vsyncs, |
| jlong java_reporting_interval_start_time, |
| jlong java_reporting_interval_duration, |
| jint java_scenario_enum) { |
| RecordJankMetrics(env, java_durations_ns, java_missed_vsyncs, |
| java_reporting_interval_start_time, |
| java_reporting_interval_duration, java_scenario_enum); |
| } |
| |
| void RecordJankMetrics( |
| JNIEnv* env, |
| const base::android::JavaParamRef<jlongArray>& java_durations_ns, |
| const base::android::JavaParamRef<jintArray>& java_missed_vsyncs, |
| jlong java_reporting_interval_start_time, |
| jlong java_reporting_interval_duration, |
| jint java_scenario_enum) { |
| std::vector<int64_t> durations_ns; |
| JavaLongArrayToInt64Vector(env, java_durations_ns, &durations_ns); |
| |
| std::vector<int> missed_vsyncs; |
| JavaIntArrayToIntVector(env, java_missed_vsyncs, &missed_vsyncs); |
| |
| JankScenario scenario = static_cast<JankScenario>(java_scenario_enum); |
| |
| const char* frame_duration_histogram_name = |
| GetAndroidFrameTimelineDurationHistogramName(scenario); |
| const char* janky_frames_per_scenario_histogram_name = |
| GetAndroidFrameTimelineJankHistogramName(scenario); |
| |
| for (const int64_t frame_duration_ns : durations_ns) { |
| base::UmaHistogramTimes(frame_duration_histogram_name, |
| base::Nanoseconds(frame_duration_ns)); |
| } |
| |
| int janky_frame_count = 0; |
| int missed_vsyncs_max = 0; |
| int missed_vsyncs_sum = 0; |
| const int num_presented_frames = static_cast<int>(missed_vsyncs.size()); |
| |
| for (int curr_frame_missed_vsyncs : missed_vsyncs) { |
| bool is_janky = curr_frame_missed_vsyncs > 0; |
| if (curr_frame_missed_vsyncs > missed_vsyncs_max) { |
| missed_vsyncs_max = curr_frame_missed_vsyncs; |
| } |
| missed_vsyncs_sum += curr_frame_missed_vsyncs; |
| |
| base::UmaHistogramEnumeration( |
| janky_frames_per_scenario_histogram_name, |
| is_janky ? FrameJankStatus::kJanky : FrameJankStatus::kNonJanky); |
| if (is_janky) { |
| ++janky_frame_count; |
| } |
| } |
| |
| if (num_presented_frames > 0) { |
| EmitMetrics(scenario, janky_frame_count, missed_vsyncs_max, |
| missed_vsyncs_sum, num_presented_frames, |
| java_reporting_interval_start_time, |
| java_reporting_interval_duration); |
| } |
| } |
| |
| } // namespace base::android |