// Copyright 2019 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_tracker.h" #include #include #include #include #include "base/bind.h" #include "base/containers/contains.h" #include "base/containers/cxx20_erase.h" #include "base/logging.h" #include "base/metrics/histogram.h" #include "base/metrics/histogram_macros.h" #include "base/trace_event/trace_event.h" #include "base/trace_event/traced_value.h" #include "components/viz/common/frame_sinks/begin_frame_args.h" #include "components/viz/common/quads/compositor_frame_metadata.h" #include "ui/gfx/presentation_feedback.h" // This macro is used with DCHECK to provide addition debug info. #if DCHECK_IS_ON() #define TRACKER_TRACE_STREAM frame_sequence_trace_ #define TRACKER_DCHECK_MSG \ " in " << GetFrameSequenceTrackerTypeName(this->type()) \ << " tracker: " << frame_sequence_trace_.str() << " (" \ << frame_sequence_trace_.str().size() << ")"; #else #define TRACKER_TRACE_STREAM EAT_STREAM_PARAMETERS #define TRACKER_DCHECK_MSG "" #endif namespace cc { using ThreadType = FrameSequenceMetrics::ThreadType; // In the |TRACKER_TRACE_STREAM|, we mod the numbers such as frame sequence // number, or frame token, such that the debug string is not too long. constexpr int kDebugStrMod = 1000; const char* FrameSequenceTracker::GetFrameSequenceTrackerTypeName( FrameSequenceTrackerType type) { switch (type) { case FrameSequenceTrackerType::kCompositorAnimation: return "CompositorAnimation"; case FrameSequenceTrackerType::kMainThreadAnimation: return "MainThreadAnimation"; case FrameSequenceTrackerType::kPinchZoom: return "PinchZoom"; case FrameSequenceTrackerType::kRAF: return "RAF"; case FrameSequenceTrackerType::kTouchScroll: return "TouchScroll"; case FrameSequenceTrackerType::kVideo: return "Video"; case FrameSequenceTrackerType::kWheelScroll: return "WheelScroll"; case FrameSequenceTrackerType::kScrollbarScroll: return "ScrollbarScroll"; case FrameSequenceTrackerType::kCustom: return "Custom"; case FrameSequenceTrackerType::kCanvasAnimation: return "CanvasAnimation"; case FrameSequenceTrackerType::kJSAnimation: return "JSAnimation"; case FrameSequenceTrackerType::kMaxType: return ""; } } FrameSequenceTracker::FrameSequenceTracker( FrameSequenceTrackerType type, ThroughputUkmReporter* throughput_ukm_reporter) : custom_sequence_id_(-1), metrics_( std::make_unique(type, throughput_ukm_reporter)) { DCHECK_LT(type, FrameSequenceTrackerType::kMaxType); DCHECK(type != FrameSequenceTrackerType::kCustom); // TODO(crbug.com/1158439): remove the trace event once the validation is // completed. TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1( "cc,benchmark", "TrackerValidation", TRACE_ID_LOCAL(this), base::TimeTicks::Now(), "name", GetFrameSequenceTrackerTypeName(type)); } FrameSequenceTracker::FrameSequenceTracker( int custom_sequence_id, FrameSequenceMetrics::CustomReporter custom_reporter) : custom_sequence_id_(custom_sequence_id), metrics_(std::make_unique( FrameSequenceTrackerType::kCustom, /*ukm_reporter=*/nullptr)) { DCHECK_GT(custom_sequence_id_, 0); metrics_->SetCustomReporter(std::move(custom_reporter)); } FrameSequenceTracker::~FrameSequenceTracker() { TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0( "cc,benchmark", "TrackerValidation", TRACE_ID_LOCAL(this), base::TimeTicks::Now()); CleanUp(); } void FrameSequenceTracker::ScheduleTerminate() { // If the last frame has ended and there is no frame awaiting presentation, // then it is ready to terminate. if (!is_inside_frame_ && last_submitted_frame_ == 0) termination_status_ = TerminationStatus::kReadyForTermination; else termination_status_ = TerminationStatus::kScheduledForTermination; } void FrameSequenceTracker::ReportBeginImplFrame( const viz::BeginFrameArgs& args) { if (termination_status_ != TerminationStatus::kActive) return; if (ShouldIgnoreBeginFrameSource(args.frame_id.source_id)) return; TRACKER_TRACE_STREAM << "b(" << args.frame_id.sequence_number % kDebugStrMod << ")"; DCHECK(!is_inside_frame_) << TRACKER_DCHECK_MSG; is_inside_frame_ = true; #if DCHECK_IS_ON() if (args.type == viz::BeginFrameArgs::NORMAL) impl_frames_.insert(args.frame_id); #endif DCHECK_EQ(last_started_impl_sequence_, 0u) << TRACKER_DCHECK_MSG; last_started_impl_sequence_ = args.frame_id.sequence_number; if (reset_all_state_) { begin_impl_frame_data_ = {}; begin_main_frame_data_ = {}; reset_all_state_ = false; } DCHECK(!frame_had_no_compositor_damage_) << TRACKER_DCHECK_MSG; DCHECK(!compositor_frame_submitted_) << TRACKER_DCHECK_MSG; UpdateTrackedFrameData(&begin_impl_frame_data_, args.frame_id.source_id, args.frame_id.sequence_number, args.frames_throttled_since_last); impl_throughput().frames_expected += begin_impl_frame_data_.previous_sequence_delta; #if DCHECK_IS_ON() ++impl_throughput().frames_received; #endif if (first_frame_timestamp_.is_null()) first_frame_timestamp_ = args.frame_time; } void FrameSequenceTracker::ReportBeginMainFrame( const viz::BeginFrameArgs& args) { if (termination_status_ != TerminationStatus::kActive) return; if (ShouldIgnoreBeginFrameSource(args.frame_id.source_id)) return; TRACKER_TRACE_STREAM << "B(" << begin_main_frame_data_.previous_sequence % kDebugStrMod << "," << args.frame_id.sequence_number % kDebugStrMod << ")"; if (first_received_main_sequence_ && first_received_main_sequence_ > args.frame_id.sequence_number) { return; } if (!first_received_main_sequence_ && ShouldIgnoreSequence(args.frame_id.sequence_number)) { return; } #if DCHECK_IS_ON() if (args.type == viz::BeginFrameArgs::NORMAL) { DCHECK(impl_frames_.contains(args.frame_id)) << TRACKER_DCHECK_MSG; } #endif DCHECK_EQ(awaiting_main_response_sequence_, 0u) << TRACKER_DCHECK_MSG; last_processed_main_sequence_latency_ = 0; awaiting_main_response_sequence_ = args.frame_id.sequence_number; UpdateTrackedFrameData(&begin_main_frame_data_, args.frame_id.source_id, args.frame_id.sequence_number, args.frames_throttled_since_last); if (!first_received_main_sequence_ || first_received_main_sequence_ <= last_no_main_damage_sequence_) { first_received_main_sequence_ = args.frame_id.sequence_number; } main_throughput().frames_expected += begin_main_frame_data_.previous_sequence_delta; previous_begin_main_sequence_ = current_begin_main_sequence_; current_begin_main_sequence_ = args.frame_id.sequence_number; } void FrameSequenceTracker::ReportMainFrameProcessed( const viz::BeginFrameArgs& args) { if (termination_status_ != TerminationStatus::kActive) return; if (ShouldIgnoreBeginFrameSource(args.frame_id.source_id)) return; TRACKER_TRACE_STREAM << "E(" << args.frame_id.sequence_number % kDebugStrMod << ")"; const bool previous_main_frame_submitted_or_no_damage = previous_begin_main_sequence_ != 0 && (last_submitted_main_sequence_ == previous_begin_main_sequence_ || last_no_main_damage_sequence_ == previous_begin_main_sequence_); if (last_processed_main_sequence_ != 0 && !had_impl_frame_submitted_between_commits_ && !previous_main_frame_submitted_or_no_damage) { DCHECK_GE(main_throughput().frames_expected, begin_main_frame_data_.previous_sequence_delta) << TRACKER_DCHECK_MSG; main_throughput().frames_expected -= begin_main_frame_data_.previous_sequence_delta; last_no_main_damage_sequence_ = previous_begin_main_sequence_; } had_impl_frame_submitted_between_commits_ = false; if (first_received_main_sequence_ && args.frame_id.sequence_number >= first_received_main_sequence_) { if (awaiting_main_response_sequence_) { DCHECK_EQ(awaiting_main_response_sequence_, args.frame_id.sequence_number) << TRACKER_DCHECK_MSG; } DCHECK_EQ(last_processed_main_sequence_latency_, 0u) << TRACKER_DCHECK_MSG; last_processed_main_sequence_ = args.frame_id.sequence_number; last_processed_main_sequence_latency_ = std::max(last_started_impl_sequence_, last_processed_impl_sequence_) - args.frame_id.sequence_number; awaiting_main_response_sequence_ = 0; } } void FrameSequenceTracker::ReportSubmitFrame( uint32_t frame_token, bool has_missing_content, const viz::BeginFrameAck& ack, const viz::BeginFrameArgs& origin_args) { DCHECK_NE(termination_status_, TerminationStatus::kReadyForTermination); // TODO(crbug.com/1072482): find a proper way to terminate a tracker. // Right now, we define a magical number |frames_to_terminate_tracker| = 3, // which means that if this frame_token is more than 3 frames compared with // the last submitted frame, then we assume that the last submitted frame is // not going to be presented, and thus terminate this tracker. const uint32_t frames_to_terminate_tracker = 3; if (termination_status_ == TerminationStatus::kScheduledForTermination && last_submitted_frame_ != 0 && viz::FrameTokenGT(frame_token, last_submitted_frame_ + frames_to_terminate_tracker)) { termination_status_ = TerminationStatus::kReadyForTermination; return; } if (ShouldIgnoreBeginFrameSource(ack.frame_id.source_id) || ShouldIgnoreSequence(ack.frame_id.sequence_number)) { ignored_frame_tokens_.insert(frame_token); return; } #if DCHECK_IS_ON() DCHECK(is_inside_frame_) << TRACKER_DCHECK_MSG; DCHECK_LT(impl_throughput().frames_processed, impl_throughput().frames_received) << TRACKER_DCHECK_MSG; ++impl_throughput().frames_processed; #endif last_processed_impl_sequence_ = ack.frame_id.sequence_number; if (first_submitted_frame_ == 0) first_submitted_frame_ = frame_token; last_submitted_frame_ = frame_token; compositor_frame_submitted_ = true; TRACKER_TRACE_STREAM << "s(" << frame_token % kDebugStrMod << ")"; had_impl_frame_submitted_between_commits_ = true; metrics()->NotifySubmitForJankReporter( FrameSequenceMetrics::ThreadType::kCompositor, frame_token, ack.frame_id.sequence_number); const bool main_changes_after_sequence_started = first_received_main_sequence_ && origin_args.frame_id.sequence_number >= first_received_main_sequence_; const bool main_changes_include_new_changes = last_submitted_main_sequence_ == 0 || origin_args.frame_id.sequence_number > last_submitted_main_sequence_; const bool main_change_had_no_damage = last_no_main_damage_sequence_ != 0 && origin_args.frame_id.sequence_number == last_no_main_damage_sequence_; const bool origin_args_is_valid = origin_args.frame_id.sequence_number <= begin_main_frame_data_.previous_sequence; if (!ShouldIgnoreBeginFrameSource(origin_args.frame_id.source_id) && origin_args_is_valid) { if (main_changes_after_sequence_started && main_changes_include_new_changes && !main_change_had_no_damage) { submitted_frame_had_new_main_content_ = true; TRACKER_TRACE_STREAM << "S(" << origin_args.frame_id.sequence_number % kDebugStrMod << ")"; metrics()->NotifySubmitForJankReporter( FrameSequenceMetrics::ThreadType::kMain, frame_token, origin_args.frame_id.sequence_number); last_submitted_main_sequence_ = origin_args.frame_id.sequence_number; main_frames_.push_back(frame_token); DCHECK_GE(main_throughput().frames_expected, main_frames_.size()) << TRACKER_DCHECK_MSG; } } if (has_missing_content) { checkerboarding_.frames.push_back(frame_token); } } void FrameSequenceTracker::ReportFrameEnd( const viz::BeginFrameArgs& args, const viz::BeginFrameArgs& main_args) { DCHECK_NE(termination_status_, TerminationStatus::kReadyForTermination); if (ShouldIgnoreBeginFrameSource(args.frame_id.source_id)) return; TRACKER_TRACE_STREAM << "e(" << args.frame_id.sequence_number % kDebugStrMod << "," << main_args.frame_id.sequence_number % kDebugStrMod << ")"; bool should_ignore_sequence = ShouldIgnoreSequence(args.frame_id.sequence_number); if (reset_all_state_) { begin_impl_frame_data_ = {}; begin_main_frame_data_ = {}; reset_all_state_ = false; } if (should_ignore_sequence) { is_inside_frame_ = false; return; } if (compositor_frame_submitted_ && submitted_frame_had_new_main_content_ && last_processed_main_sequence_latency_) { // If a compositor frame was submitted with new content from the // main-thread, then make sure the latency gets accounted for. main_throughput().frames_expected += last_processed_main_sequence_latency_; } // It is possible that the compositor claims there was no damage from the // compositor, but before the frame ends, it submits a compositor frame (e.g. // with some damage from main). In such cases, the compositor is still // responsible for processing the update, and therefore the 'no damage' claim // is ignored. if (frame_had_no_compositor_damage_ && !compositor_frame_submitted_) { DCHECK_GT(impl_throughput().frames_expected, 0u) << TRACKER_DCHECK_MSG; DCHECK_GT(impl_throughput().frames_expected, impl_throughput().frames_produced) << TRACKER_DCHECK_MSG; DCHECK_GE(impl_throughput().frames_produced, impl_throughput().frames_ontime) << TRACKER_DCHECK_MSG; --impl_throughput().frames_expected; metrics()->NotifyNoUpdateForJankReporter( FrameSequenceMetrics::ThreadType::kCompositor, args.frame_id.sequence_number, args.interval); #if DCHECK_IS_ON() ++impl_throughput().frames_processed; // If these two are the same, it means that each impl frame is either // no-damage or submitted. That's expected, so we don't need those in the // output of DCHECK. if (impl_throughput().frames_processed == impl_throughput().frames_received) ignored_trace_char_count_ = frame_sequence_trace_.str().size(); else NOTREACHED() << TRACKER_DCHECK_MSG; #endif begin_impl_frame_data_.previous_sequence = 0; } // last_submitted_frame_ == 0 means the last impl frame has been presented. if (termination_status_ == TerminationStatus::kScheduledForTermination && last_submitted_frame_ == 0) termination_status_ = TerminationStatus::kReadyForTermination; frame_had_no_compositor_damage_ = false; compositor_frame_submitted_ = false; submitted_frame_had_new_main_content_ = false; last_processed_main_sequence_latency_ = 0; DCHECK(is_inside_frame_) << TRACKER_DCHECK_MSG; is_inside_frame_ = false; DCHECK_EQ(last_started_impl_sequence_, last_processed_impl_sequence_) << TRACKER_DCHECK_MSG; last_started_impl_sequence_ = 0; } void FrameSequenceTracker::ReportFramePresented( uint32_t frame_token, const gfx::PresentationFeedback& feedback) { // TODO(xidachen): We should early exit if |last_submitted_frame_| = 0, as it // means that we are presenting the same frame_token again. const bool submitted_frame_since_last_presentation = !!last_submitted_frame_; // !viz::FrameTokenGT(a, b) is equivalent to b >= a. const bool frame_token_acks_last_frame = !viz::FrameTokenGT(last_submitted_frame_, frame_token); // Even if the presentation timestamp is null, we set last_submitted_frame_ to // 0 such that the tracker can be terminated. if (last_submitted_frame_ && frame_token_acks_last_frame) last_submitted_frame_ = 0; // Update termination status if this is scheduled for termination, and it is // not waiting for any frames, or it has received the presentation-feedback // for the latest frame it is tracking. // // We should always wait for an impl frame to end, that is, ReportFrameEnd. if (termination_status_ == TerminationStatus::kScheduledForTermination && last_submitted_frame_ == 0 && !is_inside_frame_) { termination_status_ = TerminationStatus::kReadyForTermination; } if (first_submitted_frame_ == 0 || viz::FrameTokenGT(first_submitted_frame_, frame_token)) { // We are getting presentation feedback for frames that were submitted // before this sequence started. So ignore these. return; } TRACKER_TRACE_STREAM << "P(" << frame_token % kDebugStrMod << ")"; base::EraseIf(ignored_frame_tokens_, [frame_token](const uint32_t& token) { return viz::FrameTokenGT(frame_token, token); }); if (ignored_frame_tokens_.contains(frame_token)) return; uint32_t impl_frames_produced = 0; uint32_t main_frames_produced = 0; uint32_t impl_frames_ontime = 0; uint32_t main_frames_ontime = 0; const auto vsync_interval = (feedback.interval.is_zero() ? viz::BeginFrameArgs::DefaultInterval() : feedback.interval); DCHECK(!vsync_interval.is_zero()) << TRACKER_DCHECK_MSG; base::TimeTicks safe_deadline_for_frame = last_frame_presentation_timestamp_ + vsync_interval * 1.5; const bool was_presented = !feedback.failed(); if (was_presented && submitted_frame_since_last_presentation) { if (!last_frame_presentation_timestamp_.is_null() && (safe_deadline_for_frame < feedback.timestamp)) { DCHECK_LE(impl_throughput().frames_ontime, impl_throughput().frames_produced) << TRACKER_DCHECK_MSG; ++impl_throughput().frames_ontime; ++impl_frames_ontime; } DCHECK_LT(impl_throughput().frames_produced, impl_throughput().frames_expected) << TRACKER_DCHECK_MSG; ++impl_throughput().frames_produced; ++impl_frames_produced; if (metrics()->GetEffectiveThread() == ThreadType::kCompositor) { metrics()->AdvanceTrace(feedback.timestamp); } metrics()->ComputeJank(FrameSequenceMetrics::ThreadType::kCompositor, frame_token, feedback.timestamp, vsync_interval); } if (was_presented) { // This presentation includes the visual update from all main frame tokens // <= |frame_token|. const unsigned size_before_erase = main_frames_.size(); while (!main_frames_.empty() && !viz::FrameTokenGT(main_frames_.front(), frame_token)) { main_frames_.pop_front(); } if (main_frames_.size() < size_before_erase) { DCHECK_LT(main_throughput().frames_produced, main_throughput().frames_expected) << TRACKER_DCHECK_MSG; ++main_throughput().frames_produced; ++main_frames_produced; if (metrics()->GetEffectiveThread() == ThreadType::kMain) { metrics()->AdvanceTrace(feedback.timestamp); } metrics()->ComputeJank(FrameSequenceMetrics::ThreadType::kMain, frame_token, feedback.timestamp, vsync_interval); } if (main_frames_.size() < size_before_erase) { if (!last_frame_presentation_timestamp_.is_null() && (safe_deadline_for_frame < feedback.timestamp)) { DCHECK_LE(main_throughput().frames_ontime, main_throughput().frames_produced) << TRACKER_DCHECK_MSG; ++main_throughput().frames_ontime; ++main_frames_ontime; } } last_frame_presentation_timestamp_ = feedback.timestamp; if (checkerboarding_.last_frame_had_checkerboarding) { DCHECK(!checkerboarding_.last_frame_timestamp.is_null()) << TRACKER_DCHECK_MSG; DCHECK(!feedback.timestamp.is_null()) << TRACKER_DCHECK_MSG; // |feedback.timestamp| is the timestamp when the latest frame was // presented. |checkerboarding_.last_frame_timestamp| is the timestamp // when the previous frame (which had checkerboarding) was presented. Use // |feedback.interval| to compute the number of vsyncs that have passed // between the two frames (since that is how many times the user saw that // checkerboarded frame). base::TimeDelta difference = feedback.timestamp - checkerboarding_.last_frame_timestamp; const auto& interval = feedback.interval.is_zero() ? viz::BeginFrameArgs::DefaultInterval() : feedback.interval; DCHECK(!interval.is_zero()) << TRACKER_DCHECK_MSG; constexpr base::TimeDelta kEpsilon = base::TimeDelta::FromMilliseconds(1); int64_t frames = (difference + kEpsilon).IntDiv(interval); metrics_->add_checkerboarded_frames(frames); } const bool frame_had_checkerboarding = base::Contains(checkerboarding_.frames, frame_token); checkerboarding_.last_frame_had_checkerboarding = frame_had_checkerboarding; checkerboarding_.last_frame_timestamp = feedback.timestamp; } while (!checkerboarding_.frames.empty() && !viz::FrameTokenGT(checkerboarding_.frames.front(), frame_token)) { checkerboarding_.frames.pop_front(); } } void FrameSequenceTracker::ReportImplFrameCausedNoDamage( const viz::BeginFrameAck& ack) { DCHECK_NE(termination_status_, TerminationStatus::kReadyForTermination); if (ShouldIgnoreBeginFrameSource(ack.frame_id.source_id)) return; TRACKER_TRACE_STREAM << "n(" << ack.frame_id.sequence_number % kDebugStrMod << ")"; // This tracker would be scheduled to terminate, and this frame doesn't belong // to that tracker. if (ShouldIgnoreSequence(ack.frame_id.sequence_number)) return; last_processed_impl_sequence_ = ack.frame_id.sequence_number; // If there is no damage for this frame (and no frame is submitted), then the // impl-sequence needs to be reset. However, this should be done after the // processing the frame is complete (i.e. in ReportFrameEnd()), so that other // notifications (e.g. 'no main damage' etc.) can be handled correctly. DCHECK_EQ(begin_impl_frame_data_.previous_sequence, ack.frame_id.sequence_number); frame_had_no_compositor_damage_ = true; } void FrameSequenceTracker::ReportMainFrameCausedNoDamage( const viz::BeginFrameArgs& args) { if (termination_status_ != TerminationStatus::kActive) return; if (ShouldIgnoreBeginFrameSource(args.frame_id.source_id)) return; TRACKER_TRACE_STREAM << "N(" << begin_main_frame_data_.previous_sequence % kDebugStrMod << "," << args.frame_id.sequence_number % kDebugStrMod << ")"; if (!first_received_main_sequence_ || first_received_main_sequence_ > args.frame_id.sequence_number) { return; } if (last_no_main_damage_sequence_ == args.frame_id.sequence_number) return; // It is possible for |awaiting_main_response_sequence_| to be zero here if a // commit had already happened before (e.g. B(x)E(x)N(x)). So check that case // here. if (awaiting_main_response_sequence_) { DCHECK_EQ(awaiting_main_response_sequence_, args.frame_id.sequence_number) << TRACKER_DCHECK_MSG; } else { DCHECK_EQ(last_processed_main_sequence_, args.frame_id.sequence_number) << TRACKER_DCHECK_MSG; } awaiting_main_response_sequence_ = 0; DCHECK_GT(main_throughput().frames_expected, 0u) << TRACKER_DCHECK_MSG; DCHECK_GT(main_throughput().frames_expected, main_throughput().frames_produced) << TRACKER_DCHECK_MSG; DCHECK_GE(main_throughput().frames_produced, main_throughput().frames_ontime) << TRACKER_DCHECK_MSG; last_no_main_damage_sequence_ = args.frame_id.sequence_number; --main_throughput().frames_expected; metrics()->NotifyNoUpdateForJankReporter( FrameSequenceMetrics::ThreadType::kMain, args.frame_id.sequence_number, args.interval); DCHECK_GE(main_throughput().frames_expected, main_frames_.size()) << TRACKER_DCHECK_MSG; // Could be 0 if there were a pause frame production. if (begin_main_frame_data_.previous_sequence != 0) { DCHECK_EQ(begin_main_frame_data_.previous_sequence, args.frame_id.sequence_number) << TRACKER_DCHECK_MSG; } begin_main_frame_data_.previous_sequence = 0; } void FrameSequenceTracker::PauseFrameProduction() { // The states need to be reset, so that the tracker ignores the vsyncs until // the next received begin-frame. However, defer doing that until the frame // ends (or a new frame starts), so that in case a frame is in-progress, // subsequent notifications for that frame can be handled correctly. TRACKER_TRACE_STREAM << 'R'; reset_all_state_ = true; } void FrameSequenceTracker::UpdateTrackedFrameData( TrackedFrameData* frame_data, uint64_t source_id, uint64_t sequence_number, uint64_t throttled_frame_count) { if (frame_data->previous_sequence && frame_data->previous_source == source_id) { uint32_t current_latency = sequence_number - frame_data->previous_sequence - throttled_frame_count; DCHECK_GT(current_latency, 0u) << TRACKER_DCHECK_MSG; frame_data->previous_sequence_delta = current_latency; } else { frame_data->previous_sequence_delta = 1; } frame_data->previous_source = source_id; frame_data->previous_sequence = sequence_number; } bool FrameSequenceTracker::ShouldIgnoreBeginFrameSource( uint64_t source_id) const { if (begin_impl_frame_data_.previous_source == 0) return source_id == viz::BeginFrameArgs::kManualSourceId; return source_id != begin_impl_frame_data_.previous_source; } // This check handles two cases: // 1. When there is a call to ReportBeginMainFrame, or ReportSubmitFrame, or // ReportFramePresented, there must be a ReportBeginImplFrame for that sequence. // Otherwise, the begin_impl_frame_data_.previous_sequence would be 0. // 2. A tracker is scheduled to terminate, then any new request to handle a new // impl frame whose sequence_number > begin_impl_frame_data_.previous_sequence // should be ignored. // Note that sequence_number < begin_impl_frame_data_.previous_sequence cannot // happen. bool FrameSequenceTracker::ShouldIgnoreSequence( uint64_t sequence_number) const { return sequence_number != begin_impl_frame_data_.previous_sequence; } bool FrameSequenceTracker::ShouldReportMetricsNow( const viz::BeginFrameArgs& args) const { return metrics_->HasEnoughDataForReporting() && !first_frame_timestamp_.is_null() && args.frame_time - first_frame_timestamp_ >= time_delta_to_report_; } std::unique_ptr FrameSequenceTracker::TakeMetrics() { #if DCHECK_IS_ON() DCHECK_EQ(impl_throughput().frames_received, impl_throughput().frames_processed) << frame_sequence_trace_.str().substr(ignored_trace_char_count_); #endif return std::move(metrics_); } void FrameSequenceTracker::CleanUp() { if (metrics_) metrics_->ReportLeftoverData(); } FrameSequenceTracker::CheckerboardingData::CheckerboardingData() = default; FrameSequenceTracker::CheckerboardingData::~CheckerboardingData() = default; } // namespace cc