[go: nahoru, domu]

blob: fa2db3de1e1e96caa0d1d4bebc0801d4fdf5b2a0 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "cc/metrics/event_latency_tracing_recorder.h"
#include "base/notreached.h"
#include "base/ranges/algorithm.h"
#include "base/time/time.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/metrics/event_metrics.h"
#include "third_party/perfetto/include/perfetto/tracing/track.h"
namespace cc {
namespace {
constexpr char kTracingCategory[] = "cc,benchmark,input,input.scrolling";
bool IsTracingEnabled() {
bool enabled;
TRACE_EVENT_CATEGORY_GROUP_ENABLED(kTracingCategory, &enabled);
return enabled;
}
constexpr base::TimeDelta high_latency_threshold = base::Milliseconds(90);
constexpr perfetto::protos::pbzero::EventLatency::EventType ToProtoEnum(
EventMetrics::EventType event_type) {
#define CASE(event_type, proto_event_type) \
case EventMetrics::EventType::event_type: \
return perfetto::protos::pbzero::EventLatency::proto_event_type
switch (event_type) {
CASE(kMousePressed, MOUSE_PRESSED);
CASE(kMouseReleased, MOUSE_RELEASED);
CASE(kMouseWheel, MOUSE_WHEEL);
CASE(kKeyPressed, KEY_PRESSED);
CASE(kKeyReleased, KEY_RELEASED);
CASE(kTouchPressed, TOUCH_PRESSED);
CASE(kTouchReleased, TOUCH_RELEASED);
CASE(kTouchMoved, TOUCH_MOVED);
CASE(kGestureScrollBegin, GESTURE_SCROLL_BEGIN);
CASE(kGestureScrollUpdate, GESTURE_SCROLL_UPDATE);
CASE(kGestureScrollEnd, GESTURE_SCROLL_END);
CASE(kGestureDoubleTap, GESTURE_DOUBLE_TAP);
CASE(kGestureLongPress, GESTURE_LONG_PRESS);
CASE(kGestureLongTap, GESTURE_LONG_TAP);
CASE(kGestureShowPress, GESTURE_SHOW_PRESS);
CASE(kGestureTap, GESTURE_TAP);
CASE(kGestureTapCancel, GESTURE_TAP_CANCEL);
CASE(kGestureTapDown, GESTURE_TAP_DOWN);
CASE(kGestureTapUnconfirmed, GESTURE_TAP_UNCONFIRMED);
CASE(kGestureTwoFingerTap, GESTURE_TWO_FINGER_TAP);
CASE(kFirstGestureScrollUpdate, FIRST_GESTURE_SCROLL_UPDATE);
CASE(kMouseDragged, MOUSE_DRAGGED);
CASE(kGesturePinchBegin, GESTURE_PINCH_BEGIN);
CASE(kGesturePinchEnd, GESTURE_PINCH_END);
CASE(kGesturePinchUpdate, GESTURE_PINCH_UPDATE);
CASE(kInertialGestureScrollUpdate, INERTIAL_GESTURE_SCROLL_UPDATE);
CASE(kMouseMoved, MOUSE_MOVED_EVENT);
}
}
} // namespace
// static
const char* EventLatencyTracingRecorder::GetDispatchBreakdownName(
EventMetrics::DispatchStage start_stage,
EventMetrics::DispatchStage end_stage) {
switch (start_stage) {
case EventMetrics::DispatchStage::kGenerated:
switch (end_stage) {
case EventMetrics::DispatchStage::
kScrollsBlockingTouchDispatchedToRenderer:
case EventMetrics::DispatchStage::kArrivedInBrowserMain:
return "GenerationToBrowserMain";
case EventMetrics::DispatchStage::kArrivedInRendererCompositor:
return "GenerationToRendererCompositor";
default:
NOTREACHED() << static_cast<int>(end_stage);
return "";
}
case EventMetrics::DispatchStage::kScrollsBlockingTouchDispatchedToRenderer:
switch (end_stage) {
case EventMetrics::DispatchStage::kArrivedInBrowserMain:
// This stage can only be in a Scroll EventLatency. It means a path of
// a corresponding blocking TouchMove from BrowserMain To Renderer To
// BrowserMain. Look at the corresponding TouchMove EventLatency for
// a more detailed breakdown of this stage.
return "TouchRendererHandlingToBrowserMain";
default:
NOTREACHED() << static_cast<int>(end_stage);
return "";
}
case EventMetrics::DispatchStage::kArrivedInBrowserMain:
DCHECK_EQ(end_stage,
EventMetrics::DispatchStage::kArrivedInRendererCompositor);
return "BrowserMainToRendererCompositor";
case EventMetrics::DispatchStage::kArrivedInRendererCompositor:
switch (end_stage) {
case EventMetrics::DispatchStage::kRendererCompositorStarted:
return "RendererCompositorQueueingDelay";
case EventMetrics::DispatchStage::kRendererMainStarted:
return "RendererCompositorToMain";
default:
NOTREACHED() << static_cast<int>(end_stage);
return "";
}
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 "";
}
}
// static
const char* EventLatencyTracingRecorder::GetDispatchToCompositorBreakdownName(
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:
// TODO(crbug.com/1366253): Logs are added to debug NOTREACHED() begin
// hit in crbug/1366253. Remove after investigation is finished.
NOTREACHED() << "Invalid CC stage after compositor thread: "
<< static_cast<int>(compositor_stage);
return "";
}
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:
// TODO(crbug.com/1366253): Logs are added to debug NOTREACHED() begin
// hit in crbug/1366253. Remove after investigation is finished.
NOTREACHED() << "Invalid CC stage after main thread: "
<< static_cast<int>(compositor_stage);
return "";
}
default:
NOTREACHED();
return "";
}
}
// static
const char* EventLatencyTracingRecorder::GetDispatchToTerminationBreakdownName(
EventMetrics::DispatchStage dispatch_stage) {
switch (dispatch_stage) {
case EventMetrics::DispatchStage::kArrivedInRendererCompositor:
return "ArrivedInRendererCompositorToTermination";
case EventMetrics::DispatchStage::kRendererCompositorStarted:
return "RendererCompositorStartedToTermination";
case EventMetrics::DispatchStage::kRendererCompositorFinished:
return "RendererCompositorFinishedToTermination";
case EventMetrics::DispatchStage::kRendererMainStarted:
return "RendererMainStartedToTermination";
case EventMetrics::DispatchStage::kRendererMainFinished:
return "RendererMainFinishedToTermination";
default:
NOTREACHED();
return "";
}
}
// static
void EventLatencyTracingRecorder::RecordEventLatencyTraceEvent(
EventMetrics* event_metrics,
base::TimeTicks termination_time,
base::TimeDelta vsync_interval,
const std::vector<CompositorFrameReporter::StageData>* stage_history,
const CompositorFrameReporter::ProcessedVizBreakdown* viz_breakdown) {
// As there are multiple teardown paths for EventMetrics, we want to denote
// the attempt to trace, even if tracing is currently disabled.
if (IsTracingEnabled()) {
RecordEventLatencyTraceEventInternal(event_metrics, termination_time,
vsync_interval, stage_history,
viz_breakdown);
}
event_metrics->tracing_recorded();
}
void EventLatencyTracingRecorder::RecordEventLatencyTraceEventInternal(
const EventMetrics* event_metrics,
base::TimeTicks termination_time,
base::TimeDelta vsync_interval,
const std::vector<CompositorFrameReporter::StageData>* stage_history,
const CompositorFrameReporter::ProcessedVizBreakdown* viz_breakdown) {
DCHECK(event_metrics);
DCHECK(event_metrics->should_record_tracing());
const base::TimeTicks generated_timestamp =
event_metrics->GetDispatchStageTimestamp(
EventMetrics::DispatchStage::kGenerated);
const auto trace_track =
perfetto::Track(base::trace_event::GetNextGlobalTraceId());
TRACE_EVENT_BEGIN(
kTracingCategory, "EventLatency", trace_track, generated_timestamp,
[&](perfetto::EventContext context) {
auto* event =
context.event<perfetto::protos::pbzero::ChromeTrackEvent>();
auto* event_latency = event->set_event_latency();
event_latency->set_event_type(ToProtoEnum(event_metrics->type()));
bool has_high_latency =
(termination_time - generated_timestamp) > high_latency_threshold;
event_latency->set_has_high_latency(has_high_latency);
for (auto stage : event_metrics->GetHighLatencyStages()) {
// TODO(crbug.com/1334827): Consider changing the high_latency_stage
// type from a string to enum type in chrome_track_event.proto,
// similar to event_type.
event_latency->add_high_latency_stage(stage);
}
if (event_metrics->trace_id().has_value()) {
event_latency->set_event_latency_id(
event_metrics->trace_id()->value());
}
const ScrollUpdateEventMetrics* scroll_update =
event_metrics->AsScrollUpdate();
if (scroll_update &&
scroll_update->is_janky_scrolled_frame().has_value()) {
event_latency->set_is_janky_scrolled_frame(
scroll_update->is_janky_scrolled_frame().value());
}
event_latency->set_vsync_interval_ms(vsync_interval.InMillisecondsF());
});
// 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 =
GetDispatchBreakdownName(dispatch_stage, end_stage);
TRACE_EVENT_BEGIN(kTracingCategory, perfetto::StaticString{breakdown_name},
trace_track, dispatch_timestamp);
TRACE_EVENT_END(kTracingCategory, trace_track, end_timestamp);
dispatch_stage = end_stage;
dispatch_timestamp = end_timestamp;
}
if (stage_history) {
DCHECK(viz_breakdown);
// Find the first compositor stage that starts at the same time or after the
// end of the final event dispatch stage.
auto stage_it = base::ranges::lower_bound(
*stage_history, dispatch_timestamp, {},
&CompositorFrameReporter::StageData::start_time);
// TODO(crbug.com/1330903): Ideally, at least the start time of
// SubmitCompositorFrameToPresentationCompositorFrame stage should be
// greater than or equal to the final event dispatch timestamp, but
// apparently, this is not always the case (see crbug.com/1330903). Skip
// recording compositor stages for now until we investigate the issue.
if (stage_it != stage_history->end()) {
DCHECK(dispatch_stage ==
EventMetrics::DispatchStage::kRendererCompositorFinished ||
dispatch_stage ==
EventMetrics::DispatchStage::kRendererMainFinished);
// Record dispatch-to-compositor stage only if it has non-zero duration.
if (dispatch_timestamp < stage_it->start_time) {
const char* d2c_breakdown_name = GetDispatchToCompositorBreakdownName(
dispatch_stage, stage_it->stage_type);
TRACE_EVENT_BEGIN(kTracingCategory,
perfetto::StaticString{d2c_breakdown_name},
trace_track, dispatch_timestamp);
TRACE_EVENT_END(kTracingCategory, trace_track, stage_it->start_time);
}
// Compositor stages.
for (; stage_it != stage_history->end(); ++stage_it) {
if (stage_it->start_time >= 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 =
CompositorFrameReporter::GetStageName(stage_it->stage_type);
TRACE_EVENT_BEGIN(kTracingCategory, perfetto::StaticString{stage_name},
trace_track, stage_it->start_time);
if (stage_it->stage_type ==
CompositorFrameReporter::StageType::
kSubmitCompositorFrameToPresentationCompositorFrame) {
DCHECK(viz_breakdown);
for (auto it = 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 =
CompositorFrameReporter::GetVizBreakdownName(it.GetBreakdown());
TRACE_EVENT_BEGIN(kTracingCategory,
perfetto::StaticString{breakdown_name},
trace_track, start_time);
TRACE_EVENT_END(kTracingCategory, trace_track, end_time);
}
}
TRACE_EVENT_END(kTracingCategory, trace_track, stage_it->end_time);
}
}
} else {
DCHECK(!viz_breakdown);
const char* d2t_breakdown_name =
GetDispatchToTerminationBreakdownName(dispatch_stage);
TRACE_EVENT_BEGIN(kTracingCategory,
perfetto::StaticString{d2t_breakdown_name}, trace_track,
dispatch_timestamp);
TRACE_EVENT_END(kTracingCategory, trace_track, termination_time);
}
TRACE_EVENT_END(kTracingCategory, trace_track, termination_time);
}
} // namespace cc