// 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 thresholds) { shared_client_.max_window_size = window_size; for (auto& t : thresholds) { t *= kFixedPointMultiplier; } thresholds_ = std::move(thresholds); std::unique_ptr histogram = std::make_unique(); histogram_ = histogram.get(); analyzer_ = std::make_unique( &client_, &shared_client_, thresholds_, std::move(histogram)); } protected: size_t window_size; TestStreamAnalyzerClient client_; SharedWindowedAnalyzerClient shared_client_; std::vector thresholds_; TestHistogram* histogram_; std::unique_ptr analyzer_; }; TEST_F(StreamAnalyzerTest, AllResultsTheSame) { // 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_EQ(expected_value, analyzer_->ComputeRMS()); EXPECT_NEAR_SMR(analyzer_->ComputeSMR(), expected_value, weight); EXPECT_DOUBLE_EQ(0, analyzer_->ComputeStdDev()); EXPECT_NEAR_VARIANCE_OF_ROOT(0, analyzer_->ComputeVarianceOfRoots(), expected_value, weight); // Verify values are forwarded to the WindowedAnalyzer. EXPECT_EQ(expected_value, analyzer_->window().ComputeWorstMean().value); EXPECT_EQ(expected_value, analyzer_->window().ComputeWorstRMS().value); EXPECT_NEAR_SMR(expected_value, analyzer_->window().ComputeWorstSMR().value, weight); } } } // 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_EQ(expected_value, analyzer_->ComputeRMS()); EXPECT_NEAR_SMR(expected_value, analyzer_->ComputeSMR(), weight); EXPECT_DOUBLE_EQ(0, analyzer_->ComputeStdDev()); EXPECT_NEAR_VARIANCE_OF_ROOT(0, analyzer_->ComputeVarianceOfRoots(), expected_value, weight); // Verify values are forwarded to the WindowedAnalyzer. EXPECT_EQ(expected_value, analyzer_->window().ComputeWorstMean().value); EXPECT_EQ(expected_value, analyzer_->window().ComputeWorstRMS().value); EXPECT_NEAR_SMR(expected_value, analyzer_->window().ComputeWorstSMR().value, weight); } } } // 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 pattern49 = {4, 9, 4, 9, 4, 9}; const std::vector pattern4 = {4, 4, 4, 4, 4, 4}; const std::vector 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 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()); EXPECT_NEAR_SMR(expected_smr, analyzer_->ComputeSMR(), kSampleWeight); EXPECT_DOUBLE_EQ(expected_rms, analyzer_->ComputeRMS()); EXPECT_DOUBLE_EQ(expected_std_dev, analyzer_->ComputeStdDev()); EXPECT_DOUBLE_EQ(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_SMR(expected_smr, analyzer_->ComputeSMR(), kSampleWeight); EXPECT_DOUBLE_EQ(expected_rms, analyzer_->ComputeRMS()); EXPECT_DOUBLE_EQ(expected_std_dev, analyzer_->ComputeStdDev()); EXPECT_DOUBLE_EQ(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_SMR(expected_smr, analyzer_->ComputeSMR(), kSampleWeight); EXPECT_DOUBLE_EQ(expected_rms, analyzer_->ComputeRMS()); EXPECT_DOUBLE_EQ(expected_std_dev, analyzer_->ComputeStdDev()); EXPECT_DOUBLE_EQ(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 pattern = {4, 9, 16, 25, 36, 49}; AddPatternHelper(&shared_client_, analyzer(), pattern, kSampleWeight); std::vector 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(weight) * value; root_accumulator_ += static_cast(weight) * std::sqrt(value); square_accumulator_ += static_cast(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(large_sample_count) * large_weight + static_cast(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_DOUBLE_EQ(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_SMR(expected_smr, analyzer_->ComputeSMR(), 1); } } // namespace } // namespace frame_metrics } // namespace ui