| // Copyright 2020 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_metrics.h" |
| |
| #include <algorithm> |
| #include <array> |
| #include <optional> |
| #include <ostream> |
| #include <string> |
| #include <utility> |
| |
| #include "base/check.h" |
| #include "base/compiler_specific.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/notreached.h" |
| #include "base/time/default_tick_clock.h" |
| #include "cc/metrics/event_latency_tracing_recorder.h" |
| #include "ui/events/types/event_type.h" |
| |
| namespace cc { |
| namespace { |
| |
| // Histogram bucketing for scroll event metrics. |
| constexpr base::TimeDelta kScrollHistogramMin = base::Milliseconds(4); |
| constexpr base::TimeDelta kScrollHistogramMax = base::Milliseconds(500); |
| constexpr size_t kScrollHistogramBucketCount = 50; |
| |
| struct InterestingEvents { |
| EventMetrics::EventType metrics_event_type; |
| const char* name; |
| |
| ui::EventType ui_event_type; |
| std::optional<bool> scroll_is_inertial = std::nullopt; |
| std::optional<ScrollUpdateEventMetrics::ScrollUpdateType> scroll_update_type = |
| std::nullopt; |
| |
| std::optional<EventMetrics::HistogramBucketing> histogram_bucketing = |
| std::nullopt; |
| }; |
| constexpr auto kInterestingEvents = std::to_array<InterestingEvents>({ |
| #define EVENT_TYPE(type_name, ui_type, ...) \ |
| { \ |
| .metrics_event_type = EventMetrics::EventType::k##type_name, \ |
| .name = #type_name, .ui_event_type = ui_type, __VA_ARGS__ \ |
| } |
| EVENT_TYPE(MousePressed, ui::EventType::kMousePressed), |
| EVENT_TYPE(MouseReleased, ui::EventType::kMouseReleased), |
| EVENT_TYPE(MouseWheel, ui::EventType::kMousewheel), |
| EVENT_TYPE(KeyPressed, ui::EventType::kKeyPressed), |
| EVENT_TYPE(KeyReleased, ui::EventType::kKeyReleased), |
| EVENT_TYPE(TouchPressed, ui::EventType::kTouchPressed), |
| EVENT_TYPE(TouchReleased, ui::EventType::kTouchReleased), |
| EVENT_TYPE(TouchMoved, ui::EventType::kTouchMoved), |
| EVENT_TYPE(GestureScrollBegin, |
| ui::EventType::kGestureScrollBegin, |
| .scroll_is_inertial = false, |
| .histogram_bucketing = {{.min = kScrollHistogramMin, |
| .max = kScrollHistogramMax, |
| .count = kScrollHistogramBucketCount, |
| .version_suffix = "2"}}), |
| EVENT_TYPE(GestureScrollUpdate, |
| ui::EventType::kGestureScrollUpdate, |
| .scroll_is_inertial = false, |
| .scroll_update_type = |
| ScrollUpdateEventMetrics::ScrollUpdateType::kContinued, |
| .histogram_bucketing = {{.min = kScrollHistogramMin, |
| .max = kScrollHistogramMax, |
| .count = kScrollHistogramBucketCount, |
| .version_suffix = "2"}}), |
| EVENT_TYPE(GestureScrollEnd, |
| ui::EventType::kGestureScrollEnd, |
| .scroll_is_inertial = false, |
| .histogram_bucketing = {{.min = kScrollHistogramMin, |
| .max = kScrollHistogramMax, |
| .count = kScrollHistogramBucketCount, |
| .version_suffix = "2"}}), |
| EVENT_TYPE(GestureDoubleTap, ui::EventType::kGestureDoubleTap), |
| EVENT_TYPE(GestureLongPress, ui::EventType::kGestureLongPress), |
| EVENT_TYPE(GestureLongTap, ui::EventType::kGestureLongTap), |
| EVENT_TYPE(GestureShowPress, ui::EventType::kGestureShowPress), |
| EVENT_TYPE(GestureTap, ui::EventType::kGestureTap), |
| EVENT_TYPE(GestureTapCancel, ui::EventType::kGestureTapCancel), |
| EVENT_TYPE(GestureTapDown, ui::EventType::kGestureTapDown), |
| EVENT_TYPE(GestureTapUnconfirmed, ui::EventType::kGestureTapUnconfirmed), |
| EVENT_TYPE(GestureTwoFingerTap, ui::EventType::kGestureTwoFingerTap), |
| EVENT_TYPE(FirstGestureScrollUpdate, |
| ui::EventType::kGestureScrollUpdate, |
| .scroll_is_inertial = false, |
| .scroll_update_type = |
| ScrollUpdateEventMetrics::ScrollUpdateType::kStarted, |
| .histogram_bucketing = {{.min = kScrollHistogramMin, |
| .max = kScrollHistogramMax, |
| .count = kScrollHistogramBucketCount, |
| .version_suffix = "2"}}), |
| EVENT_TYPE(MouseDragged, ui::EventType::kMouseDragged), |
| EVENT_TYPE(GesturePinchBegin, ui::EventType::kGesturePinchBegin), |
| EVENT_TYPE(GesturePinchEnd, ui::EventType::kGesturePinchEnd), |
| EVENT_TYPE(GesturePinchUpdate, ui::EventType::kGesturePinchUpdate), |
| EVENT_TYPE(InertialGestureScrollUpdate, |
| ui::EventType::kGestureScrollUpdate, |
| .scroll_is_inertial = true, |
| .scroll_update_type = |
| ScrollUpdateEventMetrics::ScrollUpdateType::kContinued, |
| .histogram_bucketing = {{.min = kScrollHistogramMin, |
| .max = kScrollHistogramMax, |
| .count = kScrollHistogramBucketCount, |
| .version_suffix = "2"}}), |
| EVENT_TYPE(MouseMoved, ui::EventType::kMouseMoved), |
| EVENT_TYPE(InertialGestureScrollEnd, |
| ui::EventType::kGestureScrollEnd, |
| .scroll_is_inertial = true), |
| #undef EVENT_TYPE |
| }); |
| static_assert(std::size(kInterestingEvents) == |
| static_cast<int>(EventMetrics::EventType::kMaxValue) + 1, |
| "EventMetrics::EventType has changed."); |
| |
| struct ScrollTypes { |
| ScrollEventMetrics::ScrollType metrics_scroll_type; |
| ui::ScrollInputType ui_input_type; |
| const char* name; |
| }; |
| constexpr auto kScrollTypes = std::to_array<ScrollTypes>({ |
| #define SCROLL_TYPE(name) \ |
| { \ |
| ScrollEventMetrics::ScrollType::k##name, ui::ScrollInputType::k##name, \ |
| #name \ |
| } |
| SCROLL_TYPE(Autoscroll), |
| SCROLL_TYPE(Scrollbar), |
| SCROLL_TYPE(Touchscreen), |
| SCROLL_TYPE(Wheel), |
| #undef SCROLL_TYPE |
| }); |
| static_assert(std::size(kScrollTypes) == |
| static_cast<int>(ScrollEventMetrics::ScrollType::kMaxValue) + |
| 1, |
| "ScrollEventMetrics::ScrollType has changed."); |
| |
| struct PinchTypes { |
| PinchEventMetrics::PinchType metrics_pinch_type; |
| ui::ScrollInputType ui_input_type; |
| const char* name; |
| }; |
| constexpr auto kPinchTypes = std::to_array<PinchTypes>({ |
| #define PINCH_TYPE(metrics_name, ui_name) \ |
| { \ |
| PinchEventMetrics::PinchType::k##metrics_name, \ |
| ui::ScrollInputType::k##ui_name, #metrics_name \ |
| } |
| PINCH_TYPE(Touchpad, Wheel), |
| PINCH_TYPE(Touchscreen, Touchscreen), |
| #undef PINCH_TYPE |
| }); |
| static_assert(std::size(kPinchTypes) == |
| static_cast<int>(PinchEventMetrics::PinchType::kMaxValue) + 1, |
| "PinchEventMetrics::PinchType has changed."); |
| |
| std::optional<EventMetrics::EventType> ToInterestingEventType( |
| ui::EventType ui_event_type, |
| std::optional<bool> scroll_is_inertial, |
| std::optional<ScrollUpdateEventMetrics::ScrollUpdateType> |
| scroll_update_type) { |
| for (size_t i = 0; i < std::size(kInterestingEvents); i++) { |
| const auto& interesting_event = kInterestingEvents[i]; |
| if (ui_event_type == interesting_event.ui_event_type && |
| scroll_is_inertial == interesting_event.scroll_is_inertial && |
| scroll_update_type == interesting_event.scroll_update_type) { |
| EventMetrics::EventType metrics_event_type = |
| static_cast<EventMetrics::EventType>(i); |
| DCHECK_EQ(metrics_event_type, interesting_event.metrics_event_type); |
| return metrics_event_type; |
| } |
| } |
| return std::nullopt; |
| } |
| |
| ScrollEventMetrics::ScrollType ToScrollType(ui::ScrollInputType ui_input_type) { |
| for (size_t i = 0; i < std::size(kScrollTypes); i++) { |
| if (ui_input_type == kScrollTypes[i].ui_input_type) { |
| auto metrics_scroll_type = static_cast<ScrollEventMetrics::ScrollType>(i); |
| DCHECK_EQ(metrics_scroll_type, kScrollTypes[i].metrics_scroll_type); |
| return metrics_scroll_type; |
| } |
| } |
| NOTREACHED(); |
| } |
| |
| PinchEventMetrics::PinchType ToPinchType(ui::ScrollInputType ui_input_type) { |
| for (size_t i = 0; i < std::size(kPinchTypes); i++) { |
| if (ui_input_type == kPinchTypes[i].ui_input_type) { |
| auto metrics_pinch_type = static_cast<PinchEventMetrics::PinchType>(i); |
| DCHECK_EQ(metrics_pinch_type, kPinchTypes[i].metrics_pinch_type); |
| return metrics_pinch_type; |
| } |
| } |
| NOTREACHED(); |
| } |
| |
| bool IsGestureScroll(ui::EventType type) { |
| return type == ui::EventType::kGestureScrollBegin || |
| type == ui::EventType::kGestureScrollUpdate || |
| type == ui::EventType::kGestureScrollEnd; |
| } |
| |
| bool IsGesturePinch(ui::EventType type) { |
| return type == ui::EventType::kGesturePinchBegin || |
| type == ui::EventType::kGesturePinchUpdate || |
| type == ui::EventType::kGesturePinchEnd; |
| } |
| |
| bool IsGestureScrollUpdate(ui::EventType type) { |
| return type == ui::EventType::kGestureScrollUpdate; |
| } |
| |
| } // namespace |
| |
| // EventMetrics: |
| |
| // static |
| std::unique_ptr<EventMetrics> EventMetrics::Create( |
| ui::EventType type, |
| base::TimeTicks timestamp, |
| std::optional<TraceId> trace_id) { |
| return Create(type, timestamp, base::TimeTicks(), trace_id); |
| } |
| |
| // static |
| std::unique_ptr<EventMetrics> EventMetrics::Create( |
| ui::EventType type, |
| base::TimeTicks timestamp, |
| base::TimeTicks arrived_in_browser_main_timestamp, |
| std::optional<TraceId> trace_id) { |
| // TODO(crbug.com/40160689): We expect that `timestamp` is not null, but there |
| // seems to be some tests that are emitting events with null timestamp. We |
| // should investigate and try to fix those cases and add a `DCHECK` here to |
| // assert `timestamp` is not null. |
| |
| DCHECK(!IsGestureScroll(type) && !IsGesturePinch(type)); |
| |
| std::unique_ptr<EventMetrics> metrics = |
| CreateInternal(type, timestamp, arrived_in_browser_main_timestamp, |
| base::DefaultTickClock::GetInstance(), trace_id); |
| if (!metrics) |
| return nullptr; |
| |
| metrics->SetDispatchStageTimestamp( |
| DispatchStage::kArrivedInRendererCompositor); |
| return metrics; |
| } |
| |
| // static |
| std::unique_ptr<EventMetrics> EventMetrics::CreateForTesting( |
| ui::EventType type, |
| base::TimeTicks timestamp, |
| base::TimeTicks arrived_in_browser_main_timestamp, |
| const base::TickClock* tick_clock, |
| std::optional<TraceId> trace_id) { |
| DCHECK(!timestamp.is_null()); |
| |
| std::unique_ptr<EventMetrics> metrics = |
| CreateInternal(type, timestamp, base::TimeTicks(), tick_clock, trace_id); |
| if (!metrics) |
| return nullptr; |
| |
| metrics->SetDispatchStageTimestamp( |
| DispatchStage::kArrivedInRendererCompositor); |
| return metrics; |
| } |
| |
| // static |
| std::unique_ptr<EventMetrics> EventMetrics::CreateFromExisting( |
| ui::EventType type, |
| DispatchStage last_dispatch_stage, |
| const EventMetrics* existing) { |
| // Generally, if `existing` is `nullptr` (the existing event is not of an |
| // interesting type), the new event won't be of an interesting type, too, and |
| // we can immediately return `nullptr`. The only exception is some tests that |
| // are not interested in reporting metrics, in which case we can immediately |
| // return `nullptr`, too, as they are not interested in reporting metrics. |
| if (!existing) |
| return nullptr; |
| |
| std::unique_ptr<EventMetrics> metrics = |
| CreateInternal(type, base::TimeTicks(), base::TimeTicks(), |
| existing->tick_clock_, std::nullopt); |
| if (!metrics) |
| return nullptr; |
| |
| // Use timestamps of all stages (including "Generated" stage) up to |
| // `last_dispatch_stage` from `existing`. |
| metrics->CopyTimestampsFrom(*existing, last_dispatch_stage); |
| |
| return metrics; |
| } |
| |
| // static |
| std::unique_ptr<EventMetrics> EventMetrics::CreateInternal( |
| ui::EventType type, |
| base::TimeTicks timestamp, |
| base::TimeTicks arrived_in_browser_main_timestamp, |
| const base::TickClock* tick_clock, |
| std::optional<TraceId> trace_id) { |
| std::optional<EventType> interesting_type = |
| ToInterestingEventType(type, /*scroll_is_inertial=*/std::nullopt, |
| /*scroll_update_type=*/std::nullopt); |
| if (!interesting_type) |
| return nullptr; |
| return base::WrapUnique(new EventMetrics(*interesting_type, timestamp, |
| arrived_in_browser_main_timestamp, |
| tick_clock, trace_id)); |
| } |
| |
| EventMetrics::EventMetrics(EventType type, |
| base::TimeTicks timestamp, |
| const base::TickClock* tick_clock, |
| std::optional<TraceId> trace_id) |
| : type_(type), tick_clock_(tick_clock), trace_id_(trace_id) { |
| dispatch_stage_timestamps_[static_cast<int>(DispatchStage::kGenerated)] = |
| timestamp; |
| } |
| |
| EventMetrics::EventMetrics(EventType type, |
| base::TimeTicks timestamp, |
| base::TimeTicks arrived_in_browser_main_timestamp, |
| const base::TickClock* tick_clock, |
| std::optional<TraceId> trace_id) |
| : EventMetrics(type, timestamp, tick_clock, trace_id) { |
| dispatch_stage_timestamps_[static_cast<int>( |
| DispatchStage::kArrivedInBrowserMain)] = |
| arrived_in_browser_main_timestamp; |
| } |
| |
| EventMetrics::EventMetrics(const EventMetrics& other) |
| : type_(other.type_), |
| tick_clock_(other.tick_clock_), |
| should_record_tracing_(false) { |
| CopyTimestampsFrom(other, DispatchStage::kMaxValue); |
| } |
| |
| EventMetrics::~EventMetrics() { |
| if (should_record_tracing()) { |
| EventLatencyTracingRecorder::RecordEventLatencyTraceEvent( |
| this, base::TimeTicks::Now(), nullptr, nullptr, nullptr, std::nullopt); |
| } |
| } |
| |
| void EventMetrics::CoalesceWith(const EventMetrics& newer_event) { |
| caused_frame_update_ |= newer_event.caused_frame_update_; |
| } |
| |
| const char* EventMetrics::GetTypeName() const { |
| return GetTypeName(type_); |
| } |
| |
| // static |
| const char* EventMetrics::GetTypeName(EventMetrics::EventType type) { |
| return kInterestingEvents[static_cast<int>(type)].name; |
| } |
| |
| // static |
| bool EventMetrics::ShouldKeepEvenWithoutCausingFrameUpdate( |
| EventMetrics::EventType type) { |
| switch (type) { |
| // `CompositorFrameReporter::ReportScrollJankMetrics()` needs to know about |
| // all gesture scroll updates, so that the scroll jank metric could |
| // correctly evaluate the fast scroll and fling continuity rules. |
| case EventType::kGestureScrollUpdate: |
| case EventType::kInertialGestureScrollUpdate: |
| case EventType::kFirstGestureScrollUpdate: |
| // `CompositorFrameReporter::ReportScrollJankMetrics()` needs to know about |
| // all gesture scroll ends, so that it could correctly report per-scroll |
| // jank metrics at the end of each scroll. |
| case EventType::kGestureScrollEnd: |
| case EventType::kInertialGestureScrollEnd: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| const std::optional<EventMetrics::HistogramBucketing>& |
| EventMetrics::GetHistogramBucketing() const { |
| return kInterestingEvents[static_cast<int>(type_)].histogram_bucketing; |
| } |
| |
| void EventMetrics::SetHighLatencyStage(const std::string& stage) { |
| high_latency_stages_.push_back(stage); |
| } |
| |
| void EventMetrics::SetDispatchStageTimestamp(DispatchStage stage) { |
| DCHECK(UNSAFE_TODO(dispatch_stage_timestamps_[static_cast<size_t>(stage)]) |
| .is_null()); |
| |
| UNSAFE_TODO(dispatch_stage_timestamps_[static_cast<size_t>(stage)]) = |
| tick_clock_->NowTicks(); |
| } |
| |
| void EventMetrics::SetDispatchStageTimestamp(DispatchStage stage, |
| base::TimeTicks timestamp) { |
| DCHECK(UNSAFE_TODO(dispatch_stage_timestamps_[static_cast<size_t>(stage)]) |
| .is_null()); |
| |
| UNSAFE_TODO(dispatch_stage_timestamps_[static_cast<size_t>(stage)]) = |
| timestamp; |
| } |
| |
| base::TimeTicks EventMetrics::GetDispatchStageTimestamp( |
| DispatchStage stage) const { |
| return UNSAFE_TODO(dispatch_stage_timestamps_[static_cast<size_t>(stage)]); |
| } |
| |
| void EventMetrics::ResetToDispatchStage(DispatchStage stage) { |
| for (size_t stage_index = static_cast<size_t>(stage) + 1; |
| stage_index <= static_cast<size_t>(DispatchStage::kMaxValue); |
| stage_index++) { |
| UNSAFE_TODO(dispatch_stage_timestamps_[stage_index] = base::TimeTicks()); |
| } |
| } |
| |
| bool EventMetrics::HasSmoothInputEvent() const { |
| return type_ == EventType::kMouseDragged || type_ == EventType::kTouchMoved; |
| } |
| |
| ScrollEventMetrics* EventMetrics::AsScroll() { |
| return nullptr; |
| } |
| |
| const ScrollEventMetrics* EventMetrics::AsScroll() const { |
| return const_cast<EventMetrics*>(this)->AsScroll(); |
| } |
| |
| ScrollUpdateEventMetrics* EventMetrics::AsScrollUpdate() { |
| return nullptr; |
| } |
| |
| const ScrollUpdateEventMetrics* EventMetrics::AsScrollUpdate() const { |
| return const_cast<EventMetrics*>(this)->AsScrollUpdate(); |
| } |
| |
| PinchEventMetrics* EventMetrics::AsPinch() { |
| return nullptr; |
| } |
| |
| const PinchEventMetrics* EventMetrics::AsPinch() const { |
| return const_cast<EventMetrics*>(this)->AsPinch(); |
| } |
| |
| std::unique_ptr<EventMetrics> EventMetrics::Clone() const { |
| return base::WrapUnique(new EventMetrics(*this)); |
| } |
| |
| void EventMetrics::CopyTimestampsFrom(const EventMetrics& other, |
| DispatchStage last_dispatch_stage) { |
| DCHECK_LE(last_dispatch_stage, DispatchStage::kMaxValue); |
| UNSAFE_TODO(std::copy(other.dispatch_stage_timestamps_, |
| other.dispatch_stage_timestamps_ + |
| static_cast<size_t>(last_dispatch_stage) + 1, |
| dispatch_stage_timestamps_)); |
| } |
| |
| // ScrollEventMetrics |
| |
| // static |
| std::unique_ptr<ScrollEventMetrics> ScrollEventMetrics::Create( |
| ui::EventType type, |
| ui::ScrollInputType input_type, |
| bool is_inertial, |
| base::TimeTicks timestamp, |
| base::TimeTicks arrived_in_browser_main_timestamp, |
| base::TimeTicks blocking_touch_dispatched_to_renderer, |
| std::optional<TraceId> trace_id) { |
| // TODO(crbug.com/40160689): We expect that `timestamp` is not null, but there |
| // seems to be some tests that are emitting events with null timestamp. We |
| // should investigate and try to fix those cases and add a `DCHECK` here to |
| // assert `timestamp` is not null. |
| |
| DCHECK(IsGestureScroll(type) && !IsGestureScrollUpdate(type)); |
| |
| std::unique_ptr<ScrollEventMetrics> metrics = |
| CreateInternal(type, input_type, is_inertial, timestamp, |
| arrived_in_browser_main_timestamp, |
| base::DefaultTickClock::GetInstance(), trace_id); |
| if (!metrics) |
| return nullptr; |
| |
| metrics->SetDispatchStageTimestamp( |
| DispatchStage::kArrivedInRendererCompositor); |
| metrics->SetDispatchStageTimestamp( |
| DispatchStage::kScrollsBlockingTouchDispatchedToRenderer, |
| blocking_touch_dispatched_to_renderer); |
| return metrics; |
| } |
| |
| // static |
| std::unique_ptr<ScrollEventMetrics> ScrollEventMetrics::CreateForBrowser( |
| ui::EventType type, |
| ui::ScrollInputType input_type, |
| bool is_inertial, |
| base::TimeTicks timestamp, |
| std::optional<TraceId> trace_id) { |
| return Create(type, input_type, is_inertial, timestamp, |
| /*arrived_in_browser_main_timestamp=*/base::TimeTicks(), |
| /*blocking_touch_dispatched_to_renderer=*/base::TimeTicks(), |
| trace_id); |
| } |
| |
| // static |
| std::unique_ptr<ScrollEventMetrics> ScrollEventMetrics::CreateForTesting( |
| ui::EventType type, |
| ui::ScrollInputType input_type, |
| bool is_inertial, |
| base::TimeTicks timestamp, |
| base::TimeTicks arrived_in_browser_main_timestamp, |
| const base::TickClock* tick_clock) { |
| DCHECK(!timestamp.is_null()); |
| |
| std::unique_ptr<ScrollEventMetrics> metrics = CreateInternal( |
| type, input_type, is_inertial, timestamp, |
| arrived_in_browser_main_timestamp, tick_clock, std::nullopt); |
| if (!metrics) |
| return nullptr; |
| |
| metrics->SetDispatchStageTimestamp( |
| DispatchStage::kArrivedInRendererCompositor); |
| return metrics; |
| } |
| |
| // static |
| std::unique_ptr<ScrollEventMetrics> ScrollEventMetrics::CreateFromExisting( |
| ui::EventType type, |
| ui::ScrollInputType input_type, |
| bool is_inertial, |
| DispatchStage last_dispatch_stage, |
| const EventMetrics* existing) { |
| // Generally, if `existing` is `nullptr` (the existing event is not of an |
| // interesting type), the new event won't be of an interesting type, too, and |
| // we can immediately return `nullptr`. The only exception is some tests that |
| // are not interested in reporting metrics, in which case we can immediately |
| // return `nullptr`, too, as they are not interested in reporting metrics. |
| if (!existing) |
| return nullptr; |
| |
| std::unique_ptr<ScrollEventMetrics> metrics = |
| CreateInternal(type, input_type, is_inertial, base::TimeTicks(), |
| base::TimeTicks(), existing->tick_clock_, std::nullopt); |
| if (!metrics) |
| return nullptr; |
| |
| // Use timestamps of all stages (including "Generated" stage) up to |
| // `last_dispatch_stage` from `existing`. |
| metrics->CopyTimestampsFrom(*existing, last_dispatch_stage); |
| |
| return metrics; |
| } |
| |
| // static |
| std::unique_ptr<ScrollEventMetrics> ScrollEventMetrics::CreateInternal( |
| ui::EventType type, |
| ui::ScrollInputType input_type, |
| bool is_inertial, |
| base::TimeTicks timestamp, |
| base::TimeTicks arrived_in_browser_main_timestamp, |
| const base::TickClock* tick_clock, |
| std::optional<TraceId> trace_id) { |
| std::optional<EventType> interesting_type = |
| ToInterestingEventType(type, is_inertial, |
| /*scroll_update_type=*/std::nullopt); |
| if (!interesting_type) |
| return nullptr; |
| return base::WrapUnique(new ScrollEventMetrics( |
| *interesting_type, ToScrollType(input_type), timestamp, |
| arrived_in_browser_main_timestamp, tick_clock, trace_id)); |
| } |
| |
| ScrollEventMetrics::ScrollEventMetrics( |
| EventType type, |
| ScrollType scroll_type, |
| base::TimeTicks timestamp, |
| base::TimeTicks arrived_in_browser_main_timestamp, |
| const base::TickClock* tick_clock, |
| std::optional<TraceId> trace_id) |
| : EventMetrics(type, |
| timestamp, |
| arrived_in_browser_main_timestamp, |
| tick_clock, |
| trace_id), |
| scroll_type_(scroll_type) {} |
| |
| ScrollEventMetrics::ScrollEventMetrics(const ScrollEventMetrics&) = default; |
| |
| ScrollEventMetrics::~ScrollEventMetrics() { |
| if (should_record_tracing()) { |
| EventLatencyTracingRecorder::RecordEventLatencyTraceEvent( |
| this, base::TimeTicks::Now(), nullptr, nullptr, nullptr, std::nullopt); |
| } |
| } |
| |
| const char* ScrollEventMetrics::GetScrollTypeName() const { |
| return kScrollTypes[static_cast<int>(scroll_type_)].name; |
| } |
| |
| ScrollEventMetrics* ScrollEventMetrics::AsScroll() { |
| return this; |
| } |
| |
| std::unique_ptr<EventMetrics> ScrollEventMetrics::Clone() const { |
| return base::WrapUnique(new ScrollEventMetrics(*this)); |
| } |
| |
| // ScrollUpdateEventMetrics |
| |
| // static |
| std::unique_ptr<ScrollUpdateEventMetrics> ScrollUpdateEventMetrics::Create( |
| ui::EventType type, |
| ui::ScrollInputType input_type, |
| bool is_inertial, |
| ScrollUpdateType scroll_update_type, |
| float delta, |
| base::TimeTicks timestamp, |
| base::TimeTicks arrived_in_browser_main_timestamp, |
| base::TimeTicks blocking_touch_dispatched_to_renderer, |
| std::optional<TraceId> trace_id) { |
| // TODO(crbug.com/40160689): We expect that `timestamp` is not null, but there |
| // seems to be some tests that are emitting events with null timestamp. We |
| // should investigate and try to fix those cases and add a `DCHECK` here to |
| // assert `timestamp` is not null. |
| |
| DCHECK(IsGestureScrollUpdate(type)); |
| |
| std::unique_ptr<ScrollUpdateEventMetrics> metrics = |
| CreateInternal(type, input_type, is_inertial, scroll_update_type, delta, |
| timestamp, arrived_in_browser_main_timestamp, |
| base::DefaultTickClock::GetInstance(), trace_id); |
| if (!metrics) |
| return nullptr; |
| |
| metrics->SetDispatchStageTimestamp( |
| DispatchStage::kArrivedInRendererCompositor); |
| metrics->SetDispatchStageTimestamp( |
| DispatchStage::kScrollsBlockingTouchDispatchedToRenderer, |
| blocking_touch_dispatched_to_renderer); |
| return metrics; |
| } |
| |
| // static |
| std::unique_ptr<ScrollUpdateEventMetrics> |
| ScrollUpdateEventMetrics::CreateForBrowser(ui::EventType type, |
| ui::ScrollInputType input_type, |
| bool is_inertial, |
| ScrollUpdateType scroll_update_type, |
| float delta, |
| base::TimeTicks timestamp, |
| TraceId trace_id) { |
| return Create( |
| type, input_type, is_inertial, scroll_update_type, delta, timestamp, |
| /*arrived_in_browser_main_timestamp=*/base::TimeTicks(), |
| /*blocking_touch_dispatched_to_renderer=*/base::TimeTicks(), trace_id); |
| } |
| |
| // static |
| std::unique_ptr<ScrollUpdateEventMetrics> |
| ScrollUpdateEventMetrics::CreateForTesting( |
| ui::EventType type, |
| ui::ScrollInputType input_type, |
| bool is_inertial, |
| ScrollUpdateType scroll_update_type, |
| float delta, |
| base::TimeTicks timestamp, |
| base::TimeTicks arrived_in_browser_main_timestamp, |
| const base::TickClock* tick_clock, |
| std::optional<TraceId> trace_id) { |
| DCHECK(!timestamp.is_null()); |
| |
| std::unique_ptr<ScrollUpdateEventMetrics> metrics = CreateInternal( |
| type, input_type, is_inertial, scroll_update_type, delta, timestamp, |
| arrived_in_browser_main_timestamp, tick_clock, trace_id); |
| if (!metrics) |
| return nullptr; |
| |
| metrics->SetDispatchStageTimestamp( |
| DispatchStage::kArrivedInRendererCompositor); |
| return metrics; |
| } |
| |
| // static |
| std::unique_ptr<ScrollUpdateEventMetrics> |
| ScrollUpdateEventMetrics::CreateFromExisting( |
| ui::EventType type, |
| ui::ScrollInputType input_type, |
| bool is_inertial, |
| ScrollUpdateType scroll_update_type, |
| float delta, |
| DispatchStage last_dispatch_stage, |
| const EventMetrics* existing) { |
| // Since the new event is of an interesting type, we expect the existing event |
| // to be of an interesting type, too; which means `existing` should not be |
| // `nullptr`. However, some tests that are not interested in reporting |
| // metrics, don't create metrics objects even for events of interesting types. |
| // Return `nullptr` if that's the case. |
| if (!existing) |
| return nullptr; |
| |
| base::TimeTicks generation_ts = |
| existing->GetDispatchStageTimestamp(DispatchStage::kGenerated); |
| std::unique_ptr<ScrollUpdateEventMetrics> metrics = CreateInternal( |
| type, input_type, is_inertial, scroll_update_type, delta, generation_ts, |
| base::TimeTicks(), existing->tick_clock_, std::nullopt); |
| if (!metrics) |
| return nullptr; |
| |
| // Use timestamps of all stages (including "Generated" stage) up to |
| // `last_dispatch_stage` from `existing`. |
| metrics->CopyTimestampsFrom(*existing, last_dispatch_stage); |
| |
| return metrics; |
| } |
| |
| // static |
| std::unique_ptr<ScrollUpdateEventMetrics> |
| ScrollUpdateEventMetrics::CreateInternal( |
| ui::EventType type, |
| ui::ScrollInputType input_type, |
| bool is_inertial, |
| ScrollUpdateType scroll_update_type, |
| float delta, |
| base::TimeTicks timestamp, |
| base::TimeTicks arrived_in_browser_main_timestamp, |
| const base::TickClock* tick_clock, |
| std::optional<TraceId> trace_id) { |
| std::optional<EventType> interesting_type = |
| ToInterestingEventType(type, is_inertial, scroll_update_type); |
| if (!interesting_type) |
| return nullptr; |
| return base::WrapUnique(new ScrollUpdateEventMetrics( |
| *interesting_type, ToScrollType(input_type), scroll_update_type, delta, |
| timestamp, arrived_in_browser_main_timestamp, tick_clock, trace_id)); |
| } |
| |
| ScrollUpdateEventMetrics::ScrollUpdateEventMetrics( |
| EventType type, |
| ScrollType scroll_type, |
| ScrollUpdateType scroll_update_type, |
| float delta, |
| base::TimeTicks timestamp, |
| base::TimeTicks arrived_in_browser_main_timestamp, |
| const base::TickClock* tick_clock, |
| std::optional<TraceId> trace_id) |
| : ScrollEventMetrics(type, |
| scroll_type, |
| timestamp, |
| arrived_in_browser_main_timestamp, |
| tick_clock, |
| trace_id), |
| delta_(delta), |
| predicted_delta_(delta), |
| last_timestamp_(timestamp) {} |
| |
| ScrollUpdateEventMetrics::ScrollUpdateEventMetrics( |
| const ScrollUpdateEventMetrics&) = default; |
| |
| ScrollUpdateEventMetrics::~ScrollUpdateEventMetrics() { |
| if (should_record_tracing()) { |
| EventLatencyTracingRecorder::RecordEventLatencyTraceEvent( |
| this, base::TimeTicks::Now(), nullptr, nullptr, nullptr, std::nullopt); |
| } |
| } |
| |
| void ScrollUpdateEventMetrics::CoalesceWith( |
| const ScrollUpdateEventMetrics& newer_scroll_update) { |
| EventMetrics::CoalesceWith(newer_scroll_update); |
| last_timestamp_ = newer_scroll_update.last_timestamp_; |
| delta_ += newer_scroll_update.delta_; |
| predicted_delta_ += newer_scroll_update.predicted_delta_; |
| coalesced_event_count_ += newer_scroll_update.coalesced_event_count_; |
| did_scroll_ |= newer_scroll_update.did_scroll_; |
| } |
| |
| ScrollUpdateEventMetrics* ScrollUpdateEventMetrics::AsScrollUpdate() { |
| return this; |
| } |
| |
| std::unique_ptr<EventMetrics> ScrollUpdateEventMetrics::Clone() const { |
| return base::WrapUnique(new ScrollUpdateEventMetrics(*this)); |
| } |
| |
| // PinchEventMetrics |
| |
| // static |
| std::unique_ptr<PinchEventMetrics> PinchEventMetrics::Create( |
| ui::EventType type, |
| ui::ScrollInputType input_type, |
| base::TimeTicks timestamp, |
| TraceId trace_id) { |
| // TODO(crbug.com/40160689): We expect that `timestamp` is not null, but there |
| // seems to be some tests that are emitting events with null timestamp. We |
| // should investigate and try to fix those cases and add a `DCHECK` here to |
| // assert `timestamp` is not null. |
| |
| DCHECK(IsGesturePinch(type)); |
| |
| std::unique_ptr<PinchEventMetrics> metrics = |
| CreateInternal(type, input_type, timestamp, |
| base::DefaultTickClock::GetInstance(), trace_id); |
| if (!metrics) |
| return nullptr; |
| |
| metrics->SetDispatchStageTimestamp( |
| DispatchStage::kArrivedInRendererCompositor); |
| return metrics; |
| } |
| |
| // static |
| std::unique_ptr<PinchEventMetrics> PinchEventMetrics::CreateForTesting( |
| ui::EventType type, |
| ui::ScrollInputType input_type, |
| base::TimeTicks timestamp, |
| const base::TickClock* tick_clock) { |
| DCHECK(!timestamp.is_null()); |
| |
| std::unique_ptr<PinchEventMetrics> metrics = |
| CreateInternal(type, input_type, timestamp, tick_clock, std::nullopt); |
| if (!metrics) |
| return nullptr; |
| |
| metrics->SetDispatchStageTimestamp( |
| DispatchStage::kArrivedInRendererCompositor); |
| return metrics; |
| } |
| |
| // static |
| std::unique_ptr<PinchEventMetrics> PinchEventMetrics::CreateInternal( |
| ui::EventType type, |
| ui::ScrollInputType input_type, |
| base::TimeTicks timestamp, |
| const base::TickClock* tick_clock, |
| std::optional<TraceId> trace_id) { |
| std::optional<EventType> interesting_type = |
| ToInterestingEventType(type, /*scroll_is_inertial=*/std::nullopt, |
| /*scroll_update_type=*/std::nullopt); |
| if (!interesting_type) |
| return nullptr; |
| return base::WrapUnique( |
| new PinchEventMetrics(*interesting_type, ToPinchType(input_type), |
| timestamp, tick_clock, trace_id)); |
| } |
| |
| PinchEventMetrics::PinchEventMetrics(EventType type, |
| PinchType pinch_type, |
| base::TimeTicks timestamp, |
| const base::TickClock* tick_clock, |
| std::optional<TraceId> trace_id) |
| : EventMetrics(type, timestamp, tick_clock, trace_id), |
| pinch_type_(pinch_type) {} |
| |
| PinchEventMetrics::PinchEventMetrics(const PinchEventMetrics&) = default; |
| |
| PinchEventMetrics::~PinchEventMetrics() { |
| if (should_record_tracing()) { |
| EventLatencyTracingRecorder::RecordEventLatencyTraceEvent( |
| this, base::TimeTicks::Now(), nullptr, nullptr, nullptr, std::nullopt); |
| } |
| } |
| |
| const char* PinchEventMetrics::GetPinchTypeName() const { |
| return kPinchTypes[static_cast<int>(pinch_type_)].name; |
| } |
| |
| PinchEventMetrics* PinchEventMetrics::AsPinch() { |
| return this; |
| } |
| |
| std::unique_ptr<EventMetrics> PinchEventMetrics::Clone() const { |
| return base::WrapUnique(new PinchEventMetrics(*this)); |
| } |
| |
| // EventMetricsSet |
| EventMetricsSet::EventMetricsSet() = default; |
| EventMetricsSet::~EventMetricsSet() = default; |
| EventMetricsSet::EventMetricsSet(EventMetrics::List main_thread_event_metrics, |
| EventMetrics::List impl_thread_event_metrics, |
| EventMetrics::List raster_thread_event_metrics) |
| : main_event_metrics(std::move(main_thread_event_metrics)), |
| impl_event_metrics(std::move(impl_thread_event_metrics)), |
| raster_event_metrics(std::move(raster_thread_event_metrics)) {} |
| EventMetricsSet::EventMetricsSet(EventMetricsSet&& other) = default; |
| EventMetricsSet& EventMetricsSet::operator=(EventMetricsSet&& other) = default; |
| |
| } // namespace cc |