| // Copyright 2014 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/scheduler/compositor_timing_history.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/stl_util.h" |
| #include "base/trace_event/trace_event.h" |
| #include "cc/debug/rendering_stats_instrumentation.h" |
| |
| namespace cc { |
| |
| class CompositorTimingHistory::UMAReporter { |
| public: |
| virtual ~UMAReporter() = default; |
| |
| // Throughput measurements |
| virtual void AddBeginMainFrameIntervalCritical(base::TimeDelta interval) = 0; |
| virtual void AddBeginMainFrameIntervalNotCritical( |
| base::TimeDelta interval) = 0; |
| virtual void AddCommitInterval(base::TimeDelta interval) = 0; |
| virtual void AddDrawInterval(base::TimeDelta interval) = 0; |
| |
| // Latency measurements |
| virtual void AddBeginImplFrameLatency(base::TimeDelta delta) = 0; |
| virtual void AddBeginMainFrameQueueDurationCriticalDuration( |
| base::TimeDelta duration) = 0; |
| virtual void AddBeginMainFrameQueueDurationNotCriticalDuration( |
| base::TimeDelta duration) = 0; |
| virtual void AddBeginMainFrameStartToCommitDuration( |
| base::TimeDelta duration) = 0; |
| virtual void AddCommitToReadyToActivateDuration(base::TimeDelta duration, |
| TreePriority priority) = 0; |
| virtual void AddInvalidationToReadyToActivateDuration( |
| base::TimeDelta duration, |
| TreePriority priority) = 0; |
| virtual void AddReadyToActivateToWillActivateDuration( |
| base::TimeDelta duration, |
| bool pending_tree_is_impl_side) = 0; |
| virtual void AddPrepareTilesDuration(base::TimeDelta duration) = 0; |
| virtual void AddActivateDuration(base::TimeDelta duration) = 0; |
| virtual void AddDrawDuration(base::TimeDelta duration) = 0; |
| virtual void AddSubmitToAckLatency(base::TimeDelta duration) = 0; |
| |
| // crbug.com/758439: the following 3 functions are used to report timing in |
| // certain conditions targeting blink / compositor animations. |
| // Only the renderer would get the meaningful data. |
| virtual void AddDrawIntervalWithCompositedAnimations( |
| base::TimeDelta duration) = 0; |
| virtual void AddDrawIntervalWithMainThreadAnimations( |
| base::TimeDelta duration) = 0; |
| |
| // Synchronization measurements |
| virtual void AddMainAndImplFrameTimeDelta(base::TimeDelta delta) = 0; |
| }; |
| |
| namespace { |
| |
| // Using the 90th percentile will disable latency recovery |
| // if we are missing the deadline approximately ~6 times per |
| // second. |
| // TODO(brianderson): Fine tune the percentiles below. |
| const size_t kDurationHistorySize = 60; |
| const double kBeginMainFrameQueueDurationEstimationPercentile = 90.0; |
| const double kBeginMainFrameQueueDurationCriticalEstimationPercentile = 90.0; |
| const double kBeginMainFrameQueueDurationNotCriticalEstimationPercentile = 90.0; |
| const double kBeginMainFrameStartToReadyToCommitEstimationPercentile = 90.0; |
| const double kCommitEstimatePercentile = 90.0; |
| const double kCommitToReadyToActivateEstimationPercentile = 90.0; |
| const double kPrepareTilesEstimationPercentile = 90.0; |
| const double kActivateEstimationPercentile = 90.0; |
| const double kDrawEstimationPercentile = 90.0; |
| |
| constexpr base::TimeDelta kSubmitAckWatchdogTimeout = |
| base::TimeDelta::FromSeconds(8); |
| |
| // This macro is deprecated since its bucket count uses too much bandwidth. |
| // It also has sub-optimal range and bucket distribution. |
| // TODO(brianderson): Delete this macro and associated UMAs once there is |
| // sufficient overlap with the re-bucketed UMAs. |
| #define UMA_HISTOGRAM_CUSTOM_TIMES_MICROS(name, sample) \ |
| UMA_HISTOGRAM_CUSTOM_COUNTS(name, sample.InMicroseconds(), \ |
| kUmaDurationMinMicros, kUmaDurationMaxMicros, \ |
| kUmaDurationBucketCount); |
| |
| // ~90 VSync aligned UMA buckets. |
| const int kUMAVSyncBuckets[] = { |
| // Powers of two from 0 to 2048 us @ 50% precision |
| 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, |
| // Every 8 Hz from 256 Hz to 128 Hz @ 3-6% precision |
| 3906, 4032, 4167, 4310, 4464, 4630, 4808, 5000, 5208, 5435, 5682, 5952, |
| 6250, 6579, 6944, 7353, |
| // Every 4 Hz from 128 Hz to 64 Hz @ 3-6% precision |
| 7813, 8065, 8333, 8621, 8929, 9259, 9615, 10000, 10417, 10870, 11364, 11905, |
| 12500, 13158, 13889, 14706, |
| // Every 2 Hz from 64 Hz to 32 Hz @ 3-6% precision |
| 15625, 16129, 16667, 17241, 17857, 18519, 19231, 20000, 20833, 21739, 22727, |
| 23810, 25000, 26316, 27778, 29412, |
| // Every 1 Hz from 32 Hz to 1 Hz @ 3-33% precision |
| 31250, 32258, 33333, 34483, 35714, 37037, 38462, 40000, 41667, 43478, 45455, |
| 47619, 50000, 52632, 55556, 58824, 62500, 66667, 71429, 76923, 83333, 90909, |
| 100000, 111111, 125000, 142857, 166667, 200000, 250000, 333333, 500000, |
| // Powers of two from 1s to 32s @ 50% precision |
| 1000000, 2000000, 4000000, 8000000, 16000000, 32000000, |
| }; |
| |
| // ~50 UMA buckets with high precision from ~100 us to 1s. |
| const int kUMADurationBuckets[] = { |
| // Powers of 2 from 1 us to 64 us @ 50% precision. |
| 1, 2, 4, 8, 16, 32, 64, |
| // 1.25^20, 1.25^21, ..., 1.25^62 @ 20% precision. |
| 87, 108, 136, 169, 212, 265, 331, 414, 517, 646, 808, 1010, 1262, 1578, |
| 1972, 2465, 3081, 3852, 4815, 6019, 7523, 9404, 11755, 14694, 18367, 22959, |
| 28699, 35873, 44842, 56052, 70065, 87581, 109476, 136846, 171057, 213821, |
| 267276, 334096, 417619, 522024, 652530, 815663, 1019579, |
| // Powers of 2 from 2s to 32s @ 50% precision. |
| 2000000, 4000000, 8000000, 16000000, 32000000, |
| }; |
| |
| #define UMA_HISTOGRAM_CUSTOM_TIMES_VSYNC_ALIGNED(name, sample) \ |
| do { \ |
| UMA_HISTOGRAM_CUSTOM_ENUMERATION( \ |
| name "2", sample.InMicroseconds(), \ |
| std::vector<int>(kUMAVSyncBuckets, \ |
| kUMAVSyncBuckets + base::size(kUMAVSyncBuckets))); \ |
| } while (false) |
| |
| #define UMA_HISTOGRAM_CUSTOM_TIMES_DURATION_SUFFIX(name, suffix, sample) \ |
| do { \ |
| UMA_HISTOGRAM_CUSTOM_ENUMERATION( \ |
| name "2" suffix, sample.InMicroseconds(), \ |
| std::vector<int>( \ |
| kUMADurationBuckets, \ |
| kUMADurationBuckets + base::size(kUMADurationBuckets))); \ |
| } while (false) |
| |
| #define UMA_HISTOGRAM_CUSTOM_TIMES_DURATION(name, sample) \ |
| UMA_HISTOGRAM_CUSTOM_TIMES_DURATION_SUFFIX(name, "", sample) |
| |
| #define UMA_HISTOGRAM_READY_TO_ACTIVATE(name, sample, priority) \ |
| do { \ |
| UMA_HISTOGRAM_CUSTOM_TIMES_DURATION(name, sample); \ |
| switch (priority) { \ |
| case SAME_PRIORITY_FOR_BOTH_TREES: \ |
| UMA_HISTOGRAM_CUSTOM_TIMES_DURATION_SUFFIX(name, ".Same", sample); \ |
| break; \ |
| case SMOOTHNESS_TAKES_PRIORITY: \ |
| UMA_HISTOGRAM_CUSTOM_TIMES_DURATION_SUFFIX(name, ".Smoothness", \ |
| sample); \ |
| break; \ |
| case NEW_CONTENT_TAKES_PRIORITY: \ |
| UMA_HISTOGRAM_CUSTOM_TIMES_DURATION_SUFFIX(name, ".NewContent", \ |
| sample); \ |
| break; \ |
| } \ |
| } while (false) |
| |
| class RendererUMAReporter : public CompositorTimingHistory::UMAReporter { |
| public: |
| ~RendererUMAReporter() override = default; |
| |
| void AddBeginMainFrameIntervalCritical(base::TimeDelta interval) override { |
| UMA_HISTOGRAM_CUSTOM_TIMES_VSYNC_ALIGNED( |
| "Scheduling.Renderer.BeginMainFrameIntervalCritical", interval); |
| } |
| |
| void AddBeginMainFrameIntervalNotCritical(base::TimeDelta interval) override { |
| UMA_HISTOGRAM_CUSTOM_TIMES_VSYNC_ALIGNED( |
| "Scheduling.Renderer.BeginMainFrameIntervalNotCritical", interval); |
| } |
| |
| void AddCommitInterval(base::TimeDelta interval) override { |
| UMA_HISTOGRAM_CUSTOM_TIMES_VSYNC_ALIGNED( |
| "Scheduling.Renderer.CommitInterval", interval); |
| } |
| |
| void AddDrawInterval(base::TimeDelta interval) override { |
| UMA_HISTOGRAM_CUSTOM_TIMES_VSYNC_ALIGNED("Scheduling.Renderer.DrawInterval", |
| interval); |
| } |
| |
| void AddDrawIntervalWithCompositedAnimations( |
| base::TimeDelta interval) override { |
| UMA_HISTOGRAM_CUSTOM_TIMES_VSYNC_ALIGNED( |
| "Scheduling.Renderer.DrawIntervalWithCompositedAnimations", interval); |
| } |
| |
| void AddDrawIntervalWithMainThreadAnimations( |
| base::TimeDelta interval) override { |
| UMA_HISTOGRAM_CUSTOM_TIMES_VSYNC_ALIGNED( |
| "Scheduling.Renderer.DrawIntervalWithMainThreadAnimations", interval); |
| } |
| |
| void AddBeginImplFrameLatency(base::TimeDelta delta) override { |
| UMA_HISTOGRAM_CUSTOM_TIMES_DURATION( |
| "Scheduling.Renderer.BeginImplFrameLatency", delta); |
| } |
| |
| void AddBeginMainFrameQueueDurationCriticalDuration( |
| base::TimeDelta duration) override { |
| UMA_HISTOGRAM_CUSTOM_TIMES_DURATION( |
| "Scheduling.Renderer.BeginMainFrameQueueDurationCritical", duration); |
| } |
| |
| void AddBeginMainFrameQueueDurationNotCriticalDuration( |
| base::TimeDelta duration) override { |
| UMA_HISTOGRAM_CUSTOM_TIMES_DURATION( |
| "Scheduling.Renderer.BeginMainFrameQueueDurationNotCritical", duration); |
| } |
| |
| void AddBeginMainFrameStartToCommitDuration( |
| base::TimeDelta duration) override { |
| UMA_HISTOGRAM_CUSTOM_TIMES_DURATION( |
| "Scheduling.Renderer.BeginMainFrameStartToCommitDuration", duration); |
| } |
| |
| void AddCommitToReadyToActivateDuration(base::TimeDelta duration, |
| TreePriority priority) override { |
| UMA_HISTOGRAM_READY_TO_ACTIVATE( |
| "Scheduling.Renderer.CommitToReadyToActivateDuration", duration, |
| priority); |
| } |
| |
| void AddInvalidationToReadyToActivateDuration( |
| base::TimeDelta duration, |
| TreePriority priority) override { |
| UMA_HISTOGRAM_READY_TO_ACTIVATE( |
| "Scheduling.Renderer.InvalidationToReadyToActivateDuration", duration, |
| priority); |
| } |
| |
| void AddReadyToActivateToWillActivateDuration( |
| base::TimeDelta duration, |
| bool pending_tree_is_impl_side) override { |
| if (pending_tree_is_impl_side) { |
| UMA_HISTOGRAM_CUSTOM_TIMES_DURATION_SUFFIX( |
| "Scheduling.Renderer.ReadyToActivateToActivationDuration", ".Impl", |
| duration); |
| } else { |
| UMA_HISTOGRAM_CUSTOM_TIMES_DURATION_SUFFIX( |
| "Scheduling.Renderer.ReadyToActivateToActivationDuration", ".Main", |
| duration); |
| } |
| } |
| |
| void AddPrepareTilesDuration(base::TimeDelta duration) override { |
| UMA_HISTOGRAM_CUSTOM_TIMES_DURATION( |
| "Scheduling.Renderer.PrepareTilesDuration", duration); |
| } |
| |
| void AddActivateDuration(base::TimeDelta duration) override { |
| UMA_HISTOGRAM_CUSTOM_TIMES_DURATION("Scheduling.Renderer.ActivateDuration", |
| duration); |
| } |
| |
| void AddDrawDuration(base::TimeDelta duration) override { |
| UMA_HISTOGRAM_CUSTOM_TIMES_DURATION("Scheduling.Renderer.DrawDuration", |
| duration); |
| } |
| |
| void AddSubmitToAckLatency(base::TimeDelta duration) override { |
| UMA_HISTOGRAM_CUSTOM_TIMES_DURATION("Scheduling.Renderer.SwapToAckLatency", |
| duration); |
| } |
| |
| void AddMainAndImplFrameTimeDelta(base::TimeDelta delta) override { |
| UMA_HISTOGRAM_CUSTOM_TIMES_VSYNC_ALIGNED( |
| "Scheduling.Renderer.MainAndImplFrameTimeDelta", delta); |
| } |
| }; |
| |
| class BrowserUMAReporter : public CompositorTimingHistory::UMAReporter { |
| public: |
| ~BrowserUMAReporter() override = default; |
| |
| // BeginMainFrameIntervalCritical is not meaningful to measure on browser |
| // side because browser rendering fps is not at 60. |
| void AddBeginMainFrameIntervalCritical(base::TimeDelta interval) override {} |
| |
| void AddBeginMainFrameIntervalNotCritical(base::TimeDelta interval) override { |
| } |
| |
| // CommitInterval is not meaningful to measure on browser side because |
| // browser rendering fps is not at 60. |
| void AddCommitInterval(base::TimeDelta interval) override {} |
| |
| // DrawInterval is not meaningful to measure on browser side because |
| // browser rendering fps is not at 60. |
| void AddDrawInterval(base::TimeDelta interval) override {} |
| |
| void AddDrawIntervalWithCompositedAnimations( |
| base::TimeDelta interval) override {} |
| |
| void AddDrawIntervalWithMainThreadAnimations( |
| base::TimeDelta interval) override {} |
| |
| void AddBeginImplFrameLatency(base::TimeDelta delta) override { |
| UMA_HISTOGRAM_CUSTOM_TIMES_DURATION( |
| "Scheduling.Browser.BeginImplFrameLatency", delta); |
| } |
| |
| void AddBeginMainFrameQueueDurationCriticalDuration( |
| base::TimeDelta duration) override { |
| UMA_HISTOGRAM_CUSTOM_TIMES_DURATION( |
| "Scheduling.Browser.BeginMainFrameQueueDurationCritical", duration); |
| } |
| |
| void AddBeginMainFrameQueueDurationNotCriticalDuration( |
| base::TimeDelta duration) override {} |
| |
| void AddBeginMainFrameStartToCommitDuration( |
| base::TimeDelta duration) override { |
| UMA_HISTOGRAM_CUSTOM_TIMES_DURATION( |
| "Scheduling.Browser.BeginMainFrameStartToCommitDuration", duration); |
| } |
| |
| void AddCommitToReadyToActivateDuration(base::TimeDelta duration, |
| TreePriority priority) override { |
| UMA_HISTOGRAM_READY_TO_ACTIVATE( |
| "Scheduling.Browser.CommitToReadyToActivateDuration", duration, |
| priority); |
| } |
| |
| void AddInvalidationToReadyToActivateDuration( |
| base::TimeDelta duration, |
| TreePriority priority) override { |
| UMA_HISTOGRAM_READY_TO_ACTIVATE( |
| "Scheduling.Browser.InvalidationToReadyToActivateDuration", duration, |
| priority); |
| } |
| |
| void AddReadyToActivateToWillActivateDuration( |
| base::TimeDelta duration, |
| bool pending_tree_is_impl_side) override { |
| UMA_HISTOGRAM_CUSTOM_TIMES_DURATION_SUFFIX( |
| "Scheduling.Browser.ReadyToActivateToActivationDuration", ".Main", |
| duration); |
| } |
| |
| void AddPrepareTilesDuration(base::TimeDelta duration) override { |
| UMA_HISTOGRAM_CUSTOM_TIMES_DURATION( |
| "Scheduling.Browser.PrepareTilesDuration", duration); |
| } |
| |
| void AddActivateDuration(base::TimeDelta duration) override {} |
| |
| void AddDrawDuration(base::TimeDelta duration) override { |
| UMA_HISTOGRAM_CUSTOM_TIMES_DURATION("Scheduling.Browser.DrawDuration", |
| duration); |
| } |
| |
| void AddSubmitToAckLatency(base::TimeDelta duration) override { |
| UMA_HISTOGRAM_CUSTOM_TIMES_DURATION("Scheduling.Browser.SwapToAckLatency", |
| duration); |
| } |
| |
| void AddMainAndImplFrameTimeDelta(base::TimeDelta delta) override {} |
| }; |
| |
| class NullUMAReporter : public CompositorTimingHistory::UMAReporter { |
| public: |
| ~NullUMAReporter() override = default; |
| void AddBeginMainFrameIntervalCritical(base::TimeDelta interval) override {} |
| void AddBeginMainFrameIntervalNotCritical(base::TimeDelta interval) override { |
| } |
| void AddCommitInterval(base::TimeDelta interval) override {} |
| void AddDrawInterval(base::TimeDelta interval) override {} |
| void AddDrawIntervalWithCompositedAnimations( |
| base::TimeDelta inverval) override {} |
| void AddDrawIntervalWithMainThreadAnimations( |
| base::TimeDelta inverval) override {} |
| void AddBeginImplFrameLatency(base::TimeDelta delta) override {} |
| void AddBeginMainFrameQueueDurationCriticalDuration( |
| base::TimeDelta duration) override {} |
| void AddBeginMainFrameQueueDurationNotCriticalDuration( |
| base::TimeDelta duration) override {} |
| void AddBeginMainFrameStartToCommitDuration( |
| base::TimeDelta duration) override {} |
| void AddCommitToReadyToActivateDuration(base::TimeDelta duration, |
| TreePriority priority) override {} |
| void AddInvalidationToReadyToActivateDuration( |
| base::TimeDelta duration, |
| TreePriority priority) override {} |
| void AddReadyToActivateToWillActivateDuration( |
| base::TimeDelta duration, |
| bool pending_tree_is_impl_side) override {} |
| void AddPrepareTilesDuration(base::TimeDelta duration) override {} |
| void AddActivateDuration(base::TimeDelta duration) override {} |
| void AddDrawDuration(base::TimeDelta duration) override {} |
| void AddSubmitToAckLatency(base::TimeDelta duration) override {} |
| void AddMainAndImplFrameTimeDelta(base::TimeDelta delta) override {} |
| }; |
| |
| } // namespace |
| |
| CompositorTimingHistory::CompositorTimingHistory( |
| bool using_synchronous_renderer_compositor, |
| UMACategory uma_category, |
| RenderingStatsInstrumentation* rendering_stats_instrumentation) |
| : using_synchronous_renderer_compositor_( |
| using_synchronous_renderer_compositor), |
| enabled_(false), |
| did_send_begin_main_frame_(false), |
| begin_main_frame_needed_continuously_(false), |
| begin_main_frame_committing_continuously_(false), |
| compositor_drawing_continuously_(false), |
| begin_main_frame_queue_duration_history_(kDurationHistorySize), |
| begin_main_frame_queue_duration_critical_history_(kDurationHistorySize), |
| begin_main_frame_queue_duration_not_critical_history_( |
| kDurationHistorySize), |
| begin_main_frame_start_to_ready_to_commit_duration_history_( |
| kDurationHistorySize), |
| commit_duration_history_(kDurationHistorySize), |
| commit_to_ready_to_activate_duration_history_(kDurationHistorySize), |
| prepare_tiles_duration_history_(kDurationHistorySize), |
| activate_duration_history_(kDurationHistorySize), |
| draw_duration_history_(kDurationHistorySize), |
| begin_main_frame_on_critical_path_(false), |
| submit_ack_watchdog_enabled_(false), |
| uma_reporter_(CreateUMAReporter(uma_category)), |
| rendering_stats_instrumentation_(rendering_stats_instrumentation) {} |
| |
| CompositorTimingHistory::~CompositorTimingHistory() = default; |
| |
| std::unique_ptr<CompositorTimingHistory::UMAReporter> |
| CompositorTimingHistory::CreateUMAReporter(UMACategory category) { |
| switch (category) { |
| case RENDERER_UMA: |
| return base::WrapUnique(new RendererUMAReporter); |
| break; |
| case BROWSER_UMA: |
| return base::WrapUnique(new BrowserUMAReporter); |
| break; |
| case NULL_UMA: |
| return base::WrapUnique(new NullUMAReporter); |
| break; |
| } |
| NOTREACHED(); |
| return base::WrapUnique<CompositorTimingHistory::UMAReporter>(nullptr); |
| } |
| |
| void CompositorTimingHistory::AsValueInto( |
| base::trace_event::TracedValue* state) const { |
| state->SetDouble( |
| "begin_main_frame_queue_critical_estimate_ms", |
| BeginMainFrameQueueDurationCriticalEstimate().InMillisecondsF()); |
| state->SetDouble( |
| "begin_main_frame_queue_not_critical_estimate_ms", |
| BeginMainFrameQueueDurationNotCriticalEstimate().InMillisecondsF()); |
| state->SetDouble( |
| "begin_main_frame_start_to_ready_to_commit_estimate_ms", |
| BeginMainFrameStartToReadyToCommitDurationEstimate().InMillisecondsF()); |
| state->SetDouble("commit_to_ready_to_activate_estimate_ms", |
| CommitToReadyToActivateDurationEstimate().InMillisecondsF()); |
| state->SetDouble("prepare_tiles_estimate_ms", |
| PrepareTilesDurationEstimate().InMillisecondsF()); |
| state->SetDouble("activate_estimate_ms", |
| ActivateDurationEstimate().InMillisecondsF()); |
| state->SetDouble("draw_estimate_ms", |
| DrawDurationEstimate().InMillisecondsF()); |
| } |
| |
| base::TimeTicks CompositorTimingHistory::Now() const { |
| return base::TimeTicks::Now(); |
| } |
| |
| void CompositorTimingHistory::SetRecordingEnabled(bool enabled) { |
| enabled_ = enabled; |
| } |
| |
| void CompositorTimingHistory::SetBeginMainFrameNeededContinuously(bool active) { |
| if (active == begin_main_frame_needed_continuously_) |
| return; |
| begin_main_frame_end_time_prev_ = base::TimeTicks(); |
| begin_main_frame_needed_continuously_ = active; |
| } |
| |
| void CompositorTimingHistory::SetBeginMainFrameCommittingContinuously( |
| bool active) { |
| if (active == begin_main_frame_committing_continuously_) |
| return; |
| new_active_tree_draw_end_time_prev_committing_continuously_ = |
| base::TimeTicks(); |
| begin_main_frame_committing_continuously_ = active; |
| } |
| |
| void CompositorTimingHistory::SetCompositorDrawingContinuously(bool active) { |
| if (active == compositor_drawing_continuously_) |
| return; |
| draw_end_time_prev_ = base::TimeTicks(); |
| compositor_drawing_continuously_ = active; |
| } |
| |
| base::TimeDelta |
| CompositorTimingHistory::BeginMainFrameQueueDurationCriticalEstimate() const { |
| base::TimeDelta all = begin_main_frame_queue_duration_history_.Percentile( |
| kBeginMainFrameQueueDurationEstimationPercentile); |
| base::TimeDelta critical = |
| begin_main_frame_queue_duration_critical_history_.Percentile( |
| kBeginMainFrameQueueDurationCriticalEstimationPercentile); |
| // Return the min since critical BeginMainFrames are likely fast if |
| // the non critical ones are. |
| return std::min(critical, all); |
| } |
| |
| base::TimeDelta |
| CompositorTimingHistory::BeginMainFrameQueueDurationNotCriticalEstimate() |
| const { |
| base::TimeDelta all = begin_main_frame_queue_duration_history_.Percentile( |
| kBeginMainFrameQueueDurationEstimationPercentile); |
| base::TimeDelta not_critical = |
| begin_main_frame_queue_duration_not_critical_history_.Percentile( |
| kBeginMainFrameQueueDurationNotCriticalEstimationPercentile); |
| // Return the max since, non critical BeginMainFrames are likely slow if |
| // the critical ones are. |
| return std::max(not_critical, all); |
| } |
| |
| base::TimeDelta |
| CompositorTimingHistory::BeginMainFrameStartToReadyToCommitDurationEstimate() |
| const { |
| return begin_main_frame_start_to_ready_to_commit_duration_history_.Percentile( |
| kBeginMainFrameStartToReadyToCommitEstimationPercentile); |
| } |
| |
| base::TimeDelta CompositorTimingHistory::CommitDurationEstimate() const { |
| return commit_duration_history_.Percentile(kCommitEstimatePercentile); |
| } |
| |
| base::TimeDelta |
| CompositorTimingHistory::CommitToReadyToActivateDurationEstimate() const { |
| return commit_to_ready_to_activate_duration_history_.Percentile( |
| kCommitToReadyToActivateEstimationPercentile); |
| } |
| |
| base::TimeDelta CompositorTimingHistory::PrepareTilesDurationEstimate() const { |
| return prepare_tiles_duration_history_.Percentile( |
| kPrepareTilesEstimationPercentile); |
| } |
| |
| base::TimeDelta CompositorTimingHistory::ActivateDurationEstimate() const { |
| return activate_duration_history_.Percentile(kActivateEstimationPercentile); |
| } |
| |
| base::TimeDelta CompositorTimingHistory::DrawDurationEstimate() const { |
| return draw_duration_history_.Percentile(kDrawEstimationPercentile); |
| } |
| |
| void CompositorTimingHistory::DidCreateAndInitializeLayerTreeFrameSink() { |
| // After we get a new output surface, we won't get a spurious |
| // CompositorFrameAck from the old output surface. |
| submit_start_time_ = base::TimeTicks(); |
| submit_ack_watchdog_enabled_ = false; |
| } |
| |
| void CompositorTimingHistory::WillBeginImplFrame( |
| bool new_active_tree_is_likely, |
| base::TimeTicks frame_time, |
| viz::BeginFrameArgs::BeginFrameArgsType frame_type, |
| base::TimeTicks now) { |
| // The check for whether a BeginMainFrame was sent anytime between two |
| // BeginImplFrames protects us from not detecting a fast main thread that |
| // does all it's work and goes idle in between BeginImplFrames. |
| // For example, this may happen if an animation is being driven with |
| // setInterval(17) or if input events just happen to arrive in the |
| // middle of every frame. |
| if (!new_active_tree_is_likely && !did_send_begin_main_frame_) { |
| SetBeginMainFrameNeededContinuously(false); |
| SetBeginMainFrameCommittingContinuously(false); |
| } |
| |
| if (submit_ack_watchdog_enabled_) { |
| base::TimeDelta submit_not_acked_time_ = now - submit_start_time_; |
| if (submit_not_acked_time_ >= kSubmitAckWatchdogTimeout) { |
| // Only record this UMA once per submitted CompositorFrame. |
| submit_ack_watchdog_enabled_ = false; |
| } |
| } |
| |
| if (frame_type == viz::BeginFrameArgs::NORMAL) |
| uma_reporter_->AddBeginImplFrameLatency(now - frame_time); |
| |
| did_send_begin_main_frame_ = false; |
| } |
| |
| void CompositorTimingHistory::WillFinishImplFrame(bool needs_redraw) { |
| if (!needs_redraw) |
| SetCompositorDrawingContinuously(false); |
| } |
| |
| void CompositorTimingHistory::BeginImplFrameNotExpectedSoon() { |
| SetBeginMainFrameNeededContinuously(false); |
| SetBeginMainFrameCommittingContinuously(false); |
| SetCompositorDrawingContinuously(false); |
| } |
| |
| void CompositorTimingHistory::WillBeginMainFrame( |
| bool on_critical_path, |
| base::TimeTicks main_frame_time) { |
| DCHECK_EQ(base::TimeTicks(), begin_main_frame_sent_time_); |
| DCHECK_EQ(base::TimeTicks(), begin_main_frame_frame_time_); |
| |
| begin_main_frame_on_critical_path_ = on_critical_path; |
| begin_main_frame_sent_time_ = Now(); |
| begin_main_frame_frame_time_ = main_frame_time; |
| |
| did_send_begin_main_frame_ = true; |
| SetBeginMainFrameNeededContinuously(true); |
| } |
| |
| void CompositorTimingHistory::BeginMainFrameStarted( |
| base::TimeTicks main_thread_start_time) { |
| DCHECK_NE(base::TimeTicks(), begin_main_frame_sent_time_); |
| DCHECK_EQ(base::TimeTicks(), begin_main_frame_start_time_); |
| begin_main_frame_start_time_ = main_thread_start_time; |
| } |
| |
| void CompositorTimingHistory::BeginMainFrameAborted() { |
| SetBeginMainFrameCommittingContinuously(false); |
| base::TimeTicks begin_main_frame_end_time = Now(); |
| DidBeginMainFrame(begin_main_frame_end_time); |
| begin_main_frame_frame_time_ = base::TimeTicks(); |
| } |
| |
| void CompositorTimingHistory::NotifyReadyToCommit() { |
| DCHECK_NE(begin_main_frame_start_time_, base::TimeTicks()); |
| begin_main_frame_start_to_ready_to_commit_duration_history_.InsertSample( |
| Now() - begin_main_frame_start_time_); |
| } |
| |
| void CompositorTimingHistory::WillCommit() { |
| DCHECK_NE(begin_main_frame_start_time_, base::TimeTicks()); |
| commit_start_time_ = Now(); |
| } |
| |
| void CompositorTimingHistory::DidCommit() { |
| DCHECK_EQ(base::TimeTicks(), pending_tree_main_frame_time_); |
| DCHECK_EQ(pending_tree_creation_time_, base::TimeTicks()); |
| DCHECK_NE(commit_start_time_, base::TimeTicks()); |
| |
| SetBeginMainFrameCommittingContinuously(true); |
| base::TimeTicks begin_main_frame_end_time = Now(); |
| DidBeginMainFrame(begin_main_frame_end_time); |
| commit_duration_history_.InsertSample(begin_main_frame_end_time - |
| commit_start_time_); |
| |
| pending_tree_is_impl_side_ = false; |
| pending_tree_creation_time_ = begin_main_frame_end_time; |
| pending_tree_main_frame_time_ = begin_main_frame_frame_time_; |
| begin_main_frame_frame_time_ = base::TimeTicks(); |
| } |
| |
| void CompositorTimingHistory::DidBeginMainFrame( |
| base::TimeTicks begin_main_frame_end_time) { |
| DCHECK_NE(base::TimeTicks(), begin_main_frame_sent_time_); |
| |
| // If the BeginMainFrame start time isn't know, assume it was immediate |
| // for scheduling purposes, but don't report it for UMA to avoid skewing |
| // the results. |
| bool begin_main_frame_start_time_is_valid = |
| !begin_main_frame_start_time_.is_null(); |
| if (!begin_main_frame_start_time_is_valid) |
| begin_main_frame_start_time_ = begin_main_frame_sent_time_; |
| |
| base::TimeDelta begin_main_frame_sent_to_commit_duration = |
| begin_main_frame_end_time - begin_main_frame_sent_time_; |
| base::TimeDelta begin_main_frame_queue_duration = |
| begin_main_frame_start_time_ - begin_main_frame_sent_time_; |
| base::TimeDelta begin_main_frame_start_to_commit_duration = |
| begin_main_frame_end_time - begin_main_frame_start_time_; |
| |
| rendering_stats_instrumentation_->AddBeginMainFrameToCommitDuration( |
| begin_main_frame_sent_to_commit_duration); |
| |
| if (begin_main_frame_start_time_is_valid) { |
| if (begin_main_frame_on_critical_path_) { |
| uma_reporter_->AddBeginMainFrameQueueDurationCriticalDuration( |
| begin_main_frame_queue_duration); |
| } else { |
| uma_reporter_->AddBeginMainFrameQueueDurationNotCriticalDuration( |
| begin_main_frame_queue_duration); |
| } |
| } |
| |
| uma_reporter_->AddBeginMainFrameStartToCommitDuration( |
| begin_main_frame_start_to_commit_duration); |
| |
| if (enabled_) { |
| begin_main_frame_queue_duration_history_.InsertSample( |
| begin_main_frame_queue_duration); |
| if (begin_main_frame_on_critical_path_) { |
| begin_main_frame_queue_duration_critical_history_.InsertSample( |
| begin_main_frame_queue_duration); |
| } else { |
| begin_main_frame_queue_duration_not_critical_history_.InsertSample( |
| begin_main_frame_queue_duration); |
| } |
| } |
| |
| if (begin_main_frame_needed_continuously_) { |
| if (!begin_main_frame_end_time_prev_.is_null()) { |
| base::TimeDelta commit_interval = |
| begin_main_frame_end_time - begin_main_frame_end_time_prev_; |
| if (begin_main_frame_on_critical_path_) |
| uma_reporter_->AddBeginMainFrameIntervalCritical(commit_interval); |
| else |
| uma_reporter_->AddBeginMainFrameIntervalNotCritical(commit_interval); |
| } |
| begin_main_frame_end_time_prev_ = begin_main_frame_end_time; |
| } |
| |
| begin_main_frame_sent_time_ = base::TimeTicks(); |
| begin_main_frame_start_time_ = base::TimeTicks(); |
| } |
| |
| void CompositorTimingHistory::WillInvalidateOnImplSide() { |
| DCHECK(!pending_tree_is_impl_side_); |
| DCHECK_EQ(pending_tree_creation_time_, base::TimeTicks()); |
| |
| pending_tree_is_impl_side_ = true; |
| pending_tree_creation_time_ = base::TimeTicks::Now(); |
| } |
| |
| void CompositorTimingHistory::WillPrepareTiles() { |
| DCHECK_EQ(base::TimeTicks(), prepare_tiles_start_time_); |
| prepare_tiles_start_time_ = Now(); |
| } |
| |
| void CompositorTimingHistory::DidPrepareTiles() { |
| DCHECK_NE(base::TimeTicks(), prepare_tiles_start_time_); |
| |
| base::TimeDelta prepare_tiles_duration = Now() - prepare_tiles_start_time_; |
| uma_reporter_->AddPrepareTilesDuration(prepare_tiles_duration); |
| if (enabled_) |
| prepare_tiles_duration_history_.InsertSample(prepare_tiles_duration); |
| |
| prepare_tiles_start_time_ = base::TimeTicks(); |
| } |
| |
| void CompositorTimingHistory::ReadyToActivate() { |
| DCHECK_NE(pending_tree_creation_time_, base::TimeTicks()); |
| DCHECK_EQ(pending_tree_ready_to_activate_time_, base::TimeTicks()); |
| |
| pending_tree_ready_to_activate_time_ = Now(); |
| if (pending_tree_is_impl_side_) { |
| base::TimeDelta time_since_invalidation = |
| pending_tree_ready_to_activate_time_ - pending_tree_creation_time_; |
| uma_reporter_->AddInvalidationToReadyToActivateDuration( |
| time_since_invalidation, tree_priority_); |
| } else { |
| base::TimeDelta time_since_commit = |
| pending_tree_ready_to_activate_time_ - pending_tree_creation_time_; |
| |
| // Before adding the new data point to the timing history, see what we would |
| // have predicted for this frame. This allows us to keep track of the |
| // accuracy of our predictions. |
| |
| base::TimeDelta commit_to_ready_to_activate_estimate = |
| CommitToReadyToActivateDurationEstimate(); |
| uma_reporter_->AddCommitToReadyToActivateDuration(time_since_commit, |
| tree_priority_); |
| rendering_stats_instrumentation_->AddCommitToActivateDuration( |
| time_since_commit, commit_to_ready_to_activate_estimate); |
| |
| if (enabled_) { |
| commit_to_ready_to_activate_duration_history_.InsertSample( |
| time_since_commit); |
| } |
| } |
| } |
| |
| void CompositorTimingHistory::WillActivate() { |
| DCHECK_EQ(base::TimeTicks(), activate_start_time_); |
| |
| activate_start_time_ = Now(); |
| |
| // Its possible to activate the pending tree before it is ready for |
| // activation, for instance in the case of a context loss or visibility |
| // changes. |
| if (pending_tree_ready_to_activate_time_ != base::TimeTicks()) { |
| base::TimeDelta time_since_ready = |
| activate_start_time_ - pending_tree_ready_to_activate_time_; |
| uma_reporter_->AddReadyToActivateToWillActivateDuration( |
| time_since_ready, pending_tree_is_impl_side_); |
| } |
| |
| pending_tree_is_impl_side_ = false; |
| pending_tree_creation_time_ = base::TimeTicks(); |
| pending_tree_ready_to_activate_time_ = base::TimeTicks(); |
| } |
| |
| void CompositorTimingHistory::DidActivate() { |
| DCHECK_NE(base::TimeTicks(), activate_start_time_); |
| base::TimeDelta activate_duration = Now() - activate_start_time_; |
| |
| uma_reporter_->AddActivateDuration(activate_duration); |
| if (enabled_) |
| activate_duration_history_.InsertSample(activate_duration); |
| |
| // The synchronous compositor doesn't necessarily draw every new active tree. |
| if (!using_synchronous_renderer_compositor_) |
| DCHECK_EQ(base::TimeTicks(), active_tree_main_frame_time_); |
| active_tree_main_frame_time_ = pending_tree_main_frame_time_; |
| |
| activate_start_time_ = base::TimeTicks(); |
| pending_tree_main_frame_time_ = base::TimeTicks(); |
| } |
| |
| void CompositorTimingHistory::WillDraw() { |
| DCHECK_EQ(base::TimeTicks(), draw_start_time_); |
| draw_start_time_ = Now(); |
| } |
| |
| void CompositorTimingHistory::DrawAborted() { |
| active_tree_main_frame_time_ = base::TimeTicks(); |
| } |
| |
| void CompositorTimingHistory::DidDraw( |
| bool used_new_active_tree, |
| base::TimeTicks impl_frame_time, |
| size_t composited_animations_count, |
| size_t main_thread_animations_count, |
| bool current_frame_had_raf, |
| bool next_frame_has_pending_raf) { |
| DCHECK_NE(base::TimeTicks(), draw_start_time_); |
| base::TimeTicks draw_end_time = Now(); |
| base::TimeDelta draw_duration = draw_end_time - draw_start_time_; |
| |
| // Before adding the new data point to the timing history, see what we would |
| // have predicted for this frame. This allows us to keep track of the accuracy |
| // of our predictions. |
| base::TimeDelta draw_estimate = DrawDurationEstimate(); |
| rendering_stats_instrumentation_->AddDrawDuration(draw_duration, |
| draw_estimate); |
| |
| uma_reporter_->AddDrawDuration(draw_duration); |
| |
| if (enabled_) { |
| draw_duration_history_.InsertSample(draw_duration); |
| } |
| |
| SetCompositorDrawingContinuously(true); |
| if (!draw_end_time_prev_.is_null()) { |
| base::TimeDelta draw_interval = draw_end_time - draw_end_time_prev_; |
| uma_reporter_->AddDrawInterval(draw_interval); |
| if (composited_animations_count > 0 && |
| previous_frame_had_composited_animations_) |
| uma_reporter_->AddDrawIntervalWithCompositedAnimations(draw_interval); |
| } |
| previous_frame_had_composited_animations_ = composited_animations_count > 0; |
| draw_end_time_prev_ = draw_end_time; |
| |
| if (used_new_active_tree) { |
| DCHECK_NE(base::TimeTicks(), active_tree_main_frame_time_); |
| base::TimeDelta main_and_impl_delta = |
| impl_frame_time - active_tree_main_frame_time_; |
| DCHECK_GE(main_and_impl_delta, base::TimeDelta()); |
| TRACE_EVENT2(TRACE_DISABLED_BY_DEFAULT("cc.debug.scheduler.frames"), |
| "CompositorTimingHistory::DidDraw", |
| "active_tree_main_frame_time", active_tree_main_frame_time_, |
| "impl_frame_time", impl_frame_time); |
| uma_reporter_->AddMainAndImplFrameTimeDelta(main_and_impl_delta); |
| active_tree_main_frame_time_ = base::TimeTicks(); |
| |
| bool current_main_frame_had_visual_update = |
| main_thread_animations_count > 0 || current_frame_had_raf; |
| bool previous_main_frame_had_visual_update = |
| previous_frame_had_main_thread_animations_ || previous_frame_had_raf_; |
| if (current_main_frame_had_visual_update && |
| previous_main_frame_had_visual_update) { |
| base::TimeDelta draw_interval = |
| draw_end_time - new_active_tree_draw_end_time_prev_; |
| uma_reporter_->AddDrawIntervalWithMainThreadAnimations(draw_interval); |
| } |
| previous_frame_had_main_thread_animations_ = |
| main_thread_animations_count > 0; |
| // It's possible that two consecutive main frames both run a rAF but are |
| // separated by idle time (for example: calling requestAnimationFrame from a |
| // setInterval function, with nothing else producing a main frame |
| // in-between). To avoid incorrectly counting those cases as long draw |
| // intervals, we only update previous_frame_had_raf_ if the current frame |
| // also already has a future raf scheduled. |
| previous_frame_had_raf_ = |
| current_frame_had_raf && next_frame_has_pending_raf; |
| |
| new_active_tree_draw_end_time_prev_ = draw_end_time; |
| |
| if (begin_main_frame_committing_continuously_) { |
| if (!new_active_tree_draw_end_time_prev_committing_continuously_ |
| .is_null()) { |
| base::TimeDelta draw_interval = |
| draw_end_time - |
| new_active_tree_draw_end_time_prev_committing_continuously_; |
| uma_reporter_->AddCommitInterval(draw_interval); |
| } |
| new_active_tree_draw_end_time_prev_committing_continuously_ = |
| draw_end_time; |
| } |
| } |
| draw_start_time_ = base::TimeTicks(); |
| } |
| |
| void CompositorTimingHistory::DidSubmitCompositorFrame() { |
| DCHECK_EQ(base::TimeTicks(), submit_start_time_); |
| submit_start_time_ = Now(); |
| submit_ack_watchdog_enabled_ = true; |
| } |
| |
| void CompositorTimingHistory::DidReceiveCompositorFrameAck() { |
| DCHECK_NE(base::TimeTicks(), submit_start_time_); |
| base::TimeDelta submit_to_ack_duration = Now() - submit_start_time_; |
| uma_reporter_->AddSubmitToAckLatency(submit_to_ack_duration); |
| if (submit_ack_watchdog_enabled_) |
| submit_ack_watchdog_enabled_ = false; |
| submit_start_time_ = base::TimeTicks(); |
| } |
| |
| void CompositorTimingHistory::SetTreePriority(TreePriority priority) { |
| tree_priority_ = priority; |
| } |
| |
| void CompositorTimingHistory::ClearHistory() { |
| TRACE_EVENT0("cc,benchmark", "CompositorTimingHistory::ClearHistory"); |
| |
| begin_main_frame_queue_duration_history_.Clear(); |
| begin_main_frame_queue_duration_critical_history_.Clear(); |
| begin_main_frame_queue_duration_not_critical_history_.Clear(); |
| begin_main_frame_start_to_ready_to_commit_duration_history_.Clear(); |
| commit_duration_history_.Clear(); |
| commit_to_ready_to_activate_duration_history_.Clear(); |
| prepare_tiles_duration_history_.Clear(); |
| activate_duration_history_.Clear(); |
| draw_duration_history_.Clear(); |
| } |
| |
| } // namespace cc |