| // Copyright 2020 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "cc/metrics/frame_sequence_metrics.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/metrics/histogram.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/strcat.h" |
| #include "base/trace_event/trace_event.h" |
| #include "base/trace_event/traced_value.h" |
| #include "cc/metrics/frame_sequence_tracker.h" |
| #include "cc/metrics/jank_metrics.h" |
| #include "cc/metrics/throughput_ukm_reporter.h" |
| #include "components/viz/common/frame_sinks/begin_frame_args.h" |
| |
| namespace cc { |
| |
| using SmoothEffectDrivingThread = FrameInfo::SmoothEffectDrivingThread; |
| |
| bool ShouldReportForAnimation(FrameSequenceTrackerType sequence_type, |
| SmoothEffectDrivingThread thread_type) { |
| if (sequence_type == FrameSequenceTrackerType::kCompositorAnimation) |
| return thread_type == SmoothEffectDrivingThread::kCompositor; |
| |
| if (sequence_type == FrameSequenceTrackerType::kMainThreadAnimation || |
| sequence_type == FrameSequenceTrackerType::kJSAnimation) |
| return thread_type == SmoothEffectDrivingThread::kMain; |
| |
| return false; |
| } |
| |
| bool ShouldReportForInteraction( |
| FrameSequenceTrackerType sequence_type, |
| SmoothEffectDrivingThread reporting_thread_type, |
| SmoothEffectDrivingThread metrics_effective_thread_type) { |
| // For scrollbar/touch/wheel scroll, the slower thread is the one we want to |
| // report. For pinch-zoom, it's the compositor-thread. |
| if (sequence_type == FrameSequenceTrackerType::kScrollbarScroll || |
| sequence_type == FrameSequenceTrackerType::kTouchScroll || |
| sequence_type == FrameSequenceTrackerType::kWheelScroll) |
| return reporting_thread_type == metrics_effective_thread_type; |
| |
| if (sequence_type == FrameSequenceTrackerType::kPinchZoom) |
| return reporting_thread_type == SmoothEffectDrivingThread::kCompositor; |
| |
| return false; |
| } |
| |
| namespace { |
| |
| // Avoid reporting any throughput metric for sequences that do not have a |
| // sufficient number of frames. |
| constexpr int kMinFramesForThroughputMetric = 100; |
| |
| constexpr int kBuiltinSequenceNum = |
| static_cast<int>(FrameSequenceTrackerType::kMaxType) + 1; |
| constexpr int kMaximumHistogramIndex = 3 * kBuiltinSequenceNum; |
| |
| int GetIndexForMetric(SmoothEffectDrivingThread thread_type, |
| FrameSequenceTrackerType type) { |
| if (thread_type == SmoothEffectDrivingThread::kMain) |
| return static_cast<int>(type); |
| if (thread_type == SmoothEffectDrivingThread::kCompositor) |
| return static_cast<int>(type) + kBuiltinSequenceNum; |
| return static_cast<int>(type) + 2 * kBuiltinSequenceNum; |
| } |
| |
| std::string GetCheckerboardingHistogramName(FrameSequenceTrackerType type) { |
| return base::StrCat( |
| {"Graphics.Smoothness.Checkerboarding.", |
| FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type)}); |
| } |
| |
| std::string GetThroughputHistogramName(FrameSequenceTrackerType type, |
| const char* thread_name) { |
| return base::StrCat( |
| {"Graphics.Smoothness.PercentDroppedFrames.", thread_name, ".", |
| FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type)}); |
| } |
| |
| std::string GetMissedDeadlineHistogramName(FrameSequenceTrackerType type, |
| const char* thread_name) { |
| return base::StrCat( |
| {"Graphics.Smoothness.PercentMissedDeadlineFrames.", thread_name, ".", |
| FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type)}); |
| } |
| |
| bool IsInteractionType(FrameSequenceTrackerType sequence_type) { |
| return sequence_type == FrameSequenceTrackerType::kScrollbarScroll || |
| sequence_type == FrameSequenceTrackerType::kTouchScroll || |
| sequence_type == FrameSequenceTrackerType::kWheelScroll || |
| sequence_type == FrameSequenceTrackerType::kPinchZoom; |
| } |
| |
| } // namespace |
| |
| FrameSequenceMetrics::FrameSequenceMetrics(FrameSequenceTrackerType type, |
| ThroughputUkmReporter* ukm_reporter) |
| : type_(type), throughput_ukm_reporter_(ukm_reporter) { |
| SmoothEffectDrivingThread thread_type = GetEffectiveThread(); |
| |
| // Only construct |jank_reporter_| if it has a valid tracker and thread type. |
| // For scrolling tracker types, |jank_reporter_| may be constructed later in |
| // SetScrollingThread(). |
| if (thread_type == SmoothEffectDrivingThread::kCompositor || |
| thread_type == SmoothEffectDrivingThread::kMain) { |
| jank_reporter_ = std::make_unique<JankMetrics>(type, thread_type); |
| } |
| } |
| |
| FrameSequenceMetrics::~FrameSequenceMetrics() = default; |
| |
| void FrameSequenceMetrics::ReportLeftoverData() { |
| if (HasDataLeftForReporting() || type_ == FrameSequenceTrackerType::kCustom) |
| ReportMetrics(); |
| } |
| |
| void FrameSequenceMetrics::SetScrollingThread( |
| SmoothEffectDrivingThread scrolling_thread) { |
| DCHECK(type_ == FrameSequenceTrackerType::kTouchScroll || |
| type_ == FrameSequenceTrackerType::kWheelScroll || |
| type_ == FrameSequenceTrackerType::kScrollbarScroll); |
| DCHECK_EQ(scrolling_thread_, SmoothEffectDrivingThread::kUnknown); |
| scrolling_thread_ = scrolling_thread; |
| |
| DCHECK(!jank_reporter_); |
| DCHECK_NE(scrolling_thread, SmoothEffectDrivingThread::kUnknown); |
| jank_reporter_ = std::make_unique<JankMetrics>(type_, scrolling_thread); |
| } |
| |
| void FrameSequenceMetrics::SetCustomReporter(CustomReporter custom_reporter) { |
| DCHECK_EQ(FrameSequenceTrackerType::kCustom, type_); |
| custom_reporter_ = std::move(custom_reporter); |
| } |
| |
| SmoothEffectDrivingThread FrameSequenceMetrics::GetEffectiveThread() const { |
| switch (type_) { |
| case FrameSequenceTrackerType::kCompositorAnimation: |
| case FrameSequenceTrackerType::kPinchZoom: |
| case FrameSequenceTrackerType::kVideo: |
| return SmoothEffectDrivingThread::kCompositor; |
| |
| case FrameSequenceTrackerType::kMainThreadAnimation: |
| case FrameSequenceTrackerType::kRAF: |
| case FrameSequenceTrackerType::kCanvasAnimation: |
| case FrameSequenceTrackerType::kJSAnimation: |
| return SmoothEffectDrivingThread::kMain; |
| |
| case FrameSequenceTrackerType::kTouchScroll: |
| case FrameSequenceTrackerType::kScrollbarScroll: |
| case FrameSequenceTrackerType::kWheelScroll: |
| return scrolling_thread_; |
| |
| case FrameSequenceTrackerType::kCustom: |
| return SmoothEffectDrivingThread::kMain; |
| |
| case FrameSequenceTrackerType::kMaxType: |
| NOTREACHED(); |
| } |
| return SmoothEffectDrivingThread::kUnknown; |
| } |
| |
| void FrameSequenceMetrics::Merge( |
| std::unique_ptr<FrameSequenceMetrics> metrics) { |
| // Merging custom trackers are not supported. |
| DCHECK_NE(type_, FrameSequenceTrackerType::kCustom); |
| DCHECK_EQ(type_, metrics->type_); |
| DCHECK_EQ(GetEffectiveThread(), metrics->GetEffectiveThread()); |
| impl_throughput_.Merge(metrics->impl_throughput_); |
| main_throughput_.Merge(metrics->main_throughput_); |
| frames_checkerboarded_ += metrics->frames_checkerboarded_; |
| |
| v2_.frames_expected += metrics->v2_.frames_expected; |
| v2_.frames_dropped += metrics->v2_.frames_dropped; |
| |
| if (jank_reporter_) |
| jank_reporter_->Merge(std::move(metrics->jank_reporter_)); |
| |
| // Reset the state of |metrics| before destroying it, so that it doesn't end |
| // up reporting the metrics. |
| metrics->impl_throughput_ = {}; |
| metrics->main_throughput_ = {}; |
| metrics->frames_checkerboarded_ = 0; |
| } |
| |
| bool FrameSequenceMetrics::HasEnoughDataForReporting() const { |
| return impl_throughput_.frames_expected >= kMinFramesForThroughputMetric || |
| main_throughput_.frames_expected >= kMinFramesForThroughputMetric; |
| } |
| |
| bool FrameSequenceMetrics::HasDataLeftForReporting() const { |
| return impl_throughput_.frames_expected > 0 || |
| main_throughput_.frames_expected > 0; |
| } |
| |
| void FrameSequenceMetrics::AdoptTrace(FrameSequenceMetrics* adopt_from) { |
| DCHECK(!trace_data_.trace_id); |
| trace_data_.trace_id = adopt_from->trace_data_.trace_id; |
| adopt_from->trace_data_.trace_id = nullptr; |
| } |
| |
| void FrameSequenceMetrics::AdvanceTrace(base::TimeTicks timestamp) { |
| uint32_t expected = 0, dropped = 0; |
| switch (GetEffectiveThread()) { |
| case SmoothEffectDrivingThread::kCompositor: |
| expected = impl_throughput_.frames_expected; |
| dropped = |
| impl_throughput_.frames_expected - impl_throughput_.frames_produced; |
| break; |
| |
| case SmoothEffectDrivingThread::kMain: |
| expected = main_throughput_.frames_expected; |
| dropped = |
| main_throughput_.frames_expected - main_throughput_.frames_produced; |
| break; |
| |
| case SmoothEffectDrivingThread::kUnknown: |
| NOTREACHED(); |
| } |
| trace_data_.Advance(timestamp, expected, dropped); |
| } |
| |
| void FrameSequenceMetrics::ReportMetrics() { |
| DCHECK_LE(impl_throughput_.frames_produced, impl_throughput_.frames_expected); |
| DCHECK_LE(main_throughput_.frames_produced, main_throughput_.frames_expected); |
| DCHECK_LE(impl_throughput_.frames_ontime, impl_throughput_.frames_expected); |
| DCHECK_LE(main_throughput_.frames_ontime, main_throughput_.frames_expected); |
| |
| // Terminates |trace_data_| for all types of FrameSequenceTracker. |
| trace_data_.Terminate(); |
| |
| if (type_ == FrameSequenceTrackerType::kCustom) { |
| DCHECK(!custom_reporter_.is_null()); |
| std::move(custom_reporter_) |
| .Run({ |
| main_throughput_.frames_expected, |
| main_throughput_.frames_produced, |
| jank_reporter_->jank_count(), |
| }); |
| |
| main_throughput_ = {}; |
| impl_throughput_ = {}; |
| jank_reporter_->Reset(); |
| frames_checkerboarded_ = 0; |
| return; |
| } |
| |
| const bool main_report = ThroughputData::CanReportHistogram( |
| this, SmoothEffectDrivingThread::kMain, main_throughput_); |
| const bool compositor_report = ThroughputData::CanReportHistogram( |
| this, SmoothEffectDrivingThread::kCompositor, impl_throughput_); |
| |
| if (v2_.frames_expected >= kMinFramesForThroughputMetric) { |
| const auto thread_type = GetEffectiveThread(); |
| const bool is_animation = ShouldReportForAnimation(type(), thread_type); |
| const bool is_interaction = |
| ShouldReportForInteraction(type(), thread_type, thread_type); |
| int percent = v2_.frames_expected == 0 |
| ? 0 |
| : std::ceil(100. * v2_.frames_dropped / |
| static_cast<double>(v2_.frames_expected)); |
| |
| if (is_animation) { |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Graphics.Smoothness.PercentDroppedFrames2.AllAnimations", percent); |
| } |
| if (is_interaction) { |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Graphics.Smoothness.PercentDroppedFrames2.AllInteractions", percent); |
| } |
| if (is_animation || is_interaction) { |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Graphics.Smoothness.PercentDroppedFrames2.AllSequences", percent); |
| } |
| v2_ = {}; |
| } |
| |
| absl::optional<int> impl_throughput_percent_dropped; |
| absl::optional<int> impl_throughput_percent_missed; |
| absl::optional<int> main_throughput_percent_dropped; |
| absl::optional<int> main_throughput_percent_missed; |
| |
| // Report the throughput metrics. |
| if (compositor_report) { |
| impl_throughput_percent_dropped = |
| ThroughputData::ReportDroppedFramePercentHistogram( |
| this, SmoothEffectDrivingThread::kCompositor, |
| GetIndexForMetric(SmoothEffectDrivingThread::kCompositor, type_), |
| impl_throughput_); |
| impl_throughput_percent_missed = |
| ThroughputData::ReportMissedDeadlineFramePercentHistogram( |
| this, SmoothEffectDrivingThread::kCompositor, |
| GetIndexForMetric(SmoothEffectDrivingThread::kCompositor, type_), |
| impl_throughput_); |
| } |
| if (main_report) { |
| main_throughput_percent_dropped = |
| ThroughputData::ReportDroppedFramePercentHistogram( |
| this, SmoothEffectDrivingThread::kMain, |
| GetIndexForMetric(SmoothEffectDrivingThread::kMain, type_), |
| main_throughput_); |
| main_throughput_percent_missed = |
| ThroughputData::ReportMissedDeadlineFramePercentHistogram( |
| this, SmoothEffectDrivingThread::kMain, |
| GetIndexForMetric(SmoothEffectDrivingThread::kMain, type_), |
| main_throughput_); |
| } |
| |
| // Report for the 'scrolling thread' for the scrolling interactions. |
| if (scrolling_thread_ != SmoothEffectDrivingThread::kUnknown) { |
| absl::optional<int> scrolling_thread_throughput_dropped; |
| absl::optional<int> scrolling_thread_throughput_missed; |
| switch (scrolling_thread_) { |
| case SmoothEffectDrivingThread::kCompositor: |
| scrolling_thread_throughput_dropped = impl_throughput_percent_dropped; |
| scrolling_thread_throughput_missed = impl_throughput_percent_missed; |
| break; |
| case SmoothEffectDrivingThread::kMain: |
| scrolling_thread_throughput_dropped = main_throughput_percent_dropped; |
| scrolling_thread_throughput_missed = main_throughput_percent_missed; |
| break; |
| case SmoothEffectDrivingThread::kUnknown: |
| NOTREACHED(); |
| break; |
| } |
| // It's OK to use the UMA histogram in the following code while still |
| // using |GetThroughputHistogramName()| to get the name of the metric, |
| // since the input-params to the function never change at runtime. |
| if (scrolling_thread_throughput_dropped.has_value() && |
| scrolling_thread_throughput_missed.has_value()) { |
| if (type_ == FrameSequenceTrackerType::kWheelScroll) { |
| UMA_HISTOGRAM_PERCENTAGE( |
| GetThroughputHistogramName(FrameSequenceTrackerType::kWheelScroll, |
| "ScrollingThread"), |
| scrolling_thread_throughput_dropped.value()); |
| UMA_HISTOGRAM_PERCENTAGE( |
| GetMissedDeadlineHistogramName( |
| FrameSequenceTrackerType::kWheelScroll, "ScrollingThread"), |
| scrolling_thread_throughput_missed.value()); |
| } else if (type_ == FrameSequenceTrackerType::kTouchScroll) { |
| UMA_HISTOGRAM_PERCENTAGE( |
| GetThroughputHistogramName(FrameSequenceTrackerType::kTouchScroll, |
| "ScrollingThread"), |
| scrolling_thread_throughput_dropped.value()); |
| UMA_HISTOGRAM_PERCENTAGE( |
| GetMissedDeadlineHistogramName( |
| FrameSequenceTrackerType::kTouchScroll, "ScrollingThread"), |
| scrolling_thread_throughput_missed.value()); |
| } else { |
| DCHECK_EQ(type_, FrameSequenceTrackerType::kScrollbarScroll); |
| UMA_HISTOGRAM_PERCENTAGE( |
| GetThroughputHistogramName( |
| FrameSequenceTrackerType::kScrollbarScroll, "ScrollingThread"), |
| scrolling_thread_throughput_dropped.value()); |
| UMA_HISTOGRAM_PERCENTAGE( |
| GetMissedDeadlineHistogramName( |
| FrameSequenceTrackerType::kScrollbarScroll, "ScrollingThread"), |
| scrolling_thread_throughput_missed.value()); |
| } |
| } |
| } |
| |
| // Report the checkerboarding metrics. |
| if (impl_throughput_.frames_expected >= kMinFramesForThroughputMetric) { |
| const int checkerboarding_percent = static_cast<int>( |
| 100 * frames_checkerboarded_ / impl_throughput_.frames_expected); |
| STATIC_HISTOGRAM_POINTER_GROUP( |
| GetCheckerboardingHistogramName(type_), static_cast<int>(type_), |
| static_cast<int>(FrameSequenceTrackerType::kMaxType), |
| Add(checkerboarding_percent), |
| base::LinearHistogram::FactoryGet( |
| GetCheckerboardingHistogramName(type_), 1, 100, 101, |
| base::HistogramBase::kUmaTargetedHistogramFlag)); |
| frames_checkerboarded_ = 0; |
| } |
| |
| // Report the jank metrics |
| if (jank_reporter_) { |
| if (jank_reporter_->thread_type() == |
| SmoothEffectDrivingThread::kCompositor && |
| impl_throughput_.frames_expected >= kMinFramesForThroughputMetric) { |
| DCHECK_EQ(jank_reporter_->thread_type(), this->GetEffectiveThread()); |
| jank_reporter_->ReportJankMetrics(impl_throughput_.frames_expected); |
| } else if (jank_reporter_->thread_type() == |
| SmoothEffectDrivingThread::kMain && |
| main_throughput_.frames_expected >= |
| kMinFramesForThroughputMetric) { |
| DCHECK_EQ(jank_reporter_->thread_type(), this->GetEffectiveThread()); |
| jank_reporter_->ReportJankMetrics(main_throughput_.frames_expected); |
| } |
| } |
| |
| // Reset the metrics that reach reporting threshold. |
| if (impl_throughput_.frames_expected >= kMinFramesForThroughputMetric) |
| impl_throughput_ = {}; |
| if (main_throughput_.frames_expected >= kMinFramesForThroughputMetric) |
| main_throughput_ = {}; |
| } |
| |
| void FrameSequenceMetrics::ComputeJank(SmoothEffectDrivingThread thread_type, |
| uint32_t frame_token, |
| base::TimeTicks presentation_time, |
| base::TimeDelta frame_interval) { |
| if (!jank_reporter_) |
| return; |
| |
| if (thread_type == jank_reporter_->thread_type()) |
| jank_reporter_->AddPresentedFrame(frame_token, presentation_time, |
| frame_interval); |
| } |
| |
| void FrameSequenceMetrics::NotifySubmitForJankReporter( |
| SmoothEffectDrivingThread thread_type, |
| uint32_t frame_token, |
| uint32_t sequence_number) { |
| if (!jank_reporter_) |
| return; |
| |
| if (thread_type == jank_reporter_->thread_type()) |
| jank_reporter_->AddSubmitFrame(frame_token, sequence_number); |
| } |
| |
| void FrameSequenceMetrics::NotifyNoUpdateForJankReporter( |
| SmoothEffectDrivingThread thread_type, |
| uint32_t sequence_number, |
| base::TimeDelta frame_interval) { |
| if (!jank_reporter_) |
| return; |
| |
| if (thread_type == jank_reporter_->thread_type()) |
| jank_reporter_->AddFrameWithNoUpdate(sequence_number, frame_interval); |
| } |
| |
| bool FrameSequenceMetrics::ThroughputData::CanReportHistogram( |
| FrameSequenceMetrics* metrics, |
| SmoothEffectDrivingThread thread_type, |
| const ThroughputData& data) { |
| const auto sequence_type = metrics->type(); |
| DCHECK_LT(sequence_type, FrameSequenceTrackerType::kMaxType); |
| |
| // All video frames are compositor thread only. |
| if (sequence_type == FrameSequenceTrackerType::kVideo && |
| thread_type == SmoothEffectDrivingThread::kMain) |
| return false; |
| |
| // RAF and CanvasAnimation are main thread only. |
| if ((sequence_type == FrameSequenceTrackerType::kRAF || |
| sequence_type == FrameSequenceTrackerType::kCanvasAnimation) && |
| thread_type == SmoothEffectDrivingThread::kCompositor) |
| return false; |
| |
| if (data.frames_expected < kMinFramesForThroughputMetric) |
| return false; |
| |
| const bool is_animation = |
| ShouldReportForAnimation(sequence_type, thread_type); |
| |
| return is_animation || IsInteractionType(sequence_type) || |
| sequence_type == FrameSequenceTrackerType::kVideo || |
| sequence_type == FrameSequenceTrackerType::kRAF || |
| sequence_type == FrameSequenceTrackerType::kCanvasAnimation; |
| } |
| |
| int FrameSequenceMetrics::ThroughputData::ReportDroppedFramePercentHistogram( |
| FrameSequenceMetrics* metrics, |
| SmoothEffectDrivingThread thread_type, |
| int metric_index, |
| const ThroughputData& data) { |
| const auto sequence_type = metrics->type(); |
| DCHECK_LT(sequence_type, FrameSequenceTrackerType::kMaxType); |
| DCHECK(CanReportHistogram(metrics, thread_type, data)); |
| |
| // Throughput means the percent of frames that was expected to show on the |
| // screen but didn't. In other words, the lower the throughput is, the |
| // smoother user experience. |
| const int percent = data.DroppedFramePercent(); |
| |
| const bool is_animation = |
| ShouldReportForAnimation(sequence_type, thread_type); |
| const bool is_interaction = ShouldReportForInteraction( |
| metrics->type(), thread_type, metrics->GetEffectiveThread()); |
| |
| ThroughputUkmReporter* const ukm_reporter = metrics->ukm_reporter(); |
| |
| if (is_animation) { |
| TRACE_EVENT_INSTANT2("cc,benchmark", "PercentDroppedFrames.AllAnimations", |
| TRACE_EVENT_SCOPE_THREAD, "frames_expected", |
| data.frames_expected, "frames_produced", |
| data.frames_produced); |
| |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Graphics.Smoothness.PercentDroppedFrames.AllAnimations", percent); |
| if (ukm_reporter) { |
| ukm_reporter->ReportAggregateThroughput(AggregationType::kAllAnimations, |
| percent); |
| } |
| } |
| |
| if (is_interaction) { |
| TRACE_EVENT_INSTANT2("cc,benchmark", "PercentDroppedFrames.AllInteractions", |
| TRACE_EVENT_SCOPE_THREAD, "frames_expected", |
| data.frames_expected, "frames_produced", |
| data.frames_produced); |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Graphics.Smoothness.PercentDroppedFrames.AllInteractions", percent); |
| if (ukm_reporter) { |
| ukm_reporter->ReportAggregateThroughput(AggregationType::kAllInteractions, |
| percent); |
| } |
| } |
| |
| if (is_animation || is_interaction) { |
| TRACE_EVENT_INSTANT2("cc,benchmark", "PercentDroppedFrames.AllSequences", |
| TRACE_EVENT_SCOPE_THREAD, "frames_expected", |
| data.frames_expected, "frames_produced", |
| data.frames_produced); |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Graphics.Smoothness.PercentDroppedFrames.AllSequences", percent); |
| if (ukm_reporter) { |
| ukm_reporter->ReportAggregateThroughput(AggregationType::kAllSequences, |
| percent); |
| } |
| } |
| |
| const char* thread_name = |
| thread_type == SmoothEffectDrivingThread::kCompositor ? "CompositorThread" |
| : "MainThread"; |
| STATIC_HISTOGRAM_POINTER_GROUP( |
| GetThroughputHistogramName(sequence_type, thread_name), metric_index, |
| kMaximumHistogramIndex, Add(percent), |
| base::LinearHistogram::FactoryGet( |
| GetThroughputHistogramName(sequence_type, thread_name), 1, 100, 101, |
| base::HistogramBase::kUmaTargetedHistogramFlag)); |
| return percent; |
| } |
| |
| int FrameSequenceMetrics::ThroughputData:: |
| ReportMissedDeadlineFramePercentHistogram( |
| FrameSequenceMetrics* metrics, |
| SmoothEffectDrivingThread thread_type, |
| int metric_index, |
| const ThroughputData& data) { |
| const auto sequence_type = metrics->type(); |
| DCHECK_LT(sequence_type, FrameSequenceTrackerType::kMaxType); |
| |
| // Throughput means the percent of frames that was expected to show on the |
| // screen but didn't. In other words, the lower the throughput is, the |
| // smoother user experience. |
| const int percent = data.MissedDeadlineFramePercent(); |
| |
| const bool is_animation = |
| ShouldReportForAnimation(sequence_type, thread_type); |
| const bool is_interaction = ShouldReportForInteraction( |
| metrics->type(), thread_type, metrics->GetEffectiveThread()); |
| |
| if (is_animation) { |
| TRACE_EVENT_INSTANT2( |
| "cc,benchmark", "PercentMissedDeadlineFrames.AllAnimations", |
| TRACE_EVENT_SCOPE_THREAD, "frames_expected", data.frames_expected, |
| "frames_ontime", data.frames_ontime); |
| |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Graphics.Smoothness.PercentMissedDeadlineFrames.AllAnimations", |
| percent); |
| } |
| |
| if (is_interaction) { |
| TRACE_EVENT_INSTANT2( |
| "cc,benchmark", "PercentMissedDeadlineFrames.AllInteractions", |
| TRACE_EVENT_SCOPE_THREAD, "frames_expected", data.frames_expected, |
| "frames_ontime", data.frames_ontime); |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Graphics.Smoothness.PercentMissedDeadlineFrames.AllInteractions", |
| percent); |
| } |
| |
| if (is_animation || is_interaction) { |
| TRACE_EVENT_INSTANT2( |
| "cc,benchmark", "PercentMissedDeadlineFrames.AllSequences", |
| TRACE_EVENT_SCOPE_THREAD, "frames_expected", data.frames_expected, |
| "frames_ontime", data.frames_ontime); |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Graphics.Smoothness.PercentMissedDeadlineFrames.AllSequences", |
| percent); |
| } |
| |
| const char* thread_name = |
| thread_type == SmoothEffectDrivingThread::kCompositor ? "CompositorThread" |
| : "MainThread"; |
| STATIC_HISTOGRAM_POINTER_GROUP( |
| GetMissedDeadlineHistogramName(sequence_type, thread_name), metric_index, |
| kMaximumHistogramIndex, Add(percent), |
| base::LinearHistogram::FactoryGet( |
| GetMissedDeadlineHistogramName(sequence_type, thread_name), 1, 100, |
| 101, base::HistogramBase::kUmaTargetedHistogramFlag)); |
| return percent; |
| } |
| |
| std::unique_ptr<base::trace_event::TracedValue> |
| FrameSequenceMetrics::ThroughputData::ToTracedValue( |
| const ThroughputData& impl, |
| const ThroughputData& main, |
| SmoothEffectDrivingThread effective_thread) { |
| auto dict = std::make_unique<base::trace_event::TracedValue>(); |
| if (effective_thread == SmoothEffectDrivingThread::kMain) { |
| dict->SetInteger("main-frames-produced", main.frames_produced); |
| dict->SetInteger("main-frames-expected", main.frames_expected); |
| dict->SetInteger("main-frames-ontime", main.frames_ontime); |
| } else { |
| dict->SetInteger("impl-frames-produced", impl.frames_produced); |
| dict->SetInteger("impl-frames-expected", impl.frames_expected); |
| dict->SetInteger("impl-frames-ontime", impl.frames_ontime); |
| } |
| return dict; |
| } |
| |
| FrameSequenceMetrics::TraceData::TraceData(FrameSequenceMetrics* m) |
| : metrics(m) { |
| TRACE_EVENT_CATEGORY_GROUP_ENABLED("cc,benchmark", &enabled); |
| } |
| |
| FrameSequenceMetrics::TraceData::~TraceData() = default; |
| |
| void FrameSequenceMetrics::TraceData::Terminate() { |
| if (!enabled || !trace_id) |
| return; |
| TRACE_EVENT_NESTABLE_ASYNC_END2( |
| "cc,benchmark", "FrameSequenceTracker", TRACE_ID_LOCAL(trace_id), "args", |
| ThroughputData::ToTracedValue(metrics->impl_throughput(), |
| metrics->main_throughput(), |
| metrics->GetEffectiveThread()), |
| "checkerboard", metrics->frames_checkerboarded()); |
| trace_id = nullptr; |
| } |
| |
| void FrameSequenceMetrics::TraceData::Advance(base::TimeTicks new_timestamp, |
| uint32_t expected, |
| uint32_t dropped) { |
| if (!enabled) |
| return; |
| if (!trace_id) { |
| trace_id = this; |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1( |
| "cc,benchmark", "FrameSequenceTracker", TRACE_ID_LOCAL(trace_id), |
| this->last_timestamp, "name", |
| FrameSequenceTracker::GetFrameSequenceTrackerTypeName(metrics->type())); |
| } |
| // Use different names, because otherwise the trace-viewer shows the slices in |
| // the same color, and that makes it difficult to tell the traces apart from |
| // each other. |
| const char* trace_names[] = {"Frame", "Frame ", "Frame "}; |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0( |
| "cc,benchmark", trace_names[++this->frame_count % 3], |
| TRACE_ID_LOCAL(trace_id), this->last_timestamp); |
| TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP2( |
| "cc,benchmark", trace_names[this->frame_count % 3], |
| TRACE_ID_LOCAL(trace_id), new_timestamp, "expected", expected, "dropped", |
| dropped); |
| this->last_timestamp = new_timestamp; |
| } |
| |
| void FrameSequenceMetrics::AddSortedFrame(const viz::BeginFrameArgs& args, |
| const FrameInfo& frame_info) { |
| switch (GetEffectiveThread()) { |
| case SmoothEffectDrivingThread::kCompositor: |
| if (frame_info.WasSmoothCompositorUpdateDropped()) { |
| ++v2_.frames_dropped; |
| } |
| ++v2_.frames_expected; |
| break; |
| case SmoothEffectDrivingThread::kMain: |
| if (frame_info.WasSmoothMainUpdateDropped()) { |
| ++v2_.frames_dropped; |
| } |
| ++v2_.frames_expected; |
| break; |
| case SmoothEffectDrivingThread::kUnknown: |
| NOTREACHED(); |
| break; |
| } |
| } |
| |
| } // namespace cc |