// Copyright 2017 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 "ui/latency/latency_tracker.h" #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" #include "base/rand_util.h" #include "base/trace_event/trace_event.h" #include "services/metrics/public/cpp/ukm_entry_builder.h" #include "services/metrics/public/cpp/ukm_recorder.h" #include "ui/latency/latency_histogram_macros.h" // Impose some restrictions for tests etc, but also be lenient since some of the // data come from untrusted sources. #define DCHECK_AND_RETURN_ON_FAIL(x) \ DCHECK(x); \ if (!(x)) \ return; namespace ui { namespace { std::string LatencySourceEventTypeToInputModalityString( ui::SourceEventType type) { switch (type) { case ui::SourceEventType::WHEEL: return "Wheel"; case ui::SourceEventType::MOUSE: return "Mouse"; case ui::SourceEventType::TOUCH: return "Touch"; case ui::SourceEventType::KEY_PRESS: return "KeyPress"; default: return ""; } } // This UMA metric tracks the time from when the original wheel event is created // to when the scroll gesture results in final frame swap. All scroll events are // included in this metric. void RecordUmaEventLatencyScrollWheelTimeToScrollUpdateSwapBegin2Histogram( const ui::LatencyInfo::LatencyComponent& start, const ui::LatencyInfo::LatencyComponent& end) { CONFIRM_EVENT_TIMES_EXIST(start, end); UMA_HISTOGRAM_CUSTOM_COUNTS( "Event.Latency.Scroll.Wheel.TimeToScrollUpdateSwapBegin2", std::max(static_cast(0), (end.last_event_time - start.first_event_time).InMicroseconds()), 1, 1000000, 100); } } // namespace LatencyTracker::LatencyTracker(bool metric_sampling, ukm::SourceId ukm_source_id) : metric_sampling_(metric_sampling), ukm_source_id_(ukm_source_id) { } void LatencyTracker::OnEventStart(LatencyInfo* latency) { static uint64_t global_trace_id = 0; latency->set_trace_id(++global_trace_id); latency->set_ukm_source_id(ukm_source_id_); } void LatencyTracker::OnGpuSwapBuffersCompleted(const LatencyInfo& latency) { LatencyInfo::LatencyComponent gpu_swap_end_component; if (!latency.FindLatency( ui::INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT, 0, &gpu_swap_end_component)) { return; } LatencyInfo::LatencyComponent gpu_swap_begin_component; bool found_component = latency.FindLatency( ui::INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT, 0, &gpu_swap_begin_component); DCHECK_AND_RETURN_ON_FAIL(found_component); LatencyInfo::LatencyComponent tab_switch_component; if (latency.FindLatency(ui::TAB_SHOW_COMPONENT, &tab_switch_component)) { base::TimeDelta delta = gpu_swap_end_component.event_time - tab_switch_component.event_time; for (size_t i = 0; i < tab_switch_component.event_count; i++) { UMA_HISTOGRAM_TIMES("MPArch.RWH_TabSwitchPaintDuration", delta); TRACE_EVENT_ASYNC_END0("latency", "TabSwitching::Latency", latency.trace_id()); } } if (!latency.FindLatency(ui::INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT, nullptr)) { return; } ui::SourceEventType source_event_type = latency.source_event_type(); if (source_event_type == ui::SourceEventType::WHEEL || source_event_type == ui::SourceEventType::MOUSE || source_event_type == ui::SourceEventType::TOUCH || source_event_type == ui::SourceEventType::KEY_PRESS) { ComputeEndToEndLatencyHistograms(gpu_swap_begin_component, gpu_swap_end_component, latency); } } void LatencyTracker::ReportUkmScrollLatency( const InputMetricEvent& metric_event, const LatencyInfo::LatencyComponent& start_component, const LatencyInfo::LatencyComponent& time_to_scroll_update_swap_begin_component, const LatencyInfo::LatencyComponent& time_to_handled_component, bool is_main_thread, const ukm::SourceId ukm_source_id) { CONFIRM_EVENT_TIMES_EXIST(start_component, time_to_scroll_update_swap_begin_component) CONFIRM_EVENT_TIMES_EXIST(start_component, time_to_handled_component) // Only report a subset of this metric as the volume is too high. if (metric_sampling_ && !sampling_scheme_[static_cast(metric_event)].ShouldReport()) return; ukm::UkmRecorder* ukm_recorder = ukm::UkmRecorder::Get(); if (ukm_source_id == ukm::kInvalidSourceId || !ukm_recorder) return; std::string event_name = ""; switch (metric_event) { case InputMetricEvent::SCROLL_BEGIN_TOUCH: event_name = "Event.ScrollBegin.Touch"; break; case InputMetricEvent::SCROLL_UPDATE_TOUCH: event_name = "Event.ScrollUpdate.Touch"; break; case InputMetricEvent::SCROLL_BEGIN_WHEEL: event_name = "Event.ScrollBegin.Wheel"; break; case InputMetricEvent::SCROLL_UPDATE_WHEEL: event_name = "Event.ScrollUpdate.Wheel"; break; } std::unique_ptr builder = ukm_recorder->GetEntryBuilder(ukm_source_id, event_name.c_str()); builder->AddMetric( "TimeToScrollUpdateSwapBegin", std::max(static_cast(0), (time_to_scroll_update_swap_begin_component.last_event_time - start_component.first_event_time) .InMicroseconds())); builder->AddMetric("TimeToHandled", std::max(static_cast(0), (time_to_handled_component.last_event_time - start_component.first_event_time) .InMicroseconds())); builder->AddMetric("IsMainThread", is_main_thread); } void LatencyTracker::ComputeEndToEndLatencyHistograms( const ui::LatencyInfo::LatencyComponent& gpu_swap_begin_component, const ui::LatencyInfo::LatencyComponent& gpu_swap_end_component, const ui::LatencyInfo& latency) { DCHECK_AND_RETURN_ON_FAIL(!latency.coalesced()); LatencyInfo::LatencyComponent original_component; std::string scroll_name = "Uninitialized"; const std::string input_modality = LatencySourceEventTypeToInputModalityString(latency.source_event_type()); if (latency.FindLatency( ui::INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT, &original_component)) { scroll_name = "ScrollBegin"; DCHECK(input_modality == "Wheel" || input_modality == "Touch"); // This UMA metric tracks the time between the final frame swap for the // first scroll event in a sequence and the original timestamp of that // scroll event's underlying touch/wheel event. UMA_HISTOGRAM_INPUT_LATENCY_HIGH_RESOLUTION_MICROSECONDS( "Event.Latency.ScrollBegin." + input_modality + ".TimeToScrollUpdateSwapBegin2", original_component, gpu_swap_begin_component); if (input_modality == "Wheel") { RecordUmaEventLatencyScrollWheelTimeToScrollUpdateSwapBegin2Histogram( original_component, gpu_swap_begin_component); } } else if (latency.FindLatency( ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT, &original_component)) { scroll_name = "ScrollUpdate"; DCHECK(input_modality == "Wheel" || input_modality == "Touch"); // This UMA metric tracks the time from when the original touch/wheel event // is created to when the scroll gesture results in final frame swap. // First scroll events are excluded from this metric. UMA_HISTOGRAM_INPUT_LATENCY_HIGH_RESOLUTION_MICROSECONDS( "Event.Latency.ScrollUpdate." + input_modality + ".TimeToScrollUpdateSwapBegin2", original_component, gpu_swap_begin_component); if (input_modality == "Wheel") { RecordUmaEventLatencyScrollWheelTimeToScrollUpdateSwapBegin2Histogram( original_component, gpu_swap_begin_component); } } else if (latency.FindLatency(ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0, &original_component)) { if (latency.source_event_type() == SourceEventType::KEY_PRESS) { UMA_HISTOGRAM_INPUT_LATENCY_HIGH_RESOLUTION_MICROSECONDS( "Event.Latency.EndToEnd.KeyPress", original_component, gpu_swap_begin_component); } else if (latency.source_event_type() == SourceEventType::MOUSE) { UMA_HISTOGRAM_INPUT_LATENCY_HIGH_RESOLUTION_MICROSECONDS( "Event.Latency.EndToEnd.Mouse", original_component, gpu_swap_begin_component); } return; } else { // No original component found. return; } // Record scroll latency metrics. DCHECK(scroll_name == "ScrollBegin" || scroll_name == "ScrollUpdate"); LatencyInfo::LatencyComponent rendering_scheduled_component; bool rendering_scheduled_on_main = latency.FindLatency( ui::INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_MAIN_COMPONENT, 0, &rendering_scheduled_component); if (!rendering_scheduled_on_main) { bool found_component = latency.FindLatency( ui::INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_IMPL_COMPONENT, 0, &rendering_scheduled_component); DCHECK_AND_RETURN_ON_FAIL(found_component); } if (input_modality == "Touch" || input_modality == "Wheel") { InputMetricEvent input_metric_event; if (scroll_name == "ScrollBegin") { input_metric_event = input_modality == "Touch" ? InputMetricEvent::SCROLL_BEGIN_TOUCH : InputMetricEvent::SCROLL_BEGIN_WHEEL; } else { DCHECK_EQ(scroll_name, "ScrollUpdate"); input_metric_event = input_modality == "Touch" ? InputMetricEvent::SCROLL_UPDATE_TOUCH : InputMetricEvent::SCROLL_UPDATE_WHEEL; } ReportUkmScrollLatency( input_metric_event, original_component, gpu_swap_begin_component, rendering_scheduled_component, rendering_scheduled_on_main, latency.ukm_source_id()); } const std::string thread_name = rendering_scheduled_on_main ? "Main" : "Impl"; UMA_HISTOGRAM_SCROLL_LATENCY_LONG_2( "Event.Latency." + scroll_name + "." + input_modality + ".TimeToHandled2_" + thread_name, original_component, rendering_scheduled_component); if (input_modality == "Wheel") { UMA_HISTOGRAM_SCROLL_LATENCY_LONG_2( "Event.Latency.Scroll.Wheel.TimeToHandled2_" + thread_name, original_component, rendering_scheduled_component); } LatencyInfo::LatencyComponent renderer_swap_component; bool found_component = latency.FindLatency(ui::INPUT_EVENT_LATENCY_RENDERER_SWAP_COMPONENT, 0, &renderer_swap_component); DCHECK_AND_RETURN_ON_FAIL(found_component); UMA_HISTOGRAM_SCROLL_LATENCY_LONG_2( "Event.Latency." + scroll_name + "." + input_modality + ".HandledToRendererSwap2_" + thread_name, rendering_scheduled_component, renderer_swap_component); LatencyInfo::LatencyComponent browser_received_swap_component; found_component = latency.FindLatency(ui::DISPLAY_COMPOSITOR_RECEIVED_FRAME_COMPONENT, 0, &browser_received_swap_component); DCHECK_AND_RETURN_ON_FAIL(found_component); UMA_HISTOGRAM_SCROLL_LATENCY_SHORT_2( "Event.Latency." + scroll_name + "." + input_modality + ".RendererSwapToBrowserNotified2", renderer_swap_component, browser_received_swap_component); UMA_HISTOGRAM_SCROLL_LATENCY_LONG_2( "Event.Latency." + scroll_name + "." + input_modality + ".BrowserNotifiedToBeforeGpuSwap2", browser_received_swap_component, gpu_swap_begin_component); UMA_HISTOGRAM_SCROLL_LATENCY_SHORT_2( "Event.Latency." + scroll_name + "." + input_modality + ".GpuSwap2", gpu_swap_begin_component, gpu_swap_end_component); } } // namespace ui