|  | // 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_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. | 
|  | class TestRatioBoundaries { | 
|  | public: | 
|  | TestRatioBoundaries(); | 
|  | uint64_t operator[](size_t i) const { return boundaries[i]; } | 
|  | size_t size() const { return boundaries.size(); } | 
|  |  | 
|  | public: | 
|  | // uint64_t since the last boundary needs 33 bits. | 
|  | std::array<uint64_t, 112> boundaries; | 
|  | }; | 
|  |  | 
|  | // An explicit list of VSync boundaries to verify the procedurally generated | 
|  | // ones in the implementation. | 
|  | static constexpr std::array<uint32_t, 99> kTestVSyncBoundries = { | 
|  | {// C0: [0,1) (1 bucket). | 
|  | 0, | 
|  | // C1: Powers of two from 1 to 2048 us @ 50% precision (12 buckets) | 
|  | 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, | 
|  | // C2: Every 8 Hz from 256 Hz to 128 Hz @ 3-6% precision (16 buckets) | 
|  | 3906, 4032, 4167, 4310, 4464, 4630, 4808, 5000, 5208, 5435, 5682, 5952, | 
|  | 6250, 6579, 6944, 7353, | 
|  | // C3: Every 4 Hz from 128 Hz to 64 Hz @ 3-6% precision (16 buckets) | 
|  | 7813, 8065, 8333, 8621, 8929, 9259, 9615, 10000, 10417, 10870, 11364, | 
|  | 11905, 12500, 13158, 13889, 14706, | 
|  | // C4: Every 2 Hz from 64 Hz to 32 Hz @ 3-6% precision (16 buckets) | 
|  | 15625, 16129, 16667, 17241, 17857, 18519, 19231, 20000, 20833, 21739, | 
|  | 22727, 23810, 25000, 26316, 27778, 29412, | 
|  | // C5: Every 1 Hz from 32 Hz to 1 Hz @ 3-33% precision (31 buckets) | 
|  | 31250, 32258, 33333, 34483, 35714, 37037, 38462, 40000, 41667, 43478, | 
|  | 45455, 47619, 50000, 52632, 55556, 58824, 62500, 66667, 71429, 76923, | 
|  | 83333, 90909, 100000, 111111, 125000, 142857, 166667, 200000, 250000, | 
|  | 333333, 500000, | 
|  | // C6: Powers of two from 1s to 32s @ 50% precision (6 buckets) | 
|  | 1000000, 2000000, 4000000, 8000000, 16000000, 32000000, | 
|  | // 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 ComputePercentiles() 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_FRAME_METRICS_TEST_COMMON_H_ |