Commit 08edd23f authored by Brian Anderson's avatar Brian Anderson Committed by Commit Bot

ui: Add FrameMetrics class.

This is the high level helper class that will be used by all
frame sources that we want to instrument.

It takes in timing information about frames produced and
displayed and calculates metrics for a frame's throughput,
latency, latency speed, and latency acceleration.

It forwards those computations to StreamAnalyzers which
compute statistics of each metric over time, including
mean, RMS, standard deviation, percentiles, and worst
performing time period.

Bug: 790761
Change-Id: Ibe2980e861acf0af3904b1e8d8caf134d9ef141b
Reviewed-on: https://chromium-review.googlesource.com/979120Reviewed-by: default avatarSadrul Chowdhury <sadrul@chromium.org>
Reviewed-by: default avatarBrian Anderson <brianderson@chromium.org>
Reviewed-by: default avatarTimothy Dresser <tdresser@chromium.org>
Commit-Queue: Brian Anderson <brianderson@chromium.org>
Cr-Commit-Position: refs/heads/master@{#555561}
parent e342a1ca
...@@ -9,6 +9,8 @@ jumbo_source_set("latency") { ...@@ -9,6 +9,8 @@ jumbo_source_set("latency") {
sources = [ sources = [
"fixed_point.cc", "fixed_point.cc",
"fixed_point.h", "fixed_point.h",
"frame_metrics.cc",
"frame_metrics.h",
"histograms.cc", "histograms.cc",
"histograms.h", "histograms.h",
"latency_histogram_macros.h", "latency_histogram_macros.h",
...@@ -48,6 +50,7 @@ test("latency_unittests") { ...@@ -48,6 +50,7 @@ test("latency_unittests") {
"fixed_point_unittest.cc", "fixed_point_unittest.cc",
"frame_metrics_test_common.cc", "frame_metrics_test_common.cc",
"frame_metrics_test_common.h", "frame_metrics_test_common.h",
"frame_metrics_unittest.cc",
"histograms_unittest.cc", "histograms_unittest.cc",
"latency_info_unittest.cc", "latency_info_unittest.cc",
"stream_analyzer_unittest.cc", "stream_analyzer_unittest.cc",
......
This diff is collapsed.
// 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 UI_LATENCY_FRAME_METRICS_H_
#define UI_LATENCY_FRAME_METRICS_H_
#include "ui/latency/stream_analyzer.h"
#include <cstdint>
#include "base/containers/circular_deque.h"
#include "base/macros.h"
#include "base/time/time.h"
namespace ui {
namespace frame_metrics {
class SkipClient : public frame_metrics::StreamAnalyzerClient {
double TransformResult(double result) const override;
};
class LatencyClient : public frame_metrics::StreamAnalyzerClient {
double TransformResult(double result) const override;
};
class LatencySpeedClient : public frame_metrics::StreamAnalyzerClient {
double TransformResult(double result) const override;
};
class LatencyAccelerationClient : public frame_metrics::StreamAnalyzerClient {
double TransformResult(double result) const override;
};
} // namespace frame_metrics
struct FrameMetricsSettings {
// This is needed for telemetry results.
bool trace_results_every_frame = false;
// Maximum window size in number of samples.
// This is forwarded to each WindowAnalyzer.
size_t max_window_size = 60;
};
// Calculates all metrics for a frame source.
// Every frame source that we wish to instrument will own an instance of
// this class and will call AddFrameProduced and AddFrameDisplayed.
// Statistics will be reported automatically. Either periodically, based
// on the client interface, or on destruction if any samples were added since
// the last call to StartNewReportPeriod.
class FrameMetrics {
public:
// |source_name| must have a global lifetime for tracing and reporting
// purposes.
FrameMetrics(const FrameMetricsSettings& settings, const char* source_name);
virtual ~FrameMetrics();
// Resets all data and history as if the class were just created.
void Reset();
// AddFrameProduced should be called every time a source produces a frame.
// The information added here affects the number of frames skipped.
void AddFrameProduced(base::TimeTicks source_timestamp,
base::TimeDelta amount_produced,
base::TimeDelta amount_skipped);
// AddFrameDisplayed should be called whenever a frame causes damage and
// we know when the result became visible on the display.
// This will affect all latency derived metrics, including latency speed,
// latency acceleration, and latency itself.
// If a frame is produced but not displayed, do not call this; there was
// no change in the displayed result and thus no change to track the visual
// latency of. Guessing a displayed time will only skew the results.
void AddFrameDisplayed(base::TimeTicks source_timestamp,
base::TimeTicks display_timestamp);
protected:
void TraceProducedStats();
void TraceDisplayedStats();
// virtual for testing.
virtual base::TimeDelta ReportPeriod();
// Starts a new reporting period that resets the various accumulators
// and memory of worst regions encountered, but does not destroy recent
// sample history in the windowed analyzers and in the derivatives
// for latency speed and latency acceleration. This avoids small gaps
// in coverage when starting a new reporting period.
void StartNewReportPeriod();
FrameMetricsSettings settings_;
const char* source_name_;
frame_metrics::SharedWindowedAnalyzerClient shared_skip_client_;
base::circular_deque<base::TimeTicks> skip_timestamp_queue_;
frame_metrics::SharedWindowedAnalyzerClient shared_latency_client_;
base::circular_deque<base::TimeTicks> latency_timestamp_queue_;
base::TimeDelta time_since_start_of_report_period_;
uint32_t frames_produced_since_start_of_report_period_ = 0;
uint64_t latencies_added_ = 0;
base::TimeTicks source_timestamp_prev_;
base::TimeDelta latency_prev_;
base::TimeDelta source_duration_prev_;
base::TimeDelta latency_delta_prev_;
frame_metrics::SkipClient skip_client_;
frame_metrics::LatencyClient latency_client_;
frame_metrics::LatencySpeedClient latency_speed_client_;
frame_metrics::LatencyAccelerationClient latency_acceleration_client_;
frame_metrics::StreamAnalyzer frame_skips_analyzer_;
frame_metrics::StreamAnalyzer latency_analyzer_;
frame_metrics::StreamAnalyzer latency_speed_analyzer_;
frame_metrics::StreamAnalyzer latency_acceleration_analyzer_;
DISALLOW_COPY_AND_ASSIGN(FrameMetrics);
};
} // namespace ui
#endif // UI_LATENCY_FRAME_METRICS_H_
...@@ -157,7 +157,7 @@ class TestHistogram : public Histogram { ...@@ -157,7 +157,7 @@ class TestHistogram : public Histogram {
// Histogram interface. // Histogram interface.
void AddSample(uint32_t value, uint32_t weight) override; void AddSample(uint32_t value, uint32_t weight) override;
PercentileResults ComputePercentiles() const override; PercentileResults ComputePercentiles() const override;
void Reset() override{}; void Reset() override {}
// Test interface. // Test interface.
std::vector<ValueWeightPair> GetAndResetAllAddedSamples(); std::vector<ValueWeightPair> GetAndResetAllAddedSamples();
......
This diff is collapsed.
...@@ -55,7 +55,6 @@ ui::PercentileResults PercentilesHelper( ...@@ -55,7 +55,6 @@ ui::PercentileResults PercentilesHelper(
boundary_right = boundary_iterator->Next(); boundary_right = boundary_iterator->Next();
} }
NOTREACHED();
return result; return result;
} }
......
...@@ -13,6 +13,10 @@ ...@@ -13,6 +13,10 @@
namespace ui { namespace ui {
// Used to communicate percentile results to clients. // Used to communicate percentile results to clients.
// If entries in |values| are zero, that means there were no samples.
// A non-zero value implies samples were added since, even if those samples
// were zero, they would go into the [0,N) bucket and result in a non-zero
// estimate.
struct PercentileResults { struct PercentileResults {
static constexpr double kPercentiles[] = {.50, .99}; static constexpr double kPercentiles[] = {.50, .99};
static constexpr size_t kCount = arraysize(kPercentiles); static constexpr size_t kCount = arraysize(kPercentiles);
......
...@@ -41,6 +41,32 @@ TEST(FrameMetricsHistogramsTest, VSyncBoundariesDirect) { ...@@ -41,6 +41,32 @@ TEST(FrameMetricsHistogramsTest, VSyncBoundariesDirect) {
} }
} }
// Results should be 0 if no samples have been added yet.
TEST(FrameMetricsHistogramsTest, ResultsAreZeroWithoutSamples) {
RatioHistogram ratio_histogram;
EXPECT_EQ(0, ratio_histogram.ComputePercentiles().values[0]);
EXPECT_EQ(0, ratio_histogram.ComputePercentiles().values[1]);
VSyncHistogram vsync_histogram;
EXPECT_EQ(0, vsync_histogram.ComputePercentiles().values[0]);
EXPECT_EQ(0, vsync_histogram.ComputePercentiles().values[1]);
}
// A non-zero value implies samples were added since, even if those samples
// were zero, they would go into the [0,N) bucket and result in a non-zero
// estimate.
TEST(FrameMetricsHistogramsTest, ResultsAreNonZeroWithSamplesOfZero) {
RatioHistogram ratio_histogram;
ratio_histogram.AddSample(0, 1);
EXPECT_LT(0, ratio_histogram.ComputePercentiles().values[0]);
EXPECT_LT(0, ratio_histogram.ComputePercentiles().values[1]);
VSyncHistogram vsync_histogram;
vsync_histogram.AddSample(0, 1);
EXPECT_LT(0, vsync_histogram.ComputePercentiles().values[0]);
EXPECT_LT(0, vsync_histogram.ComputePercentiles().values[1]);
}
template <typename ReferenceBoundaryT> template <typename ReferenceBoundaryT>
void BoundaryTestCommon(const ReferenceBoundaryT& reference_boundaries, void BoundaryTestCommon(const ReferenceBoundaryT& reference_boundaries,
std::unique_ptr<Histogram> histogram) { std::unique_ptr<Histogram> histogram) {
......
...@@ -5,6 +5,10 @@ ...@@ -5,6 +5,10 @@
#include "ui/latency/stream_analyzer.h" #include "ui/latency/stream_analyzer.h"
namespace ui { namespace ui {
StreamAnalysis::StreamAnalysis() = default;
StreamAnalysis::~StreamAnalysis() = default;
namespace frame_metrics { namespace frame_metrics {
StreamAnalyzer::StreamAnalyzer( StreamAnalyzer::StreamAnalyzer(
...@@ -42,10 +46,10 @@ void StreamAnalyzer::StartNewReportPeriod() { ...@@ -42,10 +46,10 @@ void StreamAnalyzer::StartNewReportPeriod() {
void StreamAnalyzer::AddSample(const uint32_t value, const uint32_t weight) { void StreamAnalyzer::AddSample(const uint32_t value, const uint32_t weight) {
DCHECK_GT(weight, 0u); DCHECK_GT(weight, 0u);
uint64_t weighted_value = static_cast<uint64_t>(weight) * value; const uint64_t weighted_value = static_cast<uint64_t>(weight) * value;
uint64_t weighted_root = weight * std::sqrt(static_cast<double>(value) * const uint64_t weighted_root = weight * std::sqrt(static_cast<double>(value) *
kFixedPointRootMultiplier); kFixedPointRootMultiplier);
Accumulator96b weighted_square(value, weight); const Accumulator96b weighted_square(value, weight);
// Verify overflow isn't an issue. // Verify overflow isn't an issue.
// square_accumulator_ has DCHECKs internally, so we don't worry about // square_accumulator_ has DCHECKs internally, so we don't worry about
...@@ -143,6 +147,19 @@ PercentileResults StreamAnalyzer::ComputePercentiles() const { ...@@ -143,6 +147,19 @@ PercentileResults StreamAnalyzer::ComputePercentiles() const {
return result; return result;
} }
void StreamAnalyzer::ComputeSummary(StreamAnalysis* results) const {
results->mean = ComputeMean();
results->rms = ComputeRMS();
results->smr = ComputeSMR();
results->std_dev = ComputeStdDev();
results->variance_of_roots = ComputeVarianceOfRoots();
results->thresholds = ComputeThresholds();
results->percentiles = ComputePercentiles();
results->worst_mean = windowed_analyzer_.ComputeWorstMean();
results->worst_rms = windowed_analyzer_.ComputeWorstRMS();
results->worst_smr = windowed_analyzer_.ComputeWorstSMR();
}
std::unique_ptr<base::trace_event::ConvertableToTraceFormat> std::unique_ptr<base::trace_event::ConvertableToTraceFormat>
StreamAnalyzer::AsValue() const { StreamAnalyzer::AsValue() const {
auto state = std::make_unique<base::trace_event::TracedValue>(); auto state = std::make_unique<base::trace_event::TracedValue>();
......
...@@ -24,6 +24,27 @@ struct ThresholdResult { ...@@ -24,6 +24,27 @@ struct ThresholdResult {
double ge_fraction = 0.0; double ge_fraction = 0.0;
}; };
struct StreamAnalysis {
StreamAnalysis();
~StreamAnalysis();
double mean;
double rms;
double smr;
double std_dev;
double variance_of_roots;
std::vector<ThresholdResult> thresholds;
PercentileResults percentiles;
FrameRegionResult worst_mean;
FrameRegionResult worst_rms;
FrameRegionResult worst_smr;
DISALLOW_COPY_AND_ASSIGN(StreamAnalysis);
};
namespace frame_metrics { namespace frame_metrics {
// The StreamAnalyzerClient interface is currently the same as // The StreamAnalyzerClient interface is currently the same as
...@@ -93,6 +114,7 @@ class StreamAnalyzer { ...@@ -93,6 +114,7 @@ class StreamAnalyzer {
// available directly. // available directly.
const WindowedAnalyzer& window() const { return windowed_analyzer_; } const WindowedAnalyzer& window() const { return windowed_analyzer_; }
void ComputeSummary(StreamAnalysis* results) const;
std::unique_ptr<base::trace_event::ConvertableToTraceFormat> AsValue() const; std::unique_ptr<base::trace_event::ConvertableToTraceFormat> AsValue() const;
void AsValueInto(base::trace_event::TracedValue* state) const; void AsValueInto(base::trace_event::TracedValue* state) const;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment