| // Copyright 2020 The Chromium Authors |
| // 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_macros.h" |
| #include "base/notreached.h" |
| #include "base/strings/strcat.h" |
| #include "base/time/time.h" |
| #include "base/trace_event/trace_event.h" |
| #include "base/trace_event/traced_value.h" |
| #include "cc/metrics/frame_sequence_tracker.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) { |
| // kSETMainThreadAnimation and kSETCompositorAnimation sequences are a subset |
| // of kMainThreadAnimation and kCompositorAnimation sequences. So these are |
| // excluded from the AllAnimation metric to avoid double counting. |
| |
| 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 { |
| |
| constexpr uint32_t kMaxNoUpdateFrameCount = 100; |
| |
| const char* GetThreadTypeName(SmoothEffectDrivingThread type) { |
| switch (type) { |
| case SmoothEffectDrivingThread::kCompositor: |
| return "CompositorThread"; |
| case SmoothEffectDrivingThread::kMain: |
| return "MainThread"; |
| default: |
| NOTREACHED(); |
| return ""; |
| } |
| } |
| |
| // 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; |
| constexpr int kMaximumJankV3HistogramIndex = 2 * 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; |
| } |
| |
| int GetIndexForJankV3Metric(SmoothEffectDrivingThread thread_type, |
| FrameSequenceTrackerType type) { |
| if (thread_type == SmoothEffectDrivingThread::kMain) { |
| return static_cast<int>(type); |
| } |
| DCHECK_EQ(thread_type, SmoothEffectDrivingThread::kCompositor); |
| return static_cast<int>(type) + kBuiltinSequenceNum; |
| } |
| |
| std::string GetCheckerboardingV3HistogramName(FrameSequenceTrackerType type) { |
| return base::StrCat( |
| {"Graphics.Smoothness.Checkerboarding3.", |
| FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type)}); |
| } |
| |
| std::string GetCheckerboardingV3ThreadedHistogramName( |
| FrameSequenceTrackerType type, |
| const char* thread_name) { |
| return base::StrCat( |
| {"Graphics.Smoothness.Checkerboarding3.", thread_name, ".", |
| FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type)}); |
| } |
| |
| std::string GetJankV3HistogramName(FrameSequenceTrackerType type, |
| const char* thread_name) { |
| return base::StrCat( |
| {"Graphics.Smoothness.Jank3.", thread_name, ".", |
| FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type)}); |
| } |
| |
| std::string GetThroughputV3HistogramName(FrameSequenceTrackerType type, |
| const char* thread_name) { |
| return base::StrCat( |
| {"Graphics.Smoothness.PercentDroppedFrames3.", thread_name, ".", |
| FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type)}); |
| } |
| |
| } // namespace |
| |
| FrameSequenceMetrics::V3::V3() = default; |
| FrameSequenceMetrics::V3::~V3() = default; |
| |
| FrameSequenceMetrics::FrameSequenceMetrics(FrameSequenceTrackerType type) |
| : type_(type) {} |
| |
| FrameSequenceMetrics::~FrameSequenceMetrics() { |
| // If we did not see sufficient frames to report we will be kept around to |
| // have the data `Merge` into the next sequence of `type_`. We do not |
| // terminate active traces immediately when we stop, as the sequence may |
| // restart, leading to `AdoptTrace`. |
| // |
| // However we may not be merged before teardown, if so terminate the trace |
| // now. |
| if (trace_data_v3_.trace_id) { |
| trace_data_v3_.TerminateV3(v3_, GetEffectiveThread()); |
| } |
| } |
| |
| 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; |
| } |
| |
| 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::kSETCompositorAnimation: |
| case FrameSequenceTrackerType::kPinchZoom: |
| case FrameSequenceTrackerType::kVideo: |
| return SmoothEffectDrivingThread::kCompositor; |
| |
| case FrameSequenceTrackerType::kMainThreadAnimation: |
| case FrameSequenceTrackerType::kSETMainThreadAnimation: |
| 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()); |
| |
| v3_.frames_expected += metrics->v3_.frames_expected; |
| v3_.frames_dropped += metrics->v3_.frames_dropped; |
| v3_.frames_missing_content += metrics->v3_.frames_missing_content; |
| v3_.jank_count += metrics->v3_.jank_count; |
| v3_.no_update_count += metrics->v3_.no_update_count; |
| if (v3_.last_begin_frame_args.frame_time < |
| metrics->v3_.last_begin_frame_args.frame_time) { |
| v3_.last_begin_frame_args = metrics->v3_.last_begin_frame_args; |
| v3_.last_frame = metrics->v3_.last_frame; |
| v3_.last_presented_frame = metrics->v3_.last_presented_frame; |
| v3_.last_frame_delta = metrics->v3_.last_frame_delta; |
| v3_.no_update_duration = metrics->v3_.no_update_duration; |
| } |
| } |
| |
| bool FrameSequenceMetrics::HasEnoughDataForReporting() const { |
| return v3_.frames_expected >= kMinFramesForThroughputMetric; |
| } |
| |
| bool FrameSequenceMetrics::HasDataLeftForReporting() const { |
| return v3_.frames_expected > 0; |
| } |
| |
| void FrameSequenceMetrics::AdoptTrace(FrameSequenceMetrics* adopt_from) { |
| DCHECK(!trace_data_.trace_id); |
| trace_data_.trace_id = adopt_from->trace_data_.trace_id; |
| trace_data_v3_.trace_id = adopt_from->trace_data_v3_.trace_id; |
| trace_data_v3_.last_presented_sequence_number = |
| adopt_from->trace_data_v3_.trace_id; |
| trace_data_v3_.last_timestamp = adopt_from->trace_data_v3_.last_timestamp; |
| trace_data_v3_.frame_count = adopt_from->trace_data_v3_.frame_count; |
| adopt_from->trace_data_.trace_id = 0u; |
| adopt_from->trace_data_v3_.trace_id = 0u; |
| } |
| |
| void FrameSequenceMetrics::ReportMetrics() { |
| // Terminates |trace_data_| for all types of FrameSequenceTracker. |
| trace_data_v3_.TerminateV3(v3_, GetEffectiveThread()); |
| |
| if (type_ == FrameSequenceTrackerType::kCustom) { |
| DCHECK(!custom_reporter_.is_null()); |
| std::move(custom_reporter_) |
| .Run({ |
| v3_.frames_expected, |
| v3_.frames_dropped, |
| v3_.jank_count, |
| }); |
| |
| v3_.frames_expected = 0u; |
| v3_.frames_dropped = 0u; |
| v3_.frames_missing_content = 0u; |
| v3_.no_update_count = 0u; |
| v3_.jank_count = 0u; |
| return; |
| } |
| |
| const auto thread_type = GetEffectiveThread(); |
| const bool is_animation = ShouldReportForAnimation(type(), thread_type); |
| const bool is_interaction = |
| ShouldReportForInteraction(type(), thread_type, thread_type); |
| |
| if (v3_.frames_expected >= kMinFramesForThroughputMetric) { |
| const int percent_missing_content = |
| std::ceil(100. * v3_.frames_missing_content / |
| static_cast<double>(v3_.frames_expected)); |
| const int percent = |
| v3_.frames_expected == 0 |
| ? 0 |
| : std::ceil(100. * v3_.frames_dropped / |
| static_cast<double>(v3_.frames_expected)); |
| const int percent_jank = std::ceil( |
| 100. * v3_.jank_count / static_cast<double>(v3_.frames_expected)); |
| |
| if (is_animation) { |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Graphics.Smoothness.Checkerboarding3.AllAnimations", |
| percent_missing_content); |
| UMA_HISTOGRAM_PERCENTAGE("Graphics.Smoothness.Jank3.AllAnimations", |
| percent_jank); |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Graphics.Smoothness.PercentDroppedFrames3.AllAnimations", percent); |
| } |
| if (is_interaction) { |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Graphics.Smoothness.Checkerboarding3.AllInteractions", |
| percent_missing_content); |
| UMA_HISTOGRAM_PERCENTAGE("Graphics.Smoothness.Jank3.AllInteractions", |
| percent_jank); |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Graphics.Smoothness.PercentDroppedFrames3.AllInteractions", percent); |
| } |
| if (is_animation || is_interaction) { |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Graphics.Smoothness.Checkerboarding3.AllSequences", |
| percent_missing_content); |
| UMA_HISTOGRAM_PERCENTAGE("Graphics.Smoothness.Jank3.AllSequences", |
| percent_jank); |
| UMA_HISTOGRAM_PERCENTAGE( |
| "Graphics.Smoothness.PercentDroppedFrames3.AllSequences", percent); |
| } |
| |
| const char* thread_name = GetThreadTypeName(thread_type); |
| |
| STATIC_HISTOGRAM_POINTER_GROUP( |
| GetThroughputV3HistogramName(type(), thread_name), |
| GetIndexForMetric(thread_type, type_), kMaximumHistogramIndex, |
| Add(percent), |
| base::LinearHistogram::FactoryGet( |
| GetThroughputV3HistogramName(type(), thread_name), 1, 100, 101, |
| base::HistogramBase::kUmaTargetedHistogramFlag)); |
| |
| STATIC_HISTOGRAM_POINTER_GROUP( |
| GetCheckerboardingV3HistogramName(type_), static_cast<int>(type_), |
| static_cast<int>(FrameSequenceTrackerType::kMaxType), |
| Add(percent_missing_content), |
| base::LinearHistogram::FactoryGet( |
| GetCheckerboardingV3HistogramName(type_), 1, 100, 101, |
| base::HistogramBase::kUmaTargetedHistogramFlag)); |
| |
| if (scrolling_thread_ != SmoothEffectDrivingThread::kUnknown) { |
| STATIC_HISTOGRAM_POINTER_GROUP( |
| GetCheckerboardingV3ThreadedHistogramName(type_, thread_name), |
| GetIndexForMetric(thread_type, type_), kMaximumHistogramIndex, |
| Add(percent_missing_content), |
| base::LinearHistogram::FactoryGet( |
| GetCheckerboardingV3ThreadedHistogramName(type_, thread_name), 1, |
| 100, 101, base::HistogramBase::kUmaTargetedHistogramFlag)); |
| } |
| |
| STATIC_HISTOGRAM_POINTER_GROUP( |
| GetJankV3HistogramName(type_, thread_name), |
| GetIndexForJankV3Metric(thread_type, type_), |
| kMaximumJankV3HistogramIndex, Add(percent_jank), |
| base::LinearHistogram::FactoryGet( |
| GetJankV3HistogramName(type_, thread_name), 1, 100, 101, |
| base::HistogramBase::kUmaTargetedHistogramFlag)); |
| v3_.frames_expected = 0u; |
| v3_.frames_dropped = 0u; |
| v3_.frames_missing_content = 0u; |
| v3_.no_update_count = 0u; |
| v3_.jank_count = 0u; |
| } |
| } |
| |
| FrameSequenceMetrics::TraceData::TraceData(FrameSequenceMetrics* m) |
| : metrics(m) { |
| TRACE_EVENT_CATEGORY_GROUP_ENABLED("cc,benchmark", &enabled); |
| } |
| |
| FrameSequenceMetrics::TraceData::~TraceData() = default; |
| |
| void FrameSequenceMetrics::TraceData::TerminateV3( |
| const V3& v3, |
| FrameInfo::SmoothEffectDrivingThread effective_thread) { |
| if (!enabled || !trace_id) { |
| return; |
| } |
| auto dict = std::make_unique<base::trace_event::TracedValue>(); |
| dict->BeginDictionary("data"); |
| dict->SetInteger("expected", v3.frames_expected); |
| dict->SetInteger("dropped", v3.frames_dropped); |
| dict->SetInteger("missing_content", v3.frames_missing_content); |
| dict->EndDictionary(); |
| base::TimeTicks termination_time = |
| v3.last_presented_frame.GetTerminationTimeForThread(effective_thread); |
| // FrameSequenceTracker termination is based on FrameSorter. This way it can |
| // reflect delays in gfx::PresentationFeedback arriving, while also not |
| // terminating due to out-of-order dropped frames. The default termination is |
| // when the final frame produced for the sequence has been presented. |
| // |
| // Otherwise we will terminate once a frame, newer than the final one of the |
| // sequence has been produced, not dropped, and sorted. This can be a |
| // FrameInfo::FrameFinalState::NoUpdateDesired, which has no termination time, |
| // due to never being presented. When this occurs after a series of dropped |
| // frames, we can potentially have an entire sequence that was dropped. |
| // Leading to `v3.last_presented_frame` having no valid timestamp. |
| // |
| // Here we check for alternative timestamps, so as not to provide a null |
| // timestamp to the trace. |
| if (termination_time.is_null()) { |
| termination_time = |
| v3.last_frame.GetTerminationTimeForThread(effective_thread); |
| if (termination_time.is_null()) { |
| termination_time = base::TimeTicks::Now(); |
| } |
| } |
| TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP1( |
| "cc,benchmark", "FrameSequenceTrackerV3", TRACE_ID_LOCAL(trace_id), |
| termination_time, "args", std::move(dict)); |
| trace_id = 0u; |
| } |
| |
| void FrameSequenceMetrics::TraceData::Advance(base::TimeTicks start_timestamp, |
| base::TimeTicks new_timestamp, |
| uint32_t expected, |
| uint32_t dropped, |
| uint64_t sequence_number, |
| const char* histogram_name) { |
| if (!enabled) |
| return; |
| if (!trace_id) { |
| // The underlying usage of TRACE_ID_LOCAL is mapping the raw uint64_t from |
| // the point into either `trace_event_internal::TraceID::LocalId` or |
| // `perfetto::internal::LegacyTraceId`. However the trace macros don't |
| // support just providing that object directly. Here we do the cast |
| // ourselves ahead, and save the resulting value. This value will be used to |
| // nest other traces, as well as close the async trace at a later time. The |
| // value can also be merged into future sequences. This avoids holding |
| // dangling ptrs. |
| trace_id = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(this)); |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1( |
| "cc,benchmark", histogram_name, TRACE_ID_LOCAL(trace_id), |
| start_timestamp, "name", |
| FrameSequenceTracker::GetFrameSequenceTrackerTypeName(metrics->type())); |
| } |
| |
| auto dict = std::make_unique<base::trace_event::TracedValue>(); |
| dict->BeginDictionary("values"); |
| dict->SetInteger("sequence_number", sequence_number); |
| dict->SetInteger("last_sequence", last_presented_sequence_number); |
| dict->SetInteger("expected", expected); |
| dict->SetInteger("dopped", dropped); |
| dict->EndDictionary(); |
| |
| // 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), start_timestamp); |
| TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP1( |
| "cc,benchmark", trace_names[this->frame_count % 3], |
| TRACE_ID_LOCAL(trace_id), new_timestamp, "data", std::move(dict)); |
| this->last_presented_sequence_number = sequence_number; |
| this->last_timestamp = new_timestamp; |
| } |
| |
| void FrameSequenceMetrics::AddSortedFrame(const viz::BeginFrameArgs& args, |
| const FrameInfo& frame_info) { |
| const auto effective_thread = GetEffectiveThread(); |
| const auto last_presented_termination_time = |
| v3_.last_presented_frame.GetTerminationTimeForThread(effective_thread); |
| const auto termination_time = |
| frame_info.GetTerminationTimeForThread(effective_thread); |
| switch (effective_thread) { |
| case SmoothEffectDrivingThread::kCompositor: |
| if (frame_info.WasSmoothCompositorUpdateDropped()) { |
| ++v3_.frames_dropped; |
| } |
| ++v3_.frames_expected; |
| CalculateCheckerboardingAndJankV3( |
| args, frame_info, frame_info.GetFinalStateForThread(effective_thread), |
| last_presented_termination_time, termination_time); |
| break; |
| case SmoothEffectDrivingThread::kMain: |
| if (frame_info.WasSmoothMainUpdateExpected()) { |
| if (frame_info.WasSmoothMainUpdateDropped()) { |
| ++v3_.frames_dropped; |
| } |
| ++v3_.frames_expected; |
| CalculateCheckerboardingAndJankV3( |
| args, frame_info, |
| frame_info.GetFinalStateForThread(effective_thread), |
| last_presented_termination_time, termination_time); |
| } else { |
| IncrementJankIdleTimeV3(last_presented_termination_time, |
| termination_time); |
| } |
| break; |
| case SmoothEffectDrivingThread::kUnknown: |
| NOTREACHED(); |
| break; |
| } |
| v3_.last_begin_frame_args = args; |
| v3_.last_frame = frame_info; |
| } |
| |
| void FrameSequenceMetrics::CalculateCheckerboardingAndJankV3( |
| const viz::BeginFrameArgs& args, |
| const FrameInfo& frame_info, |
| FrameInfo::FrameFinalState final_state, |
| base::TimeTicks last_presented_termination_time, |
| base::TimeTicks termination_time) { |
| switch (final_state) { |
| case FrameInfo::FrameFinalState::kNoUpdateDesired: |
| IncrementJankIdleTimeV3(last_presented_termination_time, |
| termination_time); |
| ABSL_FALLTHROUGH_INTENDED; |
| case FrameInfo::FrameFinalState::kDropped: |
| if (v3_.last_presented_frame.has_missing_content) { |
| ++v3_.frames_missing_content; |
| } |
| break; |
| case FrameInfo::FrameFinalState::kPresentedAll: |
| case FrameInfo::FrameFinalState::kPresentedPartialOldMain: |
| case FrameInfo::FrameFinalState::kPresentedPartialNewMain: |
| if (frame_info.has_missing_content) { |
| ++v3_.frames_missing_content; |
| } |
| |
| // The first frame of a sequence will have no previous timestamp. We don't |
| // calculate it for jank. However we start the tracing from when the |
| // sequence was started. |
| bool will_ignore_current_frame = |
| v3_.no_update_count >= kMaxNoUpdateFrameCount; |
| if (last_presented_termination_time.is_null()) { |
| last_presented_termination_time = trace_data_v3_.last_timestamp; |
| will_ignore_current_frame = true; |
| } |
| |
| // TODO(crbug.com/1450940): A new FrameSequenceTracker, that has yet to |
| // process its first frame uses its creation time as starting point of |
| // nested traces. FrameSorter processes a FrameInfo when both threads are |
| // complete. It's possible for the smoothness thread component to have |
| // completed before this tracker started. We do not include them in the |
| // traces. |
| if (!last_presented_termination_time.is_null() && |
| termination_time > last_presented_termination_time) { |
| trace_data_v3_.Advance(last_presented_termination_time, |
| termination_time, v3_.frames_expected, |
| v3_.frames_dropped, frame_info.sequence_number, |
| "FrameSequenceTrackerV3"); |
| } |
| |
| const base::TimeDelta zero_delta = base::Milliseconds(0); |
| base::TimeDelta current_frame_delta = |
| will_ignore_current_frame |
| ? zero_delta |
| : termination_time - last_presented_termination_time - |
| v3_.no_update_duration; |
| // Guard against the situation when the physical presentation interval is |
| // shorter than |no_update_duration|. For example, consider two |
| // BeginFrames A and B separated by 5 vsync cycles of no-updates (i.e. |
| // |no_update_duration| = 5 vsync cycles); the Presentation of A occurs 2 |
| // vsync cycles after BeginFrame A, whereas Presentation B occurs in the |
| // same vsync cycle as BeginFrame B. In this situation, the physical |
| // presentation interval is shorter than 5 vsync cycles and will result |
| // in a negative |current_frame_delta|. |
| if (current_frame_delta < zero_delta) { |
| current_frame_delta = zero_delta; |
| } |
| |
| // The presentation interval is typically a multiple of VSync intervals |
| // (i.e. 16.67ms, 33.33ms, 50ms ... on a 60Hz display) with small |
| // fluctuations. The 0.5 * |frame_interval| criterion is chosen so that |
| // the jank detection is robust to those fluctuations. |
| if (!v3_.last_frame_delta.is_zero() && |
| current_frame_delta > v3_.last_frame_delta + 0.5 * args.interval) { |
| ++v3_.jank_count; |
| TraceJankV3(frame_info.sequence_number, last_presented_termination_time, |
| termination_time); |
| } |
| |
| v3_.last_frame_delta = current_frame_delta; |
| v3_.no_update_duration = base::TimeDelta(); |
| v3_.no_update_count = 0; |
| // It is possible for `frame_info` to have been terminated without |
| // presentation before `last_presented_frame` was presented. We do not |
| // update `last_presented_frame` in these cases so that the nested frames |
| // in the trace are all aligned on presentations. |
| if (v3_.last_presented_frame.GetTerminationTimeForThread( |
| GetEffectiveThread()) > |
| frame_info.GetTerminationTimeForThread(GetEffectiveThread())) { |
| } else { |
| v3_.last_presented_frame = frame_info; |
| } |
| break; |
| } |
| } |
| |
| void FrameSequenceMetrics::IncrementJankIdleTimeV3( |
| base::TimeTicks last_presented_termination_time, |
| base::TimeTicks termination_time) { |
| // If `frame_info.sequence_number` of N takes a long time to present, it can |
| // present after N-1 was either Dropped or NoUpdateDesired. We don't offset |
| // jank calculation for these frames. |
| if (last_presented_termination_time.is_null() || |
| termination_time < last_presented_termination_time) { |
| return; |
| } |
| |
| v3_.no_update_duration += |
| termination_time - |
| v3_.last_frame.GetTerminationTimeForThread(GetEffectiveThread()); |
| ++v3_.no_update_count; |
| } |
| |
| void FrameSequenceMetrics::TraceJankV3(uint64_t sequence_number, |
| base::TimeTicks last_termination_time, |
| base::TimeTicks termination_time) { |
| if (!trace_data_v3_.enabled) { |
| return; |
| } |
| auto dict = std::make_unique<base::trace_event::TracedValue>(); |
| dict->BeginDictionary("data"); |
| dict->SetInteger("frame_sequence_number", sequence_number); |
| dict->SetInteger("last_presented_frame_sequence_number", |
| v3_.last_presented_frame.sequence_number); |
| dict->SetString("thread-type", GetThreadTypeName(GetEffectiveThread())); |
| dict->SetString("tracker-type", |
| FrameSequenceTracker::GetFrameSequenceTrackerTypeName(type_)); |
| dict->EndDictionary(); |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1( |
| "cc,benchmark", "JankV3", TRACE_ID_LOCAL(this), last_termination_time, |
| "data", std::move(dict)); |
| TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0( |
| "cc,benchmark", "JankV3", TRACE_ID_LOCAL(this), termination_time); |
| } |
| |
| } // namespace cc |