blob: 13019c9b66438c98539f29de681c46c69416d036 [file] [log] [blame]
// Copyright 2018 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.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_LOCAL_FRAME_UKM_AGGREGATOR_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_LOCAL_FRAME_UKM_AGGREGATOR_H_
#include "cc/metrics/frame_sequence_tracker_collection.h"
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/platform/instrumentation/histogram.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
namespace base {
class TickClock;
}
namespace cc {
struct BeginMainFrameMetrics;
}
namespace ukm {
class UkmRecorder;
}
namespace blink {
enum class DocumentUpdateReason;
// This class aggregaties and records time based UKM and UMA metrics
// for LocalFrameView. The simplest way to use it is via the
// SCOPED_UMA_AND_UKM_TIMER macro combined with
// LocalFrameView::RecordEndOfFrameMetrics.
//
// It takes the following constructor parameters:
// - source_id: UKM Source ID associated with the events.
// - recorder: UkmRecorder which will handle the events
//
// The aggregator manages all of the UKM and UMA names for LocalFrameView.
// It constructs and takes ownership of the UMA counters when constructed
// itself. We do this to localize all UMA and UKM metrics in one place, so
// that adding a metric is localized to the cc file of this class, protected
// from errors that might arise when adding names in multiple places.
//
// After the aggregator is created, one can create ScopedUkmHierarchicalTimer
// objects that will measure the time, in microseconds, from creation until
// the object is destroyed for sub-metrics. When destroyed, it may record
// a sample into the aggregator and the current frame's accumulated time for
// that metric, and it always reports UMA values.
//
// See the MetricNames enum below for the set of metrics recorded. Add an
// entry to that enum to add a new metric.
//
// When the primary timed execution completes, this aggregator stores the
// primary time and computes metrics that depend on it. UMA metrics are updated
// at this time.
//
// A UKM event is generated according to a sampling strategy, with the goal
// being to choose one frame to report before First Contentful Paint and
// one frame to report during the subsequent document lifetime. We maintain
// a copy of the current sample, and randomly choose to update it on each frame
// such that any given frame is equally likely to be the final sample.
//
// Sample usage (see also SCOPED_UMA_AND_UKM_TIMER):
// std::unique_ptr<LocalFrameUkmAggregator> aggregator(
// new LocalFrameUkmAggregator(
// GetSourceId(),
// GetUkmRecorder());
// ...
// {
// auto timer =
// aggregator->GetScopedTimer(static_cast<size_t>(
// LocalFrameUkmAggregator::MetricNames::kMetric2));
// ...
// }
// // At this point data for kMetric2 is recorded.
// ...
// // When the primary time completes
// aggregator->RecordEndOfFrameMetrics(start, end, trackers);
// // This records a primary sample and the sub-metrics that depend on it.
// // It may generate an event. trackers is a bit encoding of the active frame
//. // sequence trackers, informing us of why the BeginMainFrame was requested.
//
// In the example above, the event name is "my_event". It will measure 4
// metrics:
// "primary_metric",
// "sub_metric1",
// "sub_metric2",
// "sub_metric3"
//
// It will report 4 UMA values:
// "primary_uma_counter",
// "sub_uma_metric1", "sub_uma_metric2", "sub_uma_metric3"
//
// Note that these have to be specified in the appropriate ukm.xml file
// and histograms.xml file. Runtime errors indicate missing or mis-named
// metrics.
//
// If the source_id/recorder changes then a new LocalFrameUkmAggregator has to
// be created.
// Defines a UKM that is part of a hierarchical ukm, recorded in
// microseconds equal to the duration of the current lexical scope after
// declaration of the macro. Example usage:
//
// void LocalFrameView::DoExpensiveThing() {
// SCOPED_UMA_AND_UKM_TIMER(kUkmEnumName);
// // Do computation of expensive thing
//
// }
//
// |ukm_enum| should be an entry in LocalFrameUkmAggregator's enum of
// metric names (which in turn corresponds to names in from ukm.xml).
#define SCOPED_UMA_AND_UKM_TIMER(aggregator, ukm_enum) \
auto scoped_ukm_hierarchical_timer = \
aggregator.GetScopedTimer(static_cast<size_t>(ukm_enum));
class CORE_EXPORT LocalFrameUkmAggregator
: public RefCounted<LocalFrameUkmAggregator> {
public:
// Changing these values requires changing the names of metrics specified
// below. For every metric name added here, add an entry in the array in
// metrics_data() below.
enum MetricId {
kCompositingAssignments,
kCompositingCommit,
kCompositingInputs,
kImplCompositorCommit,
kIntersectionObservation,
kPaint,
kPrePaint,
kStyle,
kLayout,
kHandleInputEvents,
kAnimate,
kUpdateLayers,
kWaitForCommit,
kDisplayLockIntersectionObserver,
kJavascriptIntersectionObserver,
kLazyLoadIntersectionObserver,
kMediaIntersectionObserver,
kUpdateViewportIntersection,
kForcedStyleAndLayout,
kContentDocumentUpdate,
kHitTestDocumentUpdate,
kJavascriptDocumentUpdate,
kScrollDocumentUpdate,
kServiceDocumentUpdate,
kUserDrivenDocumentUpdate,
kCount,
kMainFrame
};
typedef struct MetricInitializationData {
const char* const name;
bool has_uma;
} MetricInitializationData;
private:
friend class LocalFrameUkmAggregatorTest;
// Primary metric name
static const char* primary_metric_name() { return "MainFrame"; }
// Add an entry in this array every time a new metric is added.
static base::span<const MetricInitializationData> metrics_data() {
static const MetricInitializationData data[] = {
{"CompositingAssignments", true},
{"CompositingCommit", true},
{"CompositingInputs", true},
{"ImplCompositorCommit", true},
{"IntersectionObservation", true},
{"Paint", true},
{"PrePaint", true},
{"Style", true},
{"Layout", true},
{"HandleInputEvents", true},
{"Animate", true},
{"UpdateLayers", false},
{"WaitForCommit", true},
{"DisplayLockIntersectionObserver", true},
{"JavascriptIntersectionObserver", true},
{"LazyLoadIntersectionObserver", true},
{"MediaIntersectionObserver", true},
{"UpdateViewportIntersection", true},
{"ForcedStyleAndLayout", true},
{"ContentDocumentUpdate", true},
{"HitTestDocumentUpdate", true},
{"JavascriptDocumentUpdate", true},
{"ScrollDocumentUpdate", true},
{"ServiceDocumentUpdate", true},
{"UserDrivenDocumentUpdate", true}};
static_assert(base::size(data) == kCount, "Metrics data mismatch");
return data;
}
public:
// This class will start a timer upon creation, which will end when the
// object is destroyed. Upon destruction it will record a sample into the
// aggregator that created the scoped timer. It will also record an event
// into the histogram counter.
class CORE_EXPORT ScopedUkmHierarchicalTimer {
STACK_ALLOCATED();
public:
ScopedUkmHierarchicalTimer(ScopedUkmHierarchicalTimer&&);
~ScopedUkmHierarchicalTimer();
private:
friend class LocalFrameUkmAggregator;
ScopedUkmHierarchicalTimer(scoped_refptr<LocalFrameUkmAggregator>,
size_t metric_index,
const base::TickClock* clock);
scoped_refptr<LocalFrameUkmAggregator> aggregator_;
const size_t metric_index_;
const base::TickClock* clock_;
const base::TimeTicks start_time_;
DISALLOW_COPY_AND_ASSIGN(ScopedUkmHierarchicalTimer);
};
LocalFrameUkmAggregator(int64_t source_id, ukm::UkmRecorder*);
~LocalFrameUkmAggregator();
// Create a scoped timer with the index of the metric. Note the index must
// correspond to the matching index in metric_names.
ScopedUkmHierarchicalTimer GetScopedTimer(size_t metric_index);
// Record a main frame time metric, that also computes the ratios for the
// sub-metrics and generates UMA samples. UKM is only reported when
// BeginMainFrame() had been called. All counters are cleared when this method
// is called. trackers is a bit encoding of the active frame sequence
// trackers, telling us the reasons for requesting a BeginMainFrame.
void RecordEndOfFrameMetrics(base::TimeTicks start,
base::TimeTicks end,
cc::ActiveFrameSequenceTrackers trackers);
// Record a sample for a sub-metric. This should only be used when
// a ScopedUkmHierarchicalTimer cannot be used (such as when the timed
// interval does not fall inside a single calling function).
void RecordSample(size_t metric_index,
base::TimeTicks start,
base::TimeTicks end);
// Record a ForcedLayout sample. The reason will determine which, if any,
// additional metrics are reported in order to diagnose the cause of
// ForcedLayout regressions.
void RecordForcedLayoutSample(DocumentUpdateReason reason,
base::TimeTicks start,
base::TimeTicks end);
// Record a sample for the impl-side compositor processing.
// - requested is the time the renderer proxy requests a commit
// - started is the time the impl thread begins processing the request
// - completed is the time the renderer proxy receives notification that the
// commit is complete.
void RecordImplCompositorSample(base::TimeTicks requested,
base::TimeTicks started,
base::TimeTicks completed);
// Mark the beginning of a main frame update.
void BeginMainFrame();
// Inform the aggregator that we have reached First Contentful Paint.
// The UKM event for the pre-FCP period will be recorded and UMA for
// aggregated contributions to FCP are reported if are_painting_main_frame
// is true.
void DidReachFirstContentfulPaint(bool are_painting_main_frame);
bool InMainFrameUpdate() { return in_main_frame_update_; }
// Populate a BeginMainFrameMetrics structure with the latency numbers for
// the most recent frame. Must be called when within a main frame update.
// That is, after calling BeginMainFrame and before calling
// RecordEndOfFrameMetrics.
std::unique_ptr<cc::BeginMainFrameMetrics> GetBeginMainFrameMetrics();
private:
struct AbsoluteMetricRecord {
std::unique_ptr<CustomCountHistogram> pre_fcp_uma_counter;
std::unique_ptr<CustomCountHistogram> post_fcp_uma_counter;
std::unique_ptr<CustomCountHistogram> uma_aggregate_counter;
// Accumulated at each sample, then reset with a call to
// RecordEndOfFrameMetrics.
base::TimeDelta interval_duration;
// Accumulated at each sample when within a BeginMainFrame,
// reset with a call to RecordEndOfFrameMetrics.
base::TimeDelta main_frame_duration;
// Accumulated at each sample up to the time of First Contentful Paint.
base::TimeDelta pre_fcp_aggregate;
void reset();
};
struct SampleToRecord {
base::TimeDelta primary_metric_duration;
std::array<base::TimeDelta, kCount> sub_metrics_durations;
std::array<base::TimeDelta, kCount> sub_main_frame_durations;
cc::ActiveFrameSequenceTrackers trackers;
};
void UpdateEventTimeAndUpdateSampleIfNeeded(
cc::ActiveFrameSequenceTrackers trackers);
void UpdateSample(cc::ActiveFrameSequenceTrackers trackers);
void ResetAllMetrics();
// Reports the current sample to the UKM system. Called on the first main
// frame update after First Contentful Paint and at destruction. Also resets
// the frame count.
void ReportUpdateTimeEvent();
// Reports the Blink.PageLoad to the UKM system. Called on the first main
// frame after First Contentful Paint.
void ReportPreFCPEvent();
// To test event sampling. Controls whether we update the current sample
// on the next frame, or do not. Values persist until explicitly changed.
void ChooseNextFrameForTest();
void DoNotChooseNextFrameForTest();
// Used to check that we record only for the MainFrame of a document.
bool AllMetricsAreZero();
// The caller is the owner of the |clock|. The |clock| must outlive the
// LocalFrameUkmAggregator.
void SetTickClockForTesting(const base::TickClock* clock);
// UKM system data
const int64_t source_id_;
ukm::UkmRecorder* const recorder_;
const base::TickClock* clock_;
// Event and metric data
const char* const event_name_;
AbsoluteMetricRecord primary_metric_;
std::array<AbsoluteMetricRecord, kCount> absolute_metric_records_;
// The current sample to report. When RecordEvent() is called we
// check for uniform_random[0,1) < 1 / n where n is the number of frames
// we have seen (including this one). If true, we replace the sample with
// the current frame data. The result is a uniformly randomly chosen frame
// in the period between the frame counter being reset and the recording
// to the UKM system of the current sample.
// This process is designed to get maximum utility while only sending 2
// events per page load, which in turn maximizes client counts.
SampleToRecord current_sample_;
unsigned frames_since_last_report_ = 0;
// Control for the ForcedStyleAndUpdate UMA metric sampling
unsigned mean_calls_between_forced_style_layout_uma_ = 500;
unsigned calls_to_next_forced_style_layout_uma_ = 0;
// Set by BeginMainFrame() and cleared in RecordMEndOfFrameMetrics.
// Main frame metrics are only recorded if this is true.
bool in_main_frame_update_ = false;
// A bitfield maintaining state for first contentful paint.
enum FCPState { kBeforeFCPSignal, kThisFrameReachedFCP, kHavePassedFCP };
FCPState fcp_state_ = kBeforeFCPSignal;
// A bitfield used to control updating the sample for tests.
enum SampleControlForTest {
kNoPreference,
kMustChooseNextFrame,
kMustNotChooseNextFrame
};
SampleControlForTest next_frame_sample_control_for_test_ = kNoPreference;
DISALLOW_COPY_AND_ASSIGN(LocalFrameUkmAggregator);
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_FRAME_LOCAL_FRAME_UKM_AGGREGATOR_H_