| // 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 <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/metrics/histogram_macros.h" |
| #include "base/stl_util.h" |
| #include "base/strings/strcat.h" |
| #include "base/trace_event/trace_event.h" |
| #include "cc/base/rolling_time_delta_history.h" |
| #include "cc/metrics/frame_sequence_tracker.h" |
| #include "cc/metrics/latency_ukm_reporter.h" |
| #include "ui/events/types/event_type.h" |
| |
| namespace cc { |
| namespace { |
| |
| using StageType = CompositorFrameReporter::StageType; |
| using BlinkBreakdown = CompositorFrameReporter::BlinkBreakdown; |
| using VizBreakdown = CompositorFrameReporter::VizBreakdown; |
| |
| constexpr int kFrameReportTypeCount = |
| static_cast<int>(CompositorFrameReporter::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; |
| |
| // Names for the viz breakdowns that are shown in trace as substages under |
| // PipelineReporter -> 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::kBreakdownCount: |
| NOTREACHED(); |
| return ""; |
| } |
| } |
| |
| // Names for CompositorFrameReporter::StageType, which should be updated in case |
| // of changes to the enum. |
| constexpr const char* GetStageName(int stage_type_index) { |
| switch (stage_type_index) { |
| case static_cast<int>(StageType::kBeginImplFrameToSendBeginMainFrame): |
| return "BeginImplFrameToSendBeginMainFrame"; |
| case static_cast<int>(StageType::kSendBeginMainFrameToCommit): |
| 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): |
| 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>(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::kComposite) + |
| kBlinkBreakdownInitialIndex: |
| return "SendBeginMainFrameToCommit.Composite"; |
| case static_cast<int>(BlinkBreakdown::kPaint) + kBlinkBreakdownInitialIndex: |
| return "SendBeginMainFrameToCommit.Paint"; |
| case static_cast<int>(BlinkBreakdown::kScrollingCoordinator) + |
| kBlinkBreakdownInitialIndex: |
| return "SendBeginMainFrameToCommit.ScrollingCoordinator"; |
| 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: |
| return ""; |
| } |
| } |
| |
| // Names for CompositorFrameReporter::FrameReportType, which should be |
| // updated in case of changes to the enum. |
| constexpr const char* kReportTypeNames[]{"", "MissedDeadlineFrame.", |
| "DroppedFrame."}; |
| |
| 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 int kCompositorLatencyHistogramMin = 1; |
| constexpr int kCompositorLatencyHistogramMax = 350000; |
| constexpr int kCompositorLatencyHistogramBucketCount = 50; |
| |
| constexpr int kEventLatencyEventTypeCount = |
| static_cast<int>(ui::EventType::ET_LAST); |
| constexpr int kEventLatencyScrollTypeCount = |
| static_cast<int>(ScrollInputType::kMaxValue) + 1; |
| constexpr int kMaxEventLatencyHistogramIndex = |
| kEventLatencyEventTypeCount * kEventLatencyScrollTypeCount; |
| constexpr int kEventLatencyHistogramMin = 1; |
| constexpr int kEventLatencyHistogramMax = 5000000; |
| constexpr int kEventLatencyHistogramBucketCount = 100; |
| |
| bool ShouldReportLatencyMetricsForSequenceType( |
| FrameSequenceTrackerType sequence_type) { |
| return sequence_type != FrameSequenceTrackerType::kUniversal; |
| } |
| |
| 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); |
| DCHECK( |
| ShouldReportLatencyMetricsForSequenceType(frame_sequence_tracker_type)); |
| const char* tracker_type_name = |
| FrameSequenceTracker::GetFrameSequenceTrackerTypeName( |
| frame_sequence_tracker_type); |
| DCHECK(tracker_type_name); |
| return base::StrCat({"CompositorLatency.", |
| kReportTypeNames[report_type_index], tracker_type_name, |
| *tracker_type_name ? "." : "", |
| GetStageName(stage_type_index)}); |
| } |
| |
| std::string GetEventLatencyHistogramBaseName( |
| const EventMetrics& event_metrics) { |
| const bool is_scroll = event_metrics.scroll_input_type().has_value(); |
| return base::StrCat( |
| {"EventLatency.", event_metrics.GetTypeName(), is_scroll ? "." : nullptr, |
| is_scroll ? event_metrics.GetScrollTypeName() : nullptr}); |
| } |
| |
| } // namespace |
| |
| CompositorFrameReporter::CompositorFrameReporter( |
| const base::flat_set<FrameSequenceTrackerType>* active_trackers, |
| const viz::BeginFrameId& id, |
| const base::TimeTicks frame_deadline, |
| LatencyUkmReporter* latency_ukm_reporter, |
| bool should_report_metrics) |
| : frame_id_(id), |
| should_report_metrics_(should_report_metrics), |
| active_trackers_(active_trackers), |
| latency_ukm_reporter_(latency_ukm_reporter), |
| frame_deadline_(frame_deadline) {} |
| |
| std::unique_ptr<CompositorFrameReporter> |
| CompositorFrameReporter::CopyReporterAtBeginImplStage() const { |
| if (stage_history_.empty() || |
| stage_history_.front().stage_type != |
| StageType::kBeginImplFrameToSendBeginMainFrame || |
| !did_finish_impl_frame()) |
| return nullptr; |
| auto new_reporter = std::make_unique<CompositorFrameReporter>( |
| active_trackers_, frame_id_, frame_deadline_, latency_ukm_reporter_, |
| should_report_metrics_); |
| new_reporter->did_finish_impl_frame_ = did_finish_impl_frame_; |
| new_reporter->impl_frame_finish_time_ = impl_frame_finish_time_; |
| new_reporter->current_stage_.stage_type = |
| StageType::kBeginImplFrameToSendBeginMainFrame; |
| new_reporter->current_stage_.start_time = stage_history_.front().start_time; |
| 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; |
| } |
| |
| 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::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(!did_abort_main_frame_); |
| |
| did_abort_main_frame_ = true; |
| impl_frame_finish_time_ = timestamp; |
| // impl_frame_finish_time_ can be used for the end of BeginMain to Commit |
| // stage |
| } |
| |
| void CompositorFrameReporter::OnDidNotProduceFrame() { |
| did_not_produce_frame_ = true; |
| } |
| |
| 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::SetEventsMetrics( |
| std::vector<EventMetrics> events_metrics) { |
| DCHECK_EQ(0u, events_metrics_.size()); |
| events_metrics_ = std::move(events_metrics); |
| } |
| |
| void CompositorFrameReporter::DroppedFrame() { |
| report_type_ = FrameReportType::kDroppedFrame; |
| } |
| |
| void CompositorFrameReporter::MissedDeadlineFrame() { |
| report_type_ = FrameReportType::kMissedDeadlineFrame; |
| } |
| |
| void CompositorFrameReporter::TerminateReporter() { |
| if (frame_termination_status_ == FrameTerminationStatus::kUnknown) |
| TerminateFrame(FrameTerminationStatus::kUnknown, base::TimeTicks::Now()); |
| DCHECK_EQ(current_stage_.start_time, base::TimeTicks()); |
| bool report_compositor_latency = false; |
| bool report_event_latency = false; |
| bool report_missed_deadline_frame = false; |
| const char* termination_status_str = nullptr; |
| switch (frame_termination_status_) { |
| case FrameTerminationStatus::kPresentedFrame: |
| report_compositor_latency = true; |
| report_event_latency = true; |
| termination_status_str = "presented_frame"; |
| if (frame_deadline_ < frame_termination_time_) |
| report_missed_deadline_frame = true; |
| break; |
| case FrameTerminationStatus::kDidNotPresentFrame: |
| report_compositor_latency = true; |
| DroppedFrame(); |
| termination_status_str = "did_not_present_frame"; |
| break; |
| case FrameTerminationStatus::kReplacedByNewReporter: |
| report_compositor_latency = true; |
| DroppedFrame(); |
| termination_status_str = "replaced_by_new_reporter_at_same_stage"; |
| break; |
| case FrameTerminationStatus::kDidNotProduceFrame: |
| termination_status_str = "did_not_produce_frame"; |
| break; |
| case FrameTerminationStatus::kUnknown: |
| termination_status_str = "terminated_before_ending"; |
| break; |
| } |
| |
| ReportAllTraceEvents(termination_status_str); |
| |
| // Only report compositor latency histograms if the frame was produced. |
| if (should_report_metrics_ && report_compositor_latency) { |
| 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); |
| ReportLatencyHistograms(report_event_latency, report_missed_deadline_frame); |
| } |
| } |
| |
| void CompositorFrameReporter::ReportLatencyHistograms( |
| bool report_event_latency, |
| bool report_delayed_latency) { |
| ReportCompositorLatencyHistograms(); |
| |
| if (report_delayed_latency) { |
| // If the frames are delayed also report them under MissedDeadlineFrame. |
| MissedDeadlineFrame(); |
| ReportCompositorLatencyHistograms(); |
| } |
| |
| // Only report event latency histograms if the frame was presented. |
| if (report_event_latency) |
| ReportEventLatencyHistograms(); |
| } |
| |
| void CompositorFrameReporter::ReportCompositorLatencyHistograms() const { |
| UMA_HISTOGRAM_ENUMERATION("CompositorLatency.Type", report_type_); |
| for (const StageData& stage : stage_history_) { |
| ReportStageHistogramWithBreakdown(stage); |
| |
| for (const auto& frame_sequence_tracker_type : *active_trackers_) { |
| // Report stage breakdowns. |
| ReportStageHistogramWithBreakdown(stage, frame_sequence_tracker_type); |
| } |
| } |
| if (latency_ukm_reporter_) { |
| latency_ukm_reporter_->ReportLatencyUkm(report_type_, stage_history_, |
| active_trackers_, viz_breakdown_); |
| } |
| } |
| |
| void CompositorFrameReporter::ReportStageHistogramWithBreakdown( |
| const CompositorFrameReporter::StageData& stage, |
| FrameSequenceTrackerType frame_sequence_tracker_type) const { |
| if (!ShouldReportLatencyMetricsForSequenceType(frame_sequence_tracker_type)) |
| return; |
| 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: |
| ReportBlinkBreakdowns(stage.start_time, frame_sequence_tracker_type); |
| break; |
| case StageType::kSubmitCompositorFrameToPresentationCompositorFrame: |
| ReportVizBreakdowns(stage.start_time, frame_sequence_tracker_type); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void CompositorFrameReporter::ReportBlinkBreakdowns( |
| base::TimeTicks start_time, |
| FrameSequenceTrackerType frame_sequence_tracker_type) const { |
| std::vector<std::pair<BlinkBreakdown, base::TimeDelta>> breakdowns = { |
| {BlinkBreakdown::kHandleInputEvents, |
| blink_breakdown_.handle_input_events}, |
| {BlinkBreakdown::kAnimate, blink_breakdown_.animate}, |
| {BlinkBreakdown::kStyleUpdate, blink_breakdown_.style_update}, |
| {BlinkBreakdown::kLayoutUpdate, blink_breakdown_.layout_update}, |
| {BlinkBreakdown::kPrepaint, blink_breakdown_.prepaint}, |
| {BlinkBreakdown::kComposite, blink_breakdown_.composite}, |
| {BlinkBreakdown::kPaint, blink_breakdown_.paint}, |
| {BlinkBreakdown::kScrollingCoordinator, |
| blink_breakdown_.scrolling_coordinator}, |
| {BlinkBreakdown::kCompositeCommit, blink_breakdown_.composite_commit}, |
| {BlinkBreakdown::kUpdateLayers, blink_breakdown_.update_layers}, |
| {BlinkBreakdown::kBeginMainSentToStarted, |
| begin_main_frame_start_ - start_time}}; |
| |
| for (const auto& pair : breakdowns) { |
| ReportCompositorLatencyHistogram( |
| frame_sequence_tracker_type, |
| kBlinkBreakdownInitialIndex + static_cast<int>(pair.first), |
| pair.second); |
| } |
| } |
| |
| void CompositorFrameReporter::ReportVizBreakdownStage( |
| VizBreakdown stage, |
| const base::TimeTicks start_time, |
| const base::TimeTicks end_time, |
| FrameSequenceTrackerType frame_sequence_tracker_type) const { |
| base::TimeDelta time_delta = end_time - start_time; |
| ReportCompositorLatencyHistogram( |
| frame_sequence_tracker_type, |
| kVizBreakdownInitialIndex + static_cast<int>(stage), time_delta); |
| } |
| |
| void CompositorFrameReporter::ReportVizBreakdowns( |
| base::TimeTicks start_time, |
| FrameSequenceTrackerType frame_sequence_tracker_type) const { |
| // Check if viz_breakdown is set. Testing indicates that sometimes the |
| // received_compositor_frame_timestamp can be earlier than the given |
| // start_time. Avoid reporting negative times. |
| if (viz_breakdown_.received_compositor_frame_timestamp.is_null() || |
| viz_breakdown_.received_compositor_frame_timestamp < start_time) { |
| return; |
| } |
| ReportVizBreakdownStage(VizBreakdown::kSubmitToReceiveCompositorFrame, |
| start_time, |
| viz_breakdown_.received_compositor_frame_timestamp, |
| frame_sequence_tracker_type); |
| |
| if (viz_breakdown_.draw_start_timestamp.is_null()) |
| return; |
| ReportVizBreakdownStage(VizBreakdown::kReceivedCompositorFrameToStartDraw, |
| viz_breakdown_.received_compositor_frame_timestamp, |
| viz_breakdown_.draw_start_timestamp, |
| frame_sequence_tracker_type); |
| |
| if (viz_breakdown_.swap_timings.is_null()) |
| return; |
| ReportVizBreakdownStage( |
| VizBreakdown::kStartDrawToSwapStart, viz_breakdown_.draw_start_timestamp, |
| viz_breakdown_.swap_timings.swap_start, frame_sequence_tracker_type); |
| |
| ReportVizBreakdownStage( |
| VizBreakdown::kSwapStartToSwapEnd, viz_breakdown_.swap_timings.swap_start, |
| viz_breakdown_.swap_timings.swap_end, frame_sequence_tracker_type); |
| |
| ReportVizBreakdownStage(VizBreakdown::kSwapEndToPresentationCompositorFrame, |
| viz_breakdown_.swap_timings.swap_end, |
| viz_breakdown_.presentation_feedback.timestamp, |
| frame_sequence_tracker_type); |
| } |
| |
| void CompositorFrameReporter::ReportCompositorLatencyHistogram( |
| FrameSequenceTrackerType frame_sequence_tracker_type, |
| const int stage_type_index, |
| base::TimeDelta time_delta) const { |
| 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); |
| |
| STATIC_HISTOGRAM_POINTER_GROUP( |
| GetCompositorLatencyHistogramName( |
| report_type_index, frame_sequence_tracker_type, stage_type_index), |
| histogram_index, kMaxCompositorLatencyHistogramIndex, |
| AddTimeMicrosecondsGranularity(time_delta), |
| base::Histogram::FactoryGet( |
| GetCompositorLatencyHistogramName( |
| report_type_index, frame_sequence_tracker_type, stage_type_index), |
| kCompositorLatencyHistogramMin, kCompositorLatencyHistogramMax, |
| kCompositorLatencyHistogramBucketCount, |
| base::HistogramBase::kUmaTargetedHistogramFlag)); |
| } |
| |
| void CompositorFrameReporter::ReportEventLatencyHistograms() const { |
| for (const EventMetrics& event_metrics : events_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_input_type() |
| ? static_cast<int>(*event_metrics.scroll_input_type()) |
| : 0; |
| const int histogram_index = |
| event_type_index * kEventLatencyScrollTypeCount + scroll_type_index; |
| |
| // For scroll events, report total latency up to gpu-swap-end. This is |
| // useful in comparing new EventLatency metrics with LatencyInfo-based |
| // scroll event latency metrics. |
| if (event_metrics.scroll_input_type() && |
| !viz_breakdown_.swap_timings.is_null()) { |
| const base::TimeDelta swap_end_latency = |
| viz_breakdown_.swap_timings.swap_end - event_metrics.time_stamp(); |
| const std::string swap_end_histogram_name = |
| histogram_base_name + ".TotalLatencyToSwapEnd"; |
| STATIC_HISTOGRAM_POINTER_GROUP( |
| swap_end_histogram_name, histogram_index, |
| kMaxEventLatencyHistogramIndex, |
| AddTimeMicrosecondsGranularity(swap_end_latency), |
| base::Histogram::FactoryGet( |
| swap_end_histogram_name, kEventLatencyHistogramMin, |
| kEventLatencyHistogramMax, kEventLatencyHistogramBucketCount, |
| base::HistogramBase::kUmaTargetedHistogramFlag)); |
| } |
| |
| base::TimeDelta total_latency = |
| frame_termination_time_ - event_metrics.time_stamp(); |
| const std::string histogram_name = histogram_base_name + ".TotalLatency"; |
| STATIC_HISTOGRAM_POINTER_GROUP( |
| histogram_name, histogram_index, kMaxEventLatencyHistogramIndex, |
| AddTimeMicrosecondsGranularity(total_latency), |
| base::Histogram::FactoryGet( |
| histogram_name, kEventLatencyHistogramMin, |
| kEventLatencyHistogramMax, kEventLatencyHistogramBucketCount, |
| base::HistogramBase::kUmaTargetedHistogramFlag)); |
| |
| const auto trace_id = TRACE_ID_LOCAL(&event_metrics); |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1( |
| "cc,input", "EventLatency", trace_id, event_metrics.time_stamp(), |
| "event", event_metrics.GetTypeName()); |
| |
| // Report the breakdowns. |
| // It is possible for an event to arrive in the compositor in the middle of |
| // a frame (e.g. the browser received the event *after* renderer received a |
| // begin-impl, and the event reached the compositor before that frame |
| // ended). To handle such cases, find the first stage that happens after the |
| // event's arrival in the browser. |
| // TODO(mohsen): Report the breakdowns in UMA too. |
| size_t index = 0; |
| for (; index < stage_history_.size(); ++index) { |
| const auto& stage = stage_history_[index]; |
| if (stage.start_time > event_metrics.time_stamp()) { |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0( |
| "cc,input", "BrowserToRendererCompositor", trace_id, |
| event_metrics.time_stamp()); |
| TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0( |
| "cc,input", "BrowserToRendererCompositor", trace_id, |
| stage.start_time); |
| break; |
| } |
| } |
| |
| for (; index < stage_history_.size(); ++index) { |
| const auto& stage = stage_history_[index]; |
| if (stage.stage_type == StageType::kTotalLatency) |
| break; |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0( |
| "cc,input", GetStageName(static_cast<int>(stage.stage_type)), |
| trace_id, stage.start_time); |
| TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0( |
| "cc,input", GetStageName(static_cast<int>(stage.stage_type)), |
| trace_id, stage.end_time); |
| } |
| TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0( |
| "cc,input", "EventLatency", trace_id, frame_termination_time_); |
| } |
| } |
| |
| void CompositorFrameReporter::ReportVizBreakdownTrace( |
| VizBreakdown substage, |
| const base::TimeTicks start_time, |
| const base::TimeTicks end_time) const { |
| // Do not report events with negative time span. |
| if (end_time < start_time) |
| return; |
| |
| const char* stage_name = GetVizBreakdownName(substage); |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0( |
| "cc,benchmark", stage_name, TRACE_ID_LOCAL(this), start_time); |
| |
| TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0( |
| "cc,benchmark", stage_name, TRACE_ID_LOCAL(this), end_time); |
| } |
| |
| void CompositorFrameReporter::ReportAllTraceEvents( |
| const char* termination_status_str) const { |
| if (stage_history_.empty()) |
| return; |
| |
| const auto trace_id = TRACE_ID_LOCAL(this); |
| const auto& startstage = stage_history_.front(); |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP1( |
| "cc,benchmark", "PipelineReporter", trace_id, startstage.start_time, |
| "frame_id", frame_id_.ToString()); |
| |
| // The trace-viewer cannot seem to handle a single child-event that has the |
| // same start/end timestamps as the parent-event. So avoid adding the |
| // child-events if there's only one. |
| if (stage_history_.size() > 1) { |
| 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); |
| const char* name = GetStageName(stage_type_index); |
| TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0( |
| "cc,benchmark", name, trace_id, stage.start_time); |
| |
| if (stage.stage_type == |
| StageType::kSubmitCompositorFrameToPresentationCompositorFrame) { |
| const struct { |
| VizBreakdown stage; |
| base::TimeTicks start_time; |
| base::TimeTicks end_time; |
| } sub_stages[] = { |
| {VizBreakdown::kSubmitToReceiveCompositorFrame, stage.start_time, |
| viz_breakdown_.received_compositor_frame_timestamp}, |
| {VizBreakdown::kReceivedCompositorFrameToStartDraw, |
| viz_breakdown_.received_compositor_frame_timestamp, |
| viz_breakdown_.draw_start_timestamp}, |
| {VizBreakdown::kStartDrawToSwapStart, |
| viz_breakdown_.draw_start_timestamp, |
| viz_breakdown_.swap_timings.swap_start}, |
| {VizBreakdown::kSwapStartToSwapEnd, |
| viz_breakdown_.swap_timings.swap_start, |
| viz_breakdown_.swap_timings.swap_end}, |
| {VizBreakdown::kSwapEndToPresentationCompositorFrame, |
| viz_breakdown_.swap_timings.swap_end, |
| viz_breakdown_.presentation_feedback.timestamp}}; |
| for (const auto& sub : sub_stages) { |
| if (sub.start_time.is_null() || sub.end_time.is_null()) |
| break; |
| ReportVizBreakdownTrace(sub.stage, sub.start_time, sub.end_time); |
| } |
| } |
| |
| TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP0("cc,benchmark", name, |
| trace_id, stage.end_time); |
| } |
| } |
| |
| const char* submission_status_str = |
| report_type_ == FrameReportType::kDroppedFrame ? "dropped_frame" |
| : "non_dropped_frame"; |
| TRACE_EVENT_NESTABLE_ASYNC_END_WITH_TIMESTAMP2( |
| "cc,benchmark", "PipelineReporter", trace_id, |
| stage_history_.back().end_time, "termination_status", |
| termination_status_str, "compositor_frame_submission_status", |
| submission_status_str); |
| } |
| |
| base::TimeDelta CompositorFrameReporter::SumOfStageHistory() const { |
| base::TimeDelta sum; |
| for (const StageData& stage : stage_history_) |
| sum += stage.end_time - stage.start_time; |
| return sum; |
| } |
| |
| } // namespace cc |