[go: nahoru, domu]

blob: 96290db836d23b1a3956957225a94dfc75b8b400 [file] [log] [blame]
// 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/compositor_frame_reporter.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include "base/cpu_reduction_experiment.h"
#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/trace_id_helper.h"
#include "base/trace_event/typed_macros.h"
#include "base/tracing/protos/chrome_track_event.pbzero.h"
#include "cc/base/rolling_time_delta_history.h"
#include "cc/metrics/dropped_frame_counter.h"
#include "cc/metrics/event_latency_tracing_recorder.h"
#include "cc/metrics/event_latency_tracker.h"
#include "cc/metrics/frame_sequence_tracker.h"
#include "cc/metrics/latency_ukm_reporter.h"
#include "services/tracing/public/cpp/perfetto/macros.h"
#include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_frame_reporter.pbzero.h"
#include "ui/events/types/event_type.h"
namespace cc {
namespace {
using StageType = CompositorFrameReporter::StageType;
using FrameReportType = CompositorFrameReporter::FrameReportType;
using BlinkBreakdown = CompositorFrameReporter::BlinkBreakdown;
using VizBreakdown = CompositorFrameReporter::VizBreakdown;
using FrameFinalState = FrameInfo::FrameFinalState;
constexpr int kFrameReportTypeCount =
static_cast<int>(FrameReportType::kMaxValue) + 1;
constexpr int kStageTypeCount = static_cast<int>(StageType::kStageTypeCount);
constexpr int kAllBreakdownCount =
static_cast<int>(VizBreakdown::kBreakdownCount) +
static_cast<int>(BlinkBreakdown::kBreakdownCount);
constexpr int kVizBreakdownInitialIndex = kStageTypeCount;
constexpr int kBlinkBreakdownInitialIndex =
kVizBreakdownInitialIndex + static_cast<int>(VizBreakdown::kBreakdownCount);
// For each possible FrameSequenceTrackerType there will be a UMA histogram
// plus one for general case.
constexpr int kFrameSequenceTrackerTypeCount =
static_cast<int>(FrameSequenceTrackerType::kMaxType) + 1;
// Maximum number of partial update dependents a reporter can own. When a
// reporter with too many dependents is terminated, it will terminate all its
// dependents which will block the pipeline for a long time. Too many dependents
// also means too much memory usage.
constexpr size_t kMaxOwnedPartialUpdateDependents = 300u;
// Names for CompositorFrameReporter::FrameReportType, which should be
// updated in case of changes to the enum.
constexpr const char* kReportTypeNames[]{
"", "MissedDeadlineFrame.", "DroppedFrame.", "CompositorOnlyFrame."};
static_assert(std::size(kReportTypeNames) == kFrameReportTypeCount,
"Compositor latency report types has changed.");
// This value should be recalculated in case of changes to the number of values
// in CompositorFrameReporter::DroppedFrameReportType or in
// CompositorFrameReporter::StageType
constexpr int kStagesWithBreakdownCount = kStageTypeCount + kAllBreakdownCount;
constexpr int kMaxCompositorLatencyHistogramIndex =
kFrameReportTypeCount *
(kFrameSequenceTrackerTypeCount + kStagesWithBreakdownCount);
constexpr base::TimeDelta kCompositorLatencyHistogramMin =
base::Microseconds(1);
constexpr base::TimeDelta kCompositorLatencyHistogramMax =
base::Milliseconds(350);
constexpr int kCompositorLatencyHistogramBucketCount = 50;
constexpr int kEventLatencyEventTypeCount =
static_cast<int>(EventMetrics::EventType::kMaxValue) + 1;
constexpr int kEventLatencyGestureTypeCount =
std::max(static_cast<int>(ScrollEventMetrics::ScrollType::kMaxValue),
static_cast<int>(PinchEventMetrics::PinchType::kMaxValue)) +
1;
constexpr int kMaxEventLatencyHistogramIndex =
kEventLatencyEventTypeCount * kEventLatencyGestureTypeCount;
constexpr base::TimeDelta kEventLatencyHistogramMin = base::Microseconds(1);
constexpr base::TimeDelta kEventLatencyHistogramMax = base::Seconds(5);
constexpr int kEventLatencyHistogramBucketCount = 100;
std::string GetCompositorLatencyHistogramName(
FrameReportType report_type,
FrameSequenceTrackerType frame_sequence_tracker_type,
StageType stage_type,
absl::optional<VizBreakdown> viz_breakdown,
absl::optional<BlinkBreakdown> blink_breakdown) {
DCHECK_LE(frame_sequence_tracker_type, FrameSequenceTrackerType::kMaxType);
const char* tracker_type_name =
FrameSequenceTracker::GetFrameSequenceTrackerTypeName(
frame_sequence_tracker_type);
DCHECK(tracker_type_name);
bool impl_only_frame = report_type == FrameReportType::kCompositorOnlyFrame;
return base::StrCat(
{"CompositorLatency.", kReportTypeNames[static_cast<int>(report_type)],
tracker_type_name, *tracker_type_name ? "." : "",
CompositorFrameReporter::GetStageName(
stage_type, viz_breakdown, blink_breakdown, impl_only_frame)});
}
std::string GetEventLatencyHistogramBaseName(
const EventMetrics& event_metrics) {
auto* scroll_metrics = event_metrics.AsScroll();
auto* pinch_metrics = event_metrics.AsPinch();
return base::StrCat(
{"EventLatency.", event_metrics.GetTypeName(),
scroll_metrics || pinch_metrics ? "." : "",
scroll_metrics
? scroll_metrics->GetScrollTypeName()
: pinch_metrics ? pinch_metrics->GetPinchTypeName() : ""});
}
constexpr char kTraceCategory[] =
"cc,benchmark," TRACE_DISABLED_BY_DEFAULT("devtools.timeline.frame");
base::TimeTicks ComputeSafeDeadlineForFrame(const viz::BeginFrameArgs& args) {
return args.frame_time + (args.interval * 1.5);
}
} // namespace
// CompositorFrameReporter::ProcessedBlinkBreakdown::Iterator ==================
CompositorFrameReporter::ProcessedBlinkBreakdown::Iterator::Iterator(
const ProcessedBlinkBreakdown* owner)
: owner_(owner) {}
CompositorFrameReporter::ProcessedBlinkBreakdown::Iterator::~Iterator() =
default;
bool CompositorFrameReporter::ProcessedBlinkBreakdown::Iterator::IsValid()
const {
return index_ < std::size(owner_->list_);
}
void CompositorFrameReporter::ProcessedBlinkBreakdown::Iterator::Advance() {
DCHECK(IsValid());
index_++;
}
BlinkBreakdown
CompositorFrameReporter::ProcessedBlinkBreakdown::Iterator::GetBreakdown()
const {
DCHECK(IsValid());
return static_cast<BlinkBreakdown>(index_);
}
base::TimeDelta
CompositorFrameReporter::ProcessedBlinkBreakdown::Iterator::GetLatency() const {
DCHECK(IsValid());
return owner_->list_[index_];
}
// CompositorFrameReporter::ProcessedBlinkBreakdown ============================
CompositorFrameReporter::ProcessedBlinkBreakdown::ProcessedBlinkBreakdown(
base::TimeTicks blink_start_time,
base::TimeTicks begin_main_frame_start,
const BeginMainFrameMetrics& blink_breakdown) {
if (blink_start_time.is_null())
return;
list_[static_cast<int>(BlinkBreakdown::kHandleInputEvents)] =
blink_breakdown.handle_input_events;
list_[static_cast<int>(BlinkBreakdown::kAnimate)] = blink_breakdown.animate;
list_[static_cast<int>(BlinkBreakdown::kStyleUpdate)] =
blink_breakdown.style_update;
list_[static_cast<int>(BlinkBreakdown::kLayoutUpdate)] =
blink_breakdown.layout_update;
list_[static_cast<int>(BlinkBreakdown::kAccessibility)] =
blink_breakdown.accessibility;
list_[static_cast<int>(BlinkBreakdown::kPrepaint)] = blink_breakdown.prepaint;
list_[static_cast<int>(BlinkBreakdown::kCompositingInputs)] =
blink_breakdown.compositing_inputs;
list_[static_cast<int>(BlinkBreakdown::kPaint)] = blink_breakdown.paint;
list_[static_cast<int>(BlinkBreakdown::kCompositeCommit)] =
blink_breakdown.composite_commit;
list_[static_cast<int>(BlinkBreakdown::kUpdateLayers)] =
blink_breakdown.update_layers;
list_[static_cast<int>(BlinkBreakdown::kBeginMainSentToStarted)] =
begin_main_frame_start - blink_start_time;
}
CompositorFrameReporter::ProcessedBlinkBreakdown::~ProcessedBlinkBreakdown() =
default;
CompositorFrameReporter::ProcessedBlinkBreakdown::Iterator
CompositorFrameReporter::ProcessedBlinkBreakdown::CreateIterator() const {
return Iterator(this);
}
// CompositorFrameReporter::ProcessedVizBreakdown::Iterator ====================
CompositorFrameReporter::ProcessedVizBreakdown::Iterator::Iterator(
const ProcessedVizBreakdown* owner,
bool skip_swap_start_to_swap_end)
: owner_(owner), skip_swap_start_to_swap_end_(skip_swap_start_to_swap_end) {
DCHECK(owner_);
}
CompositorFrameReporter::ProcessedVizBreakdown::Iterator::~Iterator() = default;
bool CompositorFrameReporter::ProcessedVizBreakdown::Iterator::IsValid() const {
return index_ < std::size(owner_->list_) && owner_->list_[index_];
}
void CompositorFrameReporter::ProcessedVizBreakdown::Iterator::Advance() {
DCHECK(IsValid());
index_++;
if (static_cast<VizBreakdown>(index_) == VizBreakdown::kSwapStartToSwapEnd &&
skip_swap_start_to_swap_end_) {
index_++;
}
}
VizBreakdown
CompositorFrameReporter::ProcessedVizBreakdown::Iterator::GetBreakdown() const {
DCHECK(IsValid());
return static_cast<VizBreakdown>(index_);
}
base::TimeTicks
CompositorFrameReporter::ProcessedVizBreakdown::Iterator::GetStartTime() const {
DCHECK(IsValid());
return owner_->list_[index_]->first;
}
base::TimeTicks
CompositorFrameReporter::ProcessedVizBreakdown::Iterator::GetEndTime() const {
DCHECK(IsValid());
return owner_->list_[index_]->second;
}
base::TimeDelta
CompositorFrameReporter::ProcessedVizBreakdown::Iterator::GetDuration() const {
DCHECK(IsValid());
return owner_->list_[index_]->second - owner_->list_[index_]->first;
}
// CompositorFrameReporter::ProcessedVizBreakdown ==============================
CompositorFrameReporter::ProcessedVizBreakdown::ProcessedVizBreakdown(
base::TimeTicks viz_start_time,
const viz::FrameTimingDetails& viz_breakdown) {
if (viz_start_time.is_null())
return;
// Check if `viz_breakdown` is set. Testing indicates that sometimes the
// received_compositor_frame_timestamp can be earlier than the given
// `viz_start_time`. Avoid reporting negative times.
if (viz_breakdown.received_compositor_frame_timestamp.is_null() ||
viz_breakdown.received_compositor_frame_timestamp < viz_start_time) {
return;
}
list_[static_cast<int>(VizBreakdown::kSubmitToReceiveCompositorFrame)] =
std::make_pair(viz_start_time,
viz_breakdown.received_compositor_frame_timestamp);
if (viz_breakdown.draw_start_timestamp.is_null())
return;
list_[static_cast<int>(VizBreakdown::kReceivedCompositorFrameToStartDraw)] =
std::make_pair(viz_breakdown.received_compositor_frame_timestamp,
viz_breakdown.draw_start_timestamp);
if (viz_breakdown.swap_timings.is_null())
return;
list_[static_cast<int>(VizBreakdown::kStartDrawToSwapStart)] =
std::make_pair(viz_breakdown.draw_start_timestamp,
viz_breakdown.swap_timings.swap_start);
list_[static_cast<int>(VizBreakdown::kSwapStartToSwapEnd)] =
std::make_pair(viz_breakdown.swap_timings.swap_start,
viz_breakdown.swap_timings.swap_end);
list_[static_cast<int>(VizBreakdown::kSwapEndToPresentationCompositorFrame)] =
std::make_pair(viz_breakdown.swap_timings.swap_end,
viz_breakdown.presentation_feedback.timestamp);
swap_start_ = viz_breakdown.swap_timings.swap_start;
if (viz_breakdown.presentation_feedback.ready_timestamp.is_null())
return;
buffer_ready_available_ = true;
list_[static_cast<int>(VizBreakdown::kSwapStartToBufferAvailable)] =
std::make_pair(viz_breakdown.swap_timings.swap_start,
viz_breakdown.presentation_feedback.available_timestamp);
list_[static_cast<int>(VizBreakdown::kBufferAvailableToBufferReady)] =
std::make_pair(viz_breakdown.presentation_feedback.available_timestamp,
viz_breakdown.presentation_feedback.ready_timestamp);
list_[static_cast<int>(VizBreakdown::kBufferReadyToLatch)] =
std::make_pair(viz_breakdown.presentation_feedback.ready_timestamp,
viz_breakdown.presentation_feedback.latch_timestamp);
list_[static_cast<int>(VizBreakdown::kLatchToSwapEnd)] =
std::make_pair(viz_breakdown.presentation_feedback.latch_timestamp,
viz_breakdown.swap_timings.swap_end);
}
CompositorFrameReporter::ProcessedVizBreakdown::~ProcessedVizBreakdown() =
default;
CompositorFrameReporter::ProcessedVizBreakdown::Iterator
CompositorFrameReporter::ProcessedVizBreakdown::CreateIterator(
bool skip_swap_start_to_swap_end_if_breakdown_available) const {
return Iterator(this, skip_swap_start_to_swap_end_if_breakdown_available &&
buffer_ready_available_);
}
// CompositorFrameReporter =====================================================
CompositorFrameReporter::CompositorFrameReporter(
const ActiveTrackers& active_trackers,
const viz::BeginFrameArgs& args,
bool should_report_histograms,
SmoothThread smooth_thread,
FrameInfo::SmoothEffectDrivingThread scrolling_thread,
int layer_tree_host_id,
const GlobalMetricsTrackers& trackers)
: should_report_histograms_(should_report_histograms),
args_(args),
active_trackers_(active_trackers),
scrolling_thread_(scrolling_thread),
smooth_thread_(smooth_thread),
layer_tree_host_id_(layer_tree_host_id),
global_trackers_(trackers) {
DCHECK(global_trackers_.dropped_frame_counter);
global_trackers_.dropped_frame_counter->OnBeginFrame(
args, IsScrollActive(active_trackers_));
DCHECK(IsScrollActive(active_trackers_) ||
scrolling_thread_ == FrameInfo::SmoothEffectDrivingThread::kUnknown);
if (scrolling_thread_ == FrameInfo::SmoothEffectDrivingThread::kCompositor) {
DCHECK(smooth_thread_ == SmoothThread::kSmoothCompositor ||
smooth_thread_ == SmoothThread::kSmoothBoth);
} else if (scrolling_thread_ == FrameInfo::SmoothEffectDrivingThread::kMain) {
DCHECK(smooth_thread_ == SmoothThread::kSmoothMain ||
smooth_thread_ == SmoothThread::kSmoothBoth);
}
}
// static
const char* CompositorFrameReporter::GetStageName(
StageType stage_type,
absl::optional<VizBreakdown> viz_breakdown,
absl::optional<BlinkBreakdown> blink_breakdown,
bool impl_only) {
DCHECK(!viz_breakdown ||
stage_type ==
StageType::kSubmitCompositorFrameToPresentationCompositorFrame);
DCHECK(!blink_breakdown ||
stage_type == StageType::kSendBeginMainFrameToCommit);
switch (stage_type) {
case StageType::kBeginImplFrameToSendBeginMainFrame:
return impl_only ? "BeginImplFrameToFinishImpl"
: "BeginImplFrameToSendBeginMainFrame";
case StageType::kSendBeginMainFrameToCommit:
if (!blink_breakdown) {
return impl_only ? "SendBeginMainFrameToBeginMainAbort"
: "SendBeginMainFrameToCommit";
}
switch (*blink_breakdown) {
case BlinkBreakdown::kHandleInputEvents:
return "SendBeginMainFrameToCommit.HandleInputEvents";
case BlinkBreakdown::kAnimate:
return "SendBeginMainFrameToCommit.Animate";
case BlinkBreakdown::kStyleUpdate:
return "SendBeginMainFrameToCommit.StyleUpdate";
case BlinkBreakdown::kLayoutUpdate:
return "SendBeginMainFrameToCommit.LayoutUpdate";
case BlinkBreakdown::kAccessibility:
return "SendBeginMainFrameToCommit.AccessibiltyUpdate";
case BlinkBreakdown::kPrepaint:
return "SendBeginMainFrameToCommit.Prepaint";
case BlinkBreakdown::kCompositingInputs:
return "SendBeginMainFrameToCommit.CompositingInputs";
case BlinkBreakdown::kPaint:
return "SendBeginMainFrameToCommit.Paint";
case BlinkBreakdown::kCompositeCommit:
return "SendBeginMainFrameToCommit.CompositeCommit";
case BlinkBreakdown::kUpdateLayers:
return "SendBeginMainFrameToCommit.UpdateLayers";
case BlinkBreakdown::kBeginMainSentToStarted:
return "SendBeginMainFrameToCommit.BeginMainSentToStarted";
case BlinkBreakdown::kBreakdownCount:
NOTREACHED();
return "";
}
case StageType::kCommit:
return "Commit";
case StageType::kEndCommitToActivation:
return "EndCommitToActivation";
case StageType::kActivation:
return "Activation";
case StageType::kEndActivateToSubmitCompositorFrame:
return impl_only ? "ImplFrameDoneToSubmitCompositorFrame"
: "EndActivateToSubmitCompositorFrame";
case StageType::kSubmitCompositorFrameToPresentationCompositorFrame:
if (!viz_breakdown)
return "SubmitCompositorFrameToPresentationCompositorFrame";
switch (*viz_breakdown) {
case VizBreakdown::kSubmitToReceiveCompositorFrame:
return "SubmitCompositorFrameToPresentationCompositorFrame."
"SubmitToReceiveCompositorFrame";
case VizBreakdown::kReceivedCompositorFrameToStartDraw:
return "SubmitCompositorFrameToPresentationCompositorFrame."
"ReceivedCompositorFrameToStartDraw";
case VizBreakdown::kStartDrawToSwapStart:
return "SubmitCompositorFrameToPresentationCompositorFrame."
"StartDrawToSwapStart";
case VizBreakdown::kSwapStartToSwapEnd:
return "SubmitCompositorFrameToPresentationCompositorFrame."
"SwapStartToSwapEnd";
case VizBreakdown::kSwapEndToPresentationCompositorFrame:
return "SubmitCompositorFrameToPresentationCompositorFrame."
"SwapEndToPresentationCompositorFrame";
case VizBreakdown::kSwapStartToBufferAvailable:
return "SubmitCompositorFrameToPresentationCompositorFrame."
"SwapStartToBufferAvailable";
case VizBreakdown::kBufferAvailableToBufferReady:
return "SubmitCompositorFrameToPresentationCompositorFrame."
"BufferAvailableToBufferReady";
case VizBreakdown::kBufferReadyToLatch:
return "SubmitCompositorFrameToPresentationCompositorFrame."
"BufferReadyToLatch";
case VizBreakdown::kLatchToSwapEnd:
return "SubmitCompositorFrameToPresentationCompositorFrame."
"LatchToSwapEnd";
case VizBreakdown::kBreakdownCount:
NOTREACHED();
return "";
}
case StageType::kTotalLatency:
return "TotalLatency";
case StageType::kStageTypeCount:
NOTREACHED();
return "";
}
}
// static
const char* CompositorFrameReporter::GetVizBreakdownName(
VizBreakdown breakdown) {
switch (breakdown) {
case VizBreakdown::kSubmitToReceiveCompositorFrame:
return "SubmitToReceiveCompositorFrame";
case VizBreakdown::kReceivedCompositorFrameToStartDraw:
return "ReceiveCompositorFrameToStartDraw";
case VizBreakdown::kStartDrawToSwapStart:
return "StartDrawToSwapStart";
case VizBreakdown::kSwapStartToSwapEnd:
return "Swap";
case VizBreakdown::kSwapEndToPresentationCompositorFrame:
return "SwapEndToPresentationCompositorFrame";
case VizBreakdown::kSwapStartToBufferAvailable:
return "SwapStartToBufferAvailable";
case VizBreakdown::kBufferAvailableToBufferReady:
return "BufferAvailableToBufferReady";
case VizBreakdown::kBufferReadyToLatch:
return "BufferReadyToLatch";
case VizBreakdown::kLatchToSwapEnd:
return "LatchToSwapEnd";
case VizBreakdown::kBreakdownCount:
NOTREACHED();
return "";
}
}
std::unique_ptr<CompositorFrameReporter>
CompositorFrameReporter::CopyReporterAtBeginImplStage() {
// If |this| reporter is dependent on another reporter to decide about partial
// update, then |this| should not have any such dependents.
DCHECK(!partial_update_decider_);
if (stage_history_.empty() ||
stage_history_.front().stage_type !=
StageType::kBeginImplFrameToSendBeginMainFrame ||
(!did_finish_impl_frame() && !did_not_produce_frame_time_.has_value())) {
return nullptr;
}
auto new_reporter = std::make_unique<CompositorFrameReporter>(
active_trackers_, args_, should_report_histograms_, smooth_thread_,
scrolling_thread_, layer_tree_host_id_, global_trackers_);
new_reporter->did_finish_impl_frame_ = did_finish_impl_frame_;
new_reporter->impl_frame_finish_time_ = impl_frame_finish_time_;
new_reporter->main_frame_abort_time_ = main_frame_abort_time_;
new_reporter->current_stage_.stage_type =
StageType::kBeginImplFrameToSendBeginMainFrame;
new_reporter->current_stage_.start_time = stage_history_.front().start_time;
new_reporter->set_tick_clock(tick_clock_);
// Set up the new reporter so that it depends on |this| for partial update
// information.
new_reporter->SetPartialUpdateDecider(this);
return new_reporter;
}
CompositorFrameReporter::~CompositorFrameReporter() {
TerminateReporter();
}
CompositorFrameReporter::StageData::StageData() = default;
CompositorFrameReporter::StageData::StageData(StageType stage_type,
base::TimeTicks start_time,
base::TimeTicks end_time)
: stage_type(stage_type), start_time(start_time), end_time(end_time) {}
CompositorFrameReporter::StageData::StageData(const StageData&) = default;
CompositorFrameReporter::StageData::~StageData() = default;
void CompositorFrameReporter::StartStage(
CompositorFrameReporter::StageType stage_type,
base::TimeTicks start_time) {
if (frame_termination_status_ != FrameTerminationStatus::kUnknown)
return;
EndCurrentStage(start_time);
current_stage_.stage_type = stage_type;
current_stage_.start_time = start_time;
switch (stage_type) {
case StageType::kSendBeginMainFrameToCommit:
DCHECK(blink_start_time_.is_null());
blink_start_time_ = start_time;
break;
case StageType::kSubmitCompositorFrameToPresentationCompositorFrame:
DCHECK(viz_start_time_.is_null());
viz_start_time_ = start_time;
break;
default:
break;
}
}
void CompositorFrameReporter::TerminateFrame(
FrameTerminationStatus termination_status,
base::TimeTicks termination_time) {
// If the reporter is already terminated, (possibly as a result of no damage)
// then we don't need to do anything here, otherwise the reporter will be
// terminated.
if (frame_termination_status_ != FrameTerminationStatus::kUnknown)
return;
frame_termination_status_ = termination_status;
frame_termination_time_ = termination_time;
EndCurrentStage(frame_termination_time_);
}
void CompositorFrameReporter::OnFinishImplFrame(base::TimeTicks timestamp) {
DCHECK(!did_finish_impl_frame_);
did_finish_impl_frame_ = true;
impl_frame_finish_time_ = timestamp;
}
void CompositorFrameReporter::OnAbortBeginMainFrame(base::TimeTicks timestamp) {
DCHECK(!main_frame_abort_time_.has_value());
main_frame_abort_time_ = timestamp;
impl_frame_finish_time_ = timestamp;
// impl_frame_finish_time_ can be used for the end of BeginMain to Commit
// stage.
}
void CompositorFrameReporter::OnDidNotProduceFrame(
FrameSkippedReason skip_reason) {
did_not_produce_frame_time_ = Now();
frame_skip_reason_ = skip_reason;
}
void CompositorFrameReporter::EnableCompositorOnlyReporting() {
EnableReportType(FrameReportType::kCompositorOnlyFrame);
}
void CompositorFrameReporter::SetBlinkBreakdown(
std::unique_ptr<BeginMainFrameMetrics> blink_breakdown,
base::TimeTicks begin_main_start) {
DCHECK(blink_breakdown_.paint.is_zero());
if (blink_breakdown)
blink_breakdown_ = *blink_breakdown;
else
blink_breakdown_ = BeginMainFrameMetrics();
DCHECK(begin_main_frame_start_.is_null());
begin_main_frame_start_ = begin_main_start;
}
void CompositorFrameReporter::SetVizBreakdown(
const viz::FrameTimingDetails& viz_breakdown) {
DCHECK(viz_breakdown_.received_compositor_frame_timestamp.is_null());
viz_breakdown_ = viz_breakdown;
}
void CompositorFrameReporter::AddEventsMetrics(
EventMetrics::List events_metrics) {
events_metrics_.insert(events_metrics_.end(),
std::make_move_iterator(events_metrics.begin()),
std::make_move_iterator(events_metrics.end()));
}
EventMetrics::List CompositorFrameReporter::TakeEventsMetrics() {
EventMetrics::List result = std::move(events_metrics_);
events_metrics_.clear();
return result;
}
void CompositorFrameReporter::TerminateReporter() {
if (frame_termination_status_ == FrameTerminationStatus::kUnknown)
TerminateFrame(FrameTerminationStatus::kUnknown, Now());
processed_blink_breakdown_ = std::make_unique<ProcessedBlinkBreakdown>(
blink_start_time_, begin_main_frame_start_, blink_breakdown_);
processed_viz_breakdown_ =
std::make_unique<ProcessedVizBreakdown>(viz_start_time_, viz_breakdown_);
DCHECK_EQ(current_stage_.start_time, base::TimeTicks());
const FrameInfo frame_info = GenerateFrameInfo();
switch (frame_info.final_state) {
case FrameFinalState::kDropped:
EnableReportType(FrameReportType::kDroppedFrame);
break;
case FrameFinalState::kNoUpdateDesired:
// If this reporter was cloned, and the cloned reporter was marked as
// containing 'partial update' (i.e. missing desired updates from the
// main-thread), but this reporter terminated with 'no damage', then reset
// the 'partial update' flag from the cloned reporter (as well as other
// depending reporters).
while (!partial_update_dependents_.empty()) {
auto dependent = partial_update_dependents_.front();
if (dependent)
dependent->set_has_partial_update(false);
partial_update_dependents_.pop();
}
break;
case FrameFinalState::kPresentedAll:
case FrameFinalState::kPresentedPartialNewMain:
case FrameFinalState::kPresentedPartialOldMain:
EnableReportType(FrameReportType::kNonDroppedFrame);
if (ComputeSafeDeadlineForFrame(args_) < frame_termination_time_)
EnableReportType(FrameReportType::kMissedDeadlineFrame);
break;
}
ReportCompositorLatencyTraceEvents(frame_info);
if (TestReportType(FrameReportType::kNonDroppedFrame))
ReportEventLatencyTraceEvents();
// Only report compositor latency metrics if the frame was produced.
if (report_types_.any() &&
(should_report_histograms_ || global_trackers_.latency_ukm_reporter ||
global_trackers_.event_latency_tracker)) {
DCHECK(stage_history_.size());
DCHECK_EQ(SumOfStageHistory(), stage_history_.back().end_time -
stage_history_.front().start_time);
stage_history_.emplace_back(StageType::kTotalLatency,
stage_history_.front().start_time,
stage_history_.back().end_time);
ReportCompositorLatencyMetrics();
// Only report event latency metrics if the frame was presented.
if (TestReportType(FrameReportType::kNonDroppedFrame))
ReportEventLatencyMetrics();
}
if (TestReportType(FrameReportType::kDroppedFrame)) {
global_trackers_.dropped_frame_counter->AddDroppedFrame();
} else {
if (has_partial_update_)
global_trackers_.dropped_frame_counter->AddPartialFrame();
else
global_trackers_.dropped_frame_counter->AddGoodFrame();
}
global_trackers_.dropped_frame_counter->OnEndFrame(args_, frame_info);
if (discarded_partial_update_dependents_count_ > 0)
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Graphics.Smoothness.Diagnostic.DiscardedDependentCount",
discarded_partial_update_dependents_count_, 1, 1000, 50);
}
void CompositorFrameReporter::EndCurrentStage(base::TimeTicks end_time) {
if (current_stage_.start_time == base::TimeTicks())
return;
current_stage_.end_time = end_time;
stage_history_.push_back(current_stage_);
current_stage_.start_time = base::TimeTicks();
}
void CompositorFrameReporter::ReportCompositorLatencyMetrics() const {
static base::CpuReductionExperimentFilter filter;
if (!filter.ShouldLogHistograms())
return;
if (global_trackers_.latency_ukm_reporter) {
global_trackers_.latency_ukm_reporter->ReportCompositorLatencyUkm(
report_types_, stage_history_, active_trackers_,
*processed_blink_breakdown_, *processed_viz_breakdown_);
}
if (!should_report_histograms_)
return;
for (const StageData& stage : stage_history_) {
ReportStageHistogramWithBreakdown(stage);
if (stage.stage_type == StageType::kTotalLatency) {
for (size_t type = 0; type < active_trackers_.size(); ++type) {
if (active_trackers_.test(type)) {
// Report stage breakdowns.
ReportStageHistogramWithBreakdown(
stage, static_cast<FrameSequenceTrackerType>(type));
}
}
}
}
for (size_t type = 0; type < report_types_.size(); ++type) {
if (!report_types_.test(type))
continue;
FrameReportType report_type = static_cast<FrameReportType>(type);
UMA_HISTOGRAM_ENUMERATION("CompositorLatency.Type", report_type);
bool any_active_interaction = false;
for (size_t fst_type = 0; fst_type < active_trackers_.size(); ++fst_type) {
const auto tracker_type = static_cast<FrameSequenceTrackerType>(fst_type);
if (!active_trackers_.test(fst_type) ||
tracker_type == FrameSequenceTrackerType::kCustom ||
tracker_type == FrameSequenceTrackerType::kMaxType) {
continue;
}
any_active_interaction = true;
switch (tracker_type) {
case FrameSequenceTrackerType::kCompositorAnimation:
UMA_HISTOGRAM_ENUMERATION(
"CompositorLatency.Type.CompositorAnimation", report_type);
break;
case FrameSequenceTrackerType::kMainThreadAnimation:
UMA_HISTOGRAM_ENUMERATION(
"CompositorLatency.Type.MainThreadAnimation", report_type);
break;
case FrameSequenceTrackerType::kPinchZoom:
UMA_HISTOGRAM_ENUMERATION("CompositorLatency.Type.PinchZoom",
report_type);
break;
case FrameSequenceTrackerType::kRAF:
UMA_HISTOGRAM_ENUMERATION("CompositorLatency.Type.RAF", report_type);
break;
case FrameSequenceTrackerType::kTouchScroll:
UMA_HISTOGRAM_ENUMERATION("CompositorLatency.Type.TouchScroll",
report_type);
break;
case FrameSequenceTrackerType::kVideo:
UMA_HISTOGRAM_ENUMERATION("CompositorLatency.Type.Video",
report_type);
break;
case FrameSequenceTrackerType::kWheelScroll:
UMA_HISTOGRAM_ENUMERATION("CompositorLatency.Type.WheelScroll",
report_type);
break;
case FrameSequenceTrackerType::kScrollbarScroll:
UMA_HISTOGRAM_ENUMERATION("CompositorLatency.Type.ScrollbarScroll",
report_type);
break;
case FrameSequenceTrackerType::kCanvasAnimation:
UMA_HISTOGRAM_ENUMERATION("CompositorLatency.Type.CanvasAnimation",
report_type);
break;
case FrameSequenceTrackerType::kJSAnimation:
UMA_HISTOGRAM_ENUMERATION("CompositorLatency.Type.JSAnimation",
report_type);
break;
case FrameSequenceTrackerType::kCustom:
case FrameSequenceTrackerType::kMaxType:
NOTREACHED();
break;
}
}
if (any_active_interaction) {
UMA_HISTOGRAM_ENUMERATION("CompositorLatency.Type.AnyInteraction",
report_type);
} else {
UMA_HISTOGRAM_ENUMERATION("CompositorLatency.Type.NoInteraction",
report_type);
}
}
}
void CompositorFrameReporter::ReportStageHistogramWithBreakdown(
const CompositorFrameReporter::StageData& stage,
FrameSequenceTrackerType frame_sequence_tracker_type) const {
base::TimeDelta stage_delta = stage.end_time - stage.start_time;
ReportCompositorLatencyHistogram(
frame_sequence_tracker_type, stage.stage_type,
/*viz_breakdown=*/absl::nullopt,
/*blink_breakdown=*/absl::nullopt, stage_delta);
switch (stage.stage_type) {
case StageType::kSendBeginMainFrameToCommit:
ReportCompositorLatencyBlinkBreakdowns(frame_sequence_tracker_type);
break;
case StageType::kSubmitCompositorFrameToPresentationCompositorFrame:
ReportCompositorLatencyVizBreakdowns(frame_sequence_tracker_type);
break;
default:
break;
}
}
void CompositorFrameReporter::ReportCompositorLatencyBlinkBreakdowns(
FrameSequenceTrackerType frame_sequence_tracker_type) const {
DCHECK(processed_blink_breakdown_);
for (auto it = processed_blink_breakdown_->CreateIterator(); it.IsValid();
it.Advance()) {
ReportCompositorLatencyHistogram(
frame_sequence_tracker_type, StageType::kSendBeginMainFrameToCommit,
/*viz_breakdown=*/absl::nullopt, it.GetBreakdown(), it.GetLatency());
}
}
void CompositorFrameReporter::ReportCompositorLatencyVizBreakdowns(
FrameSequenceTrackerType frame_sequence_tracker_type) const {
DCHECK(processed_viz_breakdown_);
for (auto it = processed_viz_breakdown_->CreateIterator(false); it.IsValid();
it.Advance()) {
ReportCompositorLatencyHistogram(
frame_sequence_tracker_type,
StageType::kSubmitCompositorFrameToPresentationCompositorFrame,
it.GetBreakdown(), /*blink_breakdown=*/absl::nullopt, it.GetDuration());
}
}
void CompositorFrameReporter::ReportCompositorLatencyHistogram(
FrameSequenceTrackerType frame_sequence_tracker_type,
StageType stage_type,
absl::optional<VizBreakdown> viz_breakdown,
absl::optional<BlinkBreakdown> blink_breakdown,
base::TimeDelta time_delta) const {
DCHECK(!viz_breakdown ||
stage_type ==
StageType::kSubmitCompositorFrameToPresentationCompositorFrame);
DCHECK(!blink_breakdown ||
stage_type == StageType::kSendBeginMainFrameToCommit);
for (size_t type = 0; type < report_types_.size(); ++type) {
if (!report_types_.test(type))
continue;
FrameReportType report_type = static_cast<FrameReportType>(type);
const int report_type_index = static_cast<int>(report_type);
const int frame_sequence_tracker_type_index =
static_cast<int>(frame_sequence_tracker_type);
const int stage_type_index =
blink_breakdown
? kBlinkBreakdownInitialIndex + static_cast<int>(*blink_breakdown)
: viz_breakdown
? kVizBreakdownInitialIndex + static_cast<int>(*viz_breakdown)
: static_cast<int>(stage_type);
const int histogram_index =
(stage_type_index == static_cast<int>(StageType::kTotalLatency)
? kStagesWithBreakdownCount + frame_sequence_tracker_type_index
: stage_type_index) *
kFrameReportTypeCount +
report_type_index;
CHECK_LT(stage_type_index, kStagesWithBreakdownCount);
CHECK_GE(stage_type_index, 0);
CHECK_LT(report_type_index, kFrameReportTypeCount);
CHECK_GE(report_type_index, 0);
CHECK_LT(histogram_index, kMaxCompositorLatencyHistogramIndex);
CHECK_GE(histogram_index, 0);
// Note: There's a 1:1 mapping between `histogram_index` and the name
// returned by `GetCompositorLatencyHistogramName()` which allows the use of
// `STATIC_HISTOGRAM_POINTER_GROUP()` to cache histogram objects.
STATIC_HISTOGRAM_POINTER_GROUP(
GetCompositorLatencyHistogramName(
report_type, frame_sequence_tracker_type, stage_type, viz_breakdown,
blink_breakdown),
histogram_index, kMaxCompositorLatencyHistogramIndex,
AddTimeMicrosecondsGranularity(time_delta),
base::Histogram::FactoryMicrosecondsTimeGet(
GetCompositorLatencyHistogramName(
report_type, frame_sequence_tracker_type, stage_type,
viz_breakdown, blink_breakdown),
kCompositorLatencyHistogramMin, kCompositorLatencyHistogramMax,
kCompositorLatencyHistogramBucketCount,
base::HistogramBase::kUmaTargetedHistogramFlag));
}
}
void CompositorFrameReporter::ReportEventLatencyMetrics() const {
const StageData& total_latency_stage = stage_history_.back();
DCHECK_EQ(StageType::kTotalLatency, total_latency_stage.stage_type);
if (global_trackers_.latency_ukm_reporter) {
global_trackers_.latency_ukm_reporter->ReportEventLatencyUkm(
events_metrics_, stage_history_, *processed_blink_breakdown_,
*processed_viz_breakdown_);
}
std::vector<EventLatencyTracker::LatencyData> latencies;
for (const auto& event_metrics : events_metrics_) {
DCHECK(event_metrics);
auto* scroll_metrics = event_metrics->AsScroll();
auto* pinch_metrics = event_metrics->AsPinch();
const base::TimeTicks generated_timestamp =
event_metrics->GetDispatchStageTimestamp(
EventMetrics::DispatchStage::kGenerated);
// Generally, we expect that the event timestamp is strictly smaller than
// the end timestamp of the last stage (i.e. total latency is positive);
// however, at least in tests, it is possible that the timestamps are the
// same and total latency is zero.
DCHECK_LE(generated_timestamp, total_latency_stage.end_time);
const base::TimeDelta total_latency =
total_latency_stage.end_time - generated_timestamp;
if (should_report_histograms_) {
const std::string histogram_base_name =
GetEventLatencyHistogramBaseName(*event_metrics);
const int event_type_index = static_cast<int>(event_metrics->type());
const int gesture_type_index =
scroll_metrics ? static_cast<int>(scroll_metrics->scroll_type())
: pinch_metrics ? static_cast<int>(pinch_metrics->pinch_type())
: 0;
const int event_histogram_index =
event_type_index * kEventLatencyGestureTypeCount + gesture_type_index;
const std::string total_latency_stage_name =
GetStageName(StageType::kTotalLatency);
const std::string event_total_latency_histogram_name =
base::StrCat({histogram_base_name, ".", total_latency_stage_name});
// Note: There's a 1:1 mapping between `event_histogram_index` and
// `event_total_latency_histogram_name` which allows the use of
// `STATIC_HISTOGRAM_POINTER_GROUP()` to cache histogram objects.
STATIC_HISTOGRAM_POINTER_GROUP(
event_total_latency_histogram_name, event_histogram_index,
kMaxEventLatencyHistogramIndex,
AddTimeMicrosecondsGranularity(total_latency),
base::Histogram::FactoryMicrosecondsTimeGet(
event_total_latency_histogram_name, kEventLatencyHistogramMin,
kEventLatencyHistogramMax, kEventLatencyHistogramBucketCount,
base::HistogramBase::kUmaTargetedHistogramFlag));
// Also, report total latency up to presentation for all event types in an
// aggregate histogram.
UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
"EventLatency." + total_latency_stage_name, total_latency,
kEventLatencyHistogramMin, kEventLatencyHistogramMax,
kEventLatencyHistogramBucketCount);
}
if (global_trackers_.event_latency_tracker) {
EventLatencyTracker::LatencyData& latency_data =
latencies.emplace_back(event_metrics->type(), total_latency);
if (scroll_metrics)
latency_data.input_type = scroll_metrics->scroll_type();
else if (pinch_metrics)
latency_data.input_type = pinch_metrics->pinch_type();
}
}
if (!latencies.empty()) {
DCHECK(global_trackers_.event_latency_tracker);
global_trackers_.event_latency_tracker->ReportEventLatency(
std::move(latencies));
}
}
void CompositorFrameReporter::ReportCompositorLatencyTraceEvents(
const FrameInfo& info) const {
if (stage_history_.empty())
return;
if (info.IsDroppedAffectingSmoothness()) {
devtools_instrumentation::DidDropSmoothnessFrame(
layer_tree_host_id_, args_.frame_time, args_.frame_id.sequence_number,
has_partial_update_);
}
const auto trace_track =
perfetto::Track(base::trace_event::GetNextGlobalTraceId());
TRACE_EVENT_BEGIN(
kTraceCategory, "PipelineReporter", trace_track, args_.frame_time,
[&](perfetto::EventContext context) {
using perfetto::protos::pbzero::ChromeFrameReporter;
ChromeFrameReporter::State state;
switch (info.final_state) {
case FrameInfo::FrameFinalState::kPresentedAll:
state = ChromeFrameReporter::STATE_PRESENTED_ALL;
break;
case FrameInfo::FrameFinalState::kPresentedPartialNewMain:
case FrameInfo::FrameFinalState::kPresentedPartialOldMain:
state = ChromeFrameReporter::STATE_PRESENTED_PARTIAL;
break;
case FrameInfo::FrameFinalState::kNoUpdateDesired:
state = ChromeFrameReporter::STATE_NO_UPDATE_DESIRED;
break;
case FrameInfo::FrameFinalState::kDropped:
state = ChromeFrameReporter::STATE_DROPPED;
break;
}
auto* reporter = context.event()->set_chrome_frame_reporter();
reporter->set_state(state);
reporter->set_frame_source(args_.frame_id.source_id);
reporter->set_frame_sequence(args_.frame_id.sequence_number);
reporter->set_layer_tree_host_id(layer_tree_host_id_);
reporter->set_has_missing_content(info.has_missing_content);
if (info.IsDroppedAffectingSmoothness()) {
DCHECK(state == ChromeFrameReporter::STATE_DROPPED ||
state == ChromeFrameReporter::STATE_PRESENTED_PARTIAL);
}
reporter->set_affects_smoothness(info.IsDroppedAffectingSmoothness());
ChromeFrameReporter::ScrollState scroll_state;
switch (info.scroll_thread) {
case FrameInfo::SmoothEffectDrivingThread::kMain:
scroll_state = ChromeFrameReporter::SCROLL_MAIN_THREAD;
break;
case FrameInfo::SmoothEffectDrivingThread::kCompositor:
scroll_state = ChromeFrameReporter::SCROLL_COMPOSITOR_THREAD;
break;
case FrameInfo::SmoothEffectDrivingThread::kUnknown:
scroll_state = ChromeFrameReporter::SCROLL_NONE;
break;
}
reporter->set_scroll_state(scroll_state);
reporter->set_has_main_animation(
HasMainThreadAnimation(active_trackers_));
reporter->set_has_compositor_animation(
HasCompositorThreadAnimation(active_trackers_));
bool has_smooth_input_main = false;
for (const auto& event_metrics : events_metrics_) {
has_smooth_input_main |= event_metrics->HasSmoothInputEvent();
}
reporter->set_has_smooth_input_main(has_smooth_input_main);
// TODO(crbug.com/1086974): Set 'drop reason' if applicable.
});
for (const auto& stage : stage_history_) {
if (stage.start_time >= frame_termination_time_)
break;
DCHECK_GE(stage.end_time, stage.start_time);
if (stage.start_time == stage.end_time)
continue;
const char* stage_name = GetStageName(stage.stage_type);
if (stage.stage_type == StageType::kSendBeginMainFrameToCommit) {
TRACE_EVENT_BEGIN(
kTraceCategory, perfetto::StaticString{stage_name}, trace_track,
stage.start_time, [&](perfetto::EventContext context) {
DCHECK(processed_blink_breakdown_);
auto* reporter =
context.event<perfetto::protos::pbzero::ChromeTrackEvent>()
->set_send_begin_mainframe_to_commit_breakdown();
for (auto it = processed_blink_breakdown_->CreateIterator();
it.IsValid(); it.Advance()) {
int64_t latency = it.GetLatency().InMicroseconds();
int curr_breakdown = static_cast<int>(it.GetBreakdown());
switch (curr_breakdown) {
case static_cast<int>(BlinkBreakdown::kHandleInputEvents):
reporter->set_handle_input_events_us(latency);
break;
case static_cast<int>(BlinkBreakdown::kAnimate):
reporter->set_animate_us(latency);
break;
case static_cast<int>(BlinkBreakdown::kStyleUpdate):
reporter->set_style_update_us(latency);
break;
case static_cast<int>(BlinkBreakdown::kLayoutUpdate):
reporter->set_layout_update_us(latency);
break;
case static_cast<int>(BlinkBreakdown::kAccessibility):
reporter->set_accessibility_update_us(latency);
break;
case static_cast<int>(BlinkBreakdown::kPrepaint):
reporter->set_prepaint_us(latency);
break;
case static_cast<int>(BlinkBreakdown::kCompositingInputs):
reporter->set_compositing_inputs_us(latency);
break;
case static_cast<int>(BlinkBreakdown::kPaint):
reporter->set_paint_us(latency);
break;
case static_cast<int>(BlinkBreakdown::kCompositeCommit):
reporter->set_composite_commit_us(latency);
break;
case static_cast<int>(BlinkBreakdown::kUpdateLayers):
reporter->set_update_layers_us(latency);
break;
case static_cast<int>(BlinkBreakdown::kBeginMainSentToStarted):
reporter->set_begin_main_sent_to_started_us(latency);
break;
default:
break;
}
}
});
} else {
TRACE_EVENT_BEGIN(kTraceCategory, perfetto::StaticString{stage_name},
trace_track, stage.start_time);
}
if (stage.stage_type ==
StageType::kSubmitCompositorFrameToPresentationCompositorFrame) {
DCHECK(processed_viz_breakdown_);
for (auto it = processed_viz_breakdown_->CreateIterator(true);
it.IsValid(); it.Advance()) {
base::TimeTicks start_time = it.GetStartTime();
base::TimeTicks end_time = it.GetEndTime();
if (start_time >= end_time)
continue;
const char* breakdown_name = GetVizBreakdownName(it.GetBreakdown());
TRACE_EVENT_BEGIN(kTraceCategory,
perfetto::StaticString{breakdown_name}, trace_track,
start_time);
TRACE_EVENT_END(kTraceCategory, trace_track, end_time);
}
}
TRACE_EVENT_END(kTraceCategory, trace_track, stage.end_time);
}
TRACE_EVENT_END(kTraceCategory, trace_track, frame_termination_time_);
}
void CompositorFrameReporter::ReportEventLatencyTraceEvents() const {
// TODO(mohsen): This function is becoming large and there is concerns about
// having this in the compositor critical path. crbug.com/1072740 is
// considering doing the reporting off-thread, but as a short-term solution,
// we should investigate whether we can skip this function entirely if tracing
// is off and whether that has any positive impact or not.
for (const auto& event_metrics : events_metrics_) {
EventLatencyTracingRecorder::RecordEventLatencyTraceEvent(
event_metrics.get(), frame_termination_time_, &stage_history_,
processed_viz_breakdown_.get());
}
}
base::TimeDelta CompositorFrameReporter::SumOfStageHistory() const {
base::TimeDelta sum;
for (const StageData& stage : stage_history_)
sum += stage.end_time - stage.start_time;
return sum;
}
base::TimeTicks CompositorFrameReporter::Now() const {
return tick_clock_->NowTicks();
}
void CompositorFrameReporter::AdoptReporter(
std::unique_ptr<CompositorFrameReporter> reporter) {
// If |this| reporter is dependent on another reporter to decide about partial
// update, then |this| should not have any such dependents.
DCHECK(!partial_update_decider_);
DCHECK(!partial_update_dependents_.empty());
owned_partial_update_dependents_.push(std::move(reporter));
DiscardOldPartialUpdateReporters();
}
void CompositorFrameReporter::SetPartialUpdateDecider(
CompositorFrameReporter* decider) {
DCHECK(decider);
DCHECK(partial_update_dependents_.empty());
has_partial_update_ = true;
partial_update_decider_ = decider->GetWeakPtr();
decider->partial_update_dependents_.push(GetWeakPtr());
}
void CompositorFrameReporter::DiscardOldPartialUpdateReporters() {
DCHECK_LE(owned_partial_update_dependents_.size(),
partial_update_dependents_.size());
// Remove old owned partial update dependents if there are too many.
while (owned_partial_update_dependents_.size() >
kMaxOwnedPartialUpdateDependents) {
auto& dependent = owned_partial_update_dependents_.front();
dependent->set_has_partial_update(false);
owned_partial_update_dependents_.pop();
discarded_partial_update_dependents_count_++;
}
// Remove dependent reporters from the front of `partial_update_dependents_`
// queue if they are already destroyed.
while (!partial_update_dependents_.empty() &&
!partial_update_dependents_.front()) {
partial_update_dependents_.pop();
}
}
base::WeakPtr<CompositorFrameReporter> CompositorFrameReporter::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
FrameInfo CompositorFrameReporter::GenerateFrameInfo() const {
FrameFinalState final_state = FrameFinalState::kNoUpdateDesired;
auto smooth_thread = smooth_thread_;
auto scrolling_thread = scrolling_thread_;
switch (frame_termination_status_) {
case FrameTerminationStatus::kPresentedFrame:
if (has_partial_update_) {
final_state = is_accompanied_by_main_thread_update_
? FrameFinalState::kPresentedPartialNewMain
: FrameFinalState::kPresentedPartialOldMain;
} else {
final_state = FrameFinalState::kPresentedAll;
}
break;
case FrameTerminationStatus::kDidNotPresentFrame:
case FrameTerminationStatus::kReplacedByNewReporter:
final_state = FrameFinalState::kDropped;
break;
case FrameTerminationStatus::kDidNotProduceFrame: {
const bool no_update_expected_from_main =
frame_skip_reason_.has_value() &&
frame_skip_reason() == FrameSkippedReason::kNoDamage;
const bool no_update_expected_from_compositor =
!has_partial_update_ && frame_skip_reason_.has_value() &&
frame_skip_reason() == FrameSkippedReason::kWaitingOnMain;
const bool draw_is_throttled =
frame_skip_reason_.has_value() &&
frame_skip_reason() == FrameSkippedReason::kDrawThrottled;
if (!no_update_expected_from_main &&
!no_update_expected_from_compositor) {
final_state = FrameFinalState::kDropped;
} else if (draw_is_throttled) {
final_state = FrameFinalState::kDropped;
} else {
final_state = FrameFinalState::kNoUpdateDesired;
}
// If the compositor-thread is running an animation, and it ends with
// 'did not produce frame', then that implies that the compositor
// animation did not cause any visual changes. So for such cases, update
// the `smooth_thread` for the FrameInfo created to exclude the compositor
// thread. However, it is important to keep `final_state` unchanged,
// because the main-thread update (if any) did get dropped.
if (frame_skip_reason_.has_value() &&
frame_skip_reason() == FrameSkippedReason::kWaitingOnMain) {
if (smooth_thread == SmoothThread::kSmoothBoth) {
smooth_thread = SmoothThread::kSmoothMain;
} else if (smooth_thread == SmoothThread::kSmoothCompositor) {
smooth_thread = SmoothThread::kSmoothNone;
}
if (scrolling_thread ==
FrameInfo::SmoothEffectDrivingThread::kCompositor) {
scrolling_thread = FrameInfo::SmoothEffectDrivingThread::kUnknown;
}
}
break;
}
case FrameTerminationStatus::kUnknown:
break;
}
FrameInfo info;
info.final_state = final_state;
info.smooth_thread = smooth_thread;
info.scroll_thread = scrolling_thread;
info.has_missing_content = has_missing_content_;
if (frame_skip_reason_.has_value() &&
frame_skip_reason() == FrameSkippedReason::kNoDamage) {
// If the frame was explicitly skipped because of 'no damage', then that
// means this frame contains the response ('no damage') from the
// main-thread.
info.main_thread_response = FrameInfo::MainThreadResponse::kIncluded;
} else if (partial_update_dependents_.size() > 0) {
// Only a frame containing a response from the main-thread can have
// dependent reporters.
info.main_thread_response = FrameInfo::MainThreadResponse::kIncluded;
} else if (begin_main_frame_start_.is_null() ||
(frame_skip_reason_.has_value() &&
frame_skip_reason() == FrameSkippedReason::kWaitingOnMain)) {
// If 'begin main frame' never started, or if it started, but it
// had to be skipped because it was waiting on the main-thread,
// then the main-thread update is missing from this reporter.
info.main_thread_response = FrameInfo::MainThreadResponse::kMissing;
} else {
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;
}
}
return info;
}
} // namespace cc