blob: 5aca77c7610dd74c22c756d4a8e95bb0e7960426 [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.
#include "ui/latency/stream_analyzer.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/latency/frame_metrics_test_common.h"
namespace ui {
namespace frame_metrics {
namespace {
class StreamAnalyzerTest : public testing::Test {
public:
StreamAnalyzerTest() { NewAnalyzer(10, {2, 7, 10}); }
void SetUp() override {}
StreamAnalyzer* analyzer() { return analyzer_.get(); }
void NewAnalyzer(size_t window_size, std::vector<uint32_t> thresholds) {
shared_client_.max_window_size = window_size;
for (auto& t : thresholds) {
t *= kFixedPointMultiplier;
}
thresholds_ = std::move(thresholds);
std::unique_ptr<TestHistogram> histogram =
std::make_unique<TestHistogram>();
histogram_ = histogram.get();
analyzer_ = std::make_unique<StreamAnalyzer>(
&client_, &shared_client_, thresholds_, std::move(histogram));
}
protected:
size_t window_size;
TestStreamAnalyzerClient client_;
SharedWindowedAnalyzerClient shared_client_;
std::vector<uint32_t> thresholds_;
TestHistogram* histogram_;
std::unique_ptr<StreamAnalyzer> analyzer_;
};
TEST_F(StreamAnalyzerTest, AllResultsTheSame) {
const double approx_sqrt_error = 0.0001;
// Try adding a single sample vs. multiple samples.
for (size_t samples : {1u, 100u}) {
// A power of 2 sweep for both the value and weight dimensions.
for (uint64_t value = 1; value < 0x100000000ULL; value *= 2) {
// Adding too many samples can result in overflow when multiplied by the
// weight. Divide by samples to avoid overflow.
for (uint64_t weight = 1; weight < 0x100000000ULL / samples;
weight *= 2) {
analyzer()->Reset();
AddSamplesHelper(analyzer(), value, weight, samples);
uint64_t expected_value =
value * TestStreamAnalyzerClient::result_scale;
EXPECT_EQ(expected_value, analyzer_->ComputeMean());
EXPECT_NEAR_SQRT_APPROX(expected_value, analyzer_->ComputeRMS());
EXPECT_NEAR_SQRT_APPROX(analyzer_->ComputeSMR(), expected_value);
EXPECT_NEAR_SQRT_APPROX(0, analyzer_->ComputeStdDev());
EXPECT_NEAR(0, analyzer_->ComputeVarianceOfRoots(),
approx_sqrt_error * value);
// Verify values are forwarded to the WindowedAnalyzer.
EXPECT_EQ(expected_value, analyzer_->window().ComputeWorstMean().value);
EXPECT_NEAR_SQRT_APPROX(expected_value,
analyzer_->window().ComputeWorstRMS().value);
EXPECT_NEAR_SQRT_APPROX(expected_value,
analyzer_->window().ComputeWorstSMR().value);
}
}
}
// All min/max combinations of value and weight.
for (uint64_t value : {0u, 0xFFFFFFFFu}) {
for (uint64_t weight : {1u, 0xFFFFFFFFu}) {
const size_t kSamplesToAdd = weight == 1 ? 100 : 1;
analyzer()->Reset();
AddSamplesHelper(analyzer(), value, weight, kSamplesToAdd);
// TestWindowedAnalyzerClient scales the result by 2.
uint64_t expected_value = value * TestStreamAnalyzerClient::result_scale;
// Makes sure our precision is good enough.
EXPECT_EQ(expected_value, analyzer_->ComputeMean());
EXPECT_NEAR_SQRT_APPROX(expected_value, analyzer_->ComputeRMS());
EXPECT_NEAR_SQRT_APPROX(expected_value, analyzer_->ComputeSMR());
EXPECT_NEAR_SQRT_APPROX(0, analyzer_->ComputeStdDev());
EXPECT_NEAR(0, analyzer_->ComputeVarianceOfRoots(),
approx_sqrt_error * value);
// Verify values are forwarded to the WindowedAnalyzer.
EXPECT_EQ(expected_value, analyzer_->window().ComputeWorstMean().value);
EXPECT_NEAR_SQRT_APPROX(expected_value,
analyzer_->window().ComputeWorstRMS().value);
EXPECT_NEAR_SQRT_APPROX(expected_value,
analyzer_->window().ComputeWorstSMR().value);
}
}
}
// This applies a pattern of 2 values that are easy to calculate the expected
// results for. It verifies the mean, rms, smr, standard deviation,
// variance of the roots, and thresholds are calculated properly.
// This doesn't check histogram or windowed analyzer related values since they
// are tested separately and other unit tests verify their interactions
// with StreamAnalyzer.
TEST_F(StreamAnalyzerTest, AllResultsDifferent) {
const uint32_t kSampleWeight = 100;
const std::vector<uint32_t> pattern49 = {4, 9, 4, 9, 4, 9};
const std::vector<uint32_t> pattern4 = {4, 4, 4, 4, 4, 4};
const std::vector<uint32_t> pattern9 = {9, 9, 9, 9, 9, 9};
// Calculate the expected values for an equal number of 4's and 9's.
const double expected_mean = (4 + 9) * .5 * kFixedPointMultiplier *
TestStreamAnalyzerClient::result_scale;
const double expected_rms = std::sqrt((16 + 81) * .5) *
kFixedPointMultiplier *
TestStreamAnalyzerClient::result_scale;
const double mean_root = (2 + 3) * .5;
const double expected_smr = mean_root * mean_root * kFixedPointMultiplier *
TestStreamAnalyzerClient::result_scale;
const double expected_std_dev = (9 - 4) * .5 * kFixedPointMultiplier *
TestStreamAnalyzerClient::result_scale;
const double std_dev_of_roots = (3 - 2) * .5;
const double expected_variance_of_roots =
std_dev_of_roots * std_dev_of_roots * kFixedPointMultiplier *
TestStreamAnalyzerClient::result_scale;
std::vector<ThresholdResult> thresholds;
// Alternate 4 and 9.
for (size_t i = 0; i < 1000; i++) {
AddPatternHelper(&shared_client_, analyzer(), pattern49, kSampleWeight);
EXPECT_DOUBLE_EQ(expected_mean, analyzer_->ComputeMean());
// since each value creates a difference of 0.001, the result should
EXPECT_NEAR_SQRT_APPROX(expected_smr, analyzer_->ComputeSMR());
EXPECT_NEAR_SQRT_APPROX(expected_rms, analyzer_->ComputeRMS());
EXPECT_NEAR_SQRT_APPROX(expected_std_dev, analyzer_->ComputeStdDev());
EXPECT_NEAR_SQRT_APPROX(expected_variance_of_roots,
analyzer_->ComputeVarianceOfRoots());
}
thresholds = analyzer_->ComputeThresholds();
ASSERT_EQ(3u, thresholds.size());
EXPECT_EQ(client_.TransformResult(thresholds_[0]), thresholds[0].threshold);
EXPECT_EQ(client_.TransformResult(thresholds_[1]), thresholds[1].threshold);
EXPECT_EQ(client_.TransformResult(thresholds_[2]), thresholds[2].threshold);
EXPECT_EQ(1.0, thresholds[0].ge_fraction);
EXPECT_EQ(0.5, thresholds[1].ge_fraction);
EXPECT_EQ(0.0, thresholds[2].ge_fraction);
// 4's then 9's.
analyzer()->Reset();
for (size_t i = 0; i < 500; i++) {
AddPatternHelper(&shared_client_, analyzer(), pattern4, kSampleWeight);
}
for (size_t i = 0; i < 500; i++) {
AddPatternHelper(&shared_client_, analyzer(), pattern9, kSampleWeight);
}
thresholds = analyzer_->ComputeThresholds();
EXPECT_DOUBLE_EQ(expected_mean, analyzer_->ComputeMean());
EXPECT_NEAR_SQRT_APPROX(expected_smr, analyzer_->ComputeSMR());
EXPECT_NEAR_SQRT_APPROX(expected_rms, analyzer_->ComputeRMS());
EXPECT_NEAR_SQRT_APPROX(expected_std_dev, analyzer_->ComputeStdDev());
EXPECT_NEAR_SQRT_APPROX(expected_variance_of_roots,
analyzer_->ComputeVarianceOfRoots());
EXPECT_EQ(client_.TransformResult(thresholds_[0]), thresholds[0].threshold);
EXPECT_EQ(client_.TransformResult(thresholds_[1]), thresholds[1].threshold);
EXPECT_EQ(client_.TransformResult(thresholds_[2]), thresholds[2].threshold);
EXPECT_EQ(1.0, thresholds[0].ge_fraction);
EXPECT_EQ(0.5, thresholds[1].ge_fraction);
EXPECT_EQ(0.0, thresholds[2].ge_fraction);
// 9's then 4's.
analyzer()->Reset();
for (size_t i = 0; i < 500; i++) {
AddPatternHelper(&shared_client_, analyzer(), pattern9, kSampleWeight);
}
for (size_t i = 0; i < 500; i++) {
AddPatternHelper(&shared_client_, analyzer(), pattern4, kSampleWeight);
}
thresholds = analyzer_->ComputeThresholds();
EXPECT_DOUBLE_EQ(expected_mean, analyzer_->ComputeMean());
EXPECT_NEAR_SQRT_APPROX(expected_smr, analyzer_->ComputeSMR());
EXPECT_NEAR_SQRT_APPROX(expected_rms, analyzer_->ComputeRMS());
EXPECT_NEAR_SQRT_APPROX(expected_std_dev, analyzer_->ComputeStdDev());
EXPECT_NEAR_SQRT_APPROX(expected_variance_of_roots,
analyzer_->ComputeVarianceOfRoots());
EXPECT_EQ(client_.TransformResult(thresholds_[0]), thresholds[0].threshold);
EXPECT_EQ(client_.TransformResult(thresholds_[1]), thresholds[1].threshold);
EXPECT_EQ(client_.TransformResult(thresholds_[2]), thresholds[2].threshold);
EXPECT_EQ(1.0, thresholds[0].ge_fraction);
EXPECT_EQ(0.5, thresholds[1].ge_fraction);
EXPECT_EQ(0.0, thresholds[2].ge_fraction);
}
TEST_F(StreamAnalyzerTest, SamplesForwardedToHistogram) {
const uint32_t kSampleWeight = 123;
const std::vector<uint32_t> pattern = {4, 9, 16, 25, 36, 49};
AddPatternHelper(&shared_client_, analyzer(), pattern, kSampleWeight);
std::vector<TestHistogram::ValueWeightPair> samples(
histogram_->GetAndResetAllAddedSamples());
ASSERT_EQ(pattern.size(), samples.size());
for (size_t i = 0; i < samples.size(); i++) {
EXPECT_EQ(pattern[i] * kFixedPointMultiplier, samples[i].value);
EXPECT_EQ(kSampleWeight, samples[i].weight);
}
}
TEST_F(StreamAnalyzerTest, PercentilesModifiedByClient) {
double result0 = 7;
double result1 = 11;
histogram_->SetResults({{result0, result1}});
PercentileResults results = analyzer()->ComputePercentiles();
EXPECT_EQ(client_.TransformResult(result0), results.values[0]);
EXPECT_EQ(client_.TransformResult(result1), results.values[1]);
}
// StreamAnalyzerNaive is a subset of stream analyzer that only uses single
// precision floating point accumulators and can accumulate error.
// This is used to verify patterns that accumulate error, so we can then verify
// those patterns don't result in acculated error in the actual implementation.
struct StreamAnalyzerNaive {
void AddSample(uint32_t value,
uint32_t weight,
uint64_t weighted_value,
uint64_t weighted_root,
const Accumulator96b& weighted_square) {
accumulator_ += static_cast<double>(weight) * value;
root_accumulator_ += static_cast<double>(weight) * std::sqrt(value);
square_accumulator_ += static_cast<double>(weight) * value * value;
total_weight_ += weight;
}
double ComputeMean() {
return client_.TransformResult(accumulator_ / total_weight_);
}
double ComputeRMS() {
return client_.TransformResult(
std::sqrt(square_accumulator_ / total_weight_));
}
double ComputeSMR() {
double mean_root = root_accumulator_ / total_weight_;
return client_.TransformResult(mean_root * mean_root);
}
float total_weight_ = 0;
float accumulator_ = 0;
float root_accumulator_ = 0;
float square_accumulator_ = 0;
TestStreamAnalyzerClient client_;
};
// Unlike the WindowedAnalyzer, there aren't patterns of input that would
// affect the precision of our results very much with double precision floating
// point accumulators. This is because we aren't subtracting values like the
// WindowedAnalyzer does. Nevertheless, there can be issues if the accumulators
// are only single precision.
TEST_F(StreamAnalyzerTest, Precision) {
StreamAnalyzerNaive naive_analyzer;
uint32_t large_value = 20 * base::TimeTicks::kMicrosecondsPerSecond;
uint32_t large_weight = large_value;
size_t large_sample_count = 1;
AddSamplesHelper(&naive_analyzer, large_value, large_weight,
large_sample_count);
AddSamplesHelper(analyzer(), large_value, large_weight, large_sample_count);
uint32_t small_value = 1 * base::TimeTicks::kMicrosecondsPerMillisecond;
uint32_t small_weight = small_value;
size_t small_sample_count = 60 * 60 * 60; // 1hr of 60Hz frames.
AddSamplesHelper(&naive_analyzer, small_value, small_weight,
small_sample_count);
AddSamplesHelper(analyzer(), small_value, small_weight, small_sample_count);
double total_weight = static_cast<double>(large_sample_count) * large_weight +
static_cast<double>(small_sample_count) * small_weight;
double large_value_f = large_value;
double small_value_f = small_value;
double expected_mean = client_.TransformResult(
(large_value_f * large_weight +
small_sample_count * small_value_f * small_weight) /
total_weight);
EXPECT_ABS_LT(expected_mean * .001,
expected_mean - naive_analyzer.ComputeMean());
EXPECT_DOUBLE_EQ(expected_mean, analyzer_->ComputeMean());
double large_value_squared = large_value_f * large_value_f * large_weight;
double small_value_squared = small_value_f * small_value_f * small_weight;
double mean_square =
(large_value_squared + small_sample_count * small_value_squared) /
total_weight;
double expected_rms = client_.TransformResult(std::sqrt(mean_square));
EXPECT_ABS_LT(expected_rms * .001,
expected_rms - naive_analyzer.ComputeRMS());
EXPECT_NEAR_SQRT_APPROX(expected_rms, analyzer_->ComputeRMS());
double large_value_root = std::sqrt(large_value_f) * large_weight;
double small_value_root = std::sqrt(small_value_f) * small_weight;
double mean_root =
(large_value_root + small_sample_count * small_value_root) / total_weight;
double expected_smr = client_.TransformResult(mean_root * mean_root);
EXPECT_ABS_LT(expected_smr * .001,
expected_smr - naive_analyzer.ComputeSMR());
EXPECT_NEAR_SQRT_APPROX(expected_smr, analyzer_->ComputeSMR());
}
} // namespace
} // namespace frame_metrics
} // namespace ui