blob: 277bc777f2840a34020bbcf4631ef0be648a9c1f [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h"
#include <memory>
#include "base/format_macros.h"
#include "base/metrics/histogram_functions.h"
#include "base/numerics/safe_conversions.h"
#include "base/rand_util.h"
#include "base/time/default_tick_clock.h"
#include "cc/metrics/begin_main_frame_metrics.h"
#include "services/metrics/public/cpp/metrics_utils.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "third_party/blink/public/common/metrics/document_update_reason.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace {
inline base::HistogramBase::Sample ToSample(int64_t value) {
return base::saturated_cast<base::HistogramBase::Sample>(value);
}
inline int64_t ApplyBucket(int64_t value) {
return ukm::GetExponentialBucketMinForCounts1000(value);
}
} // namespace
namespace blink {
int64_t LocalFrameUkmAggregator::ApplyBucketIfNecessary(int64_t value,
unsigned metric_id) {
if (metric_id >= kIntersectionObservationInternalCount &&
metric_id <= kIntersectionObservationJavascriptCount) {
return ApplyBucket(value);
}
return value;
}
LocalFrameUkmAggregator::ScopedUkmHierarchicalTimer::ScopedUkmHierarchicalTimer(
scoped_refptr<LocalFrameUkmAggregator> aggregator,
size_t metric_index,
const base::TickClock* clock)
: aggregator_(aggregator),
metric_index_(metric_index),
clock_(clock),
start_time_(aggregator && aggregator->ShouldMeasureMetric(metric_index)
? clock_->NowTicks()
: base::TimeTicks()) {
if (aggregator_ && !start_time_.is_null())
TRACE_EVENT_BEGIN0("blink", aggregator_->metrics_data()[metric_index].name);
}
LocalFrameUkmAggregator::ScopedUkmHierarchicalTimer::ScopedUkmHierarchicalTimer(
ScopedUkmHierarchicalTimer&& other)
: aggregator_(other.aggregator_),
metric_index_(other.metric_index_),
clock_(other.clock_),
start_time_(other.start_time_) {
other.aggregator_ = nullptr;
}
LocalFrameUkmAggregator::ScopedUkmHierarchicalTimer::
~ScopedUkmHierarchicalTimer() {
if (aggregator_ && !start_time_.is_null()) {
if (base::TimeTicks::IsHighResolution()) {
aggregator_->RecordTimerSample(metric_index_, start_time_,
clock_->NowTicks());
}
TRACE_EVENT_END1("blink", aggregator_->metrics_data()[metric_index_].name,
"preFCP", aggregator_->fcp_state_ == kBeforeFCPSignal);
}
}
LocalFrameUkmAggregator::IterativeTimer::IterativeTimer(
LocalFrameUkmAggregator& aggregator)
: aggregator_(base::TimeTicks::IsHighResolution() ? &aggregator : nullptr) {
}
LocalFrameUkmAggregator::IterativeTimer::~IterativeTimer() {
if (aggregator_.get())
Record(aggregator_->ShouldMeasureMetric(metric_index_), false);
}
void LocalFrameUkmAggregator::IterativeTimer::StartInterval(
int64_t metric_index) {
if (aggregator_.get() && metric_index != metric_index_) {
bool should_record_prev_metric =
aggregator_->ShouldMeasureMetric(metric_index_);
bool should_record_next_metric =
aggregator_->ShouldMeasureMetric(metric_index);
Record(should_record_prev_metric, should_record_next_metric);
if (should_record_next_metric)
metric_index_ = metric_index;
}
}
void LocalFrameUkmAggregator::IterativeTimer::Record(
bool should_record_prev_metric,
bool should_record_next_metric) {
DCHECK(aggregator_.get());
if (should_record_prev_metric || should_record_next_metric) {
base::TimeTicks now = aggregator_->GetClock()->NowTicks();
if (should_record_prev_metric) {
aggregator_->RecordTimerSample(
base::saturated_cast<size_t>(metric_index_), start_time_, now);
}
start_time_ = now;
}
metric_index_ = -1;
}
void LocalFrameUkmAggregator::AbsoluteMetricRecord::reset() {
interval_count = 0;
main_frame_count = 0;
}
LocalFrameUkmAggregator::LocalFrameUkmAggregator()
: clock_(base::DefaultTickClock::GetInstance()) {
// All of these are assumed to have one entry per sub-metric.
DCHECK_EQ(std::size(absolute_metric_records_), metrics_data().size());
DCHECK_EQ(std::size(current_sample_.sub_metrics_counts),
metrics_data().size());
DCHECK_EQ(std::size(current_sample_.sub_main_frame_counts),
metrics_data().size());
// Record average and worst case for the primary metric.
primary_metric_.reset();
// Define the UMA for the primary metric.
primary_metric_.pre_fcp_uma_counter = std::make_unique<CustomCountHistogram>(
"Blink.MainFrame.UpdateTime.PreFCP", kTimeBasedHistogramMinSample,
kTimeBasedHistogramMaxSample, kTimeBasedHistogramBucketCount);
primary_metric_.post_fcp_uma_counter = std::make_unique<CustomCountHistogram>(
"Blink.MainFrame.UpdateTime.PostFCP", kTimeBasedHistogramMinSample,
kTimeBasedHistogramMaxSample, kTimeBasedHistogramBucketCount);
primary_metric_.uma_aggregate_counter =
std::make_unique<CustomCountHistogram>(
"Blink.MainFrame.UpdateTime.AggregatedPreFCP",
kTimeBasedHistogramMinSample, kTimeBasedHistogramMaxSample,
kTimeBasedHistogramBucketCount);
// Set up the substrings to create the UMA names
const char* const uma_prefcp_postscript = ".PreFCP";
const char* const uma_postfcp_postscript = ".PostFCP";
const char* const uma_pre_fcp_aggregated_postscript = ".AggregatedPreFCP";
// Populate all the sub-metrics.
size_t metric_index = 0;
for (const MetricInitializationData& metric_data : metrics_data()) {
// Absolute records report the absolute time for each metric per frame.
// They also aggregate the time spent in each stage between navigation
// (LocalFrameView resets) and First Contentful Paint.
// They have an associated UMA too that we own and allocate here.
auto& absolute_record = absolute_metric_records_[metric_index];
absolute_record.reset();
absolute_record.pre_fcp_aggregate = 0;
if (metric_data.has_uma) {
StringBuilder pre_fcp_uma_name;
pre_fcp_uma_name.Append(metric_data.name);
pre_fcp_uma_name.Append(uma_prefcp_postscript);
absolute_record.pre_fcp_uma_counter =
std::make_unique<CustomCountHistogram>(
pre_fcp_uma_name.ToString().Utf8().c_str(), 1, 10000000, 50);
StringBuilder post_fcp_uma_name;
post_fcp_uma_name.Append(metric_data.name);
post_fcp_uma_name.Append(uma_postfcp_postscript);
absolute_record.post_fcp_uma_counter =
std::make_unique<CustomCountHistogram>(
post_fcp_uma_name.ToString().Utf8().c_str(), 1, 10000000, 50);
StringBuilder aggregated_uma_name;
aggregated_uma_name.Append(metric_data.name);
aggregated_uma_name.Append(uma_pre_fcp_aggregated_postscript);
absolute_record.uma_aggregate_counter =
std::make_unique<CustomCountHistogram>(
aggregated_uma_name.ToString().Utf8().c_str(), 1, 10000000, 50);
}
metric_index++;
}
}
LocalFrameUkmAggregator::~LocalFrameUkmAggregator() = default;
void LocalFrameUkmAggregator::TransmitFinalSample(int64_t source_id,
ukm::UkmRecorder* recorder,
bool is_for_main_frame) {
ReportUpdateTimeEvent(source_id, recorder);
base::UmaHistogramBoolean("Blink.LocalFrameRoot.DidReachFirstContentfulPaint",
fcp_state_ != kBeforeFCPSignal);
if (is_for_main_frame) {
base::UmaHistogramBoolean(
"Blink.LocalFrameRoot.DidReachFirstContentfulPaint.MainFrame",
fcp_state_ != kBeforeFCPSignal);
}
}
bool LocalFrameUkmAggregator::ShouldMeasureMetric(int64_t metric_id) const {
if (metric_id < 0 || metric_id > kMainFrame)
return false;
// Downsample IntersectionObserver sub-categories. Note that
// kIntersectionObservation, which measures a single aggregated time for all
// IntersectionObserver-related work, is unaffected.
if (metric_id >= kDisplayLockIntersectionObserver &&
metric_id <= kUpdateViewportIntersection) {
return frames_since_last_report_ % intersection_observer_sample_period_ ==
0;
}
return true;
}
LocalFrameUkmAggregator::ScopedUkmHierarchicalTimer
LocalFrameUkmAggregator::GetScopedTimer(size_t metric_index) {
return ScopedUkmHierarchicalTimer(this, metric_index, clock_);
}
void LocalFrameUkmAggregator::BeginMainFrame() {
DCHECK(!in_main_frame_update_);
in_main_frame_update_ = true;
request_timestamp_for_current_frame_ = animation_request_timestamp_;
animation_request_timestamp_.reset();
}
std::unique_ptr<cc::BeginMainFrameMetrics>
LocalFrameUkmAggregator::GetBeginMainFrameMetrics() {
DCHECK(InMainFrameUpdate());
// Use the main_frame_percentage_records_ because they are the ones that
// only count time between the Begin and End of a main frame update.
// Do not report hit testing because it is a sub-portion of the other
// metrics and would result in double counting.
std::unique_ptr<cc::BeginMainFrameMetrics> metrics_data =
std::make_unique<cc::BeginMainFrameMetrics>();
metrics_data->handle_input_events = base::Microseconds(
absolute_metric_records_[static_cast<unsigned>(
MetricId::kHandleInputEvents)]
.main_frame_count);
metrics_data->animate = base::Microseconds(
absolute_metric_records_[static_cast<unsigned>(MetricId::kAnimate)]
.main_frame_count);
metrics_data->style_update = base::Microseconds(
absolute_metric_records_[static_cast<unsigned>(MetricId::kStyle)]
.main_frame_count);
metrics_data->layout_update = base::Microseconds(
absolute_metric_records_[static_cast<unsigned>(MetricId::kLayout)]
.main_frame_count);
metrics_data->accessibility = base::Microseconds(
absolute_metric_records_[static_cast<unsigned>(MetricId::kAccessibility)]
.main_frame_count);
metrics_data->prepaint = base::Microseconds(
absolute_metric_records_[static_cast<unsigned>(MetricId::kPrePaint)]
.main_frame_count);
metrics_data->compositing_inputs = base::Microseconds(
absolute_metric_records_[static_cast<unsigned>(
MetricId::kCompositingInputs)]
.main_frame_count);
metrics_data->paint = base::Microseconds(
absolute_metric_records_[static_cast<unsigned>(MetricId::kPaint)]
.main_frame_count);
metrics_data->composite_commit = base::Microseconds(
absolute_metric_records_[static_cast<unsigned>(
MetricId::kCompositingCommit)]
.main_frame_count);
metrics_data->should_measure_smoothness =
(fcp_state_ >= kThisFrameReachedFCP);
return metrics_data;
}
void LocalFrameUkmAggregator::SetTickClockForTesting(
const base::TickClock* clock) {
clock_ = clock;
}
void LocalFrameUkmAggregator::DidReachFirstContentfulPaint() {
if (fcp_state_ == kBeforeFCPSignal)
fcp_state_ = kThisFrameReachedFCP;
}
void LocalFrameUkmAggregator::RecordTimerSample(size_t metric_index,
base::TimeTicks start,
base::TimeTicks end) {
RecordCountSample(metric_index, (end - start).InMicroseconds());
}
void LocalFrameUkmAggregator::RecordCountSample(size_t metric_index,
int64_t count) {
// Always use RecordForcedLayoutSample for the kForcedStyleAndLayout
// metric id.
DCHECK_NE(metric_index, static_cast<size_t>(kForcedStyleAndLayout));
bool is_pre_fcp = (fcp_state_ != kHavePassedFCP);
// Accumulate for UKM and record the UMA
DCHECK_LT(metric_index, std::size(absolute_metric_records_));
auto& record = absolute_metric_records_[metric_index];
record.interval_count += count;
if (in_main_frame_update_)
record.main_frame_count += count;
if (is_pre_fcp)
record.pre_fcp_aggregate += count;
// Subsampling these metrics reduced CPU utilization (crbug.com/1295441).
if (!metrics_subsampler_.ShouldSample(0.001)) {
return;
}
// Record the UMA
// ForcedStyleAndLayout happen so frequently on some pages that we overflow
// the signed 32 counter for number of events in a 30 minute period. So
// randomly record with probability 1/1000.
if (record.pre_fcp_uma_counter) {
if (is_pre_fcp)
record.pre_fcp_uma_counter->Count(ToSample(count));
else
record.post_fcp_uma_counter->Count(ToSample(count));
}
}
void LocalFrameUkmAggregator::BeginForcedLayout() {
TRACE_EVENT_BEGIN0("blink", metrics_data()[kForcedStyleAndLayout].name);
}
void LocalFrameUkmAggregator::RecordForcedLayoutSample(
DocumentUpdateReason reason,
base::TimeTicks start,
base::TimeTicks end) {
TRACE_EVENT_END1("blink", metrics_data()[kForcedStyleAndLayout].name,
"preFCP", fcp_state_ == kBeforeFCPSignal);
int64_t count = (end - start).InMicroseconds();
bool is_pre_fcp = (fcp_state_ != kHavePassedFCP);
// Accumulate for UKM always, but only record the UMA for a subset of cases to
// avoid overflowing the counters.
bool should_report_uma_this_frame = !calls_to_next_forced_style_layout_uma_;
if (should_report_uma_this_frame) {
calls_to_next_forced_style_layout_uma_ =
base::RandInt(0, mean_calls_between_forced_style_layout_uma_ * 2);
} else {
DCHECK_GT(calls_to_next_forced_style_layout_uma_, 0u);
--calls_to_next_forced_style_layout_uma_;
}
auto& record =
absolute_metric_records_[static_cast<size_t>(kForcedStyleAndLayout)];
record.interval_count += count;
if (in_main_frame_update_)
record.main_frame_count += count;
if (is_pre_fcp)
record.pre_fcp_aggregate += count;
if (should_report_uma_this_frame) {
if (is_pre_fcp)
record.pre_fcp_uma_counter->Count(ToSample(count));
else
record.post_fcp_uma_counter->Count(ToSample(count));
}
// Record a variety of DocumentUpdateReasons as distinct metrics
// Figure out which sub-metric, if any, we wish to report for UKM.
MetricId sub_metric = kCount;
switch (reason) {
case DocumentUpdateReason::kContextMenu:
case DocumentUpdateReason::kDragImage:
case DocumentUpdateReason::kEditing:
case DocumentUpdateReason::kFindInPage:
case DocumentUpdateReason::kFocus:
case DocumentUpdateReason::kFocusgroup:
case DocumentUpdateReason::kForm:
case DocumentUpdateReason::kInput:
case DocumentUpdateReason::kInspector:
case DocumentUpdateReason::kPrinting:
case DocumentUpdateReason::kScroll:
case DocumentUpdateReason::kSelection:
case DocumentUpdateReason::kSpatialNavigation:
case DocumentUpdateReason::kTapHighlight:
sub_metric = kUserDrivenDocumentUpdate;
break;
case DocumentUpdateReason::kAccessibility:
case DocumentUpdateReason::kBaseColor:
case DocumentUpdateReason::kComputedStyle:
case DocumentUpdateReason::kDisplayLock:
case DocumentUpdateReason::kViewTransition:
case DocumentUpdateReason::kIntersectionObservation:
case DocumentUpdateReason::kOverlay:
case DocumentUpdateReason::kPagePopup:
case DocumentUpdateReason::kPopover:
case DocumentUpdateReason::kSizeChange:
case DocumentUpdateReason::kSpellCheck:
case DocumentUpdateReason::kSMILAnimation:
case DocumentUpdateReason::kWebAnimation:
sub_metric = kServiceDocumentUpdate;
break;
case DocumentUpdateReason::kCanvas:
case DocumentUpdateReason::kPlugin:
case DocumentUpdateReason::kSVGImage:
sub_metric = kContentDocumentUpdate;
break;
case DocumentUpdateReason::kHitTest:
sub_metric = kHitTestDocumentUpdate;
break;
case DocumentUpdateReason::kJavaScript:
sub_metric = kJavascriptDocumentUpdate;
break;
// Do not report main frame because we have it already from
// in_main_frame_update_ above.
case DocumentUpdateReason::kBeginMainFrame:
// No metrics from testing.
case DocumentUpdateReason::kTest:
// Don't report if we don't know why.
case DocumentUpdateReason::kUnknown:
break;
}
if (sub_metric != kCount) {
auto& sub_record =
absolute_metric_records_[static_cast<size_t>(sub_metric)];
sub_record.interval_count += count;
if (in_main_frame_update_)
sub_record.main_frame_count += count;
if (is_pre_fcp)
sub_record.pre_fcp_aggregate += count;
if (should_report_uma_this_frame) {
if (is_pre_fcp)
sub_record.pre_fcp_uma_counter->Count(ToSample(count));
else
sub_record.post_fcp_uma_counter->Count(ToSample(count));
}
}
}
void LocalFrameUkmAggregator::RecordImplCompositorSample(
base::TimeTicks requested,
base::TimeTicks started,
base::TimeTicks completed) {
// Record the time spent waiting for the commit based on requested
// (which came from ProxyImpl::BeginMainFrame) and started as reported by
// the impl thread. If started is zero, no time was spent
// processing. This can only happen if the commit was aborted because there
// was no change and we did not wait for the impl thread at all. Attribute
// all time to the compositor commit so as to not imply that wait time was
// consumed.
if (started == base::TimeTicks()) {
RecordTimerSample(kImplCompositorCommit, requested, completed);
} else {
RecordTimerSample(kWaitForCommit, requested, started);
RecordTimerSample(kImplCompositorCommit, started, completed);
}
}
void LocalFrameUkmAggregator::RecordEndOfFrameMetrics(
base::TimeTicks start,
base::TimeTicks end,
cc::ActiveFrameSequenceTrackers trackers,
int64_t source_id,
ukm::UkmRecorder* recorder) {
last_frame_request_timestamp_for_test_ =
request_timestamp_for_current_frame_.value_or(base::TimeTicks());
const int64_t count = (end - start).InMicroseconds();
const bool have_valid_metrics =
// Any of the early outs in LocalFrameView::UpdateLifecyclePhases() will
// mean we are not in a main frame update. Recording is triggered higher
// in the stack, so we cannot know to avoid calling this method.
in_main_frame_update_ &&
// In tests it's possible to reach here with zero duration.
(count > 0);
in_main_frame_update_ = false;
if (!have_valid_metrics) {
// Reset for the next frame to start the next recording period with
// clear counters, even when we did not record anything this frame.
ResetAllMetrics();
return;
}
if (request_timestamp_for_current_frame_.has_value()) {
RecordTimerSample(kVisualUpdateDelay,
request_timestamp_for_current_frame_.value(), start);
}
bool report_as_pre_fcp = (fcp_state_ != kHavePassedFCP);
bool report_fcp_metrics = (fcp_state_ == kThisFrameReachedFCP);
// Record UMA
if (report_as_pre_fcp)
primary_metric_.pre_fcp_uma_counter->Count(ToSample(count));
else
primary_metric_.post_fcp_uma_counter->Count(ToSample(count));
// Record primary time information
primary_metric_.interval_count = count;
if (report_as_pre_fcp)
primary_metric_.pre_fcp_aggregate += count;
UpdateEventTimeAndUpdateSampleIfNeeded(trackers);
// Report the FCP metrics, if necessary, after updating the sample so that
// the sample has been recorded for the frame that produced FCP.
if (report_fcp_metrics) {
ReportPreFCPEvent(source_id, recorder);
ReportUpdateTimeEvent(source_id, recorder);
// Update the state to prevent future reporting.
fcp_state_ = kHavePassedFCP;
}
// Reset for the next frame.
ResetAllMetrics();
}
void LocalFrameUkmAggregator::UpdateEventTimeAndUpdateSampleIfNeeded(
cc::ActiveFrameSequenceTrackers trackers) {
// Update the frame count first, because it must include this frame
frames_since_last_report_++;
// Regardless of test requests, always capture the first frame.
if (frames_since_last_report_ == 1) {
UpdateSample(trackers);
return;
}
// Exit if in testing and we do not want to update this frame
if (next_frame_sample_control_for_test_ == kMustNotChooseNextFrame)
return;
// Update the sample with probability 1/frames_since_last_report_, or if
// testing demand is.
if ((next_frame_sample_control_for_test_ == kMustChooseNextFrame) ||
base::RandDouble() < 1 / static_cast<double>(frames_since_last_report_)) {
UpdateSample(trackers);
}
}
void LocalFrameUkmAggregator::UpdateSample(
cc::ActiveFrameSequenceTrackers trackers) {
current_sample_.primary_metric_count = primary_metric_.interval_count;
for (size_t i = 0; i < metrics_data().size(); ++i) {
current_sample_.sub_metrics_counts[i] =
absolute_metric_records_[i].interval_count;
current_sample_.sub_main_frame_counts[i] =
absolute_metric_records_[i].main_frame_count;
}
current_sample_.trackers = trackers;
}
void LocalFrameUkmAggregator::ReportPreFCPEvent(int64_t source_id,
ukm::UkmRecorder* recorder) {
#define RECORD_METRIC(name) \
{ \
auto& absolute_record = absolute_metric_records_[k##name]; \
if (absolute_record.uma_aggregate_counter) { \
absolute_record.uma_aggregate_counter->Count( \
ToSample(absolute_record.pre_fcp_aggregate)); \
} \
builder.Set##name(ToSample(absolute_record.pre_fcp_aggregate)); \
}
#define RECORD_BUCKETED_METRIC(name) \
{ \
auto& absolute_record = absolute_metric_records_[k##name]; \
if (absolute_record.uma_aggregate_counter) { \
absolute_record.uma_aggregate_counter->Count( \
ToSample(absolute_record.pre_fcp_aggregate)); \
} \
builder.Set##name( \
ToSample(ApplyBucket(absolute_record.pre_fcp_aggregate))); \
}
if (!recorder) {
return;
}
ukm::builders::Blink_PageLoad builder(source_id);
primary_metric_.uma_aggregate_counter->Count(
ToSample(primary_metric_.pre_fcp_aggregate));
builder.SetMainFrame(ToSample(primary_metric_.pre_fcp_aggregate));
RECORD_METRIC(CompositingCommit);
RECORD_METRIC(CompositingInputs);
RECORD_METRIC(ImplCompositorCommit);
RECORD_METRIC(IntersectionObservation);
RECORD_BUCKETED_METRIC(IntersectionObservationInternalCount);
RECORD_BUCKETED_METRIC(IntersectionObservationJavascriptCount);
RECORD_METRIC(Paint);
RECORD_METRIC(PrePaint);
RECORD_METRIC(Style);
RECORD_METRIC(Layout);
RECORD_METRIC(ForcedStyleAndLayout);
RECORD_METRIC(HandleInputEvents);
RECORD_METRIC(Animate);
RECORD_METRIC(UpdateLayers);
RECORD_METRIC(WaitForCommit);
RECORD_METRIC(DisplayLockIntersectionObserver);
RECORD_METRIC(JavascriptIntersectionObserver);
RECORD_METRIC(LazyLoadIntersectionObserver);
RECORD_METRIC(MediaIntersectionObserver);
RECORD_METRIC(PermissionElementIntersectionObserver);
RECORD_METRIC(AnchorElementMetricsIntersectionObserver);
RECORD_METRIC(UpdateViewportIntersection);
RECORD_METRIC(VisualUpdateDelay);
RECORD_METRIC(UserDrivenDocumentUpdate);
RECORD_METRIC(ServiceDocumentUpdate);
RECORD_METRIC(ContentDocumentUpdate);
RECORD_METRIC(HitTestDocumentUpdate);
RECORD_METRIC(JavascriptDocumentUpdate);
RECORD_METRIC(ParseStyleSheet);
RECORD_METRIC(Accessibility);
RECORD_METRIC(PossibleSynchronizedScrollCount2);
builder.Record(recorder);
#undef RECORD_METRIC
#undef RECORD_BUCKETED_METRIC
}
void LocalFrameUkmAggregator::ReportUpdateTimeEvent(
int64_t source_id,
ukm::UkmRecorder* recorder) {
// Don't report if we haven't generated any samples.
if (!recorder || !frames_since_last_report_) {
return;
}
#define RECORD_METRIC(name) \
builder.Set##name(current_sample_.sub_metrics_counts[k##name]) \
.Set##name##BeginMainFrame( \
current_sample_.sub_main_frame_counts[k##name]);
#define RECORD_BUCKETED_METRIC(name) \
builder.Set##name(ApplyBucket(current_sample_.sub_metrics_counts[k##name])) \
.Set##name##BeginMainFrame( \
ApplyBucket(current_sample_.sub_main_frame_counts[k##name]));
ukm::builders::Blink_UpdateTime builder(source_id);
builder.SetMainFrame(current_sample_.primary_metric_count);
builder.SetMainFrameIsBeforeFCP(fcp_state_ != kHavePassedFCP);
builder.SetMainFrameReasons(current_sample_.trackers);
RECORD_METRIC(CompositingCommit);
RECORD_METRIC(CompositingInputs);
RECORD_METRIC(ImplCompositorCommit);
RECORD_METRIC(IntersectionObservation);
RECORD_BUCKETED_METRIC(IntersectionObservationInternalCount);
RECORD_BUCKETED_METRIC(IntersectionObservationJavascriptCount);
RECORD_METRIC(Paint);
RECORD_METRIC(PrePaint);
RECORD_METRIC(Style);
RECORD_METRIC(Layout);
RECORD_METRIC(ForcedStyleAndLayout);
RECORD_METRIC(HandleInputEvents);
RECORD_METRIC(Animate);
RECORD_METRIC(UpdateLayers);
RECORD_METRIC(WaitForCommit);
RECORD_METRIC(DisplayLockIntersectionObserver);
RECORD_METRIC(JavascriptIntersectionObserver);
RECORD_METRIC(LazyLoadIntersectionObserver);
RECORD_METRIC(MediaIntersectionObserver);
RECORD_METRIC(PermissionElementIntersectionObserver);
RECORD_METRIC(AnchorElementMetricsIntersectionObserver);
RECORD_METRIC(UpdateViewportIntersection);
RECORD_METRIC(VisualUpdateDelay);
RECORD_METRIC(UserDrivenDocumentUpdate);
RECORD_METRIC(ServiceDocumentUpdate);
RECORD_METRIC(ContentDocumentUpdate);
RECORD_METRIC(HitTestDocumentUpdate);
RECORD_METRIC(JavascriptDocumentUpdate);
RECORD_METRIC(ParseStyleSheet);
RECORD_METRIC(Accessibility);
RECORD_METRIC(PossibleSynchronizedScrollCount2);
builder.Record(recorder);
#undef RECORD_METRIC
#undef RECORD_BUCKETED_METRIC
// Reset the frames since last report to ensure correct sampling.
frames_since_last_report_ = 0;
}
void LocalFrameUkmAggregator::ResetAllMetrics() {
primary_metric_.reset();
for (auto& record : absolute_metric_records_)
record.reset();
request_timestamp_for_current_frame_.reset();
}
void LocalFrameUkmAggregator::ChooseNextFrameForTest() {
next_frame_sample_control_for_test_ = kMustChooseNextFrame;
}
void LocalFrameUkmAggregator::DoNotChooseNextFrameForTest() {
next_frame_sample_control_for_test_ = kMustNotChooseNextFrame;
}
bool LocalFrameUkmAggregator::IsBeforeFCPForTesting() const {
return fcp_state_ == kBeforeFCPSignal;
}
void LocalFrameUkmAggregator::OnCommitRequested() {
// This can't be a DCHECK because this method can be called during the early
// stages of cc::ProxyMain::BeginMainFrame, before
// LocalFrameUkmAggregator::BeginMainFrame() has been invoked.
if (!animation_request_timestamp_.has_value())
animation_request_timestamp_.emplace(clock_->NowTicks());
}
} // namespace blink