Rework Graphics.Smoothness.Jank to use V3 signals For PercentDroppedFrames we upgrading from V1 to V3 with more accurate signals for the importance of the frames. V3 is now a guardrail metric. We want to eliminate the old V1 path. However some metrics still use it. Jank is one such metric. This change updated V3 to perform the calculation. Bug: 1256879 Change-Id: Idf3f9d1cd0ec677903b5642e9db5e2d5f75e3822 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4582756 Reviewed-by: Mingjing Zhang <mjzhang@chromium.org> Reviewed-by: John Chen <johnchen@chromium.org> Commit-Queue: Jonathan Ross <jonross@chromium.org> Cr-Commit-Position: refs/heads/main@{#1154887}
diff --git a/cc/metrics/compositor_frame_reporter.cc b/cc/metrics/compositor_frame_reporter.cc index 9c0fb97..51bc237 100644 --- a/cc/metrics/compositor_frame_reporter.cc +++ b/cc/metrics/compositor_frame_reporter.cc
@@ -1836,6 +1836,7 @@ info.smooth_thread = smooth_thread; info.scroll_thread = scrolling_thread; info.has_missing_content = has_missing_content_; + info.sequence_number = args_.frame_id.sequence_number; if (frame_skip_reason_.has_value() && frame_skip_reason() == FrameSkippedReason::kNoDamage) { @@ -1858,15 +1859,7 @@ info.main_thread_response = FrameInfo::MainThreadResponse::kIncluded; } - if (!stage_history_.empty()) { - const auto& stage = stage_history_.back(); - if (stage.stage_type == StageType::kTotalLatency) { - DCHECK_EQ(frame_termination_time_ - args_.frame_time, - stage.end_time - stage.start_time); - info.total_latency = frame_termination_time_ - args_.frame_time; - } - } - + info.termination_time = frame_termination_time_; return info; }
diff --git a/cc/metrics/frame_info.cc b/cc/metrics/frame_info.cc index d7306e9..ce14b20 100644 --- a/cc/metrics/frame_info.cc +++ b/cc/metrics/frame_info.cc
@@ -40,6 +40,10 @@ } // namespace +FrameInfo::FrameInfo() = default; +FrameInfo::FrameInfo(const FrameInfo& other) = default; +FrameInfo::~FrameInfo() = default; + bool FrameInfo::IsDroppedAffectingSmoothness() const { // If neither of the threads are expected to be smooth, then this frame cannot // affect smoothness. @@ -84,6 +88,11 @@ compositor_update_was_dropped = other.final_state == FrameFinalState::kDropped; + + compositor_final_state = other.final_state; + compositor_termination_time = other.termination_time; + main_final_state = final_state; + main_termination_time = termination_time; } else { // |this| does not include main-thread updates. Therefore: // - |other| must include main-thread updates. @@ -93,6 +102,11 @@ main_update_was_dropped = other.final_state == FrameFinalState::kDropped; compositor_update_was_dropped = final_state == FrameFinalState::kDropped; + + compositor_final_state = final_state; + compositor_termination_time = termination_time; + main_final_state = other.final_state; + main_termination_time = other.termination_time; } was_merged = true; @@ -131,7 +145,7 @@ smooth_thread = SmoothThread::kSmoothNone; } - total_latency = std::max(total_latency, other.total_latency); + termination_time = std::max(termination_time, other.termination_time); // Validate the state after the merge. DCHECK(Validate()); @@ -202,4 +216,34 @@ } } +FrameInfo::FrameFinalState FrameInfo::GetFinalStateForThread( + SmoothEffectDrivingThread thread) const { + if (!was_merged) { + return final_state; + } + switch (thread) { + case SmoothEffectDrivingThread::kCompositor: + return compositor_final_state; + case SmoothEffectDrivingThread::kMain: + return main_final_state; + case SmoothEffectDrivingThread::kUnknown: + return final_state; + } +} + +base::TimeTicks FrameInfo::GetTerminationTimeForThread( + SmoothEffectDrivingThread thread) const { + if (!was_merged) { + return termination_time; + } + switch (thread) { + case SmoothEffectDrivingThread::kCompositor: + return compositor_termination_time; + case SmoothEffectDrivingThread::kMain: + return main_termination_time; + case SmoothEffectDrivingThread::kUnknown: + return termination_time; + } +} + } // namespace cc
diff --git a/cc/metrics/frame_info.h b/cc/metrics/frame_info.h index 9ff45c882..486ad3e2 100644 --- a/cc/metrics/frame_info.h +++ b/cc/metrics/frame_info.h
@@ -11,6 +11,10 @@ namespace cc { struct CC_EXPORT FrameInfo { + FrameInfo(); + FrameInfo(const FrameInfo& other); + ~FrameInfo(); + enum class FrameFinalState { kNoUpdateDesired, kDropped, @@ -56,10 +60,15 @@ bool has_missing_content = false; - // The total latency for the frame. If the frame had to be 'split' (i.e. - // compositor-thread update and main-thread updates were presented in separate - // frames), then this contains the maximum latency of the two updates. - base::TimeDelta total_latency; + // The time when the frame was terminated. If the frame had to be 'split' + // (i.e. compositor-thread update and main-thread updates were presented in + // separate frames,) then this contains the maximum time when the updates were + // terminated. See GetTerminationTimeForThread to get the value for each. + base::TimeTicks termination_time; + + // The frame number associated to the viz::BeginFrameArgs that started this + // frame's production. + uint64_t sequence_number = 0u; bool IsDroppedAffectingSmoothness() const; void MergeWith(const FrameInfo& info); @@ -74,10 +83,26 @@ bool IsScrollPrioritizeFrameDropped() const; + // If this `was_merged` these return the value for `thread`, otherwise returns + // the default non-merged values. + FrameFinalState GetFinalStateForThread( + SmoothEffectDrivingThread thread) const; + base::TimeTicks GetTerminationTimeForThread( + SmoothEffectDrivingThread thread) const; + private: bool was_merged = false; bool compositor_update_was_dropped = false; bool main_update_was_dropped = false; + + // A frame that `was_merged` could have differing final states, and differing + // termination times. We track both so that each thread's jank can be + // calculated. + FrameFinalState compositor_final_state = FrameFinalState::kNoUpdateDesired; + FrameFinalState main_final_state = FrameFinalState::kNoUpdateDesired; + + base::TimeTicks compositor_termination_time; + base::TimeTicks main_termination_time; }; } // namespace cc
diff --git a/cc/metrics/frame_sequence_metrics.cc b/cc/metrics/frame_sequence_metrics.cc index e1a66a25..309dde8f 100644 --- a/cc/metrics/frame_sequence_metrics.cc +++ b/cc/metrics/frame_sequence_metrics.cc
@@ -17,6 +17,7 @@ #include "cc/metrics/frame_sequence_tracker.h" #include "cc/metrics/jank_metrics.h" #include "components/viz/common/frame_sinks/begin_frame_args.h" +#include "third_party/abseil-cpp/absl/base/attributes.h" namespace cc { @@ -57,6 +58,20 @@ namespace { +constexpr uint32_t kMaxNoUpdateFrameCount = 100; + +const char* GetJankThreadTypeName(FrameInfo::SmoothEffectDrivingThread type) { + switch (type) { + case FrameInfo::SmoothEffectDrivingThread::kCompositor: + return "Compositor"; + case FrameInfo::SmoothEffectDrivingThread::kMain: + return "Main"; + default: + NOTREACHED(); + return ""; + } +} + // Avoid reporting any throughput metric for sequences that do not have a // sufficient number of frames. constexpr int kMinFramesForThroughputMetric = 100; @@ -86,6 +101,13 @@ 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( @@ -95,6 +117,9 @@ } // namespace +FrameSequenceMetrics::V3::V3() = default; +FrameSequenceMetrics::V3::~V3() = default; + FrameSequenceMetrics::FrameSequenceMetrics(FrameSequenceTrackerType type) : type_(type) { SmoothEffectDrivingThread thread_type = GetEffectiveThread(); @@ -175,10 +200,15 @@ 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; } if (jank_reporter_) @@ -205,10 +235,13 @@ 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; adopt_from->trace_data_.trace_id = nullptr; + adopt_from->trace_data_v3_.trace_id = nullptr; } -void FrameSequenceMetrics::AdvanceTrace(base::TimeTicks timestamp) { +void FrameSequenceMetrics::AdvanceTrace(base::TimeTicks timestamp, + uint64_t sequence_number) { uint32_t expected = 0, dropped = 0; switch (GetEffectiveThread()) { case SmoothEffectDrivingThread::kCompositor: @@ -226,7 +259,8 @@ case SmoothEffectDrivingThread::kUnknown: NOTREACHED(); } - trace_data_.Advance(timestamp, expected, dropped); + trace_data_.Advance(trace_data_.last_timestamp, timestamp, expected, dropped, + sequence_number, "FrameSequenceTracker"); } void FrameSequenceMetrics::ReportMetrics() { @@ -235,6 +269,7 @@ // Terminates |trace_data_| for all types of FrameSequenceTracker. trace_data_.Terminate(); + trace_data_v3_.TerminateV3(v3_); if (type_ == FrameSequenceTrackerType::kCustom) { DCHECK(!custom_reporter_.is_null()); @@ -258,17 +293,23 @@ ShouldReportForInteraction(type(), thread_type, thread_type); if (v3_.frames_expected >= kMinFramesForThroughputMetric) { - const int percent_missing_content = static_cast<int>( - 100 * v3_.frames_missing_content / v3_.frames_expected); - int percent = v3_.frames_expected == 0 - ? 0 - : std::ceil(100. * v3_.frames_dropped / - static_cast<double>(v3_.frames_expected)); + 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); } @@ -276,6 +317,8 @@ 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); } @@ -283,6 +326,8 @@ 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); } @@ -307,9 +352,20 @@ base::LinearHistogram::FactoryGet( GetCheckerboardingV3HistogramName(type_), 1, 100, 101, base::HistogramBase::kUmaTargetedHistogramFlag)); + + const char* jank_thread_name = GetJankThreadTypeName(GetEffectiveThread()); + STATIC_HISTOGRAM_POINTER_GROUP( + GetJankV3HistogramName(type_, jank_thread_name), + static_cast<int>(type_), + static_cast<int>(FrameSequenceTrackerType::kMaxType), Add(percent_jank), + base::LinearHistogram::FactoryGet( + GetJankV3HistogramName(type_, jank_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; } // Report the checkerboarding metrics. @@ -448,49 +504,90 @@ trace_id = nullptr; } -void FrameSequenceMetrics::TraceData::Advance(base::TimeTicks new_timestamp, +void FrameSequenceMetrics::TraceData::TerminateV3(const V3& v3) { + 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(); + TRACE_EVENT_NESTABLE_ASYNC_END1("cc,benchmark", "FrameSequenceTrackerV3", + TRACE_ID_LOCAL(trace_id), "args", + std::move(dict)); + trace_id = nullptr; +} + +void FrameSequenceMetrics::TraceData::Advance(base::TimeTicks start_timestamp, + base::TimeTicks new_timestamp, uint32_t expected, - uint32_t dropped) { + uint32_t dropped, + uint64_t sequence_number, + const char* histogram_name) { 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", + "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), this->last_timestamp); - TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP2( + 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, "expected", expected, "dropped", - dropped); + 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) { - switch (GetEffectiveThread()) { + 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; } - CalculateCheckerboardingV3(frame_info); ++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; } - CalculateCheckerboardingV3(frame_info); ++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: @@ -498,12 +595,20 @@ break; } v3_.last_begin_frame_args = args; + v3_.last_frame = frame_info; } -void FrameSequenceMetrics::CalculateCheckerboardingV3( - const FrameInfo& frame_info) { - switch (frame_info.final_state) { +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; @@ -515,9 +620,105 @@ 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; 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", GetJankThreadTypeName(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
diff --git a/cc/metrics/frame_sequence_metrics.h b/cc/metrics/frame_sequence_metrics.h index 8613d1d7..118b515 100644 --- a/cc/metrics/frame_sequence_metrics.h +++ b/cc/metrics/frame_sequence_metrics.h
@@ -142,7 +142,7 @@ void ReportLeftoverData(); void AdoptTrace(FrameSequenceMetrics* adopt_from); - void AdvanceTrace(base::TimeTicks timestamp); + void AdvanceTrace(base::TimeTicks timestamp, uint64_t sequence_number); void ComputeJank(FrameInfo::SmoothEffectDrivingThread thread_type, uint32_t frame_token, @@ -160,34 +160,61 @@ base::TimeDelta frame_interval); private: - void CalculateCheckerboardingV3(const FrameInfo& frame_info); + // FrameInfo is a merger of two threads' frame production. We should only look + // at the `final_state`, `last_presented_termination_time` and + // `termination_time` for the GetEffectiveThread. + void CalculateCheckerboardingAndJankV3( + const viz::BeginFrameArgs& args, + const FrameInfo& frame_info, + FrameInfo::FrameFinalState final_state, + base::TimeTicks last_presented_termination_time, + base::TimeTicks termination_time); + void IncrementJankIdleTimeV3(base::TimeTicks last_presented_termination_time, + base::TimeTicks termination_time); + void TraceJankV3(uint64_t sequence_number, + base::TimeTicks last_termination_time, + base::TimeTicks termination_time); const FrameSequenceTrackerType type_; + // Track state for measuring the various Graphics.Smoothness V3 metrics. + struct V3 { + V3(); + ~V3(); + uint32_t frames_expected = 0; + uint32_t frames_dropped = 0; + uint32_t frames_missing_content = 0; + uint32_t no_update_count = 0; + uint32_t jank_count = 0; + viz::BeginFrameArgs last_begin_frame_args; + FrameInfo last_frame; + FrameInfo last_presented_frame; + base::TimeDelta last_frame_delta; + base::TimeDelta no_update_duration; + } v3_; + // Tracks some data to generate useful trace events. struct TraceData { explicit TraceData(FrameSequenceMetrics* metrics); ~TraceData(); raw_ptr<FrameSequenceMetrics> metrics; + uint64_t last_presented_sequence_number = 0u; base::TimeTicks last_timestamp = base::TimeTicks::Now(); int frame_count = 0; bool enabled = false; raw_ptr<void> trace_id = nullptr; - void Advance(base::TimeTicks new_timestamp, + void Advance(base::TimeTicks start_timestamp, + base::TimeTicks new_timestamp, uint32_t expected, - uint32_t dropped); + uint32_t dropped, + uint64_t sequence_number, + const char* histogram_name); void Terminate(); + void TerminateV3(const V3& v3); } trace_data_{this}; - // Track state for measuring the PercentDroppedFrames v3 metrics. - struct { - uint32_t frames_expected = 0; - uint32_t frames_dropped = 0; - uint32_t frames_missing_content = 0; - viz::BeginFrameArgs last_begin_frame_args; - FrameInfo last_presented_frame; - } v3_; + TraceData trace_data_v3_{this}; ThroughputData impl_throughput_; ThroughputData main_throughput_;
diff --git a/cc/metrics/frame_sequence_tracker.cc b/cc/metrics/frame_sequence_tracker.cc index 648d1d9d..d1ff6ce 100644 --- a/cc/metrics/frame_sequence_tracker.cc +++ b/cc/metrics/frame_sequence_tracker.cc
@@ -447,7 +447,7 @@ << TRACKER_DCHECK_MSG; ++impl_throughput().frames_produced; if (metrics()->GetEffectiveThread() == ThreadType::kCompositor) { - metrics()->AdvanceTrace(feedback.timestamp); + metrics()->AdvanceTrace(feedback.timestamp, frame_token); } metrics()->ComputeJank(FrameInfo::SmoothEffectDrivingThread::kCompositor, @@ -468,7 +468,7 @@ << TRACKER_DCHECK_MSG; ++main_throughput().frames_produced; if (metrics()->GetEffectiveThread() == ThreadType::kMain) { - metrics()->AdvanceTrace(feedback.timestamp); + metrics()->AdvanceTrace(feedback.timestamp, frame_token); } metrics()->ComputeJank(FrameInfo::SmoothEffectDrivingThread::kMain,
diff --git a/tools/metrics/histograms/metadata/compositing/histograms.xml b/tools/metrics/histograms/metadata/compositing/histograms.xml index 941704a3..e20dd9b 100644 --- a/tools/metrics/histograms/metadata/compositing/histograms.xml +++ b/tools/metrics/histograms/metadata/compositing/histograms.xml
@@ -1229,6 +1229,55 @@ </token> </histogram> +<histogram name="Graphics.Smoothness.Jank3.AllSequences" units="%" + expires_after="2024-06-05"> + <owner>jonross@chromium.org</owner> + <owner>mjzhang@chromium.org</owner> + <owner>graphics-dev@chromium.org</owner> + <summary> + Tracks the percent of janks for a sequence by aggregating all its animations + and interactions, where a sequence is a series of frames produced which + contain at least one of the animations/interactions. Such animations and + interactions encompass: Compositor-thread animations; Main-thread + animations; JS animations; scrolling; and pinch-gestures. + + Jank is measured by tracking the number of abrupt increases in frame + presentation interval, divided by the total number of frames expected to be + produced and displayed. The lower this number is, the less the smoothness + varies over time. + + Note that this metric is reported only when there are sufficient number of + frames (>= 100). If there are sequences with fewer frames, then these are + aggregated until there are enough frames to produce the metric. + </summary> +</histogram> + +<histogram name="Graphics.Smoothness.Jank3.{Thread}{Sequence}" units="%" + expires_after="2024-06-05"> + <owner>jonross@chromium.org</owner> + <owner>mjzhang@chromium.org</owner> + <owner>graphics-dev@chromium.org</owner> + <summary> + Tracks the percent of janks for a sequence by aggregating all {Sequence}, + where a sequence is a series of frames produced which contain at least one + of the aforementioned effects. + + Jank is measured by tracking the number of abrupt increases in frame + presentation interval, divided by the total number of frames expected to be + produced and displayed. The lower this number is, the less the smoothness + varies over time. + + Note that this metric is reported only when there are sufficient number of + frames (>= 100). If there are sequences with fewer frames, then these are + aggregated until there are enough frames to produce the metric. + </summary> + <token key="Thread"> + <variant name="CompositorThread."/> + <variant name="MainThread."/> + </token> + <token key="Sequence" variants="SmoothnessSequence"/> +</histogram> + <histogram name="Graphics.Smoothness.MaxPercentDroppedFrames_1sWindow" units="%" expires_after="2023-11-12"> <owner>jonross@chromium.org</owner>
diff --git a/tools/perf/benchmarks/rendering.py b/tools/perf/benchmarks/rendering.py index 2e0b4ff..782f3db 100644 --- a/tools/perf/benchmarks/rendering.py +++ b/tools/perf/benchmarks/rendering.py
@@ -29,6 +29,9 @@ 'Graphics.Smoothness.Jank.AllAnimations', 'Graphics.Smoothness.Jank.AllInteractions', 'Graphics.Smoothness.Jank.AllSequences', + 'Graphics.Smoothness.Jank3.AllAnimations', + 'Graphics.Smoothness.Jank3.AllInteractions', + 'Graphics.Smoothness.Jank3.AllSequences', 'Graphics.Smoothness.PercentDroppedFrames3.AllAnimations', 'Graphics.Smoothness.PercentDroppedFrames3.AllInteractions', 'Graphics.Smoothness.PercentDroppedFrames3.AllSequences',
diff --git a/tools/perf/contrib/system_health_scroll_jank/system_health_scroll_jank.py b/tools/perf/contrib/system_health_scroll_jank/system_health_scroll_jank.py index c14719d..fe809dc 100644 --- a/tools/perf/contrib/system_health_scroll_jank/system_health_scroll_jank.py +++ b/tools/perf/contrib/system_health_scroll_jank/system_health_scroll_jank.py
@@ -26,6 +26,8 @@ 'Graphics.Smoothness.Checkerboarding3.WheelScroll', 'Graphics.Smoothness.Jank.Compositor.TouchScroll', 'Graphics.Smoothness.Jank.Main.TouchScroll', + 'Graphics.Smoothness.Jank3.Compositor.TouchScroll', + 'Graphics.Smoothness.Jank3.Main.TouchScroll', 'Graphics.Smoothness.PercentDroppedFrames3.AllAnimations', 'Graphics.Smoothness.PercentDroppedFrames3.AllInteractions', 'Graphics.Smoothness.PercentDroppedFrames3.AllSequences',