Commit 722959dd authored by Brian Anderson's avatar Brian Anderson Committed by Commit Bot

ui: Add FrameMetrics stream analyzer helpers.

This adds the StreamAnalyzer class, which is responsible
for calculating the mean, rms, smr, standard deviation,
variance of roots, and threshold percentiles of a
continuous stream of values.

It owns a Histogram and a WindowedAnalyzer, which it uses
to delegate percentile estimates and to track regions of
time where metrics are worst.

Bug: 807463
Change-Id: I718b06778582d0628b964747f61352ae291452f0
Reviewed-on: https://chromium-review.googlesource.com/972568
Commit-Queue: Brian Anderson <brianderson@chromium.org>
Reviewed-by: default avatarSadrul Chowdhury <sadrul@chromium.org>
Reviewed-by: default avatarTimothy Dresser <tdresser@chromium.org>
Cr-Commit-Position: refs/heads/master@{#546168}
parent 026a45f3
......@@ -16,6 +16,8 @@ jumbo_source_set("latency") {
"latency_info.h",
"latency_tracker.cc",
"latency_tracker.h",
"stream_analyzer.cc",
"stream_analyzer.h",
"windowed_analyzer.cc",
"windowed_analyzer.h",
]
......@@ -44,10 +46,11 @@ jumbo_source_set("test_support") {
test("latency_unittests") {
sources = [
"fixed_point_unittest.cc",
"histograms_test_common.cc",
"histograms_test_common.h",
"frame_metrics_test_common.cc",
"frame_metrics_test_common.h",
"histograms_unittest.cc",
"latency_info_unittest.cc",
"stream_analyzer_unittest.cc",
"windowed_analyzer_unittest.cc",
]
......@@ -77,9 +80,9 @@ test("latency_unittests") {
test("latency_perftests") {
sources = [
"frame_metrics_test_common.cc",
"frame_metrics_test_common.h",
"histograms_perftest.cc",
"histograms_test_common.cc",
"histograms_test_common.h",
]
deps = [
......
......@@ -23,6 +23,8 @@ constexpr int64_t kFixedPointMultiplier{1LL << kFixedPointShift};
// root and undoing that shift after squaring in the SMR calculation.
constexpr int kFixedPointRootShift = 32;
constexpr int64_t kFixedPointRootMultiplier{1LL << kFixedPointRootShift};
constexpr int64_t kFixedPointRootMultiplierSqrt{1LL
<< (kFixedPointRootShift / 2)};
// We need a huge range to accumulate values for RMS calculations, which
// need double the range internally compared to the range we are targeting
......
......@@ -54,6 +54,15 @@ TEST(FrameMetricsFixedPointTest, kFixedPointRootMultiplier) {
EXPECT_LE(error1, 1);
}
TEST(FrameMetricsFixedPointTest, kFixedPointRootMultiplierSqrt) {
EXPECT_EQ(kFixedPointRootMultiplierSqrt,
std::sqrt(kFixedPointRootMultiplier));
}
TEST(FrameMetricsFixedPointTest, kFixedPointRootShift) {
EXPECT_EQ(kFixedPointRootMultiplier, 1LL << kFixedPointRootShift);
}
// Verify Accumulator96b's squared weight constructor.
TEST(FrameMetricsFixedPointTest, Accumulator96bConstructor) {
// A small value that fits in 32 bits.
......
......@@ -2,13 +2,29 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/latency/histograms_test_common.h"
#include "ui/latency/frame_metrics_test_common.h"
#include "base/logging.h"
namespace ui {
namespace frame_metrics {
double TestStreamAnalyzerClient::TransformResult(double result) const {
return result * result_scale;
}
template <>
void AddSamplesHelper(StreamAnalyzer* analyzer,
uint64_t value,
uint64_t weight,
size_t iterations) {
DCHECK_LE(value, std::numeric_limits<uint32_t>::max());
DCHECK_LE(weight, std::numeric_limits<uint32_t>::max());
for (size_t i = 0; i < iterations; i++) {
analyzer->AddSample(value, weight);
}
}
TestRatioBoundaries::TestRatioBoundaries() {
const uint32_t one = kFixedPointMultiplier;
const uint32_t half = one / 2;
......@@ -52,5 +68,25 @@ TestRatioBoundaries::TestRatioBoundaries() {
DCHECK_EQ(112, i);
}
TestHistogram::TestHistogram() = default;
TestHistogram::~TestHistogram() = default;
void TestHistogram::AddSample(uint32_t value, uint32_t weight) {
added_samples_.push_back({value, weight});
}
PercentileResults TestHistogram::CalculatePercentiles() const {
return results_;
}
std::vector<TestHistogram::ValueWeightPair>
TestHistogram::GetAndResetAllAddedSamples() {
return std::move(added_samples_);
}
void TestHistogram::SetResults(PercentileResults results) {
results_ = results;
}
} // namespace frame_metrics
} // namespace ui
......@@ -2,16 +2,107 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_LATENCY_HISTOGRAMS_TEST_COMMON_H_
#define UI_LATENCY_HISTOGRAMS_TEST_COMMON_H_
#ifndef UI_LATENCY_FRAME_METRICS_TEST_COMMON_H_
#define UI_LATENCY_FRAME_METRICS_TEST_COMMON_H_
#include "ui/latency/fixed_point.h"
#include "ui/latency/histograms.h"
#include "ui/latency/stream_analyzer.h"
#include "ui/latency/windowed_analyzer.h"
#include <array>
// Some convenience macros for checking expected error.
#define EXPECT_ABS_LT(a, b) EXPECT_LT(std::abs(a), std::abs(b))
#define EXPECT_ABS_LE(a, b) EXPECT_LE(std::abs(a), std::abs(b))
#define EXPECT_NEAR_SMR(expected, actual, weight) \
EXPECT_NEAR(expected, actual, MaxErrorSMR(expected, weight))
#define EXPECT_NEAR_VARIANCE_OF_ROOT(expected, actual, mean, weight) \
EXPECT_NEAR(expected, actual, MaxErrorSMR(mean, weight));
namespace ui {
namespace frame_metrics {
// A simple client to verify it is actually used.
class TestStreamAnalyzerClient : public StreamAnalyzerClient {
public:
double TransformResult(double result) const override;
static constexpr double result_scale = 2.0;
};
using TestWindowedAnalyzerClient = TestStreamAnalyzerClient;
// The WindowedAnalyzer expects the caller to give it some precomputed values,
// even though they are redundant. Precompute them with a helper function to
// remove boilerplate.
// A specialized version of this for StreamAnalyzer that doesn't pre compute
// the weighted values is defined in the implementation file.
template <typename AnalyzerType>
void AddSamplesHelper(AnalyzerType* analyzer,
uint64_t value,
uint64_t weight,
size_t iterations) {
DCHECK_LE(value, std::numeric_limits<uint32_t>::max());
DCHECK_LE(weight, std::numeric_limits<uint32_t>::max());
uint64_t weighted_value = weight * value;
uint64_t weighted_root = weight * std::sqrt(value << kFixedPointRootShift);
Accumulator96b weighted_square(value, weight);
for (size_t i = 0; i < iterations; i++) {
analyzer->AddSample(value, weight, weighted_value, weighted_root,
weighted_square);
}
}
// A specialization of the templatized AddSamplesHelper above for
// the WindowedAnalyzer, which doesn't need to have it's weighted values
// pre computed.
template <>
void AddSamplesHelper(StreamAnalyzer* analyzer,
uint64_t value,
uint64_t weight,
size_t iterations);
// Moves the |shared_client|'s window forward in time by 1 microsecond and
// adds all of the elements in |values| multipled by kFixedPointMultiplier.
template <typename AnalyzerType>
void AddPatternHelper(SharedWindowedAnalyzerClient* shared_client,
AnalyzerType* analyzer,
const std::vector<uint32_t>& values,
const uint32_t weight) {
for (auto i : values) {
shared_client->window_begin += base::TimeDelta::FromMicroseconds(1);
shared_client->window_end += base::TimeDelta::FromMicroseconds(1);
AddSamplesHelper(analyzer, i * kFixedPointMultiplier, weight, 1);
}
}
// Same as AddPatternHelper, but uses each value (+1) as its own weight.
// The "Cubed" name comes from the fact that the squared_accumulator
// for the RMS will effectively be a "cubed accumulator".
template <typename AnalyzerType>
void AddCubedPatternHelper(SharedWindowedAnalyzerClient* shared_client,
AnalyzerType* analyzer,
const std::vector<uint32_t>& values) {
for (auto i : values) {
shared_client->window_begin += base::TimeDelta::FromMicroseconds(1);
shared_client->window_end += base::TimeDelta::FromMicroseconds(1);
// weight is i+1 to avoid divide by zero.
AddSamplesHelper(analyzer, i, i + 1, 1);
}
}
// Mean and RMS can be exact for most values, however SMR loses a bit of
// precision internally when accumulating the roots. Make sure the SMR
// precision is at least within .5 (i.e. rounded to the nearest integer
// properly), or 8 decimal places if that is less precise.
// When used with kFixedPointMultiplier, this gives us a total precision of
// between ~5 and ~13 decimal places.
// The precision should be even better when the sample's |weight| > 1 since
// the implementation should only do any rounding after scaling by weight.
inline double MaxErrorSMR(double expected_value, uint64_t weight) {
return std::max(.5, 1e-8 * expected_value / weight);
}
// This class initializes the ratio boundaries on construction in a way that
// is easier to follow than the procedural code in the RatioHistogram
// implementation.
......@@ -52,7 +143,32 @@ static constexpr std::array<uint32_t, 99> kTestVSyncBoundries = {
// C7: Extra value to simplify estimate in Percentiles().
64000000}};
// A histogram that can be used for dependency injection in tests.
class TestHistogram : public Histogram {
public:
struct ValueWeightPair {
uint32_t value;
uint32_t weight;
};
TestHistogram();
~TestHistogram() override;
// Histogram interface.
void AddSample(uint32_t value, uint32_t weight) override;
PercentileResults CalculatePercentiles() const override;
void Reset() override{};
// Test interface.
std::vector<ValueWeightPair> GetAndResetAllAddedSamples();
void SetResults(PercentileResults results);
private:
PercentileResults results_;
std::vector<ValueWeightPair> added_samples_;
};
} // namespace frame_metrics
} // namespace ui
#endif // UI_LATENCY_HISTOGRAMS_TEST_COMMON_H_
#endif // UI_LATENCY_FRAME_METRICS_TEST_COMMON_H_
......@@ -12,7 +12,7 @@
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/perf/perf_test.h"
#include "ui/latency/fixed_point.h"
#include "ui/latency/histograms_test_common.h"
#include "ui/latency/frame_metrics_test_common.h"
namespace ui {
namespace frame_metrics {
......@@ -147,7 +147,7 @@ class VSyncHistogramBaseline : public Histogram {
for (const auto& b : kTestVSyncBoundries) {
bucket_ranges_.set_range(i++, b);
}
// BucketRanges needs the last elemet set to INT_MAX.
// BucketRanges needs the last element set to INT_MAX.
bucket_ranges_.set_range(i++, INT_MAX);
}
......
......@@ -11,7 +11,7 @@
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/latency/fixed_point.h"
#include "ui/latency/histograms_test_common.h"
#include "ui/latency/frame_metrics_test_common.h"
namespace ui {
namespace frame_metrics {
......
// 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.
#include "ui/latency/stream_analyzer.h"
namespace ui {
namespace frame_metrics {
StreamAnalyzer::StreamAnalyzer(
const StreamAnalyzerClient* client,
const SharedWindowedAnalyzerClient* shared_client,
std::vector<uint32_t> thresholds,
std::unique_ptr<Histogram> histogram)
: client_(client),
histogram_(std::move(histogram)),
windowed_analyzer_(client, shared_client) {
thresholds_.reserve(thresholds.size());
for (const uint32_t& t : thresholds)
thresholds_.emplace_back(t);
}
StreamAnalyzer::~StreamAnalyzer() = default;
void StreamAnalyzer::Reset() {
StartNewReportPeriod();
windowed_analyzer_.ResetHistory();
}
void StreamAnalyzer::StartNewReportPeriod() {
histogram_->Reset();
windowed_analyzer_.ResetWorstValues();
for (auto& t : thresholds_)
t.ResetAccumulators();
total_weight_ = 0;
accumulator_ = 0;
root_accumulator_ = 0;
square_accumulator_ = Accumulator96b();
}
void StreamAnalyzer::AddSample(const uint32_t value, const uint32_t weight) {
DCHECK_GT(weight, 0u);
uint64_t weighted_value = static_cast<uint64_t>(weight) * value;
uint64_t weighted_root = weight * std::sqrt(static_cast<double>(value) *
kFixedPointRootMultiplier);
Accumulator96b weighted_square(value, weight);
// Verify overflow isn't an issue.
// square_accumulator_ has DCHECKs internally, so we don't worry about
// checking that here.
DCHECK_LT(weighted_value,
std::numeric_limits<decltype(accumulator_)>::max() - accumulator_);
DCHECK_LT(weighted_root,
std::numeric_limits<decltype(root_accumulator_)>::max() -
root_accumulator_);
DCHECK_LT(weight, std::numeric_limits<decltype(total_weight_)>::max() -
total_weight_);
histogram_->AddSample(value, weight);
windowed_analyzer_.AddSample(value, weight, weighted_value, weighted_root,
weighted_square);
for (auto& t : thresholds_) {
if (value >= t.threshold)
t.ge_weight += weight;
else
t.lt_weight += weight;
}
total_weight_ += weight;
accumulator_ += weighted_value;
root_accumulator_ += weighted_root;
square_accumulator_.Add(weighted_square);
}
double StreamAnalyzer::ComputeMean() const {
double result = static_cast<double>(accumulator_) / total_weight_;
return client_->TransformResult(result);
}
double StreamAnalyzer::ComputeRMS() const {
double mean_square = square_accumulator_.ToDouble() / total_weight_;
double result = std::sqrt(mean_square);
return client_->TransformResult(result);
}
double StreamAnalyzer::ComputeSMR() const {
double mean_root = static_cast<double>(root_accumulator_) / total_weight_;
double result = (mean_root * mean_root) / kFixedPointRootMultiplier;
return client_->TransformResult(result);
}
double StreamAnalyzer::VarianceHelper(double accum, double square_accum) const {
double mean = accum / total_weight_;
double mean_squared = mean * mean;
double mean_square = square_accum / total_weight_;
double variance = mean_square - mean_squared;
// This approach to calculating the standard deviation isn't numerically
// stable if the variance is very small relative to the mean, which might
// result in a negative variance. Clamp it to 0.
return std::max(0.0, variance);
}
double StreamAnalyzer::ComputeStdDev() const {
double variance =
VarianceHelper(accumulator_, square_accumulator_.ToDouble());
double std_dev = std::sqrt(variance);
return client_->TransformResult(std_dev);
}
double StreamAnalyzer::ComputeVarianceOfRoots() const {
double normalized_root =
static_cast<double>(root_accumulator_) / kFixedPointRootMultiplierSqrt;
double variance = VarianceHelper(normalized_root, accumulator_);
return client_->TransformResult(variance);
}
void StreamAnalyzer::ThresholdState::ResetAccumulators() {
ge_weight = 0;
lt_weight = 0;
}
std::vector<ThresholdResult> StreamAnalyzer::ComputeThresholds() const {
std::vector<ThresholdResult> results;
results.reserve(thresholds_.size());
for (const auto& t : thresholds_) {
double threshold = client_->TransformResult(t.threshold);
double ge_fraction =
static_cast<double>(t.ge_weight) / (t.ge_weight + t.lt_weight);
results.push_back({threshold, ge_fraction});
}
return results;
}
PercentileResults StreamAnalyzer::ComputePercentiles() const {
PercentileResults result;
result = histogram_->CalculatePercentiles();
for (size_t i = 0; i < PercentileResults::kCount; i++) {
result.values[i] = client_->TransformResult(result.values[i]);
}
return result;
}
std::unique_ptr<base::trace_event::ConvertableToTraceFormat>
StreamAnalyzer::AsValue() const {
auto state = std::make_unique<base::trace_event::TracedValue>();
AsValueInto(state.get());
return std::move(state);
}
void StreamAnalyzer::AsValueInto(base::trace_event::TracedValue* state) const {
state->SetDouble("mean", ComputeMean());
state->SetDouble("rms", ComputeRMS());
state->SetDouble("smr", ComputeSMR());
state->SetDouble("std_dev", ComputeStdDev());
state->SetDouble("variance_of_roots", ComputeVarianceOfRoots());
state->BeginArray("percentiles");
PercentileResults result = ComputePercentiles();
for (size_t i = 0; i < PercentileResults::kCount; i++) {
state->BeginArray();
state->AppendDouble(PercentileResults::kPercentiles[i]);
state->AppendDouble(result.values[i]);
state->EndArray();
}
state->EndArray();
state->BeginArray("thresholds");
std::vector<ThresholdResult> thresholds(ComputeThresholds());
for (const auto& t : thresholds) {
state->BeginArray();
state->AppendDouble(t.threshold);
state->AppendDouble(t.ge_fraction);
state->EndArray();
}
state->EndArray();
windowed_analyzer_.AsValueInto(state);
}
} // namespace frame_metrics
} // namespace ui
// 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_STREAM_ANALYZER_H_
#define UI_LATENCY_STREAM_ANALYZER_H_
#include <cstdint>
#include <memory>
#include <vector>
#include "base/macros.h"
#include "base/trace_event/trace_event_argument.h"
#include "ui/latency/fixed_point.h"
#include "ui/latency/histograms.h"
#include "ui/latency/windowed_analyzer.h"
namespace ui {
// Used to communicate fraction of time the value of a metric was greater than
// or equal to the threshold.
struct ThresholdResult {
double threshold = 0.0;
double ge_fraction = 0.0;
};
namespace frame_metrics {
// The StreamAnalyzerClient interface is currently the same as
// WindowedAnalyzerClient and can rely on the same implementation.
using StreamAnalyzerClient = WindowedAnalyzerClient;
// Tracks the overall mean, RMS, and SMR for a metric and also owns
// the Histogram and WindowedAnalyzer.
class StreamAnalyzer {
public:
StreamAnalyzer(const StreamAnalyzerClient* client,
const SharedWindowedAnalyzerClient* shared_client,
std::vector<uint32_t> thresholds,
std::unique_ptr<Histogram> histogram);
~StreamAnalyzer();
// Resets all statistics and history.
void Reset();
// Resets the statistics without throwing away recent sample history in the
// WindowedAnalyzer.
void StartNewReportPeriod();
// To play well with the histogram range, |value| should be within the
// range [0,64000000]. If the units are milliseconds, that's 64 seconds.
// Otherwise, the histogram will clip the result.
// |weight| may be the duration the frame was active in microseconds
// or it may be 1 in case every frame is to be weighed equally.
void AddSample(const uint32_t value, const uint32_t weight);
// The mean, root-mean-squared, and squared-mean-root of all samples
// received since the last call to StartNewReportPeriod().
// The units are the same as the values added in AddSample().
double ComputeMean() const;
double ComputeRMS() const;
double ComputeSMR() const;
// StdDev calculates the standard deviation of all values in the stream.
// The units are the same as the values added in AddSample().
// The work to track this is the same as RMS, so we effectively get this for
// free. Given two of the Mean, RMS, and StdDev, we can calculate the third.
double ComputeStdDev() const;
// VarianceOfRoots calculates the variance of all square roots of values.
// The units end up being the same as the values added in AddSample().
// The work to track this is the same as SMR.
// Given two of the Mean, SMR, and VarianceOfRoots, we can calculate the
// third. Note: We don't track something like RootStdDevOfSquares since it
// would be difficult to track values raised to the fourth power.
// TODO(brianderon): Remove VarianceOfRoots if it's not useful.
double ComputeVarianceOfRoots() const;
// Thresholds returns a percentile for threshold values given to the
// constructor. This is useful for tracking improvements in really good
// sources, but it's dynamic range is limited, which prevents it from
// detecting improvements in sources where most of the frames are "bad".
std::vector<ThresholdResult> ComputeThresholds() const;
// CalculatePercentiles returns a value for certain percentiles.
// It is only an estimate, since the values are calculated from a histogram
// rather than from the entire history of actual values.
// This is useful for tracking improvements even in really bad sources
// since it's dynamic range includes all possible values.
PercentileResults ComputePercentiles() const;
// Expose the WindowedAnalyzer as const to make it's accessors
// available directly.
const WindowedAnalyzer& window() const { return windowed_analyzer_; }
std::unique_ptr<base::trace_event::ConvertableToTraceFormat> AsValue() const;
void AsValueInto(base::trace_event::TracedValue* state) const;
protected:
double VarianceHelper(double accum, double square_accum) const;
struct ThresholdState {
explicit ThresholdState(uint32_t value) : threshold(value) {}
void ResetAccumulators();
uint32_t threshold;
uint32_t ge_weight = 0;
uint32_t lt_weight = 0;
};
const StreamAnalyzerClient* const client_;
std::vector<ThresholdState> thresholds_;
std::unique_ptr<Histogram> histogram_;
WindowedAnalyzer windowed_analyzer_;
uint64_t total_weight_ = 0;
uint64_t accumulator_ = 0;
uint64_t root_accumulator_ = 0;
Accumulator96b square_accumulator_;
DISALLOW_COPY_AND_ASSIGN(StreamAnalyzer);
};
} // namespace frame_metrics
} // namespace ui
#endif // UI_LATENCY_STREAM_ANALYZER_H_
This diff is collapsed.
......@@ -24,6 +24,18 @@ WindowedAnalyzer::WindowedAnalyzer(
WindowedAnalyzer::~WindowedAnalyzer() = default;
void WindowedAnalyzer::ResetWorstValues() {
results_.reset();
}
void WindowedAnalyzer::ResetHistory() {
total_weight_ = 0;
accumulator_ = 0;
root_accumulator_ = 0;
square_accumulator_ = Accumulator96b();
window_queue_.resize(0);
}
void WindowedAnalyzer::AddSample(uint32_t value,
uint32_t weight,
uint64_t weighted_value,
......@@ -130,11 +142,5 @@ void WindowedAnalyzer::AsValueInto(
state->EndDictionary();
}
void WindowedAnalyzer::ResetWorstValues() {
// Reset the worst windows, but not the current window history so that we
// don't lose coverage at the reset boundaries.
results_.reset();
}
} // namespace frame_metrics
} // namespace ui
......@@ -69,6 +69,14 @@ class WindowedAnalyzer {
const SharedWindowedAnalyzerClient* shared_client);
virtual ~WindowedAnalyzer();
// ResetWosrtValues only resets the memory of worst values encountered,
// without resetting recent sample history.
void ResetWorstValues();
// ResetHistory only resets recent sample history without resetting memory
// of the worst values ecnountered.
void ResetHistory();
// Callers of AddSample will already have calculated weighted values to
// track cumulative results, so just let them pass in the values here
// rather than re-calculating them.
......@@ -85,8 +93,6 @@ class WindowedAnalyzer {
void AsValueInto(base::trace_event::TracedValue* state) const;
void ResetWorstValues();
protected:
struct QueueEntry {
uint32_t value = 0;
......
......@@ -6,87 +6,12 @@
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
// Some convenience macros for checking expected error.
#define EXPECT_ABS_LT(a, b) EXPECT_LT(std::abs(a), std::abs(b))
#define EXPECT_ABS_LE(a, b) EXPECT_LE(std::abs(a), std::abs(b))
#define EXPECT_NEAR_SMR(expected, actual, weight) \
EXPECT_NEAR(expected, actual, MaxErrorSMR(expected, weight))
#include "ui/latency/frame_metrics_test_common.h"
namespace ui {
namespace frame_metrics {
namespace {
// A simple client to verify it is actually used.
class TestWindowedAnalyzerClient : public WindowedAnalyzerClient {
public:
double TransformResult(double result) const override {
return result * result_scale;
}
static constexpr double result_scale = 2.0;
};
// The WindowedAnalyzer expects the caller to give it some precomputed values,
// even though they are redundant. Precompute them with a helper function to
// remove boilerplate.
template <typename AnalyzerType>
void AddSamplesHelper(AnalyzerType* analyzer,
uint64_t value,
uint64_t weight,
size_t iterations) {
DCHECK_LE(value, std::numeric_limits<uint32_t>::max());
DCHECK_LE(weight, std::numeric_limits<uint32_t>::max());
uint64_t weighted_value = weight * value;
uint64_t weighted_root = weight * std::sqrt(value << kFixedPointRootShift);
Accumulator96b weighted_square(value, weight);
for (size_t i = 0; i < iterations; i++) {
analyzer->AddSample(value, weight, weighted_value, weighted_root,
weighted_square);
}
}
// Moves the |shared_client|'s window forward in time by 1 microsecond and
// adds all of the elements in |values| multipled by kFixedPointMultiplier.
template <typename AnalyzerType>
void AddPatternHelper(SharedWindowedAnalyzerClient* shared_client,
AnalyzerType* analyzer,
const std::vector<uint32_t>& values,
const uint32_t weight) {
for (auto i : values) {
shared_client->window_begin += base::TimeDelta::FromMicroseconds(1);
shared_client->window_end += base::TimeDelta::FromMicroseconds(1);
AddSamplesHelper(analyzer, i * kFixedPointMultiplier, weight, 1);
}
}
// Same as AddPatternHelper, but uses each value (+1) as its own weight.
// The "Cubed" name comes from the fact that the squared_accumulator
// for the RMS will effectively be a "cubed accumulator".
template <typename AnalyzerType>
void AddCubedPatternHelper(SharedWindowedAnalyzerClient* shared_client,
AnalyzerType* analyzer,
const std::vector<uint32_t>& values) {
for (auto i : values) {
shared_client->window_begin += base::TimeDelta::FromMicroseconds(1);
shared_client->window_end += base::TimeDelta::FromMicroseconds(1);
// weight is i+1 to avoid divide by zero.
AddSamplesHelper(analyzer, i, i + 1, 1);
}
}
// Mean and RMS can be exact for most values, however SMR loses a bit of
// precision internally when accumulating the roots. Make sure the SMR
// precision is at least within .5 (i.e. rounded to the nearest integer
// properly), or 8 decimal places if that is less precise.
// When used with kFixedPointMultiplier, this gives us a total precision of
// between ~5 and ~13 decimal places.
// The precicion should be even better when the sample's |weight| > 1 since
// the implementation should only do any rounding after scaling by weight.
double MaxErrorSMR(double expected_value, uint64_t weight) {
return std::max(.5, 1e-8 * expected_value / weight);
}
// Verify that the worst values for Mean, SMR, and RMS are all the same if
// every value added is the same. Makes for a nice sanity check.
TEST(FrameMetricsWindowedAnalyzerTest, AllResultsTheSame) {
......@@ -393,9 +318,10 @@ TEST(FrameMetricsWindowedAnalyzerTest, ResetWorstValues) {
}
// WindowedAnalyzerNaive is a version of WindowedAnalyzer that doesn't use
// fixed point math and can accumulate error. This is used to verify patterns
// that accumulate error without fixed point math, so we can then verify those
// patterns don't result in acculated error in the actual implementation.
// fixed point math and can accumulate error, even with double precision
// accumulators. This is used to verify patterns that accumulate error without
// fixed point math, so we can then verify those patterns don't result in
// acculated error in the actual implementation.
class WindowedAnalyzerNaive {
public:
WindowedAnalyzerNaive(size_t max_window_size)
......
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