blob: 81d3a838ab632a71a64aa608e09f270d1d8bdf12 [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/cxx17_backports.h"
#include "base/metrics/histogram_macros.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 "cc/base/rolling_time_delta_history.h"
#include "cc/metrics/dropped_frame_counter.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;
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 the viz breakdowns that are shown in trace as substages under
// PipelineReporter -> SubmitCompositorFrameToPresentationCompositorFrame or
// EventLatency -> SubmitCompositorFrameToPresentationCompositorFrame.
constexpr const char* GetVizBreakdownName(VizBreakdown stage) {
switch (stage) {
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 "";
}
}
// Returns the name of the event dispatch breakdown of EventLatency histograms
// between `start_stage` and `end_stage`.
constexpr const char* GetEventLatencyDispatchBreakdownName(
EventMetrics::DispatchStage start_stage,
EventMetrics::DispatchStage end_stage) {
switch (start_stage) {
case EventMetrics::DispatchStage::kGenerated:
DCHECK_EQ(end_stage,
EventMetrics::DispatchStage::kArrivedInRendererCompositor);
return "GenerationToRendererCompositor";
case EventMetrics::DispatchStage::kArrivedInRendererCompositor:
switch (end_stage) {
case EventMetrics::DispatchStage::kRendererCompositorStarted:
return "RendererCompositorQueueingDelay";
case EventMetrics::DispatchStage::kRendererMainStarted:
return "RendererCompositorToMain";
default:
NOTREACHED();
return nullptr;
}
case EventMetrics::DispatchStage::kRendererCompositorStarted:
DCHECK_EQ(end_stage,
EventMetrics::DispatchStage::kRendererCompositorFinished);
return "RendererCompositorProcessing";
case EventMetrics::DispatchStage::kRendererCompositorFinished:
DCHECK_EQ(end_stage, EventMetrics::DispatchStage::kRendererMainStarted);
return "RendererCompositorToMain";
case EventMetrics::DispatchStage::kRendererMainStarted:
DCHECK_EQ(end_stage, EventMetrics::DispatchStage::kRendererMainFinished);
return "RendererMainProcessing";
case EventMetrics::DispatchStage::kRendererMainFinished:
NOTREACHED();
return nullptr;
}
}
// Returns the name of EventLatency breakdown between `dispatch_stage` and
// `compositor_stage`.
constexpr const char* GetEventLatencyDispatchToCompositorBreakdownName(
EventMetrics::DispatchStage dispatch_stage,
CompositorFrameReporter::StageType compositor_stage) {
switch (dispatch_stage) {
case EventMetrics::DispatchStage::kRendererCompositorFinished:
switch (compositor_stage) {
case CompositorFrameReporter::StageType::
kBeginImplFrameToSendBeginMainFrame:
return "RendererCompositorFinishedToBeginImplFrame";
case CompositorFrameReporter::StageType::kSendBeginMainFrameToCommit:
return "RendererCompositorFinishedToSendBeginMainFrame";
case CompositorFrameReporter::StageType::kCommit:
return "RendererCompositorFinishedToCommit";
case CompositorFrameReporter::StageType::kEndCommitToActivation:
return "RendererCompositorFinishedToEndCommit";
case CompositorFrameReporter::StageType::kActivation:
return "RendererCompositorFinishedToActivation";
case CompositorFrameReporter::StageType::
kEndActivateToSubmitCompositorFrame:
return "RendererCompositorFinishedToEndActivate";
case CompositorFrameReporter::StageType::
kSubmitCompositorFrameToPresentationCompositorFrame:
return "RendererCompositorFinishedToSubmitCompositorFrame";
default:
NOTREACHED();
return nullptr;
}
case EventMetrics::DispatchStage::kRendererMainFinished:
switch (compositor_stage) {
case CompositorFrameReporter::StageType::
kBeginImplFrameToSendBeginMainFrame:
return "RendererMainFinishedToBeginImplFrame";
case CompositorFrameReporter::StageType::kSendBeginMainFrameToCommit:
return "RendererMainFinishedToSendBeginMainFrame";
case CompositorFrameReporter::StageType::kCommit:
return "RendererMainFinishedToCommit";
case CompositorFrameReporter::StageType::kEndCommitToActivation:
return "RendererMainFinishedToEndCommit";
case CompositorFrameReporter::StageType::kActivation:
return "RendererMainFinishedToActivation";
case CompositorFrameReporter::StageType::
kEndActivateToSubmitCompositorFrame:
return "RendererMainFinishedToEndActivate";
case CompositorFrameReporter::StageType::
kSubmitCompositorFrameToPresentationCompositorFrame:
return "RendererMainFinishedToSubmitCompositorFrame";
default:
NOTREACHED();
return nullptr;
}
default:
NOTREACHED();
return nullptr;
}
}
// Names for CompositorFrameReporter::StageType, which should be updated in case
// of changes to the enum.
constexpr const char* GetStageName(int stage_type_index,
bool impl_only = false) {
switch (stage_type_index) {
case static_cast<int>(StageType::kBeginImplFrameToSendBeginMainFrame):
if (impl_only)
return "BeginImplFrameToFinishImpl";
return "BeginImplFrameToSendBeginMainFrame";
case static_cast<int>(StageType::kSendBeginMainFrameToCommit):
if (impl_only)
return "SendBeginMainFrameToBeginMainAbort";
return "SendBeginMainFrameToCommit";
case static_cast<int>(StageType::kCommit):
return "Commit";
case static_cast<int>(StageType::kEndCommitToActivation):
return "EndCommitToActivation";
case static_cast<int>(StageType::kActivation):
return "Activation";
case static_cast<int>(StageType::kEndActivateToSubmitCompositorFrame):
if (impl_only)
return "ImplFrameDoneToSubmitCompositorFrame";
return "EndActivateToSubmitCompositorFrame";
case static_cast<int>(
StageType::kSubmitCompositorFrameToPresentationCompositorFrame):
return "SubmitCompositorFrameToPresentationCompositorFrame";
case static_cast<int>(StageType::kTotalLatency):
return "TotalLatency";
case static_cast<int>(VizBreakdown::kSubmitToReceiveCompositorFrame) +
kVizBreakdownInitialIndex:
return "SubmitCompositorFrameToPresentationCompositorFrame."
"SubmitToReceiveCompositorFrame";
case static_cast<int>(VizBreakdown::kReceivedCompositorFrameToStartDraw) +
kVizBreakdownInitialIndex:
return "SubmitCompositorFrameToPresentationCompositorFrame."
"ReceivedCompositorFrameToStartDraw";
case static_cast<int>(VizBreakdown::kStartDrawToSwapStart) +
kVizBreakdownInitialIndex:
return "SubmitCompositorFrameToPresentationCompositorFrame."
"StartDrawToSwapStart";
case static_cast<int>(VizBreakdown::kSwapStartToSwapEnd) +
kVizBreakdownInitialIndex:
return "SubmitCompositorFrameToPresentationCompositorFrame."
"SwapStartToSwapEnd";
case static_cast<int>(VizBreakdown::kSwapEndToPresentationCompositorFrame) +
kVizBreakdownInitialIndex:
return "SubmitCompositorFrameToPresentationCompositorFrame."
"SwapEndToPresentationCompositorFrame";
case static_cast<int>(VizBreakdown::kSwapStartToBufferAvailable) +
kVizBreakdownInitialIndex:
return "SubmitCompositorFrameToPresentationCompositorFrame."
"SwapStartToBufferAvailable";
case static_cast<int>(VizBreakdown::kBufferAvailableToBufferReady) +
kVizBreakdownInitialIndex:
return "SubmitCompositorFrameToPresentationCompositorFrame."
"BufferAvailableToBufferReady";
case static_cast<int>(VizBreakdown::kBufferReadyToLatch) +
kVizBreakdownInitialIndex:
return "SubmitCompositorFrameToPresentationCompositorFrame."
"BufferReadyToLatch";
case static_cast<int>(VizBreakdown::kLatchToSwapEnd) +
kVizBreakdownInitialIndex:
return "SubmitCompositorFrameToPresentationCompositorFrame."
"LatchToSwapEnd";
case static_cast<int>(BlinkBreakdown::kHandleInputEvents) +
kBlinkBreakdownInitialIndex:
return "SendBeginMainFrameToCommit.HandleInputEvents";
case static_cast<int>(BlinkBreakdown::kAnimate) +
kBlinkBreakdownInitialIndex:
return "SendBeginMainFrameToCommit.Animate";
case static_cast<int>(BlinkBreakdown::kStyleUpdate) +
kBlinkBreakdownInitialIndex:
return "SendBeginMainFrameToCommit.StyleUpdate";
case static_cast<int>(BlinkBreakdown::kLayoutUpdate) +
kBlinkBreakdownInitialIndex:
return "SendBeginMainFrameToCommit.LayoutUpdate";
case static_cast<int>(BlinkBreakdown::kPrepaint) +
kBlinkBreakdownInitialIndex:
return "SendBeginMainFrameToCommit.Prepaint";
case static_cast<int>(BlinkBreakdown::kCompositingInputs) +
kBlinkBreakdownInitialIndex:
return "SendBeginMainFrameToCommit.CompositingInputs";
case static_cast<int>(BlinkBreakdown::kCompositingAssignments) +
kBlinkBreakdownInitialIndex:
return "SendBeginMainFrameToCommit.CompositingAssignments";
case static_cast<int>(BlinkBreakdown::kPaint) + kBlinkBreakdownInitialIndex:
return "SendBeginMainFrameToCommit.Paint";
case static_cast<int>(BlinkBreakdown::kCompositeCommit) +
kBlinkBreakdownInitialIndex:
return "SendBeginMainFrameToCommit.CompositeCommit";
case static_cast<int>(BlinkBreakdown::kUpdateLayers) +
kBlinkBreakdownInitialIndex:
return "SendBeginMainFrameToCommit.UpdateLayers";
case static_cast<int>(BlinkBreakdown::kBeginMainSentToStarted) +
kBlinkBreakdownInitialIndex:
return "SendBeginMainFrameToCommit.BeginMainSentToStarted";
default:
NOTREACHED();
return "";
}
}
// Names for CompositorFrameReporter::FrameReportType, which should be
// updated in case of changes to the enum.
constexpr const char* kReportTypeNames[]{
"", "MissedDeadlineFrame.", "DroppedFrame.", "CompositorOnlyFrame."};
static_assert(base::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 kMaxCompositorLatencyHistogramIndex =
kFrameReportTypeCount * kFrameSequenceTrackerTypeCount *
(kStageTypeCount + kAllBreakdownCount);
constexpr base::TimeDelta kCompositorLatencyHistogramMin =
base::TimeDelta::FromMicroseconds(1);
constexpr base::TimeDelta kCompositorLatencyHistogramMax =
base::TimeDelta::FromMilliseconds(350);
constexpr int kCompositorLatencyHistogramBucketCount = 50;
constexpr int kEventLatencyEventTypeCount =
static_cast<int>(EventMetrics::EventType::kMaxValue) + 1;
constexpr int kEventLatencyScrollTypeCount =
static_cast<int>(EventMetrics::ScrollType::kMaxValue) + 1;
constexpr int kMaxEventLatencyHistogramIndex =
kEventLatencyEventTypeCount * kEventLatencyScrollTypeCount;
constexpr base::TimeDelta kEventLatencyHistogramMin =
base::TimeDelta::FromMicroseconds(1);
constexpr base::TimeDelta kEventLatencyHistogramMax =
base::TimeDelta::FromSeconds(5);
constexpr int kEventLatencyHistogramBucketCount = 100;
std::string GetCompositorLatencyHistogramName(
const int report_type_index,
FrameSequenceTrackerType frame_sequence_tracker_type,
const int stage_type_index) {
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_index ==
static_cast<int>(FrameReportType::kCompositorOnlyFrame));
return base::StrCat({"CompositorLatency.",
kReportTypeNames[report_type_index], tracker_type_name,
*tracker_type_name ? "." : "",
GetStageName(stage_type_index, impl_only_frame)});
}
std::string GetEventLatencyHistogramBaseName(
const EventMetrics& event_metrics) {
const bool is_scroll = event_metrics.scroll_type().has_value();
return base::StrCat({"EventLatency.", event_metrics.GetTypeName(),
is_scroll ? "." : "",
is_scroll ? event_metrics.GetScrollTypeName() : ""});
}
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_ < base::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::kPrepaint)] = blink_breakdown.prepaint;
list_[static_cast<int>(BlinkBreakdown::kCompositingInputs)] =
blink_breakdown.compositing_inputs;
list_[static_cast<int>(BlinkBreakdown::kCompositingAssignments)] =
blink_breakdown.compositing_assignments;
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_ < base::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,
LatencyUkmReporter* latency_ukm_reporter,
bool should_report_metrics,
SmoothThread smooth_thread,
FrameSequenceMetrics::ThreadType scrolling_thread,
int layer_tree_host_id,
DroppedFrameCounter* dropped_frame_counter)
: should_report_metrics_(should_report_metrics),
args_(args),
active_trackers_(active_trackers),
scrolling_thread_(scrolling_thread),
latency_ukm_reporter_(latency_ukm_reporter),
dropped_frame_counter_(dropped_frame_counter),
smooth_thread_(smooth_thread),
layer_tree_host_id_(layer_tree_host_id) {
dropped_frame_counter_->OnBeginFrame(args, IsScrollActive(active_trackers_));
DCHECK(IsScrollActive(active_trackers_) ||
scrolling_thread_ == FrameSequenceMetrics::ThreadType::kUnknown);
}
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_, latency_ukm_reporter_, should_report_metrics_,
smooth_thread_, scrolling_thread_, layer_tree_host_id_,
dropped_frame_counter_);
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());
switch (frame_termination_status_) {
case FrameTerminationStatus::kPresentedFrame:
EnableReportType(FrameReportType::kNonDroppedFrame);
if (ComputeSafeDeadlineForFrame(args_) < frame_termination_time_)
EnableReportType(FrameReportType::kMissedDeadlineFrame);
break;
case FrameTerminationStatus::kDidNotPresentFrame:
EnableReportType(FrameReportType::kDroppedFrame);
break;
case FrameTerminationStatus::kReplacedByNewReporter:
EnableReportType(FrameReportType::kDroppedFrame);
break;
case FrameTerminationStatus::kDidNotProduceFrame: {
const bool no_update_from_main =
frame_skip_reason_.has_value() &&
frame_skip_reason() == FrameSkippedReason::kNoDamage;
const bool no_update_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_from_main) {
// 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();
}
} else if (!no_update_from_compositor) {
// If rather main thread has damage or compositor thread has partial
// damage, then it's a dropped frame.
EnableReportType(FrameReportType::kDroppedFrame);
} else if (draw_is_throttled) {
EnableReportType(FrameReportType::kDroppedFrame);
}
break;
}
case FrameTerminationStatus::kUnknown:
break;
}
ReportCompositorLatencyTraceEvents();
if (TestReportType(FrameReportType::kNonDroppedFrame))
ReportEventLatencyTraceEvents();
// Only report compositor latency histograms if the frame was produced.
if (should_report_metrics_ && report_types_.any()) {
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);
ReportCompositorLatencyHistograms();
// Only report event latency histograms if the frame was presented.
if (TestReportType(FrameReportType::kNonDroppedFrame))
ReportEventLatencyHistograms();
}
if (dropped_frame_counter_) {
if (TestReportType(FrameReportType::kDroppedFrame)) {
dropped_frame_counter_->AddDroppedFrame();
} else {
if (has_partial_update_)
dropped_frame_counter_->AddPartialFrame();
else
dropped_frame_counter_->AddGoodFrame();
}
dropped_frame_counter_->OnEndFrame(args_,
IsDroppedFrameAffectingSmoothness());
}
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::ReportCompositorLatencyHistograms() const {
for (const StageData& stage : stage_history_) {
ReportStageHistogramWithBreakdown(stage);
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);
if (latency_ukm_reporter_) {
latency_ukm_reporter_->ReportCompositorLatencyUkm(
report_type, stage_history_, active_trackers_,
*processed_blink_breakdown_, *processed_viz_breakdown_);
}
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,
static_cast<int>(stage.stage_type),
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,
kBlinkBreakdownInitialIndex + static_cast<size_t>(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,
kVizBreakdownInitialIndex + static_cast<size_t>(it.GetBreakdown()),
it.GetDuration());
}
}
void CompositorFrameReporter::ReportCompositorLatencyHistogram(
FrameSequenceTrackerType frame_sequence_tracker_type,
const int stage_type_index,
base::TimeDelta time_delta) const {
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 histogram_index =
(stage_type_index * kFrameSequenceTrackerTypeCount +
frame_sequence_tracker_type_index) *
kFrameReportTypeCount +
report_type_index;
CHECK_LT(stage_type_index, kStageTypeCount + kAllBreakdownCount);
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_index, frame_sequence_tracker_type, stage_type_index),
histogram_index, kMaxCompositorLatencyHistogramIndex,
AddTimeMicrosecondsGranularity(time_delta),
base::Histogram::FactoryMicrosecondsTimeGet(
GetCompositorLatencyHistogramName(report_type_index,
frame_sequence_tracker_type,
stage_type_index),
kCompositorLatencyHistogramMin, kCompositorLatencyHistogramMax,
kCompositorLatencyHistogramBucketCount,
base::HistogramBase::kUmaTargetedHistogramFlag));
}
}
void CompositorFrameReporter::ReportEventLatencyHistograms() const {
const StageData& total_latency_stage = stage_history_.back();
DCHECK_EQ(StageType::kTotalLatency, total_latency_stage.stage_type);
const std::string total_latency_stage_name =
GetStageName(static_cast<int>(StageType::kTotalLatency));
const std::string total_latency_histogram_name =
"EventLatency." + total_latency_stage_name;
for (const auto& event_metrics : events_metrics_) {
DCHECK(event_metrics);
const std::string histogram_base_name =
GetEventLatencyHistogramBaseName(*event_metrics);
const int event_type_index = static_cast<int>(event_metrics->type());
const int scroll_type_index =
event_metrics->scroll_type()
? static_cast<int>(*event_metrics->scroll_type())
: 0;
const int event_histogram_index =
event_type_index * kEventLatencyScrollTypeCount + scroll_type_index;
const base::TimeTicks generated_timestamp =
event_metrics->GetDispatchStageTimestamp(
EventMetrics::DispatchStage::kGenerated);
DCHECK_LT(generated_timestamp, total_latency_stage.end_time);
// For scroll events, report total latency up to gpu-swap-begin. This is
// useful in comparing new EventLatency metrics with LatencyInfo-based
// scroll event latency metrics.
if (event_metrics->ShouldReportScrollingTotalLatency() &&
!viz_breakdown_.swap_timings.is_null()) {
const base::TimeDelta swap_begin_latency =
viz_breakdown_.swap_timings.swap_start - generated_timestamp;
const std::string event_swap_begin_histogram_name =
histogram_base_name + ".TotalLatencyToSwapBegin";
// Note: There's a 1:1 mapping between `event_histogram_index` and
// `event_swap_begin_histogram_name` which allows the use of
// `STATIC_HISTOGRAM_POINTER_GROUP()` to cache histogram objects.
STATIC_HISTOGRAM_POINTER_GROUP(
event_swap_begin_histogram_name, event_histogram_index,
kMaxEventLatencyHistogramIndex,
AddTimeMicrosecondsGranularity(swap_begin_latency),
base::Histogram::FactoryMicrosecondsTimeGet(
event_swap_begin_histogram_name, kEventLatencyHistogramMin,
kEventLatencyHistogramMax, kEventLatencyHistogramBucketCount,
base::HistogramBase::kUmaTargetedHistogramFlag));
}
// Report total latency up to presentation for the event.
const base::TimeDelta total_latency =
total_latency_stage.end_time - generated_timestamp;
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(
total_latency_histogram_name, total_latency, kEventLatencyHistogramMin,
kEventLatencyHistogramMax, kEventLatencyHistogramBucketCount);
}
if (latency_ukm_reporter_) {
latency_ukm_reporter_->ReportEventLatencyUkm(
events_metrics_, stage_history_, *processed_blink_breakdown_,
*processed_viz_breakdown_);
}
}
void CompositorFrameReporter::ReportCompositorLatencyTraceEvents() const {
if (stage_history_.empty())
return;
if (IsDroppedFrameAffectingSmoothness()) {
devtools_instrumentation::DidDropSmoothnessFrame(
layer_tree_host_id_, args_.frame_time, args_.frame_id.sequence_number);
}
const auto trace_track =
perfetto::Track(base::trace_event::GetNextGlobalTraceId());
TRACE_EVENT_BEGIN(
"cc,benchmark," TRACE_DISABLED_BY_DEFAULT("devtools.timeline.frame"),
"PipelineReporter", trace_track, args_.frame_time,
[&](perfetto::EventContext context) {
using perfetto::protos::pbzero::ChromeFrameReporter;
bool frame_dropped = TestReportType(FrameReportType::kDroppedFrame);
ChromeFrameReporter::State state;
if (frame_dropped) {
state = ChromeFrameReporter::STATE_DROPPED;
} else if (frame_termination_status_ ==
FrameTerminationStatus::kDidNotProduceFrame) {
state = ChromeFrameReporter::STATE_NO_UPDATE_DESIRED;
} else {
state = has_partial_update_
? ChromeFrameReporter::STATE_PRESENTED_PARTIAL
: ChromeFrameReporter::STATE_PRESENTED_ALL;
}
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(has_missing_content_);
if (IsDroppedFrameAffectingSmoothness()) {
DCHECK(state == ChromeFrameReporter::STATE_DROPPED ||
state == ChromeFrameReporter::STATE_PRESENTED_PARTIAL);
reporter->set_affects_smoothness(true);
}
ChromeFrameReporter::ScrollState scroll_state;
switch (scrolling_thread_) {
case FrameSequenceMetrics::ThreadType::kMain:
scroll_state = ChromeFrameReporter::SCROLL_MAIN_THREAD;
break;
case FrameSequenceMetrics::ThreadType::kCompositor:
scroll_state = ChromeFrameReporter::SCROLL_COMPOSITOR_THREAD;
break;
case FrameSequenceMetrics::ThreadType::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_) {
const int stage_type_index = static_cast<int>(stage.stage_type);
CHECK_LT(stage_type_index, static_cast<int>(StageType::kStageTypeCount));
CHECK_GE(stage_type_index, 0);
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_type_index);
TRACE_EVENT_BEGIN(
"cc,benchmark," TRACE_DISABLED_BY_DEFAULT("devtools.timeline.frame"),
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("cc,benchmark",
perfetto::StaticString{breakdown_name}, trace_track,
start_time);
TRACE_EVENT_END("cc,benchmark", trace_track, end_time);
}
}
TRACE_EVENT_END("cc,benchmark", trace_track, stage.end_time);
}
TRACE_EVENT_END("cc,benchmark", 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_) {
const base::TimeTicks generated_timestamp =
event_metrics->GetDispatchStageTimestamp(
EventMetrics::DispatchStage::kGenerated);
const auto trace_id = TRACE_ID_LOCAL(event_metrics.get());
TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1(
"cc,input", "EventLatency", trace_id, generated_timestamp, "event",
event_metrics->GetTypeName());
// Event dispatch stages.
EventMetrics::DispatchStage dispatch_stage =
EventMetrics::DispatchStage::kGenerated;
base::TimeTicks dispatch_timestamp =
event_metrics->GetDispatchStageTimestamp(dispatch_stage);
while (dispatch_stage != EventMetrics::DispatchStage::kMaxValue) {
DCHECK(!dispatch_timestamp.is_null());
// Find the end dispatch stage.
auto end_stage = static_cast<EventMetrics::DispatchStage>(
static_cast<int>(dispatch_stage) + 1);
base::TimeTicks end_timestamp =
event_metrics->GetDispatchStageTimestamp(end_stage);
while (end_timestamp.is_null() &&
end_stage != EventMetrics::DispatchStage::kMaxValue) {
end_stage = static_cast<EventMetrics::DispatchStage>(
static_cast<int>(end_stage) + 1);
end_timestamp = event_metrics->GetDispatchStageTimestamp(end_stage);
}
if (end_timestamp.is_null())
break;
const char* breakdown_name =
GetEventLatencyDispatchBreakdownName(dispatch_stage, end_stage);
TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
"cc,input", breakdown_name, trace_id, dispatch_timestamp);
TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0("cc,input", breakdown_name,
trace_id, end_timestamp);
dispatch_stage = end_stage;
dispatch_timestamp = end_timestamp;
}
// Find the first compositor stage that happens after the final dispatch
// stage.
auto stage_it = std::find_if(stage_history_.begin(), stage_history_.end(),
[dispatch_timestamp](const StageData& stage) {
return stage.start_time > dispatch_timestamp;
});
// TODO(crbug.com/1079116): Ideally, at least the start time of
// SubmitCompositorFrameToPresentationCompositorFrame stage should be
// greater than the final event dispatch timestamp, but apparently, this is
// not always the case (see crbug.com/1093698). For now, skip to the next
// event in such cases. Hopefully, the work to reduce discrepancies between
// the new EventLatency and the old Event.Latency metrics would fix this
// issue. If not, we need to reconsider investigating this issue.
if (stage_it == stage_history_.end())
continue;
DCHECK(dispatch_stage ==
EventMetrics::DispatchStage::kRendererCompositorFinished ||
dispatch_stage ==
EventMetrics::DispatchStage::kRendererMainFinished);
const char* d2c_breakdown_name =
GetEventLatencyDispatchToCompositorBreakdownName(dispatch_stage,
stage_it->stage_type);
TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
"cc,input", d2c_breakdown_name, trace_id, dispatch_timestamp);
TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(
"cc,input", d2c_breakdown_name, trace_id, stage_it->start_time);
// Compositor stages.
for (; stage_it != stage_history_.end(); ++stage_it) {
const int stage_type_index = static_cast<int>(stage_it->stage_type);
CHECK_LT(stage_type_index, static_cast<int>(StageType::kStageTypeCount));
CHECK_GE(stage_type_index, 0);
if (stage_it->start_time >= frame_termination_time_)
break;
DCHECK_GE(stage_it->end_time, stage_it->start_time);
if (stage_it->start_time == stage_it->end_time)
continue;
const char* stage_name = GetStageName(stage_type_index);
TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
"cc,input", stage_name, trace_id, stage_it->start_time);
if (stage_it->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_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0(
"cc,input", breakdown_name, trace_id, start_time);
TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(
"cc,input", breakdown_name, trace_id, end_time);
}
}
TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(
"cc,input", stage_name, trace_id, stage_it->end_time);
}
TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0(
"cc,input", "EventLatency", trace_id, frame_termination_time_);
}
}
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();
}
bool CompositorFrameReporter::IsDroppedFrameAffectingSmoothness() const {
// If the frame was not shown, then it hurt smoothness only if either of the
// threads is affecting smoothness (e.g. running an animation, scroll, pinch,
// etc.).
if (TestReportType(FrameReportType::kDroppedFrame)) {
return smooth_thread_ != SmoothThread::kSmoothNone;
}
// If the frame includes new main-thread update, even if it's for an earlier
// begin-frame, then do not count it as a dropped frame affecting smoothness.
if (is_accompanied_by_main_thread_update_) {
return false;
}
// If the frame was shown, but included only partial updates, then it hurt
// smoothness only if the main-thread is affecting smoothness (e.g. running an
// animation, or scroll etc.).
if (has_partial_update_) {
return smooth_thread_ == SmoothThread::kSmoothMain ||
smooth_thread_ == SmoothThread::kSmoothBoth;
}
// If the frame was shown, and did not include partial updates, then this
// frame did not hurt smoothness.
return false;
}
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();
}
} // namespace cc