#include "cc/metrics/event_latency_tracing_recorder.h"
#include <algorithm>
#include "base/feature_list.h"
#include "base/notreached.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/base/features.h"
#include "cc/metrics/event_metrics.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.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 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);
#if BUILDFLAG(ARKWEB_DRAG_DROP)
CASE(kGestureDragLongPress, GESTURE_DRAG_LONG_PRESS);
#endif
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);
CASE(kInertialGestureScrollEnd, INERTIAL_GESTURE_SCROLL_END);
}
#undef CASE
}
const char* GetVizBreakdownToPresentationName(
CompositorFrameReporter::VizBreakdown breakdown) {
switch (breakdown) {
case CompositorFrameReporter::VizBreakdown::kSwapStartToSwapEnd:
return "SwapStartToPresentation";
case CompositorFrameReporter::VizBreakdown::kLatchToSwapEnd:
return "LatchToPresentation";
default:
return "Unknown";
}
}
}
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);
}
case EventMetrics::DispatchStage::kScrollsBlockingTouchDispatchedToRenderer:
switch (end_stage) {
case EventMetrics::DispatchStage::kArrivedInBrowserMain:
return "TouchRendererHandlingToBrowserMain";
default:
NOTREACHED() << static_cast<int>(end_stage);
}
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);
}
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();
}
}
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";
case CompositorFrameReporter::StageType::
kEndActivateToSubmitUpdateDisplayTree:
return "RendererCompositorFinishedToSubmitUpdateDisplayTree";
case CompositorFrameReporter::StageType::
kSubmitUpdateDisplayTreeToPresentationCompositorFrame:
return "RendererCompositorFinishedToPresentationCompositorFrame";
default:
NOTREACHED() << "Invalid CC stage after compositor thread: "
<< static_cast<int>(compositor_stage);
}
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";
case CompositorFrameReporter::StageType::
kEndActivateToSubmitUpdateDisplayTree:
return "RendererMainFinishedToSubmitUpdateDisplayTree";
case CompositorFrameReporter::StageType::
kSubmitUpdateDisplayTreeToPresentationCompositorFrame:
return "RendererMainFinishedToPresentationCompositorFrame";
default:
NOTREACHED() << "Invalid CC stage after main thread: "
<< static_cast<int>(compositor_stage);
}
default:
NOTREACHED();
}
}
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() << "Invalid CC stage before termination: "
<< static_cast<int>(dispatch_stage);
}
}
void EventLatencyTracingRecorder::RecordEventLatencyTraceEvent(
EventMetrics* event_metrics,
base::TimeTicks termination_time,
const viz::BeginFrameArgs* args,
const std::vector<CompositorFrameReporter::StageData>* stage_history,
const CompositorFrameReporter::ProcessedVizBreakdown* viz_breakdown,
std::optional<int64_t> display_trace_id) {
DCHECK(event_metrics);
absl::Cleanup mark_recorded = [event_metrics] {
event_metrics->tracing_recorded();
};
if (!IsTracingEnabled()) {
return;
}
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()));
static constexpr auto kHighLatencyThreshold = base::Milliseconds(90);
bool has_high_latency =
(termination_time - generated_timestamp) > kHighLatencyThreshold;
event_latency->set_has_high_latency(has_high_latency);
for (const auto& stage : event_metrics->GetHighLatencyStages()) {
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());
}
if (args) {
event_latency->set_vsync_interval_ms(
args->interval.InMillisecondsF());
event_latency->set_surface_frame_trace_id(args->trace_id);
}
if (display_trace_id) {
event_latency->set_display_trace_id(*display_trace_id);
}
});
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());
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);
auto stage_it = std::ranges::lower_bound(
*stage_history, dispatch_timestamp, {},
&CompositorFrameReporter::StageData::start_time);
if (stage_it != stage_history->end()) {
DCHECK(dispatch_stage ==
EventMetrics::DispatchStage::kRendererCompositorFinished ||
dispatch_stage ==
EventMetrics::DispatchStage::kRendererMainFinished);
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);
}
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) ||
(stage_it->stage_type ==
CompositorFrameReporter::StageType::
kSubmitUpdateDisplayTreeToPresentationCompositorFrame)) {
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 || start_time >= termination_time) {
continue;
}
CompositorFrameReporter::VizBreakdown breakdown = it.GetBreakdown();
const char* breakdown_name = nullptr;
if (end_time > termination_time) {
end_time = termination_time;
breakdown_name = GetVizBreakdownToPresentationName(breakdown);
} else {
breakdown_name =
CompositorFrameReporter::GetVizBreakdownName(breakdown);
}
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);
}
}