diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-07-16 11:45:35 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2020-07-17 08:59:23 +0000 |
commit | 552906b0f222c5d5dd11b9fd73829d510980461a (patch) | |
tree | 3a11e6ed0538a81dd83b20cf3a4783e297f26d91 /chromium/ui/latency | |
parent | 1b05827804eaf047779b597718c03e7d38344261 (diff) | |
download | qtwebengine-chromium-552906b0f222c5d5dd11b9fd73829d510980461a.tar.gz |
BASELINE: Update Chromium to 83.0.4103.122
Change-Id: Ie3a82f5bb0076eec2a7c6a6162326b4301ee291e
Reviewed-by: Michael BrĂ¼ning <michael.bruning@qt.io>
Diffstat (limited to 'chromium/ui/latency')
32 files changed, 158 insertions, 5276 deletions
diff --git a/chromium/ui/latency/BUILD.gn b/chromium/ui/latency/BUILD.gn index caaecb33393..0e3639e5cd0 100644 --- a/chromium/ui/latency/BUILD.gn +++ b/chromium/ui/latency/BUILD.gn @@ -9,58 +9,33 @@ jumbo_source_set("latency") { sources = [ "average_lag_tracker.cc", "average_lag_tracker.h", - "fixed_point.cc", - "fixed_point.h", - "frame_metrics.cc", - "frame_metrics.h", - "histograms.cc", - "histograms.h", "latency_histogram_macros.h", "latency_info.cc", "latency_info.h", "latency_tracker.cc", "latency_tracker.h", - "skipped_frame_tracker.cc", - "skipped_frame_tracker.h", - "stream_analyzer.cc", - "stream_analyzer.h", - "windowed_analyzer.cc", - "windowed_analyzer.h", ] deps = [ "//base", + "//services/tracing/public/cpp:cpp", "//ui/gfx", ] - public_deps = [ - "//services/metrics/public/cpp:metrics_cpp", - ] + public_deps = [ "//services/metrics/public/cpp:metrics_cpp" ] } jumbo_source_set("test_support") { testonly = true - sources = [ - "latency_info_test_support.cc", - ] + sources = [ "latency_info_test_support.cc" ] - public_deps = [ - ":latency", - ] + public_deps = [ ":latency" ] } test("latency_unittests") { sources = [ "average_lag_tracker_unittest.cc", - "fixed_point_unittest.cc", - "frame_metrics_test_common.cc", - "frame_metrics_test_common.h", - "frame_metrics_unittest.cc", - "histograms_unittest.cc", "latency_info_unittest.cc", - "skipped_frame_tracker_unittest.cc", - "stream_analyzer_unittest.cc", - "windowed_analyzer_unittest.cc", ] deps = [ @@ -86,21 +61,3 @@ test("latency_unittests") { ] } } - -test("latency_perftests") { - sources = [ - "frame_metrics_test_common.cc", - "frame_metrics_test_common.h", - "histograms_perftest.cc", - ] - - deps = [ - ":latency", - "//base", - "//base/test:test_support", - "//mojo/core/test:run_all_unittests", - "//testing/gmock", - "//testing/gtest", - "//testing/perf", - ] -} diff --git a/chromium/ui/latency/DEPS b/chromium/ui/latency/DEPS index 40d9b9f1297..2a39e69cb2e 100644 --- a/chromium/ui/latency/DEPS +++ b/chromium/ui/latency/DEPS @@ -1,5 +1,7 @@ include_rules = [ "+services/metrics/public/cpp", + "+services/tracing/public/cpp", + "+third_party/perfetto/protos/perfetto/trace/track_event", "+ui/gfx", ] diff --git a/chromium/ui/latency/fixed_point.cc b/chromium/ui/latency/fixed_point.cc deleted file mode 100644 index fafcf9ee85e..00000000000 --- a/chromium/ui/latency/fixed_point.cc +++ /dev/null @@ -1,61 +0,0 @@ -// 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/fixed_point.h" - -#include <cmath> -#include <limits> - -#include "base/logging.h" - -namespace ui { -namespace frame_metrics { - -namespace { - -constexpr uint64_t k2Pow32{1ULL << 32}; -constexpr uint64_t k32LsbMask{0xFFFFFFFF}; - -} // namespace - -Accumulator96b::Accumulator96b(uint32_t value_to_square, uint32_t weight) { - uint64_t square = static_cast<uint64_t>(value_to_square) * value_to_square; - uint64_t ms64b_temp = (square >> 32) * weight; - uint64_t ls32b_temp = (square & k32LsbMask) * weight; - ms64b = ms64b_temp + (ls32b_temp >> 32); - ls32b = ls32b_temp & k32LsbMask; -} - -void Accumulator96b::Add(const Accumulator96b& rhs) { - uint64_t ls32b_temp = static_cast<uint64_t>(ls32b) + rhs.ls32b; - DCHECK_LT((ls32b_temp >> 32), - std::numeric_limits<decltype(ms64b)>::max() - rhs.ms64b) - << "Accumulator96b overflow."; - uint64_t ms64b_add = rhs.ms64b + (ls32b_temp >> 32); - DCHECK_LT(ms64b_add, std::numeric_limits<decltype(ms64b)>::max() - ms64b) - << "Accumulator96b overflow."; - ms64b += ms64b_add; - ls32b = ls32b_temp & k32LsbMask; -} - -void Accumulator96b::Subtract(const Accumulator96b& rhs) { - uint64_t ls32b_temp = ls32b; - if (ls32b < rhs.ls32b) { - // Borrow from ms64b to ls32b. - ms64b--; - ls32b_temp |= k2Pow32; - } - DCHECK_GE(ms64b, rhs.ms64b) << "Accumulator96b underflow."; - DCHECK_GE(ls32b_temp, static_cast<uint64_t>(rhs.ls32b)) - << "Accumulator96b underflow."; - ms64b -= rhs.ms64b; - ls32b = ls32b_temp - rhs.ls32b; -} - -double Accumulator96b::ToDouble() const { - return (static_cast<double>(ms64b) * k2Pow32) + ls32b; -} - -} // namespace frame_metrics -} // namespace ui diff --git a/chromium/ui/latency/fixed_point.h b/chromium/ui/latency/fixed_point.h deleted file mode 100644 index 11b419116b3..00000000000 --- a/chromium/ui/latency/fixed_point.h +++ /dev/null @@ -1,71 +0,0 @@ -// 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_FRAME_METRICS_FIXED_POINT_H_ -#define UI_FRAME_METRICS_FIXED_POINT_H_ - -#include <cstdint> - -#include "base/macros.h" - -namespace ui { -namespace frame_metrics { - -// Use fixed point math so we can manage our precision explicitly and avoid -// accumulating error in our windowed accumulators. -// The 64-bit accumulators reserve 32-bits for weights, and 32-bits for values. -// The 32-bit values reserve 16 bits before and after the radix point. -constexpr int kFixedPointShift = 16; -constexpr int64_t kFixedPointMultiplier{1LL << kFixedPointShift}; - -// kFixedPointRootMultiplier is used to shift the bits before taking the square -// 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 -// after taking the square root of the accumulation. -// This efficiently emulates a 96-bit unsigned integer with weighted -// accumulation operations. -// 32-bits are reserved for weights and 64-bits for squared values. -// Overflow or underflow indicates something is seriously wrong with the higher -// level metrics logic, so this class will DCHECK if it anticipates overflow -// or underflow: -// * It doesn't need to support OVERFLOW since the frame metric classes will -// always reset the entire accumulator before the accumulated weights -// overflow. The accumulated weights correspond to a maximum of the number of -// microseconds since the last reset, which for a 32-bit weight is about -// 1 hour. We will gather and reset results much more often than every hour. -// * It doesn't need to support UNDERFLOW since only the windowed metrics use -// Subtract, and those only subtract values it has already added. -class Accumulator96b { - public: - Accumulator96b() = default; - Accumulator96b(uint32_t value_to_square, uint32_t weight); - - void Add(const Accumulator96b& rhs); - void Subtract(const Accumulator96b& rhs); - double ToDouble() const; - - public: - uint64_t ms64b{0}; - uint32_t ls32b{0}; -}; - -// Convenience function overloads for AsDouble, to help with templated code. -inline double AsDouble(const Accumulator96b& value) { - return value.ToDouble(); -} - -inline double AsDouble(double value) { - return value; -} - -} // namespace frame_metrics -} // namespace ui - -#endif // UI_FRAME_METRICS_FIXED_POINT_H_ diff --git a/chromium/ui/latency/fixed_point_unittest.cc b/chromium/ui/latency/fixed_point_unittest.cc deleted file mode 100644 index 02548fd03da..00000000000 --- a/chromium/ui/latency/fixed_point_unittest.cc +++ /dev/null @@ -1,149 +0,0 @@ -// 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/fixed_point.h" - -#include "base/time/time.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace ui { -namespace frame_metrics { - -// Verify range of a fixed point value stored as a uint32_t has enough range -// for our requirements. -TEST(FrameMetricsFixedPointTest, kFixedPointMultiplier) { - uint32_t max_fixed = std::numeric_limits<uint32_t>::max(); - double max_float = static_cast<double>(max_fixed) / kFixedPointMultiplier; - - // The maximum time delta between two frames we'd like to support. - double frame_delta = 64 * base::TimeTicks::kMicrosecondsPerSecond; - - // The minimum frame duration we'd like to support. - // 1kHz should give us plenty of headroom. - double frame_duration = base::TimeTicks::kMicrosecondsPerSecond / 1000; - - // Verify the resulting slope is within the range. - double frame_slope = frame_delta / frame_duration; - EXPECT_LE(frame_slope, max_float); -} - -// Some code will take the square root of a 32-bit value by shifting it left -// 32-bits beforehand. Verify this is okay and more accurate than not shifting -// at all. -TEST(FrameMetricsFixedPointTest, kFixedPointRootMultiplier) { - uint64_t value = 0xFFFFFFFF; - - // Calculate SMR with kFixedPointRootMultiplier. - // Truncate to 32 bits to verify multiplying by kFixedPointRootMultiplier - // will not result in overflow when stored as a 32 bit value. - uint32_t root1 = std::sqrt(value * kFixedPointRootMultiplier); - double value1 = - static_cast<uint64_t>(root1) * root1 / kFixedPointRootMultiplier; - double error1 = std::abs(value1 - value); - - // Calculate SMR without kFixedPointRootMultiplier. - uint32_t root2 = std::sqrt(value); - double value2 = root2 * root2; - double error2 = std::abs(value2 - value); - - // Verify using kFixedPointRootMultiplier is relatively more accurate. - EXPECT_LE(error1, error2); - - // Verify using kFixedPointRootMultiplier is accurate in an absolute sense. - 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. - uint64_t a = 13; - Accumulator96b a1(a, 2); - EXPECT_DOUBLE_EQ(a1.ToDouble(), a * a * 2); - - // A "medium" value that fits in 64 bits. - uint64_t b = 0x10000001; - Accumulator96b a2(b, 2); - EXPECT_DOUBLE_EQ(a2.ToDouble(), b * b * 2); - - // A large value that fits in 96 bits. - uint64_t c = 0x80000001; - Accumulator96b a3(c, c); - EXPECT_DOUBLE_EQ(a3.ToDouble(), std::pow(c, 3)); - - // The largest initial 96-bit value. - uint64_t d = 0xFFFFFFFF; - Accumulator96b a4(d, d); - EXPECT_DOUBLE_EQ(a4.ToDouble(), std::pow(d, 3)); - - // A mix of the two above. - double cf = c; - double df = d; - Accumulator96b a5(c, d); - EXPECT_DOUBLE_EQ(a5.ToDouble(), cf * cf * df); - Accumulator96b a6(d, c); - EXPECT_DOUBLE_EQ(a6.ToDouble(), df * df * cf); -} - -// Verify Accumulator96b::Add and Subtract. -TEST(FrameMetricsFixedPointTest, Accumulator96bAddSub) { - uint32_t v = 0xFFFFFFFF; - - // A small value that fits in 32 bits and would carry into - // upper most 64 bits during accumulation. - Accumulator96b a1(1, v); - Accumulator96b accum1; - for (int i = 0; i <= 0xFF; i++) { - accum1.Add(a1); - EXPECT_DOUBLE_EQ(accum1.ToDouble(), static_cast<double>(v) * (i + 1)); - } - for (int i = 0xFF; i >= 0; i--) { - accum1.Subtract(a1); - EXPECT_DOUBLE_EQ(accum1.ToDouble(), static_cast<double>(v) * i); - } - - // A larger value that fits in 64 bits and would carry into - // upper most 32 bits during accumulation. - Accumulator96b a2(v, 1); - Accumulator96b accum2; - for (int i = 0; i <= 0xFF; i++) { - accum2.Add(a2); - EXPECT_DOUBLE_EQ(accum2.ToDouble(), static_cast<double>(v) * v * (i + 1)); - } - for (int i = 0xFF; i >= 0; i--) { - accum2.Subtract(a2); - EXPECT_DOUBLE_EQ(accum2.ToDouble(), static_cast<double>(v) * v * i); - } -} - -// Verify Accumulator96b precision is always 1. -TEST(FrameMetricsFixedPointTest, Accumulator96bPrecision) { - uint32_t v = 0xFFFFFFFF; - Accumulator96b a1(1, 1); // 1. Smallest non-zero value possible. - Accumulator96b a2(v, v); // Largest initial value possible. - Accumulator96b a3(v, v); // Largest initial value possible, minus 1. - a3.Subtract(a1); - - // Verify that conversion to a double loses precision from a3. - double a2f = a2.ToDouble(); - double a3f = a3.ToDouble(); - EXPECT_DOUBLE_EQ(a2f, a3f); - EXPECT_DOUBLE_EQ(0, a2f - a3f); - - // Verify delta between a2 and a3 is 1 when computed internally. - Accumulator96b a4(a2); - a4.Subtract(a3); - EXPECT_DOUBLE_EQ(1, a4.ToDouble()); -} - -} // namespace frame_metrics -} // namespace ui diff --git a/chromium/ui/latency/frame_metrics.cc b/chromium/ui/latency/frame_metrics.cc deleted file mode 100644 index 9cd0c257300..00000000000 --- a/chromium/ui/latency/frame_metrics.cc +++ /dev/null @@ -1,494 +0,0 @@ -// 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/frame_metrics.h" - -#include <cmath> -#include <limits> -#include <vector> - -#include "base/bit_cast.h" -#include "base/trace_event/trace_event.h" -#include "base/trace_event/traced_value.h" - -namespace ui { - -namespace { - -// How often to report results. -// This needs to be short enough to avoid overflow in the accumulators. -constexpr base::TimeDelta kDefaultReportPeriod = - base::TimeDelta::FromSeconds(1); - -// Gives the histogram for skips the highest precision just above a -// skipped:produced ratio of 1. -constexpr int64_t kFixedPointMultiplierSkips = - frame_metrics::kFixedPointMultiplier; - -// Gives latency a precision of 1 microsecond in both the histogram and -// the fixed point values. -constexpr int64_t kFixedPointMultiplierLatency = 1; - -// This is used to weigh each latency sample by a constant value since -// we don't weigh it by the frame duration like other metrics. -// A larger weight improves precision in the fixed point accumulators, but we -// don't want to make it so big that it causes overflow before we start a new -// reporting period. -constexpr uint32_t kLatencySampleWeight = 1u << 10; -constexpr uint32_t kMaxFramesBeforeOverflowPossible = - std::numeric_limits<uint32_t>::max() / kLatencySampleWeight; - -// Gives the histogram for latency speed the highest precision just above a -// (latency delta : frame delta) ratio of 1. -constexpr int64_t kFixedPointMultiplierLatencySpeed = - frame_metrics::kFixedPointMultiplier; - -// Gives the histogram for latency acceleration the highest precision just -// above a (latency speed delta : frame delta) of 1/1024. -// A value ~1k was chosen since frame deltas are on the order of microseconds. -// Use 1024 instead of 1000 since powers of 2 let the compiler optimize integer -// multiplies with shifts if it wants. -// TODO(brianderson): Fine tune these values. http://crbug.com/837434 -constexpr int64_t kFixedPointMultiplierLatencyAcceleration = - frame_metrics::kFixedPointMultiplier * 1024; - -// Converts a ratio to a fixed point value. -// Each threshold is offset by 0.5 to filter out jitter/inaccuracies. -constexpr uint32_t RatioThreshold(double fraction) { - return static_cast<uint32_t>((fraction + 0.5) * - frame_metrics::kFixedPointMultiplier); -} - -// Converts frequency as a floating point value into a fixed point value -// representing microseconds of latency. -// The result is scaled by 110% to allow for slack in cases the actual refresh -// period is slightly longer (common) or if there is some jitter in the -// timestamp sampling. -constexpr uint32_t LatencyThreshold(double Hz) { - return static_cast<uint32_t>((1.1 / Hz) * - base::TimeTicks::kMicrosecondsPerSecond); -} - -// The skip thresholds are selected to track each time more than 0, 1, 2, or 4 -// frames were skipped at once. -constexpr std::initializer_list<uint32_t> kSkipThresholds = { - RatioThreshold(0), RatioThreshold(1), RatioThreshold(2), RatioThreshold(4), -}; - -// The latency thresholds are selected based on common display frequencies. -// We often begin a frames on a vsync which would result in whole vsync periods -// of latency. However, in case begin frames are offset slightly from the vsync, -// which is common on Android, the frequency goes all the way to 240Hz. -constexpr std::initializer_list<uint32_t> kLatencyThresholds = { - LatencyThreshold(240), // 4.17 ms * 110% = 4.58 ms - LatencyThreshold(120), // 8.33 ms * 110% = 9.17 ms - LatencyThreshold(60), // 16.67 ms * 110% = 18.33 ms - LatencyThreshold(30), // 33.33 ms * 110% = 36.67 ms -}; - -// The latency speed thresholds are chosen to track each frame where the -// latency was constant (0) or when there was a jump of 1, 2, or 4 frame -// periods. -constexpr std::initializer_list<uint32_t> kLatencySpeedThresholds = { - RatioThreshold(0), RatioThreshold(1), RatioThreshold(2), RatioThreshold(4), -}; - -// The latency acceleration thresholds here are tentative. -// TODO(brianderson): Fine tune these values. http://crbug.com/837434 -constexpr std::initializer_list<uint32_t> kLatencyAccelerationThresholds = { - RatioThreshold(0), RatioThreshold(1), RatioThreshold(2), RatioThreshold(4), -}; - -constexpr const char kTraceCategories[] = "gpu,benchmark"; - -// uint32_t should be plenty of range for real world values, but clip individual -// entries to make sure no single value dominates and also to avoid overflow -// in the accumulators and the fixed point math. -// This also makes sure overflowing values saturate instead of wrapping around -// and skewing our results. -// TODO(brianderson): Report warning if clipping occurred. -uint32_t CapValue(int64_t value) { - return static_cast<uint32_t>(std::min<int64_t>( - std::llabs(value), std::numeric_limits<uint32_t>::max())); -} - -uint32_t CapDuration(const base::TimeDelta duration) { - constexpr base::TimeDelta kDurationCap = base::TimeDelta::FromMinutes(1); - return std::min(duration, kDurationCap).InMicroseconds(); -} - -const char* ToString(FrameMetricsSource source) { - switch (source) { - case FrameMetricsSource::UnitTest: - return "UnitTest"; - case FrameMetricsSource::RendererCompositor: - return "RendererCompositor"; - case FrameMetricsSource::UiCompositor: - return "UiCompositor"; - case FrameMetricsSource::Unknown: - break; - }; - return "Unknown"; -} - -const char* ToString(FrameMetricsSourceThread thread) { - switch (thread) { - case FrameMetricsSourceThread::Blink: - return "Blink"; - case FrameMetricsSourceThread::RendererCompositor: - return "RendererCompositor"; - case FrameMetricsSourceThread::Ui: - return "Ui"; - case FrameMetricsSourceThread::UiCompositor: - return "UiCompositor"; - case FrameMetricsSourceThread::VizCompositor: - return "VizCompositor"; - case FrameMetricsSourceThread::Unknown: - break; - } - return "Unknown"; -} - -const char* ToString(FrameMetricsCompileTarget target) { - switch (target) { - case FrameMetricsCompileTarget::Chromium: - return "Chromium"; - case FrameMetricsCompileTarget::SynchronousCompositor: - return "SynchronousCompositor"; - case FrameMetricsCompileTarget::Headless: - return "Headless"; - case FrameMetricsCompileTarget::Unknown: - break; - } - return "Unknown"; -} - -} // namespace - -void FrameMetricsSettings::AsValueInto( - base::trace_event::TracedValue* state) const { - state->SetString("source", ToString(source)); - state->SetString("thread", ToString(source_thread)); - state->SetString("compile_target", ToString(compile_target)); -} - -namespace frame_metrics { - -// Converts result to fraction of frames skipped. -// The internal skip values are (skipped:produced). This transform converts -// the result to (skipped:total), which is: -// a) Easier to interpret as a human, and -// b) In the same units as latency speed, which may help us create a unified -// smoothness metric in the future. -// The internal representation uses (skipped:produced) to: -// a) Allow RMS, SMR, StdDev, etc to be performed on values that increase -// linearly (rather than asymptotically to 1) with the amount of jank, and -// b) Give us better precision where it's important when stored as a fixed -// point number and in histogram buckets. -double SkipClient::TransformResult(double result) const { - // Avoid divide by zero. - if (result < 1e-32) - return 0; - return 1.0 / (1.0 + (kFixedPointMultiplierSkips / result)); -} - -// Converts result to seconds. -double LatencyClient::TransformResult(double result) const { - return result / (base::TimeTicks::kMicrosecondsPerSecond * - kFixedPointMultiplierLatency); -} - -// Converts result to s/s. ie: fraction of frames traveled. -double LatencySpeedClient::TransformResult(double result) const { - return result / kFixedPointMultiplierLatencySpeed; -} - -// Converts result to (s/s^2). -// ie: change in fraction of frames traveled per second. -double LatencyAccelerationClient::TransformResult(double result) const { - return (result * base::TimeTicks::kMicrosecondsPerSecond) / - kFixedPointMultiplierLatencyAcceleration; -} - -} // namespace frame_metrics - -FrameMetrics::FrameMetrics(FrameMetricsSettings settings) - : settings_(settings), - shared_skip_client_(settings_.max_window_size), - shared_latency_client_(settings_.max_window_size), - frame_skips_analyzer_(&skip_client_, - &shared_skip_client_, - kSkipThresholds, - std::make_unique<frame_metrics::RatioHistogram>()), - latency_analyzer_(&latency_client_, - &shared_latency_client_, - kLatencyThresholds, - std::make_unique<frame_metrics::VSyncHistogram>()), - latency_speed_analyzer_( - &latency_speed_client_, - &shared_latency_client_, - kLatencySpeedThresholds, - std::make_unique<frame_metrics::RatioHistogram>()), - latency_acceleration_analyzer_( - &latency_acceleration_client_, - &shared_latency_client_, - kLatencyAccelerationThresholds, - std::make_unique<frame_metrics::RatioHistogram>()) {} - -FrameMetrics::~FrameMetrics() = default; - -base::TimeDelta FrameMetrics::ReportPeriod() { - return kDefaultReportPeriod; -} - -void FrameMetrics::AddFrameProduced(base::TimeTicks source_timestamp, - base::TimeDelta amount_produced, - base::TimeDelta amount_skipped) { - DCHECK_GE(amount_skipped, base::TimeDelta()); - DCHECK_GT(amount_produced, base::TimeDelta()); - base::TimeDelta source_timestamp_delta; - if (!skip_timestamp_queue_.empty()) { - source_timestamp_delta = source_timestamp - skip_timestamp_queue_.back(); - DCHECK_GT(source_timestamp_delta, base::TimeDelta()); - } - - // Periodically report all metrics and reset the accumulators. - // Do this before adding any samples to avoid overflow before it might happen. - time_since_start_of_report_period_ += source_timestamp_delta; - frames_produced_since_start_of_report_period_++; - if (time_since_start_of_report_period_ > ReportPeriod() || - frames_produced_since_start_of_report_period_ > - kMaxFramesBeforeOverflowPossible) { - StartNewReportPeriod(); - } - - if (skip_timestamp_queue_.size() >= settings_.max_window_size) { - skip_timestamp_queue_.pop_front(); - } - skip_timestamp_queue_.push_back(source_timestamp); - - shared_skip_client_.window_begin = skip_timestamp_queue_.front(); - shared_skip_client_.window_end = source_timestamp; - - int64_t skipped_to_produced_ratio = - (amount_skipped * kFixedPointMultiplierSkips) / amount_produced; - DCHECK_GE(skipped_to_produced_ratio, 0); - frame_skips_analyzer_.AddSample(CapValue(skipped_to_produced_ratio), - CapDuration(amount_produced)); -} - -void FrameMetrics::AddFrameDisplayed(base::TimeTicks source_timestamp, - base::TimeTicks display_timestamp) { - TRACE_EVENT0(kTraceCategories, "AddFrameDisplayed"); - - // Frame timestamps shouldn't go back in time, but check and drop them just - // in case. Much of the code assumes a positive and non-zero delta. - if (source_timestamp <= source_timestamp_prev_) { - // TODO(brianderson): Flag a warning. - return; - } - - base::TimeDelta latency = display_timestamp - source_timestamp; - - if (latency_timestamp_queue_.size() >= settings_.max_window_size) { - latency_timestamp_queue_.pop_front(); - } - latency_timestamp_queue_.push_back(source_timestamp); - - shared_latency_client_.window_begin = latency_timestamp_queue_.front(); - shared_latency_client_.window_end = source_timestamp; - - // TODO(brianderson): Handle negative latency better. - // For now, reporting the magnitude of the latency will reflect - // how far off the ideal display time the frame was, but it won't indicate - // in which direction. This might be important for sources like video, where - // a frame might be displayed a little bit earlier than its ideal display - // time. - int64_t latency_value = - latency.InMicroseconds() * kFixedPointMultiplierLatency; - latency_analyzer_.AddSample(CapValue(latency_value), kLatencySampleWeight); - - base::TimeDelta latency_delta = latency - latency_prev_; - base::TimeDelta source_duration = source_timestamp - source_timestamp_prev_; - // Only calculate speed if there's enough history. - if (latencies_added_ >= 1 && settings_.is_frame_latency_speed_on()) { - int64_t latency_velocity = - (latency_delta * kFixedPointMultiplierLatencySpeed) / source_duration; - - // This should be plenty of range for real world values, but clip - // entries to avoid overflow in the accumulators just in case. - latency_speed_analyzer_.AddSample(CapValue(latency_velocity), - CapDuration(source_duration)); - } - - // Only calculate acceleration if there's enough history. - if (latencies_added_ >= 2 && settings_.is_frame_latency_acceleration_on()) { - base::TimeDelta source_duration_average = - (source_duration + source_duration_prev_) / 2; - int64_t latency_acceleration = - (((latency_delta * kFixedPointMultiplierLatencyAcceleration) / - source_duration) - - ((latency_delta_prev_ * kFixedPointMultiplierLatencyAcceleration) / - source_duration_prev_)) / - source_duration_average.InMicroseconds(); - latency_acceleration_analyzer_.AddSample( - CapValue(latency_acceleration), CapDuration(source_duration_average)); - } - // Update history. - if (latencies_added_ >= 1) { - source_duration_prev_ = source_duration; - latency_delta_prev_ = latency_delta; - } - source_timestamp_prev_ = source_timestamp; - latency_prev_ = latency; - latencies_added_++; -} - -void FrameMetrics::Reset() { - TRACE_EVENT0(kTraceCategories, "FrameMetrics::Reset"); - - skip_timestamp_queue_.clear(); - latency_timestamp_queue_.clear(); - - time_since_start_of_report_period_ = base::TimeDelta(); - - latencies_added_ = 0; - source_timestamp_prev_ = base::TimeTicks(); - latency_prev_ = base::TimeDelta(); - source_duration_prev_ = base::TimeDelta(); - latency_delta_prev_ = base::TimeDelta(); - - frame_skips_analyzer_.Reset(); - latency_analyzer_.Reset(); - if (settings_.is_frame_latency_speed_on()) - latency_speed_analyzer_.Reset(); - if (settings_.is_frame_latency_acceleration_on()) - latency_acceleration_analyzer_.Reset(); -} - -// Reset analyzers, but don't reset resent latency history so we can get -// latency speed and acceleration values immediately. -// TODO(brianderson): Once we support UKM reporting, store the frame skips -// result and defer it's reporting until the latency numbers are also -// available. Reporting everything at this point would put some frames in -// different reporting periods, which could skew the results. -void FrameMetrics::StartNewReportPeriod() { - TRACE_EVENT0(kTraceCategories, "FrameMetrics::StartNewReportPeriod"); - - bool tracing_enabled = 0; - TRACE_EVENT_CATEGORY_GROUP_ENABLED(kTraceCategories, &tracing_enabled); - if (tracing_enabled) - TraceStats(); - - time_since_start_of_report_period_ = base::TimeDelta(); - frames_produced_since_start_of_report_period_ = 0; - - frame_skips_analyzer_.StartNewReportPeriod(); - latency_analyzer_.StartNewReportPeriod(); - latency_speed_analyzer_.StartNewReportPeriod(); - latency_acceleration_analyzer_.StartNewReportPeriod(); -} - -double FrameMetrics::FastApproximateSqrt(double x) { - if (x <= 0) - return 0; - // Basically performs x*fastinvSqrt(x) - using high precision (3 steps) - double y = x; - double xhalf = 0.5f * x; - float xf = static_cast<float>(x); - int32_t i = bit_cast<int32_t>(xf); - // Magic Number for initial guess. Reference: - // http://www.lomont.org/Math/Papers/2003/InvSqrt.pdf. - i = 0x5f3759df - (i >> 1); - x = static_cast<double>(bit_cast<float>(i)); - // Newton step. - x = x * (1.5 - xhalf * x * x); - // Newton step. - x = x * (1.5 - xhalf * x * x); - // Newton step. - x = x * (1.5 - xhalf * x * x); - return y * x; -} - -namespace { - -// FrameMetricsTraceData delegates tracing logic to TracedValue in a deferred -// manner. Rather that making copies of keys, values, and strings when the -// trace is emitted (as using TracedValue directly would), it implements -// ConvertableToTraceFormat so we do the copying after the capture period. -// i.e. when we are producing the trace output. On a Linux z840, deferring -// decreases the cost of emitting a trace from ~25us to ~7us. -class FrameMetricsTraceData - : public base::trace_event::ConvertableToTraceFormat { - public: - FrameMetricsTraceData() = default; - ~FrameMetricsTraceData() override = default; - - void ToTracedValue(base::trace_event::TracedValue* state) const { - state->BeginDictionary("Source"); - settings.AsValueInto(state); - state->EndDictionary(); - - state->BeginDictionary("Skips"); - skips.AsValueInto(state); - state->EndDictionary(); - - state->BeginDictionary("Latency"); - latency.AsValueInto(state); - state->EndDictionary(); - - if (settings.is_frame_latency_speed_on()) { - state->BeginDictionary("Speed"); - speed.AsValueInto(state); - state->EndDictionary(); - } - - if (settings.is_frame_latency_acceleration_on()) { - state->BeginDictionary("Acceleration"); - acceleration.AsValueInto(state); - state->EndDictionary(); - } - } - - void AppendAsTraceFormat(std::string* out) const override { - base::trace_event::TracedValue state; - ToTracedValue(&state); - state.AppendAsTraceFormat(out); - } - - bool AppendToProto(ProtoAppender* appender) override { - base::trace_event::TracedValue state; - ToTracedValue(&state); - return state.AppendToProto(appender); - } - - void EstimateTraceMemoryOverhead( - base::trace_event::TraceEventMemoryOverhead* overhead) override { - overhead->Add(base::trace_event::TraceEventMemoryOverhead::kFrameMetrics, - sizeof(FrameMetricsTraceData)); - } - - FrameMetricsSettings settings; - StreamAnalysis skips, latency, speed, acceleration; -}; - -} // namespace - -void FrameMetrics::TraceStats() const { - auto trace_data = std::make_unique<FrameMetricsTraceData>(); - { - TRACE_EVENT0(kTraceCategories, "CalculateFrameDisplayed"); - trace_data->settings = settings_; - frame_skips_analyzer_.ComputeSummary(&trace_data->skips); - latency_analyzer_.ComputeSummary(&trace_data->latency); - if (settings_.is_frame_latency_speed_on()) - latency_speed_analyzer_.ComputeSummary(&trace_data->speed); - if (settings_.is_frame_latency_acceleration_on()) - latency_acceleration_analyzer_.ComputeSummary(&trace_data->acceleration); - } - TRACE_EVENT_INSTANT1(kTraceCategories, "FrameMetrics", - TRACE_EVENT_SCOPE_THREAD, "Info", std::move(trace_data)); -} - -} // namespace ui diff --git a/chromium/ui/latency/frame_metrics.h b/chromium/ui/latency/frame_metrics.h deleted file mode 100644 index 147318b50f0..00000000000 --- a/chromium/ui/latency/frame_metrics.h +++ /dev/null @@ -1,203 +0,0 @@ -// 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" -#include "base/trace_event/traced_value.h" -#include "ui/latency/skipped_frame_tracker.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 - -enum class FrameMetricsSource { - Unknown = 0, - UnitTest = 1, - RendererCompositor = 2, - UiCompositor = 3, -}; - -enum class FrameMetricsSourceThread { - Unknown = 0, - Blink = 1, - RendererCompositor = 2, - Ui = 3, - UiCompositor = 4, - VizCompositor = 5, -}; - -enum class FrameMetricsCompileTarget { - Unknown = 0, - Chromium = 1, - SynchronousCompositor = 2, - Headless = 3, -}; - -struct FrameMetricsSettings { - FrameMetricsSettings() = default; - - FrameMetricsSettings(FrameMetricsSource source, - FrameMetricsSourceThread source_thread, - FrameMetricsCompileTarget compile_target, - bool trace_results_every_frame = false, - size_t max_window_size = 60) - : source(source), - source_thread(source_thread), - compile_target(compile_target), - trace_results_every_frame(trace_results_every_frame), - max_window_size(max_window_size) {} - - void set_is_frame_latency_speed_on(bool is_speed_on) { - is_frame_latency_speed_on_ = is_speed_on; - } - void set_is_frame_latency_acceleration_on(bool is_acceleration_on) { - is_frame_latency_acceleration_on_ = is_acceleration_on; - } - - bool is_frame_latency_speed_on() const { return is_frame_latency_speed_on_; } - bool is_frame_latency_acceleration_on() const { - return is_frame_latency_acceleration_on_; - } - - // Source configuration. - FrameMetricsSource source; - FrameMetricsSourceThread source_thread; - FrameMetricsCompileTarget compile_target; - - // This is needed for telemetry results. - bool trace_results_every_frame; - - // Maximum window size in number of samples. - // This is forwarded to each WindowAnalyzer. - size_t max_window_size; - - void AsValueInto(base::trace_event::TracedValue* state) const; - - private: - // Switch for frame latency speed measurements control. - bool is_frame_latency_speed_on_ = false; - - // Switch for frame latency acceleration measurements control. - bool is_frame_latency_acceleration_on_ = false; -}; - -// 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 SkippedFrameTracker::Client { - public: - explicit FrameMetrics(FrameMetricsSettings settings); - ~FrameMetrics() override; - - // 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. - // |source_timestamp| is when frame time in BeginFrameArgs(i.e. when the frame - // is produced); |amount_produced| is the expected time interval between 2 - // consecutive frames; |amount_skipped| is number of frame skipped before - // producing this frame multiplies by the interval, i.e., if 1 frame is - // skipped in 30 fps setting, then |amount_skipped| is 33.33ms; if 1 frame is - // skipped in 60FPS setting, then the |amount_skipped| is 16.67ms. Note: If - // the FrameMetrics class is hooked up to an optional SkippedFrameTracker, the - // client should not call this directly. - void AddFrameProduced(base::TimeTicks source_timestamp, - base::TimeDelta amount_produced, - base::TimeDelta amount_skipped) override; - - // AddFrameDisplayed should be called whenever a frame causes damage and - // we know when the result became visible on the display. |source_timestamp| - // is when frame time in BeginFrameArgs(i.e. when the frame is produced); - // |display_timestamp| is when the frame is displayed on screen. - // 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); - - // Compute the square root by using method described in paper: - // http://www.lomont.org/Math/Papers/2003/InvSqrt.pdf. - // It finds a result within 0.0001 and 0.1 of the true square root for |x| < - // 100 and |x| < 2^15 respectively. It's more than 2 times faster for Nexus 4 - // and other lower end android devices and ~3-5% faster on desktop. Crash when - // x is less than 0. - static double FastApproximateSqrt(double x); - - protected: - void TraceStats() const; - - // virtual for testing. - virtual base::TimeDelta ReportPeriod(); - - // Starts a new reporting period after |kDefaultReportPeriod| time 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_ diff --git a/chromium/ui/latency/frame_metrics_test_common.cc b/chromium/ui/latency/frame_metrics_test_common.cc deleted file mode 100644 index 15125fca1eb..00000000000 --- a/chromium/ui/latency/frame_metrics_test_common.cc +++ /dev/null @@ -1,92 +0,0 @@ -// 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/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; - // [0, 2^-16) => 1 bucket. - int i = 0; - boundaries[i++] = 0; - // [2^-16,1) pow of 2 strides => 16 buckets. (16x1) - for (int j = 0; j < 16; j++) - boundaries[i++] = 1ULL << j; - // [1,16) stride 1/2 => 30 buckets. (2 + 4 + 8 + 16) - for (int j = 0; j < 30; j++) - boundaries[i++] = one + (j * half); - // [16,32) stride 1 => 16 buckets. - for (int j = 0; j < 16; j++) - boundaries[i++] = (16 + j) * one; - // [32,64) stride 2 => 16 buckets. - for (int j = 0; j < 16; j++) - boundaries[i++] = (32 + 2 * j) * one; - // [64,128) stride 8 => 8 buckets. - for (int j = 0; j < 8; j++) - boundaries[i++] = (64 + 8 * j) * one; - // [128, 256) stride 16 => 8 buckets. - for (int j = 0; j < 8; j++) - boundaries[i++] = (128 + 16 * j) * one; - // [256, 512) stride 64 => 4 buckets. - for (int j = 0; j < 4; j++) - boundaries[i++] = (256 + 64 * j) * one; - // [512, 1024) stride 128 => 4 buckets. - for (int j = 0; j < 4; j++) - boundaries[i++] = (512 + 128 * j) * one; - // [1024, 2048) stride 512 => 2 buckets. - for (int j = 0; j < 2; j++) - boundaries[i++] = (1024 + 512 * j) * one; - // [2048, 4096) stride 1024 => 2 buckets. - for (int j = 0; j < 2; j++) - boundaries[i++] = (2048 + 1024 * j) * one; - // [4096, 2^16) pow of 2 strides => 4 buckets. (4x1) - for (int j = 0; j < 4; j++) - boundaries[i++] = (4096ULL << j) * one; - boundaries[i++] = 1ULL << 32; - 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::ComputePercentiles() 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 diff --git a/chromium/ui/latency/frame_metrics_test_common.h b/chromium/ui/latency/frame_metrics_test_common.h deleted file mode 100644 index 5ee17c4c89b..00000000000 --- a/chromium/ui/latency/frame_metrics_test_common.h +++ /dev/null @@ -1,157 +0,0 @@ -// 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_SQRT_APPROX(expected, actual) \ - EXPECT_NEAR(expected, actual, MaxErrorSQRTApprox(expected, actual)) - -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); - } -} - -// 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 MaxErrorSQRTApprox(double expected_value, double value) { - return std::max(0.5, std::max(expected_value, value) * 1e-3); -} - -// 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_ diff --git a/chromium/ui/latency/frame_metrics_unittest.cc b/chromium/ui/latency/frame_metrics_unittest.cc deleted file mode 100644 index 55f43a7aecf..00000000000 --- a/chromium/ui/latency/frame_metrics_unittest.cc +++ /dev/null @@ -1,918 +0,0 @@ -// 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/frame_metrics.h" - -#include "base/bind.h" -#include "base/rand_util.h" -#include "testing/gtest/include/gtest/gtest.h" - -#include "ui/latency/frame_metrics_test_common.h" - -namespace ui { -namespace frame_metrics { -namespace { - -// Converts a skipped:produced ratio into skipped:total, where -// total = skipped + produced. -// Internally we store the skipped:produced ratio since it is linear with -// the amount of time skipped, which has benefits for the fixed point -// representation as well as how it affects the RMS value. -// However, at a high level, we are more interested in the percent of total -// time skipped which is easier to interpret. -constexpr double SkipTransform(double ratio) { - return 1.0 / (1.0 + (1.0 / ratio)); -} - -// Returns the max value of an N-bit unsigned number. -constexpr uint64_t MaxValue(int N) { - return (1ULL << N) - 1; -} - -// Define lower bounds on the saturation values of each metric. -// They are much bigger than they need to be, which ensures the range of our -// metrics will be okay. -// The constants passed to MaxValue represent the number of bits before -// the radix point in each metric's fixed-point representation. -constexpr double kSkipSaturationMin = - SkipTransform(MaxValue(16)); // skipped : frame delta = 65535 -constexpr double kLatencySaturationMin = - MaxValue(32) / base::TimeTicks::kMicrosecondsPerSecond; // 4294.96 seconds -constexpr double kSpeedSaturationMin = - MaxValue(16); // latency delta : frame delta = 65535 -constexpr double kAccelerationSaturationMin = - MaxValue(16) * base::TimeTicks::kMicrosecondsPerSecond / - 1024; // speed delta : frame delta ~= 64M - -// Define upper bounds for saturation points so we can verify the tests -// are testing what they think they are testing. -constexpr double kSkipSaturationMax = kSkipSaturationMin * 1.01; -constexpr double kLatencySaturationMax = kLatencySaturationMin * 1.01; -constexpr double kSpeedSaturationMax = kSpeedSaturationMin * 1.01; -constexpr double kAccelerationSaturationMax = kAccelerationSaturationMin * 1.01; - -// TestFrameMetrics overrides some behavior of FrameMetrics for testing -// purposes. -class TestFrameMetrics : public FrameMetrics { - public: - TestFrameMetrics(const FrameMetricsSettings& settings) - : FrameMetrics(settings) {} - ~TestFrameMetrics() override = default; - - void OverrideReportPeriod(base::TimeDelta period) { - report_period_override_ = period; - } - - void UseDefaultReportPeriodScaled(int scale) { - report_period_override_ = scale * FrameMetrics::ReportPeriod(); - } - - // AtStartOfNewReportPeriod works assuming it is called after every frame - // is submitted. - bool AtStartOfNewReportPeriod() { - bool at_start = time_since_start_of_report_period_ < - time_since_start_of_report_period_previous_; - time_since_start_of_report_period_previous_ = - time_since_start_of_report_period_; - return at_start; - } - - // Convenience accessors for testing. - const frame_metrics::StreamAnalyzer& skips() const { - return frame_skips_analyzer_; - } - const frame_metrics::StreamAnalyzer& latency() const { - return latency_analyzer_; - } - const frame_metrics::StreamAnalyzer& speed() const { - return latency_speed_analyzer_; - } - const frame_metrics::StreamAnalyzer& acceleration() const { - return latency_acceleration_analyzer_; - } - - protected: - base::TimeDelta ReportPeriod() override { return report_period_override_; } - - base::TimeDelta report_period_override_ = base::TimeDelta::FromHours(1); - base::TimeDelta time_since_start_of_report_period_previous_; - bool override_report_period_ = true; -}; - -// TestStreamAnalysis enables copying of StreamAnalysis for testing purposes. -struct TestStreamAnalysis : public StreamAnalysis { - TestStreamAnalysis() = default; - ~TestStreamAnalysis() = default; - - TestStreamAnalysis(const TestStreamAnalysis& src) { *this = src; } - - TestStreamAnalysis& operator=(const TestStreamAnalysis& src) { - mean = src.mean; - rms = src.rms; - smr = src.smr; - - std_dev = src.std_dev; - variance_of_roots = src.variance_of_roots; - - thresholds = src.thresholds; - percentiles = src.percentiles; - - worst_mean = src.worst_mean; - worst_rms = src.worst_rms; - worst_smr = src.worst_smr; - - return *this; - } -}; - -// The test fixture used by all tests in this file. -class FrameMetricsTest : public testing::Test { - public: - FrameMetricsTest() - : settings(ui::FrameMetricsSource::UnitTest, - ui::FrameMetricsSourceThread::Unknown, - ui::FrameMetricsCompileTarget::Unknown) { - settings.set_is_frame_latency_speed_on(true); - settings.set_is_frame_latency_acceleration_on(true); - } - - void SetUp() override { - // Make sure we don't get an unexpected call to StartNewReportPeriod. - frame_metrics = std::make_unique<TestFrameMetrics>(settings); - source_timestamp_origin = - base::TimeTicks() + base::TimeDelta::FromSeconds(1); - current_source_timestamp = source_timestamp_origin; - } - - // A deep reset of all sample history. - void Reset() { - frame_metrics->Reset(); - current_source_timestamp = source_timestamp_origin; - } - - // Simulates frames with a repeating skip pattern, a repeating produce - // pattern, and a repeating latency pattern. Each pattern runs in parallel - // and independently of each other. - // |extra_frames| can help ensure a specific number of metric values are - // added since the speed and acceleration metrics have 1 and 2 fewer values - // than frames respectively. - void TestPattern(std::vector<base::TimeDelta> produced, - std::vector<base::TimeDelta> skipped, - std::vector<base::TimeDelta> latencies, - size_t extra_frames = 0) { - // Make sure we run each pattern a whole number of times. - size_t count = 1000 * produced.size() * skipped.size() * latencies.size() + - extra_frames; - - for (size_t i = 0; i < count; i++) { - base::TimeDelta produce = produced[i % produced.size()]; - base::TimeDelta skip = skipped[i % skipped.size()]; - base::TimeDelta latency = latencies[i % latencies.size()]; - base::TimeTicks displayed_timestamp = current_source_timestamp + latency; - frame_metrics->AddFrameProduced(current_source_timestamp, produce, skip); - frame_metrics->AddFrameDisplayed(current_source_timestamp, - displayed_timestamp); - current_source_timestamp += produce + skip; - } - } - - // The following methods return the corresponding analysis of all - // frames added since the last call to Reset(). - TestStreamAnalysis SkipAnalysis() { return Analysis(frame_metrics->skips()); } - TestStreamAnalysis LatencyAnalysis() { - return Analysis(frame_metrics->latency()); - } - TestStreamAnalysis SpeedAnalysis() { - return Analysis(frame_metrics->speed()); - } - TestStreamAnalysis AccelerationAnalysis() { - return Analysis(frame_metrics->acceleration()); - } - - using AnalysisFunc = decltype(&FrameMetricsTest::SkipAnalysis); - - void StartNewReportPeriodAvoidsOverflowTest(base::TimeDelta produced, - base::TimeDelta skipped, - base::TimeDelta latency0, - base::TimeDelta latency1, - double threshold, - AnalysisFunc analysis_method); - - protected: - static TestStreamAnalysis Analysis(const StreamAnalyzer& analyzer) { - TestStreamAnalysis analysis; - analyzer.ComputeSummary(&analysis); - return analysis; - } - - FrameMetricsSettings settings; - std::unique_ptr<TestFrameMetrics> frame_metrics; - base::TimeTicks source_timestamp_origin; - base::TimeTicks current_source_timestamp; -}; - -// Verify we get zeros for skips, speed, and acceleration when the values -// are constant. -TEST_F(FrameMetricsTest, PerfectSmoothnessScores) { - const base::TimeDelta produced = base::TimeDelta::FromMilliseconds(10); - const base::TimeDelta skip = base::TimeDelta(); - const base::TimeDelta latency = base::TimeDelta::FromMilliseconds(10); - TestPattern({produced}, {skip}, {latency}); - for (TestStreamAnalysis r : - {SkipAnalysis(), SpeedAnalysis(), AccelerationAnalysis()}) { - EXPECT_EQ(0, r.mean); - EXPECT_EQ(0, r.rms); - EXPECT_EQ(0, r.smr); - EXPECT_EQ(0, r.std_dev); - EXPECT_EQ(0, r.variance_of_roots); - EXPECT_EQ(0, r.worst_mean.value); - EXPECT_EQ(0, r.worst_rms.value); - EXPECT_EQ(0, r.worst_smr.value); - } -} - -// Verify a constant fast latency is correctly reflected in stats. -TEST_F(FrameMetricsTest, PerfectLatencyScores) { - const base::TimeDelta produced = base::TimeDelta::FromMilliseconds(10); - const base::TimeDelta skip = base::TimeDelta(); - const base::TimeDelta latency = base::TimeDelta::FromMilliseconds(1); - TestPattern({produced}, {skip}, {latency}); - - TestStreamAnalysis r = LatencyAnalysis(); - EXPECT_DOUBLE_EQ(latency.InSecondsF(), r.mean); - EXPECT_NEAR_SQRT_APPROX(latency.InSecondsF(), r.rms); - EXPECT_NEAR_SQRT_APPROX(r.smr, latency.InSecondsF()); - EXPECT_EQ(0, r.std_dev); - EXPECT_NEAR_SQRT_APPROX(0, r.variance_of_roots); - EXPECT_DOUBLE_EQ(latency.InSecondsF(), r.worst_mean.value); - EXPECT_NEAR_SQRT_APPROX(latency.InSecondsF(), r.worst_rms.value); - EXPECT_NEAR_SQRT_APPROX(r.worst_smr.value, latency.InSecondsF()); -} - -// Apply a saw tooth pattern to the frame skips with values that are easy to -// verify for SMR, RMS, etc. -TEST_F(FrameMetricsTest, SawToothShapedSkips) { - const base::TimeDelta produced = base::TimeDelta::FromSeconds(1); - const base::TimeDelta latency = base::TimeDelta::FromMilliseconds(1); - const std::vector<base::TimeDelta> skips = { - base::TimeDelta::FromSeconds(0), base::TimeDelta::FromSeconds(1), - }; - TestPattern({produced}, skips, {latency}); - - // Verify skip stats. - TestStreamAnalysis r = SkipAnalysis(); - - // 1 frame skipped per 3 frames of active time. - const double expected_skip_mean = (0 + 1.0) / 3; - EXPECT_EQ(expected_skip_mean, r.mean); - EXPECT_EQ(expected_skip_mean, r.worst_mean.value); - - // The expected value calculations for everything other than the mean are a - // bit convoluted since the internal calculations are performed in a different - // space than the final result. (skip:produce vs. skip:total). - const double expected_skip_to_produce_mean_square = (0 + 1.0) / 2; - const double expected_skip_to_produce_rms = - std::sqrt(expected_skip_to_produce_mean_square); - const double expected_skip_rms = SkipTransform(expected_skip_to_produce_rms); - EXPECT_NEAR_SQRT_APPROX(expected_skip_rms, r.rms); - EXPECT_NEAR_SQRT_APPROX(expected_skip_rms, r.worst_rms.value); - - const double expected_expected_skip_to_produce_mean_root = (0 + 1.0) / 2; - const double expected_expected_skip_to_produce_smr = - expected_expected_skip_to_produce_mean_root * - expected_expected_skip_to_produce_mean_root; - const double expected_skip_smr = - SkipTransform(expected_expected_skip_to_produce_smr); - EXPECT_NEAR_SQRT_APPROX(expected_skip_smr, r.smr); - EXPECT_NEAR_SQRT_APPROX(expected_skip_smr, r.worst_smr.value); - - const double expected_skip_to_produce_std_dev = (0.5 + 0.5) / 2; - const double expected_skip_std_dev = - SkipTransform(expected_skip_to_produce_std_dev); - EXPECT_NEAR_SQRT_APPROX(expected_skip_std_dev, r.std_dev); - - const double expected_skip_to_produce_std_dev_of_roots = (0.5 + 0.5) / 2; - const double expected_skip_to_produce_variance_of_roots = - expected_skip_to_produce_std_dev_of_roots * - expected_skip_to_produce_std_dev_of_roots; - const double expected_skip_variance_of_roots = - SkipTransform(expected_skip_to_produce_variance_of_roots); - EXPECT_NEAR_SQRT_APPROX(expected_skip_variance_of_roots, r.variance_of_roots); -} - -// Apply a saw tooth pattern to the latency with values that are easy to -// verify for SMR, RMS, etc. Furthermore, since the latency speed and -// acceleration are constant, verify that the SMR, RMS, and mean values are -// equal. -TEST_F(FrameMetricsTest, SawToothShapedLatency) { - const base::TimeDelta produced = base::TimeDelta::FromSeconds(1); - const base::TimeDelta skipped = base::TimeDelta(); - const std::vector<base::TimeDelta> latencies = { - base::TimeDelta::FromSeconds(36), base::TimeDelta::FromSeconds(100), - }; - TestPattern({produced}, {skipped}, latencies); - - // Verify latency. - TestStreamAnalysis r = LatencyAnalysis(); - const double expected_latency_mean = (100.0 + 36) / 2; - EXPECT_DOUBLE_EQ(expected_latency_mean, r.mean); - EXPECT_DOUBLE_EQ(expected_latency_mean, r.worst_mean.value); - - const double expected_latency_mean_square = (100.0 * 100 + 36 * 36) / 2; - const double expected_latency_rms = std::sqrt(expected_latency_mean_square); - EXPECT_NEAR_SQRT_APPROX(expected_latency_rms, r.rms); - EXPECT_NEAR_SQRT_APPROX(expected_latency_rms, r.worst_rms.value); - - const double expected_latency_mean_root = (10.0 + 6) / 2; - const double expected_latency_smr = - expected_latency_mean_root * expected_latency_mean_root; - EXPECT_NEAR_SQRT_APPROX(expected_latency_smr, r.smr); - EXPECT_NEAR_SQRT_APPROX(expected_latency_smr, r.worst_smr.value); - - const double expected_latency_std_dev = (100.0 - 36) / 2; - EXPECT_NEAR_SQRT_APPROX(expected_latency_std_dev, r.std_dev); - - const double expected_latency_std_dev_of_roots = (10.0 - 6) / 2; - const double expected_latency_variance_of_roots = - expected_latency_std_dev_of_roots * expected_latency_std_dev_of_roots; - EXPECT_NEAR_SQRT_APPROX(expected_latency_variance_of_roots, - r.variance_of_roots); - - // Verify latency speed, where mean, RMS, SMR, etc. should be equal. - r = SpeedAnalysis(); - const double expected_speed = 64; - EXPECT_DOUBLE_EQ(expected_speed, r.mean); - EXPECT_NEAR_SQRT_APPROX(expected_speed, r.rms); - EXPECT_NEAR_SQRT_APPROX(expected_speed, r.smr); - EXPECT_DOUBLE_EQ(0, r.std_dev); - EXPECT_NEAR_SQRT_APPROX(0, r.variance_of_roots); - EXPECT_DOUBLE_EQ(expected_speed, r.worst_mean.value); - EXPECT_NEAR_SQRT_APPROX(expected_speed, r.worst_rms.value); - EXPECT_NEAR_SQRT_APPROX(expected_speed, r.worst_smr.value); - - // Verify latency accelleration, where mean, RMS, SMR, etc. should be equal. - // The slack is relatively large since the frame durations are so long, which - // ends up in the divisor twice for acceleration; however, the slack is still - // within an acceptable range. - r = AccelerationAnalysis(); - const double expected_acceleration = expected_speed * 2; - const double slack = 0.1; - EXPECT_NEAR(expected_acceleration, r.mean, slack); - EXPECT_NEAR(expected_acceleration, r.rms, slack); - EXPECT_NEAR(expected_acceleration, r.smr, slack); - EXPECT_NEAR(0, r.std_dev, slack); - EXPECT_NEAR(0, r.variance_of_roots, slack); - EXPECT_NEAR(expected_acceleration, r.worst_mean.value, slack); - EXPECT_NEAR(expected_acceleration, r.worst_rms.value, slack); - EXPECT_NEAR(expected_acceleration, r.worst_smr.value, slack); -} - -// Makes sure rA and rB are equal. -void VerifySreamAnalysisValueEquality(const TestStreamAnalysis& rA, - const TestStreamAnalysis& rB) { - EXPECT_EQ(rA.mean, rB.mean); - EXPECT_EQ(rA.rms, rB.rms); - EXPECT_EQ(rA.smr, rB.smr); - EXPECT_EQ(rA.std_dev, rB.std_dev); - EXPECT_EQ(rA.variance_of_roots, rB.variance_of_roots); - EXPECT_EQ(rA.worst_mean.value, rB.worst_mean.value); - EXPECT_EQ(rA.worst_rms.value, rB.worst_rms.value); - EXPECT_EQ(rA.worst_smr.value, rB.worst_smr.value); -} - -// Verify that overflowing skips saturates instead of wraps, -// and that its saturation point is acceptable. -TEST_F(FrameMetricsTest, SkipSaturatesOnOverflow) { - const base::TimeDelta produced = base::TimeDelta::FromMilliseconds(1); - const base::TimeDelta latency = base::TimeDelta::FromMilliseconds(1); - const base::TimeDelta skipA = base::TimeDelta::FromSeconds(66); - const base::TimeDelta skipB = base::TimeDelta::FromSeconds(80); - TestPattern({produced}, {skipA}, {latency}); - TestStreamAnalysis rA = SkipAnalysis(); - Reset(); - TestPattern({produced}, {skipB}, {latency}); - TestStreamAnalysis rB = SkipAnalysis(); - - // Verify results are larger than a non-saturating value and smaller than - // than a number just past the expected saturation point. - EXPECT_LT(kSkipSaturationMin, rB.mean); - EXPECT_GT(kSkipSaturationMax, rB.mean); - // Verify the results are the same. - // If they wrapped around, they would be different. - VerifySreamAnalysisValueEquality(rA, rB); -} - -// Verify that overflowing latency saturates instead of wraps, -// and that its saturation point is acceptable. -TEST_F(FrameMetricsTest, LatencySaturatesOnOverflow) { - const base::TimeDelta produced = base::TimeDelta::FromMilliseconds(1); - const base::TimeDelta skipped = base::TimeDelta(); - const base::TimeDelta latencyA = base::TimeDelta::FromSeconds(4295); - const base::TimeDelta latencyB = base::TimeDelta::FromSeconds(5000); - TestPattern({produced}, {skipped}, {latencyA}); - TestStreamAnalysis rA = LatencyAnalysis(); - Reset(); - TestPattern({produced}, {skipped}, {latencyB}); - TestStreamAnalysis rB = LatencyAnalysis(); - - // Verify results are larger than a non-saturating value and smaller than - // than a number just past the expected saturation point. - EXPECT_LT(kLatencySaturationMin, rB.mean); - EXPECT_GT(kLatencySaturationMax, rB.mean); - // Verify the results are the same. - // If they wrapped around, they would be different. - VerifySreamAnalysisValueEquality(rA, rB); -} - -// Verify that overflowing latency speed saturates instead of wraps, -// and that its saturation point is acceptable. -TEST_F(FrameMetricsTest, LatencySpeedSaturatesOnOverflow) { - const base::TimeDelta produced = base::TimeDelta::FromMilliseconds(1); - const base::TimeDelta skipped = base::TimeDelta(); - const base::TimeDelta latency0 = base::TimeDelta::FromSeconds(0); - const base::TimeDelta latencyA = base::TimeDelta::FromSeconds(66); - const base::TimeDelta latencyB = base::TimeDelta::FromSeconds(70); - TestPattern({produced}, {skipped}, {latency0, latencyA}); - TestStreamAnalysis rA = SpeedAnalysis(); - Reset(); - TestPattern({produced}, {skipped}, {latency0, latencyB}); - TestStreamAnalysis rB = SpeedAnalysis(); - - // Verify results are larger than a non-saturating value and smaller than - // than a number just past the expected saturation point. - EXPECT_LT(kSpeedSaturationMin, rB.mean); - EXPECT_GT(kSpeedSaturationMax, rB.mean); - // Verify the results are the same. - // If they wrapped around, they would be different. - VerifySreamAnalysisValueEquality(rA, rB); -} - -// Verify that overflowing latency acceleration saturates instead of wraps, -// and that its saturation point is acceptable. -TEST_F(FrameMetricsTest, LatencyAccelerationSaturatesOnOverflow) { - const base::TimeDelta produced = base::TimeDelta::FromMilliseconds(1); - const base::TimeDelta skipped = base::TimeDelta(); - const base::TimeDelta latency0 = base::TimeDelta::FromSeconds(0); - const base::TimeDelta latencyA = base::TimeDelta::FromSeconds(32); - const base::TimeDelta latencyB = base::TimeDelta::FromSeconds(34); - TestPattern({produced}, {skipped}, {latency0, latencyA}); - TestStreamAnalysis rA = AccelerationAnalysis(); - Reset(); - TestPattern({produced}, {skipped}, {latency0, latencyB}); - TestStreamAnalysis rB = AccelerationAnalysis(); - - // Verify results are larger than a non-saturating value and smaller than - // than a number just past the expected saturation point. - EXPECT_LT(kAccelerationSaturationMin, rB.mean); - EXPECT_GT(kAccelerationSaturationMax, rB.mean); - // Verify the results are the same. - // If they wrapped around, they would be different. - VerifySreamAnalysisValueEquality(rA, rB); -} - -// Helps verify that: -// 1) All thresholds with index less than |i| is 1. -// 2) All thresholds with index greater than |i| is 0. -// 3) The |i|'th threshold equals |straddle_fraction|. -void VerifyThresholds(TestStreamAnalysis analysis, - size_t count, - size_t i, - double straddle_fraction) { - EXPECT_EQ(count, analysis.thresholds.size()); - EXPECT_EQ(straddle_fraction, analysis.thresholds[i].ge_fraction) << i; - for (size_t j = 0; j < i; j++) - EXPECT_EQ(1.0, analysis.thresholds[j].ge_fraction) << i << "," << j; - for (size_t j = i + 1; j < count; j++) - EXPECT_EQ(0.0, analysis.thresholds[j].ge_fraction) << i << "," << j; -} - -// Iterates through skip patterns that straddle each skip threshold -// and verifies the reported fractions are correct. -TEST_F(FrameMetricsTest, SkipThresholds) { - base::TimeDelta produced = base::TimeDelta::FromMilliseconds(1); - base::TimeDelta latency = base::TimeDelta::FromMilliseconds(10); - std::vector<base::TimeDelta> skips = { - base::TimeDelta::FromMicroseconds(0), - base::TimeDelta::FromMicroseconds(250), - base::TimeDelta::FromMilliseconds(1), - base::TimeDelta::FromMilliseconds(2), - base::TimeDelta::FromMilliseconds(4), - base::TimeDelta::FromMilliseconds(8), - }; - - const size_t kThresholdCount = skips.size() - 2; - - TestPattern({produced}, {skips[0], skips[1]}, {latency}); - TestStreamAnalysis r = SkipAnalysis(); - EXPECT_EQ(kThresholdCount, r.thresholds.size()); - for (size_t j = 0; j < kThresholdCount; j++) { - EXPECT_EQ(0, r.thresholds[j].ge_fraction); - } - - for (size_t i = 0; i < kThresholdCount; i++) { - Reset(); - TestPattern({produced}, {skips[i + 1], skips[i + 2]}, {latency}); - VerifyThresholds(SkipAnalysis(), kThresholdCount, i, 0.5); - } -} - -// Iterates through latency patterns that straddle each latency threshold -// and verifies the reported fractions are correct. -// To straddle a threshold it alternates frames above and below the threshold. -TEST_F(FrameMetricsTest, LatencyThresholds) { - base::TimeDelta produced = base::TimeDelta::FromMilliseconds(1); - base::TimeDelta skipped = base::TimeDelta(); - std::vector<base::TimeDelta> latencies = { - base::TimeDelta::FromMilliseconds(0), - base::TimeDelta::FromMilliseconds(1), - base::TimeDelta::FromMilliseconds(5), - base::TimeDelta::FromMilliseconds(10), - base::TimeDelta::FromMilliseconds(20), - base::TimeDelta::FromMilliseconds(40), - }; - - const size_t kThresholdCount = latencies.size() - 2; - - TestPattern({produced}, {skipped}, {latencies[0], latencies[1]}); - TestStreamAnalysis r = LatencyAnalysis(); - EXPECT_EQ(kThresholdCount, r.thresholds.size()); - for (size_t j = 0; j < kThresholdCount; j++) { - EXPECT_EQ(0, r.thresholds[j].ge_fraction); - } - - for (size_t i = 0; i < kThresholdCount; i++) { - Reset(); - TestPattern({produced}, {skipped}, {latencies[i + 1], latencies[i + 2]}); - VerifyThresholds(LatencyAnalysis(), kThresholdCount, i, 0.5); - } -} - -// Iterates through latency patterns that straddle each latency threshold -// and verifies the reported fractions are correct. -// To straddle a threshold it alternates frames above and below the threshold. -TEST_F(FrameMetricsTest, SpeedThresholds) { - base::TimeDelta skipped = base::TimeDelta(); - std::vector<base::TimeDelta> latencies = { - base::TimeDelta::FromMilliseconds(100), - base::TimeDelta::FromMilliseconds(200), - }; - std::vector<base::TimeDelta> produced = { - base::TimeDelta::FromMilliseconds(1000), - base::TimeDelta::FromMilliseconds(240), - base::TimeDelta::FromMilliseconds(120), - base::TimeDelta::FromMilliseconds(60), - base::TimeDelta::FromMilliseconds(30), - base::TimeDelta::FromMilliseconds(15), - }; - const size_t kThresholdCount = produced.size() - 2; - - TestPattern({produced[0], produced[1]}, {skipped}, latencies, 1); - TestStreamAnalysis r = SpeedAnalysis(); - EXPECT_EQ(kThresholdCount, r.thresholds.size()); - for (size_t j = 0; j < kThresholdCount; j++) { - EXPECT_EQ(0, r.thresholds[j].ge_fraction); - } - - for (size_t i = 0; i < kThresholdCount; i++) { - Reset(); - TestPattern({produced[i + 1], produced[i + 2]}, {skipped}, latencies, 1); - // The expected "straddle fraction" is 1/3 instead of 1/3 since we - // varied the "produced" amound of each frame, which affects the weighting. - VerifyThresholds(SpeedAnalysis(), kThresholdCount, i, 1.0 / 3); - } -} - -// Iterates through acceleration patterns that straddle each acceleration -// threshold and verifies the reported fractions are correct. -// To straddle a threshold it sends a set of frames under the threshold and -// then a second set of frames over the threshold. -TEST_F(FrameMetricsTest, AccelerationThresholds) { - base::TimeDelta skipped = base::TimeDelta(); - base::TimeDelta produced = base::TimeDelta::FromMilliseconds(1); - base::TimeDelta latency0 = base::TimeDelta::FromMilliseconds(10); - std::vector<base::TimeDelta> latencies = { - latency0 + base::TimeDelta::FromMicroseconds(100), - latency0 + base::TimeDelta::FromMicroseconds(200), - latency0 + base::TimeDelta::FromMicroseconds(500), - latency0 + base::TimeDelta::FromMicroseconds(1000), - latency0 + base::TimeDelta::FromMicroseconds(2000), - latency0 + base::TimeDelta::FromMicroseconds(4000), - }; - const size_t kThresholdCount = latencies.size() - 2; - - TestPattern({produced}, {skipped}, {latency0, latencies[0]}, 2); - TestPattern({produced}, {skipped}, {latency0, latencies[1]}, 2); - TestStreamAnalysis r = AccelerationAnalysis(); - EXPECT_EQ(kThresholdCount, r.thresholds.size()); - for (size_t j = 0; j < kThresholdCount; j++) { - EXPECT_EQ(0, r.thresholds[j].ge_fraction); - } - - for (size_t i = 0; i < kThresholdCount; i++) { - Reset(); - TestPattern({produced}, {skipped}, {latency0, latencies[i + 1]}, 2); - TestPattern({produced}, {skipped}, {latency0, latencies[i + 2]}, 2); - VerifyThresholds(AccelerationAnalysis(), kThresholdCount, i, 0.5); - } -} - -// The percentile calcuation is an estimate, so make sure it is within an -// acceptable threshold. The offset is needed in case the expected value is 0. -void VerifyPercentiles(TestStreamAnalysis r, double expected, int source_line) { - double kPercentileSlackScale = .5; - double kPercentileSlackOffset = .02; - for (size_t i = 0; i < PercentileResults::kCount; i++) { - EXPECT_LT((1 - kPercentileSlackScale) * expected - kPercentileSlackOffset, - r.percentiles.values[i]) - << i << ", " << source_line; - EXPECT_GT( - (1 + 2 * kPercentileSlackScale) * expected + kPercentileSlackOffset, - r.percentiles.values[i]) - << i << ", " << source_line; - } -} - -// This is a basic test to verify percentiles for skips are hooked up correctly. -// The histogram unit tests already test bucketing and precision in depth, -// so we don't worry about that here. -TEST_F(FrameMetricsTest, PercentilesSkipBasic) { - base::TimeDelta produced = base::TimeDelta::FromMilliseconds(1); - base::TimeDelta latency = base::TimeDelta::FromMilliseconds(1); - - // Everything fast. - base::TimeDelta skipped = base::TimeDelta(); - base::TimeTicks displayed_timestamp = current_source_timestamp + latency; - frame_metrics->AddFrameProduced(current_source_timestamp, produced, skipped); - frame_metrics->AddFrameDisplayed(current_source_timestamp, - displayed_timestamp); - current_source_timestamp += produced + skipped; - - VerifyPercentiles(SkipAnalysis(), 0, __LINE__); - VerifyPercentiles(LatencyAnalysis(), latency.InSecondsF(), __LINE__); - VerifyPercentiles(SpeedAnalysis(), 0, __LINE__); - VerifyPercentiles(AccelerationAnalysis(), 0, __LINE__); - - // Bad skip. - Reset(); - skipped = base::TimeDelta::FromSeconds(5); - displayed_timestamp = current_source_timestamp + latency; - frame_metrics->AddFrameProduced(current_source_timestamp, produced, skipped); - frame_metrics->AddFrameDisplayed(current_source_timestamp, - displayed_timestamp); - current_source_timestamp += produced + skipped; - - double expected_skip_fraction = - skipped.InSecondsF() / (skipped.InSecondsF() + produced.InSecondsF()); - VerifyPercentiles(SkipAnalysis(), expected_skip_fraction, __LINE__); - VerifyPercentiles(LatencyAnalysis(), latency.InSecondsF(), __LINE__); - VerifyPercentiles(SpeedAnalysis(), 0, __LINE__); - VerifyPercentiles(AccelerationAnalysis(), 0, __LINE__); -} - -// This is a basic test to verify percentiles for latency, speed, and -// acceleration are hooked up correctly. It uses the property that latency, -// speed, and acceleration results are delayed until there are at least -// 1, 2, and 3 frames respectively. -// The histogram unit tests already test bucketing and precision in depth, -// so we don't worry about that here. -TEST_F(FrameMetricsTest, PercentilesLatencyBasic) { - const base::TimeDelta produced = base::TimeDelta::FromMilliseconds(1); - const base::TimeDelta skipped = base::TimeDelta(); - const base::TimeDelta latency0 = base::TimeDelta::FromMilliseconds(1); - const base::TimeDelta latency_delta = base::TimeDelta::FromSeconds(5); - const std::vector<base::TimeDelta> latencies = { - latency0 + latency_delta, latency0, latency0 + latency_delta, - }; - - // Everything fast. - base::TimeTicks displayed_timestamp = current_source_timestamp + latency0; - frame_metrics->AddFrameProduced(current_source_timestamp, produced, skipped); - frame_metrics->AddFrameDisplayed(current_source_timestamp, - displayed_timestamp); - current_source_timestamp += produced + skipped; - - VerifyPercentiles(SkipAnalysis(), 0, __LINE__); - VerifyPercentiles(LatencyAnalysis(), latency0.InSecondsF(), __LINE__); - VerifyPercentiles(SpeedAnalysis(), 0, __LINE__); - VerifyPercentiles(AccelerationAnalysis(), 0, __LINE__); - - // Bad latency. - Reset(); - displayed_timestamp = current_source_timestamp + latencies[0]; - frame_metrics->AddFrameProduced(current_source_timestamp, produced, skipped); - frame_metrics->AddFrameDisplayed(current_source_timestamp, - displayed_timestamp); - current_source_timestamp += produced + skipped; - - double expected_latency = (latencies[0]).InSecondsF(); - VerifyPercentiles(SkipAnalysis(), 0, __LINE__); - VerifyPercentiles(LatencyAnalysis(), expected_latency, __LINE__); - VerifyPercentiles(SpeedAnalysis(), 0, __LINE__); - VerifyPercentiles(AccelerationAnalysis(), 0, __LINE__); - - // Bad latency speed. - displayed_timestamp = current_source_timestamp + latencies[1]; - frame_metrics->AddFrameProduced(current_source_timestamp, produced, skipped); - frame_metrics->AddFrameDisplayed(current_source_timestamp, - displayed_timestamp); - current_source_timestamp += produced + skipped; - - double expected_speed = latency_delta.InSecondsF() / produced.InSecondsF(); - VerifyPercentiles(SkipAnalysis(), 0, __LINE__); - VerifyPercentiles(SpeedAnalysis(), expected_speed, __LINE__); - VerifyPercentiles(AccelerationAnalysis(), 0, __LINE__); - - // Bad latency acceleration. - double expected_acceleration = 2 * expected_speed / produced.InSecondsF(); - displayed_timestamp = current_source_timestamp + latencies[2]; - frame_metrics->AddFrameProduced(current_source_timestamp, produced, skipped); - frame_metrics->AddFrameDisplayed(current_source_timestamp, - displayed_timestamp); - current_source_timestamp += produced + skipped; - - VerifyPercentiles(SkipAnalysis(), 0, __LINE__); - VerifyPercentiles(AccelerationAnalysis(), expected_acceleration, __LINE__); -} - -// Applies a bunch of good frames followed by one bad frame. -// Then verifies all windows jump from the beginning (just before the bad frame) -// to the end (just after the bad frame). -TEST_F(FrameMetricsTest, WorstWindowsRangesUpdateCorrectly) { - const base::TimeDelta produced = base::TimeDelta::FromMilliseconds(10); - const base::TimeDelta skipped = base::TimeDelta(); - const base::TimeDelta latency = base::TimeDelta::FromMilliseconds(1); - TestPattern({produced}, {skipped}, {latency}); - - base::TimeTicks expected_begin, expected_end; - - // Verify windows for skips and latency start at the very beginning. - expected_begin = source_timestamp_origin; - expected_end = - source_timestamp_origin + produced * (settings.max_window_size - 1); - for (TestStreamAnalysis r : {SkipAnalysis(), LatencyAnalysis()}) { - EXPECT_EQ(expected_begin, r.worst_mean.window_begin); - EXPECT_EQ(expected_end, r.worst_mean.window_end); - EXPECT_EQ(expected_begin, r.worst_rms.window_begin); - EXPECT_EQ(expected_end, r.worst_rms.window_end); - EXPECT_EQ(expected_begin, r.worst_smr.window_begin); - EXPECT_EQ(expected_end, r.worst_smr.window_end); - } - - // Verify windows for speed and acceleration start near the beginning. - // We expect their windows to be delayed by 1 and 2 frames respectively - // since their first results need to compare multiple frames. - for (TestStreamAnalysis r : {SpeedAnalysis(), AccelerationAnalysis()}) { - expected_begin += produced; - expected_end += produced; - EXPECT_EQ(expected_begin, r.worst_mean.window_begin); - EXPECT_EQ(expected_end, r.worst_mean.window_end); - EXPECT_EQ(expected_begin, r.worst_rms.window_begin); - EXPECT_EQ(expected_end, r.worst_rms.window_end); - EXPECT_EQ(expected_begin, r.worst_smr.window_begin); - EXPECT_EQ(expected_end, r.worst_smr.window_end); - } - - // Add a bad frame so the windows are updated for all the dimensions. - base::TimeTicks displayed_timestamp = - current_source_timestamp + (2 * latency); - const base::TimeDelta skipped2 = base::TimeDelta::FromMilliseconds(1); - frame_metrics->AddFrameProduced(current_source_timestamp, produced - skipped2, - skipped2); - frame_metrics->AddFrameDisplayed(current_source_timestamp, - displayed_timestamp); - - // Verify all dimensions windows have updated. - expected_begin = - current_source_timestamp - produced * (settings.max_window_size - 1); - expected_end = current_source_timestamp; - for (TestStreamAnalysis r : {SkipAnalysis(), LatencyAnalysis(), - SpeedAnalysis(), AccelerationAnalysis()}) { - EXPECT_EQ(expected_begin, r.worst_mean.window_begin); - EXPECT_EQ(expected_end, r.worst_mean.window_end); - EXPECT_EQ(expected_begin, r.worst_rms.window_begin); - EXPECT_EQ(expected_end, r.worst_rms.window_end); - EXPECT_EQ(expected_begin, r.worst_smr.window_begin); - EXPECT_EQ(expected_end, r.worst_smr.window_end); - } -} - -// Accumulating samples for too long can result in overflow of the accumulators. -// This can happen if the system sleeps / hibernates for a long time. -// Make sure values are reported often enough to avoid overflow. -void FrameMetricsTest::StartNewReportPeriodAvoidsOverflowTest( - base::TimeDelta produced, - base::TimeDelta skipped, - base::TimeDelta latency0, - base::TimeDelta latency1, - double threshold, - AnalysisFunc analysis_method) { - // We need one frame here so that we have 3 frames by the first time we call - // AccelerationAnalysis. Before 3 frames, acceleration is not defined. - base::TimeTicks displayed_timestamp = current_source_timestamp + latency1; - frame_metrics->AddFrameProduced(current_source_timestamp, produced, skipped); - frame_metrics->AddFrameDisplayed(current_source_timestamp, - displayed_timestamp); - current_source_timestamp += produced + skipped; - - do { - displayed_timestamp = current_source_timestamp + latency0; - frame_metrics->AddFrameProduced(current_source_timestamp, produced, - skipped); - frame_metrics->AddFrameDisplayed(current_source_timestamp, - displayed_timestamp); - current_source_timestamp += produced + skipped; - - displayed_timestamp = current_source_timestamp + latency1; - frame_metrics->AddFrameProduced(current_source_timestamp, produced, - skipped); - frame_metrics->AddFrameDisplayed(current_source_timestamp, - displayed_timestamp); - current_source_timestamp += produced + skipped; - - TestStreamAnalysis r = (this->*analysis_method)(); - // If there's overflow, the result will be much less than the threshold. - ASSERT_LT(threshold, r.mean); - ASSERT_LT(threshold, r.rms); - ASSERT_LT(threshold, r.smr); - } while (!frame_metrics->AtStartOfNewReportPeriod()); -} - -// Make sure values are reported often enough to avoid skip overflow. -TEST_F(FrameMetricsTest, StartNewReportPeriodAvoidsOverflowForSkips) { - base::TimeDelta produced = base::TimeDelta::FromMicroseconds(1); - base::TimeDelta latency = base::TimeDelta::FromMilliseconds(1); - base::TimeDelta skipped = base::TimeDelta::FromSeconds(2); - - frame_metrics->UseDefaultReportPeriodScaled(7); - StartNewReportPeriodAvoidsOverflowTest(produced, skipped, latency, latency, - kSkipSaturationMin, - &FrameMetricsTest::SkipAnalysis); -} - -// Make sure values are reported often enough to avoid latency overflow. -TEST_F(FrameMetricsTest, StartNewReportPeriodAvoidsOverflowForLatency) { - base::TimeDelta produced = base::TimeDelta::FromMilliseconds(1); - base::TimeDelta latency = base::TimeDelta::FromSeconds(5000); - base::TimeDelta skipped = base::TimeDelta::FromSeconds(0); - - frame_metrics->UseDefaultReportPeriodScaled(2); - StartNewReportPeriodAvoidsOverflowTest(produced, skipped, latency, latency, - kLatencySaturationMin, - &FrameMetricsTest::LatencyAnalysis); -} - -// Make sure values are reported often enough to avoid speed overflow. -TEST_F(FrameMetricsTest, StartNewReportPeriodAvoidsOverflowForSpeed) { - base::TimeDelta produced = base::TimeDelta::FromMilliseconds(1); - base::TimeDelta latency0 = base::TimeDelta::FromSeconds(0); - base::TimeDelta latency1 = base::TimeDelta::FromSeconds(70); - base::TimeDelta skipped = base::TimeDelta::FromSeconds(0); - - frame_metrics->UseDefaultReportPeriodScaled(2); - StartNewReportPeriodAvoidsOverflowTest(produced, skipped, latency0, latency1, - kSpeedSaturationMin, - &FrameMetricsTest::SpeedAnalysis); -} - -// Make sure values are reported often enough to avoid acceleration overflow. -TEST_F(FrameMetricsTest, StartNewReportPeriodAvoidsOverflowForAcceleration) { - frame_metrics->UseDefaultReportPeriodScaled(2); - base::TimeDelta produced = base::TimeDelta::FromMilliseconds(1); - base::TimeDelta latency0 = base::TimeDelta::FromSeconds(0); - base::TimeDelta latency1 = base::TimeDelta::FromSeconds(33); - base::TimeDelta skipped = base::TimeDelta::FromSeconds(0); - - frame_metrics->UseDefaultReportPeriodScaled(2); - StartNewReportPeriodAvoidsOverflowTest( - produced, skipped, latency0, latency1, kAccelerationSaturationMin, - &FrameMetricsTest::AccelerationAnalysis); -} - -// Test the accuracy of the Newton's approximate square root calculation. -// Since suqare_rooot is always used on small numbers in cc, this test only test -// accuracy of small |x| value. A random number |x| between (0 - 100) is -// generated, Test if the difference of square roots obtained from -// FastApproximateSqrt and std::sqrt is less than |error_rage| (0.0001); -TEST_F(FrameMetricsTest, SquareRootApproximation) { - const double slack = 0.001; - for (int i = 0; i < 3; i++) { - int x = base::RandInt(0, 100); - double sol1 = std::sqrt(x); - double sol2 = FrameMetrics::FastApproximateSqrt(x); - EXPECT_NEAR(sol1, sol2, slack) - << "failed to give a good approximate square root of " << x; - } - - for (int i = 0; i < 3; i++) { - double x = double{base::RandUint64()} / base::RandomBitGenerator::max(); - double sol1 = std::sqrt(x); - double sol2 = FrameMetrics::FastApproximateSqrt(x); - EXPECT_NEAR(sol1, sol2, slack) - << "failed to give a good approximate square root of " << x; - } -} - -} // namespace -} // namespace frame_metrics -} // namespace ui diff --git a/chromium/ui/latency/histograms.cc b/chromium/ui/latency/histograms.cc deleted file mode 100644 index 9a5bfee4e6f..00000000000 --- a/chromium/ui/latency/histograms.cc +++ /dev/null @@ -1,384 +0,0 @@ -// 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/histograms.h" - -#include <cmath> -#include <limits> - -#include "base/bits.h" -#include "base/time/time.h" -#include "ui/latency/fixed_point.h" - -namespace { - -// Calculates percentiles in a way that can be shared by different histograms. -ui::PercentileResults PercentilesHelper( - ui::frame_metrics::BoundaryIterator* boundary_iterator, - const uint32_t* buckets_begin, - const uint32_t* buckets_end, - uint64_t total_samples) { - ui::PercentileResults result; - uint64_t boundary_left = 0; - uint64_t boundary_right = boundary_iterator->Next(); - - double thresholds[ui::PercentileResults::kCount]; - for (size_t i = 0; i < ui::PercentileResults::kCount; i++) { - thresholds[i] = ui::PercentileResults::kPercentiles[i] * total_samples; - } - - uint64_t accumulator = 0; - size_t result_index = 0; - for (const uint32_t* bucket = buckets_begin; bucket < buckets_end; bucket++) { - accumulator += *bucket; - // Multiple percentiles might be calculated from the same bucket, - // so we use a while loop here. - while (accumulator > thresholds[result_index]) { - const double overage = accumulator - thresholds[result_index]; - double b0_fraction = overage / (*bucket); - double b1_fraction = 1.0 - b0_fraction; - - // Use a linear interpolation between two buckets. - // This assumes the samples are evenly distributed within a bucket. - // TODO(brianderson): Consider neighboring bucket sizes and fit to a - // curve. http://crbug.com/821879 - const double estimate = - b0_fraction * boundary_left + b1_fraction * boundary_right; - result.values[result_index] = estimate; - result_index++; - if (result_index >= ui::PercentileResults::kCount) - return result; - } - - boundary_left = boundary_right; - boundary_right = boundary_iterator->Next(); - } - - return result; -} - -} // namespace - -namespace ui { - -constexpr double PercentileResults::kPercentiles[]; -constexpr size_t PercentileResults::kCount; - -namespace frame_metrics { - -constexpr size_t RatioHistogram::kBucketCount; -constexpr size_t VSyncHistogram::kBucketCount; - -// Ratio Histogram. -// The distribution of each category of buckets in this historam is -// exponential, however each category is divided into N linear buckets -// depending on how much precision we want for that category. How much -// precision a category gets depends on how often we expect that bucket to be -// used and how important those buckets are. -// -// Most of the precision is allocated for values just > 1 since most ratios, -// when not ~0 will be > 1. And since ratios are likely to be near whole -// numbers (some multiple of the vsync), we only give it a precision of 1/2. -// You can think about the stride of each category as the number of vsyncs of -// precision that category will have. -// -// There will be aliasing, but because of the vsync aligned linear division -// of each category, we won't get a bucket that represents fewer vsyncs than -// its fprevious bucket. -// -// This is in contrast to the default exponential distribution of UMA -// buckets, which result in a constant precision for each bucket and would -// allocate lots of very small buckets near 0 where we don't need the -// precision. - -namespace { - -constexpr size_t kBucketHalfStrideFirstBucketIndex = 17; - -// Within the range [16, 4096), there are 9 categories of buckets that each -// start with a power of 2. Within a category, successive buckets have a fixed -// stride. Across categories, the strides increase exponentionally, encoded -// as powers of 2 in |stride_shift|, which increases linearly. -struct RatioBucketCategory { - uint8_t first_bucket_index; - uint8_t stride_shift; -}; -using RatioCategoryHelper = std::array<RatioBucketCategory, 9>; -constexpr RatioCategoryHelper kCategories16to4096 = { - // first_bucket_index of each row below is the previous one + number of - // buckets. Each entry is {first_bucket_index, stride_shift}. - {{47, 0}, // [16, 32) stride 1 => 16 buckets. - {63, 1}, // [32, 64) stride 2 => 16 buckets. - {79, 3}, // [64, 128) stride 8 => 8 buckets. - {87, 4}, // [128, 256) stride 16 => 8 buckets. - {95, 6}, // [256, 512) stride 64 => 4 buckets - {99, 7}, // [512, 1024) stride 128 => 4 buckets. - {103, 9}, // [1024, 2048) stride 512 => 2 buckets. - {105, 10}, // [2048, 4096) stride 1024 => 2 buckets. - {107, 12}}}; // [4096, 8192) stride 4096 => 1 bucket. - -// The delegate RatioBoundary::Percentiles will pass to PercentilesHelper. -struct RatioBoundaryIterator : public BoundaryIterator { - ~RatioBoundaryIterator() override = default; - - size_t bucket = 0; - uint64_t boundary = 0; - RatioCategoryHelper::const_iterator b16to4096 = kCategories16to4096.begin(); - uint64_t next_boundary_to_change_category = - 32 * frame_metrics::kFixedPointMultiplier; - - uint64_t Next() override { - if (bucket == 0) { - // The first bucket is [0, 1). - boundary = 1; - } else if (bucket < kBucketHalfStrideFirstBucketIndex || - bucket >= kCategories16to4096.back().first_bucket_index) { - // The start and end buckets increase in size by powers of 2. - boundary *= 2; - } else if (bucket < kCategories16to4096.front().first_bucket_index) { - // The 30 buckets before 47 have a stride of .5 and represent the - // range [1, 16). - boundary += (frame_metrics::kFixedPointMultiplier / 2); - } else { - // The rest of the buckets are defined by kCategories16to4096. - DCHECK(b16to4096 < kCategories16to4096.end()); - boundary += - (frame_metrics::kFixedPointMultiplier << b16to4096->stride_shift); - // The category changes for every power of 2. - if (boundary >= next_boundary_to_change_category) { - next_boundary_to_change_category *= 2; - b16to4096++; - } - } - - bucket++; - return boundary; - } -}; - -} // namespace - -std::unique_ptr<BoundaryIterator> CreateRatioIteratorForTesting() { - return std::make_unique<RatioBoundaryIterator>(); -} - -RatioHistogram::RatioHistogram() = default; -RatioHistogram::~RatioHistogram() = default; - -void RatioHistogram::AddSample(uint32_t ratio, uint32_t weight) { - size_t bucket = 0; - - // Precomputed thresholds for the log base 2 of the ratio that help - // determine which category of buckets the sample should go in. - constexpr int kLog2HalfStrideStart = kFixedPointShift; - constexpr int kLog2Cats16to4096Start = kFixedPointShift + 4; // 2^4 = 16. - constexpr int kLog2_4096Pow2Start = kFixedPointShift + 12; // 2^12 = 4096. - - if (ratio == 0) { - bucket = 0; - } else { - int log2 = base::bits::Log2Floor(ratio); - DCHECK_GE(log2, 0); - if (log2 < kLog2HalfStrideStart) { - // [2^-16, 1) pow of 2 strides => 16 buckets. (16x1) - bucket = 1 + log2; - } else if (log2 < kLog2Cats16to4096Start) { - // [1, 16) stride 1/2 => 30 buckets. (2 + 4 + 8 + 16) - const int first_bucket_index = kBucketHalfStrideFirstBucketIndex; - const int category_start = kFixedPointMultiplier; - const int total_shift = kFixedPointShift - 1; // -1 multiplies by 2. - const int category_offset = (ratio - category_start) >> total_shift; - bucket = first_bucket_index + category_offset; - } else if (log2 < kLog2_4096Pow2Start) { - // [16, 32) stride 1 => 16 buckets. - // [32, 64) stride 2 => 16 buckets. - // [64, 128) stride 8 => 8 buckets. - // [128, 256) stride 16 => 8 buckets. - // [256, 512) stride 64 => 4 buckets. - // [512, 1024) stride 128 => 4 buckets. - // [1024, 2048) stride 512 => 2 buckets. - // [2048, 4096) stride 1024 => 2 buckets. - const int category = log2 - kLog2Cats16to4096Start; - const int category_start = 1 << log2; - const int total_shift = - (kFixedPointShift + kCategories16to4096[category].stride_shift); - const int category_offset = (ratio - category_start) >> total_shift; - bucket = - kCategories16to4096[category].first_bucket_index + category_offset; - } else { - // [4096, 2^16) pow of 2 strides => 4 buckets. (4x1) - const int category_offset = log2 - kLog2_4096Pow2Start; - bucket = kCategories16to4096.back().first_bucket_index + category_offset; - } - } - DCHECK_LT(bucket, kBucketCount); - - // Verify overflow isn't an issue. - DCHECK_LT(weight, std::numeric_limits<BucketArray::value_type>::max() - - buckets_[bucket]); - DCHECK_LT(weight, std::numeric_limits<decltype(total_samples_)>::max() - - total_samples_); - - buckets_[bucket] += weight; - total_samples_ += weight; -} - -PercentileResults RatioHistogram::ComputePercentiles() const { - RatioBoundaryIterator i; - return PercentilesHelper(&i, buckets_.data(), - buckets_.data() + buckets_.size(), total_samples_); -} - -void RatioHistogram::Reset() { - total_samples_ = 0; - buckets_.fill(0); -} - -// VSyncHistogram. -namespace { - -// The number of buckets in bucket categories 1 through 6. -constexpr std::array<uint8_t, 6> kVSyncBucketCounts = {{12, 16, 16, 16, 31, 6}}; - -// Some constants used to convert values to bucket categories. -constexpr size_t kVSync1stBucketC0 = 0; -constexpr size_t kVSync1stBucketC1 = kVSync1stBucketC0 + 1; -constexpr size_t kVSync1stBucketC2 = kVSync1stBucketC1 + kVSyncBucketCounts[0]; -constexpr size_t kVSync1stBucketC3 = kVSync1stBucketC2 + kVSyncBucketCounts[1]; -constexpr size_t kVSync1stBucketC4 = kVSync1stBucketC3 + kVSyncBucketCounts[2]; -constexpr size_t kVSync1stBucketC5 = kVSync1stBucketC4 + kVSyncBucketCounts[3]; -constexpr size_t kVSync1stBucketC6 = kVSync1stBucketC5 + kVSyncBucketCounts[4]; -constexpr size_t kVSyncBucketCountC6 = kVSyncBucketCounts[5]; - -// This iterates through the microsecond VSync boundaries. -struct VSyncBoundaryIterator : public BoundaryIterator { - ~VSyncBoundaryIterator() override = default; - - uint8_t category_ = 0; - uint8_t sub_bucket_ = 0; - - uint64_t Next() override { - uint32_t boundary = 0; - switch (category_) { - case 0: // Powers of two from 1 to 2048 us @ 50% precision - boundary = 1 << sub_bucket_; - break; - case 1: // Every 8 Hz from 256 Hz to 128 Hz @ 3-6% precision - case 2: // Every 4 Hz from 128 Hz to 64 Hz @ 3-6% precision - case 3: // Every 2 Hz from 64 Hz to 32 Hz @ 3-6% precision - case 4: { // Every 1 Hz from 32 Hz to 1 Hz @ 3-33% precision - int hz_start = 256 >> (category_ - 1); - int hz_stride = 8 >> (category_ - 1); - int hz = hz_start - hz_stride * sub_bucket_; - boundary = (base::TimeTicks::kMicrosecondsPerSecond + (hz / 2)) / hz; - break; - } - case 5: // Powers of two from 1s to 32s @ 50% precision - boundary = - static_cast<uint32_t>(base::TimeTicks::kMicrosecondsPerSecond) * - (1 << sub_bucket_); - break; - case 6: // The last boundary of 64s. - // Advancing would result in out-of-bounds access of - // kVSyncBucketCounts, so just return. - return 64 * base::TimeTicks::kMicrosecondsPerSecond; - default: - NOTREACHED(); - } - - if (++sub_bucket_ >= kVSyncBucketCounts[category_]) { - category_++; - sub_bucket_ = 0; - } - - return boundary; - } -}; - -} // namespace - -std::unique_ptr<BoundaryIterator> CreateVSyncIteratorForTesting() { - return std::make_unique<VSyncBoundaryIterator>(); -} - -VSyncHistogram::VSyncHistogram() = default; -VSyncHistogram::~VSyncHistogram() = default; - -// Optimized to minimize the number of memory accesses. -void VSyncHistogram::AddSample(uint32_t microseconds, uint32_t weight) { - size_t bucket = 0; - - static constexpr int k256HzPeriodInMicroseconds = - base::TimeTicks::kMicrosecondsPerSecond / 256; - - if (microseconds == 0) { - // bucket = 0; - } else if (microseconds < k256HzPeriodInMicroseconds) { - // Powers of two from 1 to 2048 us @ 50% precision - bucket = kVSync1stBucketC1 + base::bits::Log2Floor(microseconds); - } else if (microseconds < base::TimeTicks::kMicrosecondsPerSecond) { - // [256Hz, 1Hz) - int hz = base::TimeTicks::kMicrosecondsPerSecond / (microseconds + 0.5); - DCHECK_LT(hz, 256); - switch (hz / 32) { - // Every 1 Hz from 32 Hz to 1 Hz @ 3-33% precision - case 0: - bucket = kVSync1stBucketC6 - hz; - break; - // Every 2 Hz from 64 Hz to 32 Hz @ 3-6% precision - case 1: - bucket = kVSync1stBucketC5 - ((hz - 30) / 2); - break; - // Every 4 Hz from 128 Hz to 64 Hz @ 3-6% precision - case 2: - case 3: - bucket = kVSync1stBucketC4 - ((hz - 60) / 4); - break; - // Every 8 Hz from 256 Hz to 128 Hz @ 3-6% precision - case 4: - case 5: - case 6: - case 7: - bucket = kVSync1stBucketC3 - ((hz - 120) / 8); - break; - default: - NOTREACHED(); - return; - } - } else { - // Powers of two from 1s to 32s @ 50% precision - int seconds_log2 = base::bits::Log2Floor( - microseconds / base::TimeTicks::kMicrosecondsPerSecond); - DCHECK_GE(seconds_log2, 0); - size_t offset = std::min<size_t>(kVSyncBucketCountC6 - 1, seconds_log2); - bucket = kVSync1stBucketC6 + offset; - } - - DCHECK_GE(bucket, 0u); - DCHECK_LT(bucket, kVSync1stBucketC6 + kVSyncBucketCountC6); - DCHECK_LT(bucket, kBucketCount); - - // Verify overflow isn't an issue. - DCHECK_LT(weight, std::numeric_limits<BucketArray::value_type>::max() - - buckets_[bucket]); - DCHECK_LT(weight, std::numeric_limits<decltype(total_samples_)>::max() - - total_samples_); - - buckets_[bucket] += weight; - total_samples_ += weight; -} - -PercentileResults VSyncHistogram::ComputePercentiles() const { - VSyncBoundaryIterator i; - return PercentilesHelper(&i, buckets_.data(), - buckets_.data() + buckets_.size(), total_samples_); -} - -void VSyncHistogram::Reset() { - total_samples_ = 0; - buckets_.fill(0); -} - -} // namespace frame_metrics -} // namespace ui diff --git a/chromium/ui/latency/histograms.h b/chromium/ui/latency/histograms.h deleted file mode 100644 index 3d9545b6207..00000000000 --- a/chromium/ui/latency/histograms.h +++ /dev/null @@ -1,107 +0,0 @@ -// 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_HISTOGRAMS_H_ -#define UI_LATENCY_HISTOGRAMS_H_ - -#include <array> -#include <memory> - -#include "base/stl_util.h" - -namespace ui { - -// 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 { - static constexpr double kPercentiles[] = {.50, .99}; - static constexpr size_t kCount = base::size(kPercentiles); - - double values[kCount]{}; -}; - -namespace frame_metrics { - -// This is an interface different metrics will use to inject their ideal -// histogram implementations into the StreamAnalyzer. -class Histogram { - public: - Histogram() = default; - virtual ~Histogram() = default; - - // Increases the bucket that contains |value| by |weight|. - virtual void AddSample(uint32_t value, uint32_t weight) = 0; - - // Computes and returns the approximate percentiles based on the - // histogram distribution. - virtual PercentileResults ComputePercentiles() const = 0; - - // Resets all buckets in the histogram to 0. - // Higher level logic may periodically reset the the counts after it - // gathers the percentiles in order to avoid overflow. - virtual void Reset() = 0; - - private: - DISALLOW_COPY_AND_ASSIGN(Histogram); -}; - -// Ratio histogram, with a range of [0, 2^32) and most of it's precision -// just above kFixedPointMultiplier (i.e. a fixed point of 1). -class RatioHistogram : public Histogram { - public: - RatioHistogram(); - ~RatioHistogram() override; - void AddSample(uint32_t ratio, uint32_t weight) override; - PercentileResults ComputePercentiles() const override; - void Reset() override; - - private: - static constexpr size_t kBucketCount = 111; - - uint64_t total_samples_ = 0; - using BucketArray = std::array<uint32_t, kBucketCount>; - BucketArray buckets_{}; - - DISALLOW_COPY_AND_ASSIGN(RatioHistogram); -}; - -// A histogram of 98 buckets from 0 to 64 seconds with extra precision -// around common vsync boundaries. -class VSyncHistogram : public Histogram { - public: - VSyncHistogram(); - ~VSyncHistogram() override; - void AddSample(uint32_t microseconds, uint32_t weight) override; - PercentileResults ComputePercentiles() const override; - void Reset() override; - - private: - static constexpr size_t kBucketCount = 98; - - uint64_t total_samples_ = 0; - using BucketArray = std::array<uint32_t, kBucketCount>; - BucketArray buckets_{}; - - DISALLOW_COPY_AND_ASSIGN(VSyncHistogram); -}; - -// An interface that allows PercentileHelper to iterate through the -// bucket boundaries of the delegating histogram. -// This is an implemenation detail, but is exposed here for testing purposes. -struct BoundaryIterator { - virtual ~BoundaryIterator() = default; - virtual uint64_t Next() = 0; -}; - -// These expose the internal iterators, so they can be verified in tests. -std::unique_ptr<BoundaryIterator> CreateRatioIteratorForTesting(); -std::unique_ptr<BoundaryIterator> CreateVSyncIteratorForTesting(); - -} // namespace frame_metrics -} // namespace ui - -#endif // UI_LATENCY_HISTOGRAMS_H_ diff --git a/chromium/ui/latency/histograms_perftest.cc b/chromium/ui/latency/histograms_perftest.cc deleted file mode 100644 index b05dbb841a9..00000000000 --- a/chromium/ui/latency/histograms_perftest.cc +++ /dev/null @@ -1,267 +0,0 @@ -// 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/histograms.h" - -#include <algorithm> - -#include "base/metrics/bucket_ranges.h" -#include "base/metrics/sample_vector.h" -#include "base/time/time.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "testing/perf/perf_result_reporter.h" -#include "ui/latency/fixed_point.h" -#include "ui/latency/frame_metrics_test_common.h" - -namespace ui { -namespace frame_metrics { - -constexpr base::TimeDelta kTimeLimit = base::TimeDelta::FromSeconds(2); - -// A version of RatioHistogram based on the default implementations -// of base::BucketRanges and base::SampleVector. -class RatioHistogramBaseline : public Histogram { - public: - RatioHistogramBaseline() - : ratio_boundaries_(), - bucket_ranges_(ratio_boundaries_.size()), - sample_vector_(&bucket_ranges_) { - size_t i = 0; - for (const auto& b : ratio_boundaries_.boundaries) { - bucket_ranges_.set_range(i++, std::min<uint64_t>(b, INT_MAX)); - } - } - - ~RatioHistogramBaseline() override = default; - - void AddSample(uint32_t microseconds, uint32_t weight) override { - sample_vector_.Accumulate(microseconds, weight); - } - - PercentileResults ComputePercentiles() const override { - return PercentileResults(); - } - void Reset() override {} - - private: - TestRatioBoundaries ratio_boundaries_; - base::BucketRanges bucket_ranges_; - base::SampleVector sample_vector_; - - DISALLOW_COPY_AND_ASSIGN(RatioHistogramBaseline); -}; - -perf_test::PerfResultReporter SetUpReporter(const std::string& story_name) { - perf_test::PerfResultReporter reporter("FrameMetricsHistograms", story_name); - reporter.RegisterImportantMetric(".speedup", "score"); - return reporter; -} - -TEST(FrameMetricsHistogramsPerfTest, RatioEntireRange) { - const int kStride = 0x1000; - - RatioHistogramBaseline vh_base; - RatioHistogram vh_impl; - - base::TimeDelta impl_time; - base::TimeDelta base_time; - - base::TimeTicks finish_time = base::TimeTicks::Now() + kTimeLimit; - while (base::TimeTicks::Now() < finish_time) { - // Impl then Base - for (int i = 0; i < INT_MAX - kStride; i += kStride) { - int value = (i * 37) & 0x3FFFFFFF; - base::TimeTicks t0 = base::TimeTicks::Now(); - vh_impl.AddSample(value, 1); - base::TimeTicks t1 = base::TimeTicks::Now(); - vh_base.AddSample(value, 1); - base::TimeTicks t2 = base::TimeTicks::Now(); - base::TimeTicks t3 = base::TimeTicks::Now(); - impl_time += t1 - t0 - (t3 - t2); - base_time += t2 - t1 - (t3 - t2); - } - - // Base then Impl - for (int i = 0; i < INT_MAX - kStride; i += kStride) { - int value = (i * 37) & 0x3FFFFFFF; - base::TimeTicks t0 = base::TimeTicks::Now(); - vh_base.AddSample(value, 1); - base::TimeTicks t1 = base::TimeTicks::Now(); - vh_impl.AddSample(value, 1); - base::TimeTicks t2 = base::TimeTicks::Now(); - base::TimeTicks t3 = base::TimeTicks::Now(); - base_time += t1 - t0 - (t3 - t2); - impl_time += t2 - t1 - (t3 - t2); - } - } - - double speedup = base_time.InSecondsF() / impl_time.InSecondsF(); - perf_test::PerfResultReporter reporter = SetUpReporter("RatioEntireRange"); - reporter.AddResult(".speedup", speedup); -} - -TEST(FrameMetricsHistogramsPerfTest, RatioCommonRange) { - const int kStride = 0x100; - - RatioHistogramBaseline vh_base; - RatioHistogram vh_impl; - - base::TimeDelta impl_time; - base::TimeDelta base_time; - - base::TimeTicks finish_time = base::TimeTicks::Now() + kTimeLimit; - while (base::TimeTicks::Now() < finish_time) { - // Impl then Base - for (int i = 0; i < 4 * kFixedPointMultiplier; i += kStride) { - int value = i; - base::TimeTicks t0 = base::TimeTicks::Now(); - vh_impl.AddSample(value, 1); - base::TimeTicks t1 = base::TimeTicks::Now(); - vh_base.AddSample(value, 1); - base::TimeTicks t2 = base::TimeTicks::Now(); - base::TimeTicks t3 = base::TimeTicks::Now(); - impl_time += t1 - t0 - (t3 - t2); - base_time += t2 - t1 - (t3 - t2); - } - - // Base then Impl - for (int i = 0; i < 4 * kFixedPointMultiplier; i += kStride) { - int value = i; - base::TimeTicks t0 = base::TimeTicks::Now(); - vh_base.AddSample(value, 1); - base::TimeTicks t1 = base::TimeTicks::Now(); - vh_impl.AddSample(value, 1); - base::TimeTicks t2 = base::TimeTicks::Now(); - base::TimeTicks t3 = base::TimeTicks::Now(); - base_time += t1 - t0 - (t3 - t2); - impl_time += t2 - t1 - (t3 - t2); - } - } - - double speedup = base_time.InSecondsF() / impl_time.InSecondsF(); - perf_test::PerfResultReporter reporter = SetUpReporter("RatioCommonRange"); - reporter.AddResult(".speedup", speedup); -} - -// A version of VSyncHistogram based on the default implementations -// of base::BucketRanges and base::SampleVector. -class VSyncHistogramBaseline : public Histogram { - public: - VSyncHistogramBaseline() - : bucket_ranges_(kTestVSyncBoundries.size() + 1), - sample_vector_(&bucket_ranges_) { - size_t i = 0; - for (const auto& b : kTestVSyncBoundries) { - bucket_ranges_.set_range(i++, b); - } - // BucketRanges needs the last element set to INT_MAX. - bucket_ranges_.set_range(i++, INT_MAX); - } - - ~VSyncHistogramBaseline() override = default; - - void AddSample(uint32_t microseconds, uint32_t weight) override { - sample_vector_.Accumulate(microseconds, weight); - } - - PercentileResults ComputePercentiles() const override { - return PercentileResults(); - } - void Reset() override {} - - private: - base::BucketRanges bucket_ranges_; - base::SampleVector sample_vector_; - - DISALLOW_COPY_AND_ASSIGN(VSyncHistogramBaseline); -}; - -TEST(FrameMetricsHistogramsPerfTest, VSyncEntireRange) { - const int kStride = 0x1000; - - VSyncHistogramBaseline vh_base; - VSyncHistogram vh_impl; - - base::TimeDelta impl_time; - base::TimeDelta base_time; - - base::TimeTicks finish_time = base::TimeTicks::Now() + kTimeLimit; - while (base::TimeTicks::Now() < finish_time) { - // Impl then Base - for (int i = 0; i < INT_MAX - kStride; i += kStride) { - int value = (i * 37) % 64000000; - base::TimeTicks t0 = base::TimeTicks::Now(); - vh_impl.AddSample(value, 1); - base::TimeTicks t1 = base::TimeTicks::Now(); - vh_base.AddSample(value, 1); - base::TimeTicks t2 = base::TimeTicks::Now(); - base::TimeTicks t3 = base::TimeTicks::Now(); - impl_time += t1 - t0 - (t3 - t2); - base_time += t2 - t1 - (t3 - t2); - } - - // Base then Impl - for (int i = 0; i < INT_MAX - kStride; i += kStride) { - int value = (i * 37) % 64000000; - base::TimeTicks t0 = base::TimeTicks::Now(); - vh_base.AddSample(value, 1); - base::TimeTicks t1 = base::TimeTicks::Now(); - vh_impl.AddSample(value, 1); - base::TimeTicks t2 = base::TimeTicks::Now(); - base::TimeTicks t3 = base::TimeTicks::Now(); - base_time += t1 - t0 - (t3 - t2); - impl_time += t2 - t1 - (t3 - t2); - } - } - - double speedup = base_time.InSecondsF() / impl_time.InSecondsF(); - perf_test::PerfResultReporter reporter = SetUpReporter("VSyncEntireRange"); - reporter.AddResult(".speedup", speedup); -} - -TEST(FrameMetricsHistogramsPerfTest, VSyncCommonRange) { - const int kStride = 0x100; - - VSyncHistogramBaseline vh_base; - VSyncHistogram vh_impl; - - base::TimeDelta impl_time; - base::TimeDelta base_time; - - base::TimeTicks finish_time = base::TimeTicks::Now() + kTimeLimit; - while (base::TimeTicks::Now() < finish_time) { - // Impl then Base - for (int i = 0; i < 100000; i += kStride) { - int value = i; - base::TimeTicks t0 = base::TimeTicks::Now(); - vh_impl.AddSample(value, 1); - base::TimeTicks t1 = base::TimeTicks::Now(); - vh_base.AddSample(value, 1); - base::TimeTicks t2 = base::TimeTicks::Now(); - base::TimeTicks t3 = base::TimeTicks::Now(); - impl_time += t1 - t0 - (t3 - t2); - base_time += t2 - t1 - (t3 - t2); - } - - // Base then Impl - for (int i = 0; i < 100000; i += kStride) { - int value = i; - base::TimeTicks t0 = base::TimeTicks::Now(); - vh_base.AddSample(value, 1); - base::TimeTicks t1 = base::TimeTicks::Now(); - vh_impl.AddSample(value, 1); - base::TimeTicks t2 = base::TimeTicks::Now(); - base::TimeTicks t3 = base::TimeTicks::Now(); - base_time += t1 - t0 - (t3 - t2); - impl_time += t2 - t1 - (t3 - t2); - } - } - - double speedup = base_time.InSecondsF() / impl_time.InSecondsF(); - perf_test::PerfResultReporter reporter = SetUpReporter("VSyncCommonRange"); - reporter.AddResult(".speedup", speedup); -} - -} // namespace frame_metrics -} // namespace ui diff --git a/chromium/ui/latency/histograms_unittest.cc b/chromium/ui/latency/histograms_unittest.cc deleted file mode 100644 index c19eb4b3e28..00000000000 --- a/chromium/ui/latency/histograms_unittest.cc +++ /dev/null @@ -1,177 +0,0 @@ -// 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/histograms.h" - -#include <algorithm> - -#include "base/metrics/bucket_ranges.h" -#include "base/metrics/sample_vector.h" -#include "base/time/time.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "ui/latency/fixed_point.h" -#include "ui/latency/frame_metrics_test_common.h" - -namespace ui { -namespace frame_metrics { - -// Verifies the ratio boundaries generated internally match the reference -// boundaries. -TEST(FrameMetricsHistogramsTest, RatioBoundariesDirect) { - const TestRatioBoundaries kTestRatioBoundaries; - std::unique_ptr<BoundaryIterator> ratio_impl = - CreateRatioIteratorForTesting(); - for (uint32_t boundary : kTestRatioBoundaries.boundaries) { - if (boundary == 0) - continue; - EXPECT_EQ(boundary, ratio_impl->Next()); - } -} - -// Verifies the VSync boundaries generated internally match the reference -// boundaries. -TEST(FrameMetricsHistogramsTest, VSyncBoundariesDirect) { - std::unique_ptr<BoundaryIterator> vsync_impl = - CreateVSyncIteratorForTesting(); - for (uint32_t boundary : kTestVSyncBoundries) { - if (boundary == 0) - continue; - EXPECT_EQ(boundary, vsync_impl->Next()); - } -} - -// 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> -void BoundaryTestCommon(const ReferenceBoundaryT& reference_boundaries, - std::unique_ptr<Histogram> histogram) { - PercentileResults percentiles; - - for (size_t i = 0; i < reference_boundaries.size() - 1; i++) { - uint64_t bucket_start = reference_boundaries[i]; - uint64_t bucket_end = reference_boundaries[i + 1]; - - // Verify values within the current bucket don't affect percentile. - // This also checks the first value in the bucket. - uint32_t stride = std::max<uint32_t>(1u, (bucket_end - bucket_start) / 8); - for (uint64_t value = bucket_start; value < bucket_end; value += stride) { - histogram->AddSample(value, 1); - percentiles = histogram->ComputePercentiles(); - histogram->Reset(); - EXPECT_LE(bucket_start, percentiles.values[0]); - EXPECT_GT(bucket_end, percentiles.values[0]); - } - - // Verify the value just before the next bucket doesn't affect percentile. - histogram->AddSample(bucket_end - 1, 1); - percentiles = histogram->ComputePercentiles(); - histogram->Reset(); - EXPECT_LE(bucket_start, percentiles.values[0]); - EXPECT_GT(bucket_end, percentiles.values[0]); - } -} - -TEST(FrameMetricsHistogramsTest, RatioBoundaries) { - const TestRatioBoundaries kTestRatioBoundaries; - BoundaryTestCommon(kTestRatioBoundaries, std::make_unique<RatioHistogram>()); -} - -TEST(FrameMetricsHistogramsTest, VSyncBoundaries) { - const TestRatioBoundaries kTestRatioBoundaries; - BoundaryTestCommon(kTestVSyncBoundries, std::make_unique<VSyncHistogram>()); -} - -template <typename ReferenceBoundaryT> -void PercentilesTestCommon(const ReferenceBoundaryT& reference_boundaries, - std::unique_ptr<Histogram> histogram, - int percentile_index) { - double percentile = PercentileResults::kPercentiles[percentile_index]; - PercentileResults percentiles; - for (size_t i = 0; i < reference_boundaries.size() - 1; i++) { - uint64_t bucket_start = reference_boundaries[i]; - uint64_t bucket_end = reference_boundaries[i + 1]; - - // Add samples to current bucket. - // Where the samples are added in the current bucket should not affect the - // result. - uint32_t stride = std::max<uint32_t>(1u, (bucket_end - bucket_start) / 100); - int samples_added_inside = 0; - for (uint64_t value = bucket_start; value < bucket_end; value += stride) { - histogram->AddSample(value, 10); - samples_added_inside += 10; - } - - // Add samples to left and right of current bucket. - // Don't worry about doing this for the left most and right most buckets. - int samples_added_left = 0; - int samples_added_outside = 0; - if (i != 0 && i < reference_boundaries.size() - 2) { - samples_added_outside = 10000; - samples_added_left = samples_added_outside * percentile; - histogram->AddSample(bucket_start / 3, samples_added_left); - histogram->AddSample(bucket_start * 3, - samples_added_outside - samples_added_left); - } - - percentiles = histogram->ComputePercentiles(); - histogram->Reset(); - - double index = (samples_added_inside + samples_added_outside) * percentile - - samples_added_left; - double w = index / samples_added_inside; - double expected_value = bucket_end * w + bucket_start * (1.0 - w); - EXPECT_DOUBLE_EQ(expected_value, percentiles.values[percentile_index]); - } -} - -TEST(FrameMetricsHistogramsTest, RatioPercentiles50th) { - const TestRatioBoundaries kTestRatioBoundaries; - PercentilesTestCommon(kTestRatioBoundaries, - std::make_unique<RatioHistogram>(), 0); -} - -TEST(FrameMetricsHistogramsTest, RatioPercentiles99th) { - const TestRatioBoundaries kTestRatioBoundaries; - PercentilesTestCommon(kTestRatioBoundaries, - std::make_unique<RatioHistogram>(), 1); -} - -TEST(FrameMetricsHistogramsTest, VSyncPercentiles50th) { - const TestRatioBoundaries kTestRatioBoundaries; - PercentilesTestCommon(kTestVSyncBoundries, std::make_unique<VSyncHistogram>(), - 0); -} - -TEST(FrameMetricsHistogramsTest, VSyncPercentiles99th) { - const TestRatioBoundaries kTestRatioBoundaries; - PercentilesTestCommon(kTestVSyncBoundries, std::make_unique<VSyncHistogram>(), - 1); -} - -} // namespace frame_metrics -} // namespace ui diff --git a/chromium/ui/latency/ipc/BUILD.gn b/chromium/ui/latency/ipc/BUILD.gn index 605a6380d66..19d5e4e5ce9 100644 --- a/chromium/ui/latency/ipc/BUILD.gn +++ b/chromium/ui/latency/ipc/BUILD.gn @@ -11,9 +11,7 @@ source_set("ipc") { "latency_info_param_traits_macros.h", ] - public_deps = [ - "//ui/latency", - ] + public_deps = [ "//ui/latency" ] deps = [ "//base", diff --git a/chromium/ui/latency/ipc/latency_info_param_traits.cc b/chromium/ui/latency/ipc/latency_info_param_traits.cc index d963612aa00..4bfa9f84638 100644 --- a/chromium/ui/latency/ipc/latency_info_param_traits.cc +++ b/chromium/ui/latency/ipc/latency_info_param_traits.cc @@ -32,7 +32,6 @@ namespace IPC { namespace IPC { void ParamTraits<ui::LatencyInfo>::Write(base::Pickle* m, const param_type& p) { - WriteParam(m, p.trace_name_); WriteParam(m, p.latency_components_); WriteParam(m, p.trace_id_); WriteParam(m, p.ukm_source_id_); @@ -47,8 +46,6 @@ void ParamTraits<ui::LatencyInfo>::Write(base::Pickle* m, const param_type& p) { bool ParamTraits<ui::LatencyInfo>::Read(const base::Pickle* m, base::PickleIterator* iter, param_type* p) { - if (!ReadParam(m, iter, &p->trace_name_)) - return false; if (!ReadParam(m, iter, &p->latency_components_)) return false; @@ -73,8 +70,6 @@ bool ParamTraits<ui::LatencyInfo>::Read(const base::Pickle* m, } void ParamTraits<ui::LatencyInfo>::Log(const param_type& p, std::string* l) { - LogParam(p.trace_name_, l); - l->append(" "); LogParam(p.latency_components_, l); l->append(" "); LogParam(p.trace_id_, l); diff --git a/chromium/ui/latency/latency_info.cc b/chromium/ui/latency/latency_info.cc index aa2633f5fb1..540a91ab19f 100644 --- a/chromium/ui/latency/latency_info.cc +++ b/chromium/ui/latency/latency_info.cc @@ -15,9 +15,14 @@ #include "base/macros.h" #include "base/strings/stringprintf.h" #include "base/trace_event/trace_event.h" +#include "services/tracing/public/cpp/perfetto/flow_event_utils.h" +#include "services/tracing/public/cpp/perfetto/macros.h" namespace { +using perfetto::protos::pbzero::ChromeLatencyInfo; +using perfetto::protos::pbzero::TrackEvent; + const size_t kMaxLatencyInfoNumber = 100; const char* GetComponentName(ui::LatencyComponentType type) { @@ -31,7 +36,6 @@ const char* GetComponentName(ui::LatencyComponentType type) { CASE_TYPE(INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_MAIN_COMPONENT); CASE_TYPE(INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_IMPL_COMPONENT); CASE_TYPE(INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT_COMPONENT); - CASE_TYPE(INPUT_EVENT_LATENCY_ACK_RWH_COMPONENT); CASE_TYPE(INPUT_EVENT_LATENCY_RENDERER_MAIN_COMPONENT); CASE_TYPE(INPUT_EVENT_LATENCY_RENDERER_SWAP_COMPONENT); CASE_TYPE(DISPLAY_COMPOSITOR_RECEIVED_FRAME_COMPONENT); @@ -148,14 +152,20 @@ bool LatencyInfo::Verify(const std::vector<LatencyInfo>& latency_info, void LatencyInfo::TraceIntermediateFlowEvents( const std::vector<LatencyInfo>& latency_info, - const char* event_name) { + perfetto::protos::pbzero::ChromeLatencyInfo::Step step) { for (auto& latency : latency_info) { if (latency.trace_id() == -1) continue; - TRACE_EVENT_WITH_FLOW1("input,benchmark", "LatencyInfo.Flow", - TRACE_ID_DONT_MANGLE(latency.trace_id()), - TRACE_EVENT_FLAG_FLOW_IN | TRACE_EVENT_FLAG_FLOW_OUT, - "step", event_name); + + TRACE_EVENT( + "input,benchmark", "LatencyInfo.Flow", + [&latency, &step](perfetto::EventContext ctx) { + ChromeLatencyInfo* info = ctx.event()->set_chrome_latency_info(); + info->set_step(step); + info->set_trace_id(latency.trace_id()); + tracing::FillFlowEvent(ctx, TrackEvent::LegacyEvent::FLOW_INOUT, + latency.trace_id()); + }); } } @@ -255,22 +265,19 @@ void LatencyInfo::AddLatencyNumberWithTimestampImpl( ts = base::TimeTicks::Now(); } - if (trace_name_str) { - trace_name_ = std::string("InputLatency::") + trace_name_str; - } - - TRACE_EVENT_COPY_ASYNC_BEGIN_WITH_TIMESTAMP0( - kTraceCategoriesForAsyncEvents, - trace_name_.c_str(), - TRACE_ID_DONT_MANGLE(trace_id_), - ts); + TRACE_EVENT_NESTABLE_ASYNC_BEGIN_WITH_TIMESTAMP0( + kTraceCategoriesForAsyncEvents, trace_name_str, + TRACE_ID_GLOBAL(trace_id_), ts); } - TRACE_EVENT_WITH_FLOW1("input,benchmark", - "LatencyInfo.Flow", - TRACE_ID_DONT_MANGLE(trace_id_), - TRACE_EVENT_FLAG_FLOW_OUT, - "trace_id", trace_id_); + TRACE_EVENT("input,benchmark", "LatencyInfo.Flow", + [this](perfetto::EventContext ctx) { + ChromeLatencyInfo* info = + ctx.event()->set_chrome_latency_info(); + info->set_trace_id(trace_id_); + tracing::FillFlowEvent(ctx, TrackEvent::LegacyEvent::FLOW_OUT, + trace_id_); + }); } auto it = latency_components_.find(component); @@ -290,14 +297,25 @@ void LatencyInfo::Terminate() { terminated_ = true; if (*g_latency_info_enabled.Get().latency_info_enabled) { - TRACE_EVENT_COPY_ASYNC_END1( - kTraceCategoriesForAsyncEvents, trace_name_.c_str(), - TRACE_ID_DONT_MANGLE(trace_id_), "data", AsTraceableData()); + // The name field is not needed for NESTABLE events because we only need the + // category to know which event to close. In fact the name will not be + // emitted internally. + // + // TODO(nuskos): Once we have the new TraceEvent macros that support Tracks + // we can migrate this macro to it (and the name will no longer be there). + TRACE_EVENT_NESTABLE_ASYNC_END1(kTraceCategoriesForAsyncEvents, + /* name = */ "", TRACE_ID_GLOBAL(trace_id_), + "data", AsTraceableData()); } - TRACE_EVENT_WITH_FLOW0("input,benchmark", "LatencyInfo.Flow", - TRACE_ID_DONT_MANGLE(trace_id_), - TRACE_EVENT_FLAG_FLOW_IN); + TRACE_EVENT("input,benchmark", "LatencyInfo.Flow", + [this](perfetto::EventContext ctx) { + ChromeLatencyInfo* info = + ctx.event()->set_chrome_latency_info(); + info->set_trace_id(trace_id_); + tracing::FillFlowEvent(ctx, TrackEvent::LegacyEvent::FLOW_IN, + trace_id_); + }); } void LatencyInfo::CoalesceScrollUpdateWith(const LatencyInfo& other) { diff --git a/chromium/ui/latency/latency_info.dot b/chromium/ui/latency/latency_info.dot index 673e0cbf02c..3490ac1a989 100644 --- a/chromium/ui/latency/latency_info.dot +++ b/chromium/ui/latency/latency_info.dot @@ -1,40 +1,108 @@ -# dot -Tpdf ui/latency/latency_info.dot > latency_info.pdf +// dot -Tpdf ui/latency/latency_info.dot > latency_info.pdf -digraph g { - node [shape=box]; +digraph LatencyInfo { + node[shape=box]; - INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT -> - INPUT_EVENT_LATENCY_UI_COMPONENT [label="Event.Latency.OS.*"] - INPUT_EVENT_LATENCY_UI_COMPONENT -> INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT [label="Event.Latency.Browser.INPUT_MODALITYUI"]; + // Set shape and label of metric names. + { + node[style="dotted,rounded"]; - INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT -> INPUT_EVENT_LATENCY_ACK_RWH_COMPONENT [label="Event.Latency.Browser.INPUT_MODALITYAcked"]; + "Event.Latency.EventToRender.TouchpadPinch"; + "Event.Latency.QueueingTime.<event_name><default_action_status>"; + "Event.Latency.BlockingTime.<event_name><default_action_status>"; + end_to_end_metrics + [label="\ +Event.Latency.EndToEnd.KeyPress\n\ +Event.Latency.EndToEnd.Mouse\n\ +Event.Latency.EndToEnd.TouchpadPinch\n"]; + scroll_to_gpu_swap_metrics + [label="\ +Event.Latency.<scroll_name>.TimeToScrollUpdateSwapBegin2\n\ +Event.Latency.<scroll_name>.<input_modality>.TimeToScrollUpdateSwapBegin4\n\ +Event.Latency.Scroll.Wheel.TimeToScrollUpdateSwapBegin2\n\ +UKM: Event.<scroll_name>.<input_modality>.TimeToScrollUpdateSwapBegin\n"] + scroll_to_schedule_metrics + [label="\ +Event.Latency.<scroll_name>.<input_modality>.TimeToHandled2_<thread_name>\n\ +Event.Latency.Scroll.Wheel.TimeToHandled2_<thread_name>\n\ +UKM: Event.<scroll_name>.<input_modality>.TimeToHandled\n"]; + "Event.Latency.<scroll_name>.<input_modality>.HandledToRendererSwap2_<thread_name>"; + "Event.Latency.<scroll_name>.<input_modality>.RendererSwapToBrowserNotified2"; + "Event.Latency.<scroll_name>.<input_modality>.BrowserNotifiedToBeforeGpuSwap2"; + "Event.Latency.<scroll_name>.Touch.EventTimeToRAFTime"; + "Event.Latency.<scroll_name>.Touch.RAFTimeToFrameSwapEnd"; + "Event.Latency.<scroll_name>.<input_modality>.GpuSwap2"; + } - INPUT_EVENT_LATENCY_ACK_RWH_COMPONENT -> INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_COMPONENT; + // Set labels for nodes with multiple components. + scroll_original + [label="\ +INPUT_EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT\n\ +INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT"]; + rendering_scheduled + [label="\ +INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_MAIN_COMPONENT\n\ +INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_IMPL_COMPONENT"]; - INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_COMPONENT -> INPUT_EVENT_LATENCY_RENDERER_SWAP_COMPONENT [label="Event.Latency.SCROLL.INPUT_MODALITY.HandledToRendererSwap2_THREAD"]; + // Layout "original" components at the top. + { + rank=same; + edge[style=dotted,minlen=8]; + INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT-> + scroll_original-> + INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT_COMPONENT; + } - INPUT_EVENT_LATENCY_RENDERER_SWAP_COMPONENT -> INPUT_EVENT_BROWSER_RECEIVED_RENDERER_SWAP_COMPONENT [label="Event.Latency.SCROLL.INPUT_MODALITY.RendererSwapToBrowserNotified2"]; - INPUT_EVENT_BROWSER_RECEIVED_RENDERER_SWAP_COMPONENT -> INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT [label="Event.Latency.SCROLL.INPUT_MODALITY.BrowserNotifiedToBeforeGpuSwap2"]; - INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT -> INPUT_EVENT_LATENCY_TERMINATED_FRAME_SWAP_COMPONENT [label="Event.Latency.SCROLL.INPUT_MODALITY.GpuSwap2"]; + // Layout the rest of the components. + INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT-> + "Event.Latency.EventToRender.TouchpadPinch"-> + INPUT_EVENT_LATENCY_BEGIN_RWH_COMPONENT-> + "Event.Latency.QueueingTime.<event_name><default_action_status>"-> + INPUT_EVENT_LATENCY_RENDERER_MAIN_COMPONENT-> + "Event.Latency.BlockingTime.<event_name><default_action_status>"-> + INPUT_EVENT_LATENCY_ACK_RWH_COMPONENT + [weight=3]; - edge[style="dashed"]; - INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT -> INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT [label="Event.Latency.SCROLL.INPUT_MODALITY.TimeToScrollUpdateSwapBegin2"]; + INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT-> + end_to_end_metrics-> + INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT; - INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT -> INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_COMPONENT [label="Event.Latency.SCROLL.INPUT_MODALITY.TimeToHandled2_THREAD"]; + scroll_original-> + scroll_to_gpu_swap_metrics-> + INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT; - INPUT_EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT[label="\ -EVENT_LATENCY_SCROLL_UPDATE_ORIGINAL_COMPONENT\l\ -EVENT_LATENCY_FIRST_SCROLL_UPDATE_ORIGINAL_COMPONENT\l\ -INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT \l"]; - INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_COMPONENT[label="INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_(MAIN | IMPL)_COMPONENT"]; + scroll_original-> + scroll_to_schedule_metrics-> + rendering_scheduled; + INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT_COMPONENT-> + "Event.Latency.<scroll_name>.Touch.EventTimeToRAFTime"-> + rendering_scheduled; - subgraph cluster_01 { - style=invis; - node [shape=plaintext]; - key [label="\ -INPUT_MODALITY = (Wheel | Touch\l\ -THREAD = (Main | Impl)\l\ -SCROLL = (ScrollBegin | ScrollUpdate)\l"] - } + rendering_scheduled-> + "Event.Latency.<scroll_name>.<input_modality>.HandledToRendererSwap2_<thread_name>"-> + INPUT_EVENT_LATENCY_RENDERER_SWAP_COMPONENT-> + "Event.Latency.<scroll_name>.<input_modality>.RendererSwapToBrowserNotified2"-> + DISPLAY_COMPOSITOR_RECEIVED_FRAME_COMPONENT-> + "Event.Latency.<scroll_name>.<input_modality>.BrowserNotifiedToBeforeGpuSwap2"-> + INPUT_EVENT_GPU_SWAP_BUFFER_COMPONENT-> + "Event.Latency.<scroll_name>.<input_modality>.GpuSwap2"-> + INPUT_EVENT_LATENCY_FRAME_SWAP_COMPONENT + [weight=4]; + + rendering_scheduled-> + "Event.Latency.<scroll_name>.Touch.RAFTimeToFrameSwapEnd"-> + INPUT_EVENT_LATENCY_FRAME_SWAP_COMPONENT; + + // Add legend and position it under INPUT_EVENT_LATENCY_ACK_RWH_COMPONENT. + legend + [shape=plaintext,label="\ +LEGEND:\l\ + <default_action_status> = (DefaultPrevented | DefaultAllowed)\l\ + <input_modality> = (Wheel | Touch)\l\ + <scroll_name> = (ScrollBegin | ScrollUpdate)\l\ + <thread_name> = (Main | Impl)\l"]; + INPUT_EVENT_LATENCY_ACK_RWH_COMPONENT-> + legend + [style=invis,minlen=3]; } diff --git a/chromium/ui/latency/latency_info.h b/chromium/ui/latency/latency_info.h index 997bbae74d5..1c8b1926362 100644 --- a/chromium/ui/latency/latency_info.h +++ b/chromium/ui/latency/latency_info.h @@ -16,6 +16,7 @@ #include "base/containers/flat_map.h" #include "base/time/time.h" #include "services/metrics/public/cpp/ukm_source_id.h" +#include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_latency_info.pbzero.h" #include "ui/gfx/geometry/point_f.h" #if !defined(OS_IOS) @@ -39,6 +40,11 @@ class LatencyInfoDataView; // When adding new components, or new metrics based on LatencyInfo, // please update latency_info.dot. +// +// When adding new components, please update +// //third_party/perfetto/protos/perfetto/trace/track_event/chrome_latency_info.proto +// so both this and the internal versions can be kept up to date. Or reach out +// to tracing@chromium.org so we can assist. enum LatencyComponentType { // ---------------------------BEGIN COMPONENT------------------------------- // BEGIN COMPONENT is when we show the latency begin in chrome://tracing. @@ -64,8 +70,6 @@ enum LatencyComponentType { INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_IMPL_COMPONENT, // Original timestamp of the last event that has been coalesced into this one. INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT_COMPONENT, - // Timestamp when the event's ack is received by the RWH. - INPUT_EVENT_LATENCY_ACK_RWH_COMPONENT, // Timestamp when the frame is swapped in renderer. INPUT_EVENT_LATENCY_RENDERER_SWAP_COMPONENT, // Timestamp of when the display compositor receives a compositor frame from @@ -128,7 +132,7 @@ class LatencyInfo { // Adds trace flow events only to LatencyInfos that are being traced. static void TraceIntermediateFlowEvents( const std::vector<LatencyInfo>& latency_info, - const char* trace_name); + perfetto::protos::pbzero::ChromeLatencyInfo::Step step); // Copy timestamp with type |type| from |other| into |this|. void CopyLatencyFrom(const LatencyInfo& other, LatencyComponentType type); @@ -183,7 +187,6 @@ class LatencyInfo { void set_trace_id(int64_t trace_id) { trace_id_ = trace_id; } ukm::SourceId ukm_source_id() const { return ukm_source_id_; } void set_ukm_source_id(ukm::SourceId id) { ukm_source_id_ = id; } - const std::string& trace_name() const { return trace_name_; } void set_scroll_update_delta(float delta) { scroll_update_delta_ = delta; } float scroll_update_delta() const { return scroll_update_delta_; } void set_predicted_scroll_update_delta(float delta) { @@ -202,10 +205,6 @@ class LatencyInfo { std::unique_ptr<base::trace_event::ConvertableToTraceFormat> AsTraceableData(); - // Shown as part of the name of the trace event for this LatencyInfo. - // String is empty if no tracing is enabled. - std::string trace_name_; - LatencyMap latency_components_; // The unique id for matching the ASYNC_BEGIN/END trace event. diff --git a/chromium/ui/latency/mojom/BUILD.gn b/chromium/ui/latency/mojom/BUILD.gn index 5322cf97c88..607bc5e2346 100644 --- a/chromium/ui/latency/mojom/BUILD.gn +++ b/chromium/ui/latency/mojom/BUILD.gn @@ -6,22 +6,14 @@ import("//mojo/public/tools/bindings/mojom.gni") mojom("mojom") { generate_java = true - sources = [ - "latency_info.mojom", - ] + sources = [ "latency_info.mojom" ] - public_deps = [ - "//mojo/public/mojom/base", - ] + public_deps = [ "//mojo/public/mojom/base" ] } mojom("test_interfaces") { testonly = true - sources = [ - "traits_test_service.mojom", - ] + sources = [ "traits_test_service.mojom" ] - public_deps = [ - ":mojom", - ] + public_deps = [ ":mojom" ] } diff --git a/chromium/ui/latency/mojom/latency_info.mojom b/chromium/ui/latency/mojom/latency_info.mojom index 3937eaaf951..b4d84409ec1 100644 --- a/chromium/ui/latency/mojom/latency_info.mojom +++ b/chromium/ui/latency/mojom/latency_info.mojom @@ -31,8 +31,6 @@ enum LatencyComponentType { INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_IMPL_COMPONENT, // Timestamp for last event that has been coalesced into this one. INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT_COMPONENT, - // Timestamp when the event's ack is received by the RWH. - INPUT_EVENT_LATENCY_ACK_RWH_COMPONENT, // Timestamp when the frame is swapped in renderer. INPUT_EVENT_LATENCY_RENDERER_SWAP_COMPONENT, // Timestamp of when the display compositor receives a compositor frame. @@ -60,7 +58,6 @@ enum SourceEventType { // See ui/latency/latency_info.h struct LatencyInfo { - string trace_name; map<LatencyComponentType, mojo_base.mojom.TimeTicks> latency_components; int64 trace_id; int64 ukm_source_id; diff --git a/chromium/ui/latency/mojom/latency_info_mojom_traits.cc b/chromium/ui/latency/mojom/latency_info_mojom_traits.cc index 6163e60a8dc..11fc916fd6e 100644 --- a/chromium/ui/latency/mojom/latency_info_mojom_traits.cc +++ b/chromium/ui/latency/mojom/latency_info_mojom_traits.cc @@ -63,13 +63,6 @@ ui::SourceEventType MojoSourceEventTypeToUI(ui::mojom::SourceEventType type) { } // namespace // static -const std::string& -StructTraits<ui::mojom::LatencyInfoDataView, ui::LatencyInfo>::trace_name( - const ui::LatencyInfo& info) { - return info.trace_name_; -} - -// static const ui::LatencyInfo::LatencyMap& StructTraits<ui::mojom::LatencyInfoDataView, ui::LatencyInfo>::latency_components(const ui::LatencyInfo& info) { @@ -131,8 +124,6 @@ float StructTraits<ui::mojom::LatencyInfoDataView, ui::LatencyInfo>:: bool StructTraits<ui::mojom::LatencyInfoDataView, ui::LatencyInfo>::Read( ui::mojom::LatencyInfoDataView data, ui::LatencyInfo* out) { - if (!data.ReadTraceName(&out->trace_name_)) - return false; if (!data.ReadLatencyComponents(&out->latency_components_)) return false; out->trace_id_ = data.trace_id(); @@ -178,9 +169,6 @@ EnumTraits<ui::mojom::LatencyComponentType, ui::LatencyComponentType>::ToMojom( case ui::INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT_COMPONENT: return ui::mojom::LatencyComponentType:: INPUT_EVENT_LATENCY_SCROLL_UPDATE_LAST_EVENT_COMPONENT; - case ui::INPUT_EVENT_LATENCY_ACK_RWH_COMPONENT: - return ui::mojom::LatencyComponentType:: - INPUT_EVENT_LATENCY_ACK_RWH_COMPONENT; case ui::INPUT_EVENT_LATENCY_RENDERER_SWAP_COMPONENT: return ui::mojom::LatencyComponentType:: INPUT_EVENT_LATENCY_RENDERER_SWAP_COMPONENT; @@ -234,9 +222,6 @@ bool EnumTraits<ui::mojom::LatencyComponentType, ui::LatencyComponentType>:: INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_IMPL_COMPONENT: *output = ui::INPUT_EVENT_LATENCY_RENDERING_SCHEDULED_IMPL_COMPONENT; return true; - case ui::mojom::LatencyComponentType::INPUT_EVENT_LATENCY_ACK_RWH_COMPONENT: - *output = ui::INPUT_EVENT_LATENCY_ACK_RWH_COMPONENT; - return true; case ui::mojom::LatencyComponentType:: INPUT_EVENT_LATENCY_RENDERER_SWAP_COMPONENT: *output = ui::INPUT_EVENT_LATENCY_RENDERER_SWAP_COMPONENT; diff --git a/chromium/ui/latency/mojom/latency_info_mojom_traits.h b/chromium/ui/latency/mojom/latency_info_mojom_traits.h index 3bfa5a7d1d4..ee88a55718b 100644 --- a/chromium/ui/latency/mojom/latency_info_mojom_traits.h +++ b/chromium/ui/latency/mojom/latency_info_mojom_traits.h @@ -44,7 +44,6 @@ struct ArrayTraits<ui::LatencyInfo::LatencyMap> { template <> struct StructTraits<ui::mojom::LatencyInfoDataView, ui::LatencyInfo> { - static const std::string& trace_name(const ui::LatencyInfo& info); static const ui::LatencyInfo::LatencyMap& latency_components( const ui::LatencyInfo& info); static int64_t trace_id(const ui::LatencyInfo& info); diff --git a/chromium/ui/latency/skipped_frame_tracker.cc b/chromium/ui/latency/skipped_frame_tracker.cc deleted file mode 100644 index fe5fc7a1737..00000000000 --- a/chromium/ui/latency/skipped_frame_tracker.cc +++ /dev/null @@ -1,104 +0,0 @@ -// 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/skipped_frame_tracker.h" - -#include <cmath> -#include "ui/latency/frame_metrics.h" - -namespace ui { - -SkippedFrameTracker::SkippedFrameTracker(Client* client) : client_(client) {} - -void SkippedFrameTracker::BeginFrame(base::TimeTicks frame_time, - base::TimeDelta interval) { - DCHECK(!inside_begin_frame_); - inside_begin_frame_ = true; - did_produce_this_frame_ = false; - frame_time_ = frame_time; - interval_ = interval; - - // On our first frame of activity, we may need to initialize - // will_produce_frame_time_. - if (active_state_ == ActiveState::WillProduceFirst && - will_produce_frame_time_.is_null()) { - will_produce_frame_time_ = frame_time_; - } -} - -void SkippedFrameTracker::FinishFrame() { - inside_begin_frame_ = false; - - // Assume the source is idle if it hasn't attempted to produce for an entire - // BeginFrame. - if (!did_produce_this_frame_ && active_state_ == ActiveState::WasActive) { - will_produce_frame_time_ = base::TimeTicks(); - active_state_ = ActiveState::Idle; - } -} - -void SkippedFrameTracker::WillProduceFrame() { - // Make sure we don't transition out of the WillProduceFirst state until - // we've actually produced the first frame. - if (active_state_ == ActiveState::WillProduceFirst) - return; - - // This is our first frame of activity. - if (active_state_ == ActiveState::Idle) { - active_state_ = ActiveState::WillProduceFirst; - // If we're already inside a BeginFrame when we first become active, - // we can initialize will_produce_frame_time_. - if (inside_begin_frame_) - will_produce_frame_time_ = frame_time_; - return; - } - - active_state_ = ActiveState::WillProduce; -} - -void SkippedFrameTracker::DidProduceFrame() { - // Ignore duplicate calls to DidProduceFrame. - if (did_produce_this_frame_) - return; - - // Return early if frame was pulled by sink. - bool frame_was_pushed_by_source = - (active_state_ == ActiveState::WillProduceFirst && - !will_produce_frame_time_.is_null()) || - active_state_ == ActiveState::WillProduce; - if (!frame_was_pushed_by_source) - return; - - DCHECK(!will_produce_frame_time_.is_null()); - - // Clamp the amount of time skipped to a positive value, since negative - // values aren't meaningful. - base::TimeDelta skipped_clamped = - std::max(base::TimeDelta(), (frame_time_ - will_produce_frame_time_)); - - // Snap the amount of time skipped to whole intervals in order to filter - // out jitter in the timing received by the BeginFrame source. - int skipped_intervals = (skipped_clamped + (interval_ / 2)) / interval_; - base::TimeDelta skipped_snapped = skipped_intervals * interval_; - - DCHECK_GE(skipped_snapped, base::TimeDelta()); - client_->AddFrameProduced(frame_time_, interval_, skipped_snapped); - - // Predict the next BeginFrame's frame time, so we can detect if it gets - // dropped. - will_produce_frame_time_ = frame_time_ + interval_; - active_state_ = ActiveState::WasActive; - did_produce_this_frame_ = true; -} - -void SkippedFrameTracker::WillNotProduceFrame() { - if (active_state_ != ActiveState::Idle) { - inside_begin_frame_ = false; - did_produce_this_frame_ = false; - will_produce_frame_time_ = base::TimeTicks(); - active_state_ = ActiveState::Idle; - } -} - -} // namespace ui diff --git a/chromium/ui/latency/skipped_frame_tracker.h b/chromium/ui/latency/skipped_frame_tracker.h deleted file mode 100644 index 1ee0d0d3dc3..00000000000 --- a/chromium/ui/latency/skipped_frame_tracker.h +++ /dev/null @@ -1,88 +0,0 @@ -// 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_SKIPPED_FRAME_TRACKER_H_ -#define UI_LATENCY_SKIPPED_FRAME_TRACKER_H_ - -#include "base/macros.h" -#include "base/time/time.h" - -namespace ui { - -// SkippedFrameTracker tracks skipped BeginFrames. It can be used by sources -// attempting to produce at the display rate. It properly handles -// non-consecutive BeginFrames and tracks when the source is actualy trying to -// produce, rather than passively receiving BeginFrames. -class SkippedFrameTracker { - public: - // SkippedFrameTracker calls Client::AddFrameProduced from FinishFrame - // when necessary and with the correct values. - class Client { - public: - virtual ~Client() = default; - virtual void AddFrameProduced(base::TimeTicks source_timestamp, - base::TimeDelta amount_produced, - base::TimeDelta amount_skipped) = 0; - }; - - // SkippedFrameTracker will call |client|->AddFrameProduced - // with the appropriate info automatically as frames are produced. - explicit SkippedFrameTracker(Client* client); - - // BeginFrame and FinishFrame must be called for each BeginFrame received. - // In order for this class to detect idle periods properly, the source must - // call Begin+FinishFrame without calling WillProduceFrame before going - // idle. This is necessary since there is otherwise no way to tell if a - // non-consecutive BeginFrame occured a) because we were slow or b) because - // we weren't trying to produce a frame. - void BeginFrame(base::TimeTicks frame_time, base::TimeDelta interval); - void FinishFrame(); - - // WillProduceFrame should be called when the source knows it wants to - // produce a frame. DidProduceFrame should be called when the source has - // actually submitted the frame. WillNotProduceFrame should be called when - // the source knows if doesn't need a new frame. - // It is okay for DidProduceFrame to be called without WillProduceFrame, - // which can happen in cases where a frame is "pulled" from later in the - // pipeline rather than pushed from the source. Such calls to DidProduceFrame - // will be ignored. - void WillProduceFrame(); - void DidProduceFrame(); - void WillNotProduceFrame(); - - protected: - Client* client_; - - bool inside_begin_frame_ = false; - base::TimeTicks frame_time_; - base::TimeDelta interval_; - bool did_produce_this_frame_ = false; - - base::TimeTicks will_produce_frame_time_; - - enum class ActiveState { - // Idle: The initial and idle state. - // Goto WillProduceFirst on 1st call to WillProduceFrame. - Idle, - // WillProduceFirst: Producing the first frame out of idle. - // Goto WasActive on first FinishFrame after a DidProduceFrame. - // Counts missing BeginFrames as skipped: NO. - WillProduceFirst, - // WillProduce: Producing the (N > 1)'th frame of constant activity. - // Goto WasActive on first FinishFrame after a DidProduceFrame. - // Counts missing BeginFrames as skipped: YES. - WillProduce, - // WasActive: An intermediate state to determine if we are idle or not. - // Goto WillProduce on WillProduceFrame. - // Otherwise, goto Idle on next FinishFrame. - WasActive, - }; - ActiveState active_state_ = ActiveState::Idle; - - DISALLOW_COPY_AND_ASSIGN(SkippedFrameTracker); -}; - -} // namespace ui - -#endif // UI_LATENCY_SKIPPED_FRAME_TRACKER_H_ diff --git a/chromium/ui/latency/skipped_frame_tracker_unittest.cc b/chromium/ui/latency/skipped_frame_tracker_unittest.cc deleted file mode 100644 index 06812c5c222..00000000000 --- a/chromium/ui/latency/skipped_frame_tracker_unittest.cc +++ /dev/null @@ -1,410 +0,0 @@ -// 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/skipped_frame_tracker.h" - -#include "base/bind.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace ui { -namespace { - -// TestClient observes calls to AddFrameProduced so tests can verify -// those calls. -class TestClient : public SkippedFrameTracker::Client { - public: - TestClient() = default; - ~TestClient() override = default; - - void AddFrameProduced(base::TimeTicks source_timestamp, - base::TimeDelta amount_produced, - base::TimeDelta amount_skipped) override { - source_timestamp_ = source_timestamp.since_origin().InMicroseconds(); - amount_produced_ = amount_produced.InMicroseconds(); - amount_skipped_ = amount_skipped.InMicroseconds(); - call_count_++; - } - - int GetAndResetCallCount() { - int result = call_count_; - call_count_ = 0; - return result; - } - - int call_count_ = 0; - int source_timestamp_ = 0; - int amount_produced_ = 0; - int amount_skipped_ = 0; -}; - -// TestSkippedFrameTracker let's us verify the active state from tests. -class TestSkippedFrameTracker : public SkippedFrameTracker { - public: - TestSkippedFrameTracker(Client* client) : SkippedFrameTracker(client) {} - - bool IsActive() { - switch (active_state_) { - case ActiveState::Idle: - return false; - case ActiveState::WillProduce: - case ActiveState::WillProduceFirst: - case ActiveState::WasActive: - break; - } - return true; - } -}; - -// SkippedFrameTrackerTest is the test fixture used by all tests in this file. -class SkippedFrameTrackerTest : public testing::Test { - public: - SkippedFrameTrackerTest() : tracker_(&client_) {} - - ::testing::AssertionResult BeginFrame(int timestamp, int interval) { - int call_count = client_.call_count_; - tracker_.BeginFrame( - base::TimeTicks() + base::TimeDelta::FromMicroseconds(timestamp), - base::TimeDelta::FromMicroseconds(interval)); - return MaybeCallCountFailure(call_count); - } - - ::testing::AssertionResult FinishFrame() { - int call_count = client_.call_count_; - tracker_.FinishFrame(); - return MaybeCallCountFailure(call_count); - } - - ::testing::AssertionResult WillNotProduceFrame() { - int call_count = client_.call_count_; - tracker_.WillNotProduceFrame(); - return MaybeCallCountFailure(call_count); - } - - ::testing::AssertionResult WillProduceFrame() { - int call_count = client_.call_count_; - tracker_.WillProduceFrame(); - return MaybeCallCountFailure(call_count); - } - - ::testing::AssertionResult DidProduceFrame() { - int call_count = client_.call_count_; - tracker_.DidProduceFrame(); - return MaybeCallCountFailure(call_count); - } - - protected: - static ::testing::AssertionResult MaybeCallCountFailure(int count) { - if (count == 0) - return ::testing::AssertionSuccess(); - return ::testing::AssertionFailure() - << count << " unverified calls to AddFrameProduced."; - } - - TestClient client_; - TestSkippedFrameTracker tracker_; -}; - -#define VERIFY_ADD_PRODUCED_CALLED(timestamp, produced, skipped) \ - EXPECT_EQ(1, client_.GetAndResetCallCount()); \ - EXPECT_EQ(timestamp, client_.source_timestamp_); \ - EXPECT_EQ(produced, client_.amount_produced_); \ - EXPECT_EQ(skipped, client_.amount_skipped_); - -// Producing a frame entirely within a BeginFrame works. -TEST_F(SkippedFrameTrackerTest, NoSkips_BeginThenWill) { - EXPECT_TRUE(BeginFrame(100, 10)); - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(100, 10, 0); - EXPECT_TRUE(FinishFrame()); -} - -// Starting to produce a frame before receiving the BeginFrame works. -TEST_F(SkippedFrameTrackerTest, NoSkips_WillThenBegin) { - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(BeginFrame(100, 10)); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(100, 10, 0); - EXPECT_TRUE(FinishFrame()); -} - -// A (WillProduceFrame, DidProduceFrame) that spans multiple BeginFrames -// is registered properly. -TEST_F(SkippedFrameTrackerTest, Skips_ProducedOverMultipleBeginFrames) { - EXPECT_TRUE(BeginFrame(100, 10)); - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(FinishFrame()); - EXPECT_TRUE(BeginFrame(110, 10)); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(110, 10, 10); - EXPECT_TRUE(FinishFrame()); -} - -// An unexpected jump in the frame timestamp, compared to the interval, -// is registered as skipped time. -TEST_F(SkippedFrameTrackerTest, Skips_DroppedBeginFrames) { - EXPECT_TRUE(BeginFrame(100, 10)); - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(100, 10, 0); - EXPECT_TRUE(FinishFrame()); - - EXPECT_TRUE(BeginFrame(200, 10)); - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(200, 10, 90); - EXPECT_TRUE(FinishFrame()); -} - -// Jitter just below the interval midpoint rounds down the number of dropped -// BeginFrames detected. -TEST_F(SkippedFrameTrackerTest, Skips_DroppedBeginFrames_JitterRoundsDown) { - EXPECT_TRUE(BeginFrame(100, 10)); - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(FinishFrame()); - EXPECT_TRUE(BeginFrame(114, 10)); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(114, 10, 10); - EXPECT_TRUE(FinishFrame()); -} - -// Jitter just above the interval midpoint rounds up the number of dropped -// BeginFrames detected. -TEST_F(SkippedFrameTrackerTest, Skips_DroppedBeginFrames_JitterRoundsUp) { - EXPECT_TRUE(BeginFrame(100, 10)); - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(FinishFrame()); - EXPECT_TRUE(BeginFrame(116, 10)); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(116, 10, 20); - EXPECT_TRUE(FinishFrame()); -} - -// Active, idle, then active again. -// In second active period, start to produce frame first. -TEST_F(SkippedFrameTrackerTest, NoSkips_ActiveIdleActive_WillThenBegin) { - // Active - EXPECT_TRUE(BeginFrame(100, 10)); - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(100, 10, 0); - EXPECT_TRUE(FinishFrame()); - - // Idle - EXPECT_TRUE(BeginFrame(110, 10)); - EXPECT_TRUE(FinishFrame()); - - // Active - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(BeginFrame(120, 10)); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(120, 10, 0); - EXPECT_TRUE(FinishFrame()); -} - -// Active, idle, then active again. -// In second active period, BeginFrame first. -TEST_F(SkippedFrameTrackerTest, NoSkips_ActiveIdleActive_BeginThenWill) { - EXPECT_TRUE(BeginFrame(100, 10)); - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(100, 10, 0); - EXPECT_TRUE(FinishFrame()); - EXPECT_TRUE(tracker_.IsActive()); - - EXPECT_TRUE(BeginFrame(110, 10)); - EXPECT_TRUE(FinishFrame()); - EXPECT_FALSE(tracker_.IsActive()); - - EXPECT_TRUE(BeginFrame(120, 10)); - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(120, 10, 0); - EXPECT_TRUE(FinishFrame()); - EXPECT_TRUE(tracker_.IsActive()); -} - -// Active, idle, then active again. -// Dropped BeginFrames during idle period shouldn't register as skipped. -TEST_F(SkippedFrameTrackerTest, NoSkips_ActiveIdleActive_JumpInIdle) { - EXPECT_TRUE(BeginFrame(100, 10)); - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(100, 10, 0); - EXPECT_TRUE(FinishFrame()); - EXPECT_TRUE(tracker_.IsActive()); - - EXPECT_TRUE(BeginFrame(110, 10)); - EXPECT_TRUE(FinishFrame()); - EXPECT_FALSE(tracker_.IsActive()); - - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(BeginFrame(200, 10)); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(200, 10, 0); - EXPECT_TRUE(FinishFrame()); - EXPECT_TRUE(tracker_.IsActive()); -} - -// Active, Set idle after WillProduceFrame, then active again. -TEST_F(SkippedFrameTrackerTest, WillNotProduceFrame) { - EXPECT_TRUE(BeginFrame(100, 10)); - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(100, 10, 0); - EXPECT_TRUE(FinishFrame()); - EXPECT_TRUE(tracker_.IsActive()); - - EXPECT_TRUE(BeginFrame(110, 10)); - EXPECT_TRUE(WillNotProduceFrame()); - EXPECT_FALSE(tracker_.IsActive()); - - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(BeginFrame(200, 10)); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(200, 10, 0); - EXPECT_TRUE(FinishFrame()); - EXPECT_TRUE(tracker_.IsActive()); -} - -// Active, idle, then active again. -TEST_F(SkippedFrameTrackerTest, WillNotProduceFrame2) { - EXPECT_TRUE(BeginFrame(100, 10)); - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(100, 10, 0); - EXPECT_TRUE(FinishFrame()); - EXPECT_TRUE(tracker_.IsActive()); - - EXPECT_TRUE(WillNotProduceFrame()); - EXPECT_FALSE(tracker_.IsActive()); - - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(BeginFrame(200, 10)); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(200, 10, 0); - EXPECT_TRUE(FinishFrame()); - EXPECT_TRUE(tracker_.IsActive()); -} - -// If frames are pulled from later in the pipeline when the source hasn't tried -// to create a new frame, it should not be recorded as a frame produced -// by the source. -TEST_F(SkippedFrameTrackerTest, PulledFramesNotRecorded) { - EXPECT_TRUE(BeginFrame(100, 10)); - // WillProduceFrame intentionally not called here impliles - // next call to DidProduceFrame was "pulled" not "pushed". - EXPECT_TRUE(DidProduceFrame()); - EXPECT_TRUE(FinishFrame()); - - // Even though BeginFrames might've been dropped since the pulled frame, - // act as if we should behanve just like the produce is coming out of an - // idle period. - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(BeginFrame(200, 10)); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(200, 10, 0); - EXPECT_TRUE(FinishFrame()); -} - -// Multiple calls to WillProduceFrame are legal and should behave as if only -// the first call was made. -TEST_F(SkippedFrameTrackerTest, MultipleWillProduceBeforeDidProduce) { - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(BeginFrame(100, 10)); - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(100, 10, 0); - EXPECT_TRUE(FinishFrame()); -} - -// Frame pulled before BeginFrame doesn't count. -TEST_F(SkippedFrameTrackerTest, NoSkips_ActiveIdleActive_FramePulledBeforeBF) { - EXPECT_TRUE(BeginFrame(100, 10)); - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(100, 10, 0); - EXPECT_TRUE(FinishFrame()); - EXPECT_TRUE(tracker_.IsActive()); - - EXPECT_TRUE(BeginFrame(110, 10)); - EXPECT_TRUE(FinishFrame()); - EXPECT_FALSE(tracker_.IsActive()); - - EXPECT_TRUE(WillProduceFrame()); - // Consider frame pulled since it came before the BeginFrame. - EXPECT_TRUE(DidProduceFrame()); - // Make sure we are immune to multiple pulled frames. - EXPECT_TRUE(DidProduceFrame()); - - EXPECT_TRUE(BeginFrame(120, 10)); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(120, 10, 0); - EXPECT_TRUE(FinishFrame()); - EXPECT_TRUE(tracker_.IsActive()); -} - -// Frame pulled just after a push doesn't count. -TEST_F(SkippedFrameTrackerTest, NoSkips_ActiveIdleActive_FramePulledAfterPush) { - EXPECT_TRUE(BeginFrame(100, 10)); - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(100, 10, 0); - EXPECT_TRUE(FinishFrame()); - EXPECT_TRUE(tracker_.IsActive()); - - EXPECT_TRUE(BeginFrame(110, 10)); - EXPECT_TRUE(FinishFrame()); - EXPECT_FALSE(tracker_.IsActive()); - - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(BeginFrame(120, 10)); - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(120, 10, 0); - // Consider frame pulled since we aleady pushed one this frame. - EXPECT_TRUE(DidProduceFrame()); - // Make sure we are immune to multiple pulled frames. - EXPECT_TRUE(DidProduceFrame()); - EXPECT_TRUE(FinishFrame()); - EXPECT_TRUE(tracker_.IsActive()); -} - -// Frame pulled while attempting to push counts. -TEST_F(SkippedFrameTrackerTest, NoSkips_ActiveIdleActive_FramePulledIsPush) { - EXPECT_TRUE(BeginFrame(100, 10)); - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(FinishFrame()); - EXPECT_TRUE(tracker_.IsActive()); - - // Consider frame pushed, even if we are outside the BeginFrame, since we - // were trying to push. - EXPECT_TRUE(DidProduceFrame()); - VERIFY_ADD_PRODUCED_CALLED(100, 10, 0); - // A second pulled frame shouldn't count though. - EXPECT_TRUE(DidProduceFrame()); - - EXPECT_TRUE(BeginFrame(110, 10)); - EXPECT_TRUE(FinishFrame()); - EXPECT_FALSE(tracker_.IsActive()); -} - -// Simulate that SetNeedsRedraw is called, then the client realized that it -// doesn't need a new BeginFrame. -TEST_F(SkippedFrameTrackerTest, NoFrameProduced) { - EXPECT_TRUE(WillProduceFrame()); - EXPECT_TRUE(WillNotProduceFrame()); - - // Since no BeginFrame is needed, number of frames produced and the number - // of skipped frames should all be 0. - EXPECT_EQ(0, client_.amount_produced_); - EXPECT_EQ(0, client_.amount_skipped_); -} - -} // namespace -} // namespace ui diff --git a/chromium/ui/latency/stream_analyzer.cc b/chromium/ui/latency/stream_analyzer.cc deleted file mode 100644 index 2623e46adf0..00000000000 --- a/chromium/ui/latency/stream_analyzer.cc +++ /dev/null @@ -1,210 +0,0 @@ -// 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 "ui/latency/frame_metrics.h" - -namespace ui { - -StreamAnalysis::StreamAnalysis() = default; -StreamAnalysis::~StreamAnalysis() = default; - -void StreamAnalysis::AsValueInto(base::trace_event::TracedValue* state) const { - state->SetDouble("mean", mean); - - state->SetDouble("rms", rms); - state->SetDouble("smr", smr); - - state->SetDouble("std_dev", std_dev); - state->SetDouble("variance_of_roots", variance_of_roots); - - state->BeginArray("thresholds"); - for (const auto& t : thresholds) { - state->BeginArray(); - state->AppendDouble(t.threshold); - state->AppendDouble(t.ge_fraction); - state->EndArray(); - } - state->EndArray(); - - state->BeginArray("percentiles"); - for (size_t i = 0; i < PercentileResults::kCount; i++) { - state->BeginArray(); - state->AppendDouble(PercentileResults::kPercentiles[i]); - state->AppendDouble(percentiles.values[i]); - state->EndArray(); - } - state->EndArray(); - - state->SetInteger("worst_sample_count", worst_sample_count); - - state->BeginDictionary("worst_mean"); - worst_mean.AsValueInto(state); - state->EndDictionary(); - - state->BeginDictionary("worst_rms"); - worst_rms.AsValueInto(state); - state->EndDictionary(); - - state->BeginDictionary("worst_smr"); - worst_smr.AsValueInto(state); - state->EndDictionary(); -} - -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); - - const uint64_t weighted_value = static_cast<uint64_t>(weight) * value; - const uint64_t weighted_root = - weight * FrameMetrics::FastApproximateSqrt(static_cast<double>(value) * - kFixedPointRootMultiplier); - const 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 = FrameMetrics::FastApproximateSqrt(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 = FrameMetrics::FrameMetrics::FastApproximateSqrt(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_->ComputePercentiles(); - for (size_t i = 0; i < PercentileResults::kCount; i++) { - result.values[i] = client_->TransformResult(result.values[i]); - } - 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(); - results->worst_sample_count = results->worst_mean.sample_count; -} - -} // namespace frame_metrics -} // namespace ui diff --git a/chromium/ui/latency/stream_analyzer.h b/chromium/ui/latency/stream_analyzer.h deleted file mode 100644 index cd20ff3c343..00000000000 --- a/chromium/ui/latency/stream_analyzer.h +++ /dev/null @@ -1,151 +0,0 @@ -// 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/traced_value.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; -}; - -struct StreamAnalysis { - StreamAnalysis(); - ~StreamAnalysis(); - - double mean; - double rms; - double smr; - - double std_dev; - double variance_of_roots; - - std::vector<ThresholdResult> thresholds; - PercentileResults percentiles; - - size_t worst_sample_count = 0; - FrameRegionResult worst_mean; - FrameRegionResult worst_rms; - FrameRegionResult worst_smr; - - void AsValueInto(base::trace_event::TracedValue* state) const; - - DISALLOW_COPY_AND_ASSIGN(StreamAnalysis); -}; - -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_; } - - void ComputeSummary(StreamAnalysis* results) 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_ diff --git a/chromium/ui/latency/stream_analyzer_unittest.cc b/chromium/ui/latency/stream_analyzer_unittest.cc deleted file mode 100644 index 5aca77c7610..00000000000 --- a/chromium/ui/latency/stream_analyzer_unittest.cc +++ /dev/null @@ -1,316 +0,0 @@ -// 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 diff --git a/chromium/ui/latency/windowed_analyzer.cc b/chromium/ui/latency/windowed_analyzer.cc deleted file mode 100644 index cd718d87a4d..00000000000 --- a/chromium/ui/latency/windowed_analyzer.cc +++ /dev/null @@ -1,131 +0,0 @@ -// 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/windowed_analyzer.h" - -#include "ui/latency/frame_metrics.h" - -namespace ui { - -void FrameRegionResult::AsValueInto( - base::trace_event::TracedValue* state) const { - // We don't report sample_count, since that is reported at a higher level. - state->SetDouble("value", value); - state->SetDouble("start", window_begin.since_origin().InMillisecondsF()); - state->SetDouble("duration", (window_end - window_begin).InMillisecondsF()); -} - -namespace frame_metrics { - -WindowedAnalyzer::WindowedAnalyzer( - const WindowedAnalyzerClient* client, - const SharedWindowedAnalyzerClient* shared_client) - : client_(client), shared_client_(shared_client) { - window_queue_.reserve(shared_client->max_window_size); -} - -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, - uint64_t weighted_root, - const Accumulator96b& weighted_square) { - DCHECK_GT(weight, 0u); - DCHECK_EQ(weighted_value, static_cast<uint64_t>(weight) * value); - - // Remove old values from the accumulators. - if (window_queue_.size() >= shared_client_->max_window_size) { - const uint32_t old_value = window_queue_.front().value; - const uint32_t old_weight = window_queue_.front().weight; - window_queue_.pop_front(); - - // Re-calculate some of the old values here. Although squared and root are - // passed in, we've only stored the original value to reduce memory usage. - total_weight_ -= old_weight; - accumulator_ -= static_cast<uint64_t>(old_weight) * old_value; - // Casting the whole rhs is important to ensure rounding happens at a place - // equivalent to when it was added. - root_accumulator_ -= - static_cast<uint64_t>(old_weight * FrameMetrics::FastApproximateSqrt( - static_cast<uint64_t>(old_value) - << kFixedPointRootShift)); - square_accumulator_.Subtract(Accumulator96b(old_value, old_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_); - - window_queue_.push_back({value, weight}); - total_weight_ += weight; - accumulator_ += weighted_value; - root_accumulator_ += weighted_root; - square_accumulator_.Add(weighted_square); - if (window_queue_.size() >= shared_client_->max_window_size) { - bool initialize_results = !results_; - if (initialize_results) - results_.emplace(); - UpdateWorst(accumulator_, &results_->mean, initialize_results); - UpdateWorst(root_accumulator_, &results_->root, initialize_results); - UpdateWorst(square_accumulator_, &results_->square, initialize_results); - } -} - -FrameRegionResult WindowedAnalyzer::ComputeWorstMean() const { - FrameRegionResult result; - if (results_) { - result = results_->mean; - } else { - UpdateWorst(accumulator_, &result, true); - } - result.value = client_->TransformResult(result.value); - return result; -} - -FrameRegionResult WindowedAnalyzer::ComputeWorstRMS() const { - FrameRegionResult result; - if (results_) { - result = results_->square; - } else { - UpdateWorst(square_accumulator_, &result, true); - } - result.value = - client_->TransformResult(FrameMetrics::FastApproximateSqrt(result.value)); - return result; -} - -FrameRegionResult WindowedAnalyzer::ComputeWorstSMR() const { - FrameRegionResult result; - if (results_) { - result = results_->root; - } else { - UpdateWorst(root_accumulator_, &result, true); - } - result.value = client_->TransformResult((result.value * result.value) / - kFixedPointRootMultiplier); - return result; -} - -} // namespace frame_metrics -} // namespace ui diff --git a/chromium/ui/latency/windowed_analyzer.h b/chromium/ui/latency/windowed_analyzer.h deleted file mode 100644 index 04b08cd4ac2..00000000000 --- a/chromium/ui/latency/windowed_analyzer.h +++ /dev/null @@ -1,159 +0,0 @@ -// 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_WINDOWED_ANALYZER_H_ -#define UI_LATENCY_WINDOWED_ANALYZER_H_ - -#include <cstdint> - -#include "base/containers/circular_deque.h" -#include "base/macros.h" -#include "base/optional.h" -#include "base/time/time.h" -#include "base/trace_event/traced_value.h" -#include "ui/latency/fixed_point.h" - -namespace ui { - -// FrameRegionResult encodes window of time where a metric was worst. -// The |sample_count| is the number of samples/frames within the time window -// used to calculate the result. It is reported in case the client wants to -// assess the confidence of the result. -struct FrameRegionResult { - double value = 0; - size_t sample_count = 0; - base::TimeTicks window_begin; - base::TimeTicks window_end; - - void AsValueInto(base::trace_event::TracedValue* state) const; -}; - -namespace frame_metrics { - -// Client delegates that are specific to each WindowedAnalyzer. -class WindowedAnalyzerClient { - public: - // The WorstMean,RMS,SMR methods will give TransformResult() a chance to - // modify the results via this delegate. - // This can be used to undo any tranformations applied to values added - // to AddSample, such as conversions to fixed point. - virtual double TransformResult(double result) const = 0; - - // TODO(brianderson): Replace WindowedAnalyzer::window_queue_ with a client - // interface here. All latency derived metrics should be able to share a - // common history of values. http://crbug.com/822054 -}; - -// Client delegates that can be shared by multiple WindowedAnalyzers. -// Tracks the current window of time that can be stored as the worst -// window of time if a metric detects it as such. -struct SharedWindowedAnalyzerClient { - SharedWindowedAnalyzerClient() : max_window_size(0) {} - - explicit SharedWindowedAnalyzerClient(size_t max_window_size) - : max_window_size(max_window_size) {} - - SharedWindowedAnalyzerClient(size_t max_window_size, - base::TimeTicks window_begin, - base::TimeTicks window_end) - : max_window_size(max_window_size), - window_begin(window_begin), - window_end(window_end) {} - - // Maximum window size in number of samples. - size_t max_window_size; - - // Current window of time for the samples being added. - base::TimeTicks window_begin; - base::TimeTicks window_end; -}; - -// Detects the worst windows of time for a metric. -// Tracks the current values of the current window of time for the -// mean, RMS, and SMR of a single metric. It maintains a history -// of the recent samples and, for each new sample, updates it's accumulators -// using the oldest and newest samples, without looking at any of the other -// samples in between. -class WindowedAnalyzer { - public: - WindowedAnalyzer(const WindowedAnalyzerClient* client, - 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. - void AddSample(uint32_t value, - uint32_t weight, - uint64_t weighted_value, - uint64_t weighted_root, - const Accumulator96b& weighted_square); - - // Returns the worst regions encountered so far. - FrameRegionResult ComputeWorstMean() const; - FrameRegionResult ComputeWorstRMS() const; - FrameRegionResult ComputeWorstSMR() const; - - protected: - struct QueueEntry { - uint32_t value = 0; - uint32_t weight = 0; - }; - - // Updates the result with the current value, if it is worse than the - // value in |result| or if |initialize| is true. - template <typename AccumulatorT> - void UpdateWorst(const AccumulatorT& accumulator, - FrameRegionResult* result, - bool initialize) const { - double current_mean = AsDouble(accumulator) / total_weight_; - if (initialize || current_mean > result->value) { - result->value = current_mean; - result->sample_count = window_queue_.size(); - result->window_begin = shared_client_->window_begin; - result->window_end = shared_client_->window_end; - } - } - - const WindowedAnalyzerClient* const client_; - const SharedWindowedAnalyzerClient* const shared_client_; - - // We need to maintain a history of values so we can - // remove old samples from the accumulators. - base::circular_deque<QueueEntry> window_queue_; - - uint64_t total_weight_ = 0; - uint64_t accumulator_ = 0; - uint64_t root_accumulator_ = 0; - Accumulator96b square_accumulator_; - - // Internal results that track the worst region so far. - // The time region is stored correctly, however the results are intermediate - // and must be adjusted by result_transform_ and fixed_point_multipler before - // exposure to the client. Furthermore, RMS needs to square root the result - // and SMR needs to square the result. - struct InternalResults { - FrameRegionResult mean; - FrameRegionResult root; - FrameRegionResult square; - }; - // Optional since they aren't valid until we've seen enough samples. - // This delay prevents the first couple samples from dominating the result. - base::Optional<InternalResults> results_; - - DISALLOW_COPY_AND_ASSIGN(WindowedAnalyzer); -}; - -} // namespace frame_metrics -} // namespace ui - -#endif // UI_LATENCY_WINDOWED_ANALYZER_H_ diff --git a/chromium/ui/latency/windowed_analyzer_unittest.cc b/chromium/ui/latency/windowed_analyzer_unittest.cc deleted file mode 100644 index 5aa05034910..00000000000 --- a/chromium/ui/latency/windowed_analyzer_unittest.cc +++ /dev/null @@ -1,479 +0,0 @@ -// 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/windowed_analyzer.h" - -#include "base/time/time.h" -#include "testing/gtest/include/gtest/gtest.h" -#include "ui/latency/fixed_point.h" -#include "ui/latency/frame_metrics_test_common.h" - -namespace ui { -namespace frame_metrics { -namespace { - -// 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) { - // For this test, we don't care about the timeline, so just keep it constant. - TestWindowedAnalyzerClient client; - SharedWindowedAnalyzerClient shared_client( - 60, base::TimeTicks(), - base::TimeTicks() + base::TimeDelta::FromSeconds(1)); - - // 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) { - WindowedAnalyzer analyzer(&client, &shared_client); - AddSamplesHelper(&analyzer, value, weight, samples); - uint64_t expected_value = - value * TestWindowedAnalyzerClient::result_scale; - EXPECT_EQ(analyzer.ComputeWorstMean().value, expected_value) - << value << " x " << weight; - EXPECT_NEAR_SQRT_APPROX(analyzer.ComputeWorstRMS().value, - expected_value) - << value << " x " << weight; - EXPECT_NEAR_SQRT_APPROX(analyzer.ComputeWorstSMR().value, - expected_value) - << value << " x " << 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; - WindowedAnalyzer analyzer(&client, &shared_client); - AddSamplesHelper(&analyzer, value, weight, kSamplesToAdd); - - // TestWindowedAnalyzerClient scales the result by 2. - uint64_t expected_value = - value * TestWindowedAnalyzerClient::result_scale; - // Makes sure our precision is good enough. - EXPECT_EQ(analyzer.ComputeWorstMean().value, expected_value) - << value << " x " << weight; - EXPECT_NEAR_SQRT_APPROX(analyzer.ComputeWorstRMS().value, expected_value) - << value << " x " << weight; - EXPECT_NEAR_SQRT_APPROX(analyzer.ComputeWorstSMR().value, expected_value) - << value << " x " << weight; - } - } -} - -// Verify that the worst values and their time regions are properly tracked -// seperately for mean, SMR, and RMS. -TEST(FrameMetricsWindowedAnalyzerTest, AllResultsDifferent) { - const size_t kMaxWindowSize = 6; // Same as the pattern length. - const uint32_t kSampleWeight = 100; - - TestWindowedAnalyzerClient client; - SharedWindowedAnalyzerClient shared_client( - kMaxWindowSize, base::TimeTicks(), - base::TimeTicks() + base::TimeDelta::FromSeconds(1)); - WindowedAnalyzer analyzer(&client, &shared_client); - - // Used to "clear" all the windowed accumulators. - const std::vector<uint32_t> pattern_clear = {0, 0, 0, 0, 0, 0}; - // Worst mean pattern: mean of 3, smr of 1.5, rms of ~4.2. - const std::vector<uint32_t> pattern_max_mean = {0, 6, 0, 6, 0, 6}; - double expected_worst_mean = - 3 * kFixedPointMultiplier * TestWindowedAnalyzerClient::result_scale; - // Lots of small janks maximizes the SMR. - // Worst SMR pattern: mean of 2, smr of 2, rms of 2. - const std::vector<uint32_t> pattern_max_smr = {2, 2, 2, 2, 2, 2}; - double expected_worst_smr = - 2 * kFixedPointMultiplier * TestWindowedAnalyzerClient::result_scale; - // A few big janks dominate RMS. - // Worst RMS pattern: Mean of 2, smr of ~.3, rms of ~4.9 - const std::vector<uint32_t> pattern_max_rms = {0, 0, 0, 0, 0, 12}; - double expected_worst_rms = std::sqrt((12 * 12) / 6) * kFixedPointMultiplier * - TestWindowedAnalyzerClient::result_scale; - - AddPatternHelper(&shared_client, &analyzer, pattern_clear, kSampleWeight); - AddPatternHelper(&shared_client, &analyzer, pattern_max_mean, kSampleWeight); - SharedWindowedAnalyzerClient worst_mean_client(shared_client); - - AddPatternHelper(&shared_client, &analyzer, pattern_clear, kSampleWeight); - AddPatternHelper(&shared_client, &analyzer, pattern_max_smr, kSampleWeight); - SharedWindowedAnalyzerClient worst_smr_client(shared_client); - - AddPatternHelper(&shared_client, &analyzer, pattern_clear, kSampleWeight); - AddPatternHelper(&shared_client, &analyzer, pattern_max_rms, kSampleWeight); - SharedWindowedAnalyzerClient worst_rms_client(shared_client); - - // If there is a tie, the first window detected wins. - // This can go wrong if there's any accumulation of error because the - // values added aren't exactly the same as the values removed. - // This only catches accumulation of error in one direction, so isn't - // thorough, but it does help improve coverage. - AddPatternHelper(&shared_client, &analyzer, pattern_clear, kSampleWeight); - AddPatternHelper(&shared_client, &analyzer, pattern_max_mean, kSampleWeight); - AddPatternHelper(&shared_client, &analyzer, pattern_clear, kSampleWeight); - AddPatternHelper(&shared_client, &analyzer, pattern_max_smr, kSampleWeight); - AddPatternHelper(&shared_client, &analyzer, pattern_clear, kSampleWeight); - AddPatternHelper(&shared_client, &analyzer, pattern_max_rms, kSampleWeight); - AddPatternHelper(&shared_client, &analyzer, pattern_clear, kSampleWeight); - - FrameRegionResult worst_mean = analyzer.ComputeWorstMean(); - EXPECT_DOUBLE_EQ(expected_worst_mean, worst_mean.value); - EXPECT_EQ(worst_mean_client.window_begin, worst_mean.window_begin); - EXPECT_EQ(worst_mean_client.window_end, worst_mean.window_end); - - FrameRegionResult worst_smr = analyzer.ComputeWorstSMR(); - EXPECT_NEAR_SQRT_APPROX(expected_worst_smr, worst_smr.value); - EXPECT_EQ(worst_smr_client.window_begin, worst_smr.window_begin); - EXPECT_EQ(worst_smr_client.window_end, worst_smr.window_end); - - FrameRegionResult worst_rms = analyzer.ComputeWorstRMS(); - EXPECT_NEAR_SQRT_APPROX(expected_worst_rms, worst_rms.value); - EXPECT_EQ(worst_rms_client.window_begin, worst_rms.window_begin); - EXPECT_EQ(worst_rms_client.window_end, worst_rms.window_end); -} - -// Verify that the worst values and their time regions are properly tracked -// even before a full window's worth is available. -TEST(FrameMetricsWindowedAnalyzerTest, SmallSampleSize) { - const size_t kMaxWindowSize = 6; // Bigger than the pattern length. - const uint32_t kSampleWeight = 100; - - TestWindowedAnalyzerClient client; - SharedWindowedAnalyzerClient shared_client( - kMaxWindowSize, base::TimeTicks(), - base::TimeTicks() + base::TimeDelta::FromSeconds(1)); - WindowedAnalyzer analyzer(&client, &shared_client); - - const std::vector<uint32_t> pattern_short = {2, 2, 2}; - double expected_initial_value = - 2 * kFixedPointMultiplier * TestWindowedAnalyzerClient::result_scale; - AddPatternHelper(&shared_client, &analyzer, pattern_short, kSampleWeight); - SharedWindowedAnalyzerClient short_client(shared_client); - - FrameRegionResult worst_mean = analyzer.ComputeWorstMean(); - EXPECT_DOUBLE_EQ(expected_initial_value, worst_mean.value); - EXPECT_EQ(short_client.window_begin, worst_mean.window_begin); - EXPECT_EQ(short_client.window_end, worst_mean.window_end); - - FrameRegionResult worst_smr = analyzer.ComputeWorstSMR(); - EXPECT_NEAR_SQRT_APPROX(expected_initial_value, worst_smr.value); - EXPECT_EQ(short_client.window_begin, worst_smr.window_begin); - EXPECT_EQ(short_client.window_end, worst_smr.window_end); - - FrameRegionResult worst_rms = analyzer.ComputeWorstRMS(); - EXPECT_NEAR_SQRT_APPROX(expected_initial_value, worst_rms.value); - EXPECT_EQ(short_client.window_begin, worst_rms.window_begin); - EXPECT_EQ(short_client.window_end, worst_rms.window_end); -} - -// Verify that a few bad values at the start don't dominate the result. -TEST(FrameMetricsWindowedAnalyzerTest, BadFirstSamples) { - const size_t kMaxWindowSize = 6; - const uint32_t kSampleWeight = 100; - FrameRegionResult worst_mean, worst_smr, worst_rms; - - TestWindowedAnalyzerClient client; - SharedWindowedAnalyzerClient shared_client( - kMaxWindowSize, base::TimeTicks(), - base::TimeTicks() + base::TimeDelta::FromSeconds(1)); - WindowedAnalyzer analyzer(&client, &shared_client); - - // The 7's at the start will dominate the result if the implemenationd - // doesn't only start remembering the worst values after receiving at least - // a window's worth of samples. - const std::vector<uint32_t> pattern_short = {7, 7}; - double expected_initial_value = - 7 * kFixedPointMultiplier * TestWindowedAnalyzerClient::result_scale; - AddPatternHelper(&shared_client, &analyzer, pattern_short, kSampleWeight); - SharedWindowedAnalyzerClient short_client(shared_client); - - worst_mean = analyzer.ComputeWorstMean(); - EXPECT_DOUBLE_EQ(expected_initial_value, worst_mean.value); - EXPECT_EQ(short_client.window_begin, worst_mean.window_begin); - EXPECT_EQ(short_client.window_end, worst_mean.window_end); - - worst_smr = analyzer.ComputeWorstSMR(); - EXPECT_NEAR_SQRT_APPROX(expected_initial_value, worst_smr.value); - EXPECT_EQ(short_client.window_begin, worst_smr.window_begin); - EXPECT_EQ(short_client.window_end, worst_smr.window_end); - - worst_rms = analyzer.ComputeWorstRMS(); - EXPECT_NEAR_SQRT_APPROX(expected_initial_value, worst_rms.value); - EXPECT_EQ(short_client.window_begin, worst_rms.window_begin); - EXPECT_EQ(short_client.window_end, worst_rms.window_end); - - // Clear the window. - const std::vector<uint32_t> pattern_clear = {0, 0, 0, 0, 0, 0}; - AddPatternHelper(&shared_client, &analyzer, pattern_clear, kSampleWeight); - - // Make sure a new worst window with results less than 7 is detected. - const std::vector<uint32_t> pattern_long = {6, 6, 6, 6, 6, 6}; - double expected_final_value = - 6 * kFixedPointMultiplier * TestWindowedAnalyzerClient::result_scale; - AddPatternHelper(&shared_client, &analyzer, pattern_long, kSampleWeight); - SharedWindowedAnalyzerClient long_client(shared_client); - - worst_mean = analyzer.ComputeWorstMean(); - EXPECT_DOUBLE_EQ(expected_final_value, worst_mean.value); - EXPECT_EQ(long_client.window_begin, worst_mean.window_begin); - EXPECT_EQ(long_client.window_end, worst_mean.window_end); - - worst_smr = analyzer.ComputeWorstSMR(); - EXPECT_NEAR_SQRT_APPROX(expected_final_value, worst_smr.value); - EXPECT_EQ(long_client.window_begin, worst_smr.window_begin); - EXPECT_EQ(long_client.window_end, worst_smr.window_end); - - worst_rms = analyzer.ComputeWorstRMS(); - EXPECT_NEAR_SQRT_APPROX(expected_final_value, worst_rms.value); - EXPECT_EQ(long_client.window_begin, worst_rms.window_begin); - EXPECT_EQ(long_client.window_end, worst_rms.window_end); -} - -// Verify ResetAccumulators is continuous across the reset boundary. -TEST(FrameMetricsWindowedAnalyzerTest, ResetWorstValues) { - const size_t kMaxWindowSize = 6; // Same as the pattern length. - const uint32_t kSampleWeight = 100; - FrameRegionResult worst_mean, worst_smr, worst_rms; - - TestWindowedAnalyzerClient client; - SharedWindowedAnalyzerClient shared_client( - kMaxWindowSize, base::TimeTicks(), - base::TimeTicks() + base::TimeDelta::FromSeconds(1)); - WindowedAnalyzer analyzer(&client, &shared_client); - - // Start off with the worst pattern. - const std::vector<uint32_t> pattern1 = {9, 9, 9, 9, 9, 9}; - double expected_initial_value = - 9 * kFixedPointMultiplier * TestWindowedAnalyzerClient::result_scale; - AddPatternHelper(&shared_client, &analyzer, pattern1, kSampleWeight); - SharedWindowedAnalyzerClient initial_client(shared_client); - - worst_mean = analyzer.ComputeWorstMean(); - EXPECT_DOUBLE_EQ(expected_initial_value, worst_mean.value); - EXPECT_EQ(initial_client.window_begin, worst_mean.window_begin); - EXPECT_EQ(initial_client.window_end, worst_mean.window_end); - - worst_smr = analyzer.ComputeWorstSMR(); - EXPECT_NEAR_SQRT_APPROX(expected_initial_value, worst_smr.value); - EXPECT_EQ(initial_client.window_begin, worst_smr.window_begin); - EXPECT_EQ(initial_client.window_end, worst_smr.window_end); - - worst_rms = analyzer.ComputeWorstRMS(); - EXPECT_NEAR_SQRT_APPROX(expected_initial_value, worst_rms.value); - EXPECT_EQ(initial_client.window_begin, worst_rms.window_begin); - EXPECT_EQ(initial_client.window_end, worst_rms.window_end); - - // The 4's below will affect the window, even after a reset, but - // won't affect the current worst values. - const std::vector<uint32_t> pattern2 = {4, 4, 4, 4, 4, 4}; - AddPatternHelper(&shared_client, &analyzer, pattern2, kSampleWeight); - - worst_mean = analyzer.ComputeWorstMean(); - EXPECT_DOUBLE_EQ(expected_initial_value, worst_mean.value); - EXPECT_EQ(initial_client.window_begin, worst_mean.window_begin); - EXPECT_EQ(initial_client.window_end, worst_mean.window_end); - - worst_smr = analyzer.ComputeWorstSMR(); - EXPECT_NEAR_SQRT_APPROX(expected_initial_value, worst_smr.value); - EXPECT_EQ(initial_client.window_begin, worst_smr.window_begin); - EXPECT_EQ(initial_client.window_end, worst_smr.window_end); - - worst_rms = analyzer.ComputeWorstRMS(); - EXPECT_NEAR_SQRT_APPROX(expected_initial_value, worst_rms.value); - EXPECT_EQ(initial_client.window_begin, worst_rms.window_begin); - EXPECT_EQ(initial_client.window_end, worst_rms.window_end); - - // Reset the worst value. This should not destroy sample history or - // any accumulators. - analyzer.ResetWorstValues(); - - // The first 4 below will be included with the previous 4's to detect an - // entire window of results, even though we've reset the worst values. - const std::vector<uint32_t> pattern3 = {4}; - double expected_final_value = - 4 * kFixedPointMultiplier * TestWindowedAnalyzerClient::result_scale; - AddPatternHelper(&shared_client, &analyzer, pattern3, kSampleWeight); - SharedWindowedAnalyzerClient final_client(shared_client); - - // Add a window of 1's here to verify it does not affect the window of 4's. - const std::vector<uint32_t> pattern4 = {1, 1, 1, 1, 1, 1}; - AddPatternHelper(&shared_client, &analyzer, pattern4, kSampleWeight); - - worst_mean = analyzer.ComputeWorstMean(); - EXPECT_DOUBLE_EQ(expected_final_value, worst_mean.value); - EXPECT_EQ(final_client.window_begin, worst_mean.window_begin); - EXPECT_EQ(final_client.window_end, worst_mean.window_end); - - worst_smr = analyzer.ComputeWorstSMR(); - EXPECT_NEAR_SQRT_APPROX(expected_final_value, worst_smr.value); - EXPECT_EQ(final_client.window_begin, worst_smr.window_begin); - EXPECT_EQ(final_client.window_end, worst_smr.window_end); - - worst_rms = analyzer.ComputeWorstRMS(); - EXPECT_NEAR_SQRT_APPROX(expected_final_value, worst_rms.value); - EXPECT_EQ(final_client.window_begin, worst_rms.window_begin); - EXPECT_EQ(final_client.window_end, worst_rms.window_end); -} - -// WindowedAnalyzerNaive is a version of WindowedAnalyzer that doesn't use -// 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) - : max_window_size_(max_window_size) {} - - void AddSample(uint32_t value, - uint32_t weight, - uint64_t weighted_value, - uint64_t weighted_root, - const Accumulator96b& weighted_square) { - if (history_.size() >= max_window_size_) { - Sample old = history_.front(); - history_.pop_front(); - naive_accumulator_ -= static_cast<double>(old.weight) * old.value; - naive_root_accumulator_ -= - static_cast<double>(old.weight) * std::sqrt(old.value); - naive_square_accumulator_ -= - static_cast<double>(old.weight) * old.value * old.value; - naive_total_weight_ -= old.weight; - } - - history_.push_back({value, weight}); - naive_accumulator_ += static_cast<double>(weight) * value; - naive_root_accumulator_ += static_cast<double>(weight) * std::sqrt(value); - naive_square_accumulator_ += static_cast<double>(weight) * value * value; - naive_total_weight_ += weight; - } - - // 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". - void AddCubedPatternHelper(SharedWindowedAnalyzerClient* shared_client, - 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); - uint64_t weighted_value = (i * (i + 1)); - uint64_t updated_value = static_cast<uint64_t>(i); - uint64_t weighted_root = (i + 1) * std::sqrt(updated_value << 32); - Accumulator96b weighted_square(i, (i + 1)); - AddSample(i, (i + 1), weighted_value, weighted_root, weighted_square); - } - } - - struct Sample { - uint32_t value; - uint32_t weight; - }; - - const size_t max_window_size_; - double naive_accumulator_ = 0; - double naive_root_accumulator_ = 0; - double naive_square_accumulator_ = 0; - double naive_total_weight_ = 0; - base::circular_deque<Sample> history_; -}; - -// A version of the WindowedAnalyzer that allows us to inspect the internal -// state for testing purposes. -class TestWindowedAnalyzer : public WindowedAnalyzer { - public: - TestWindowedAnalyzer(const WindowedAnalyzerClient* client, - const SharedWindowedAnalyzerClient* shared_client) - : WindowedAnalyzer(client, shared_client) {} - ~TestWindowedAnalyzer() override {} - - double CurrentAccumulator() { return accumulator_; } - double CurrentRootAccumulator() { return root_accumulator_; } - double CurrentSquareAccumulator() { return square_accumulator_.ToDouble(); } -}; - -// This test verifies that it's easy to blow the dynamic range of a floating -// point accumulator with a particular pattern. Then it verifies that same -// pattern does not result in error in the actual implementation. -void TestNoAccumulatedPrecisionError(uint32_t big_value, - uint32_t small_value, - double naive_root_error_floor, - double naive_square_error_floor) { - const size_t kRuns = 1000; - const size_t kMaxWindowSize = 6; // Same as the pattern length. - const std::vector<uint32_t> pattern_clear = {0, 0, 0, 0, 0, 0}; - const std::vector<uint32_t> pattern_bad = {big_value, small_value, - small_value, small_value, - small_value, small_value}; - - // Set up the actual WindowedAnalyzer implementation. - TestWindowedAnalyzerClient client_impl; - SharedWindowedAnalyzerClient shared_client_impl( - kMaxWindowSize, base::TimeTicks(), - base::TimeTicks() + base::TimeDelta::FromSeconds(1)); - TestWindowedAnalyzer analyzer_impl(&client_impl, &shared_client_impl); - - // Set up the naive WindowedAnalyzer implementation. - SharedWindowedAnalyzerClient shared_client_naive( - kMaxWindowSize, base::TimeTicks(), - base::TimeTicks() + base::TimeDelta::FromSeconds(1)); - WindowedAnalyzerNaive analyzer_naive(kMaxWindowSize); - - // Verify error keeps accumulating each time the bad pattern is applied. - // Note: We don't expect error in the mean accumulator since the values added - // are already truncated to whole integers before being passed in via - // AddSamples(). - double naive_root_accumulator_prev = 0; - double naive_square_accumulator_prev = 0; - for (size_t i = 1; i <= kRuns; i++) { - analyzer_naive.AddCubedPatternHelper(&shared_client_naive, pattern_bad); - analyzer_naive.AddCubedPatternHelper(&shared_client_naive, pattern_clear); - EXPECT_EQ(0, analyzer_naive.naive_accumulator_); - EXPECT_ABS_LT(naive_root_accumulator_prev, - analyzer_naive.naive_root_accumulator_); - EXPECT_ABS_LT(naive_square_accumulator_prev, - analyzer_naive.naive_square_accumulator_); - naive_root_accumulator_prev = analyzer_naive.naive_root_accumulator_; - naive_square_accumulator_prev = analyzer_naive.naive_square_accumulator_; - } - // Verify naive error is bigger than some threshold after kRuns. - EXPECT_ABS_LE(naive_root_error_floor * kRuns, - analyzer_naive.naive_root_accumulator_); - EXPECT_ABS_LE(naive_square_error_floor * kRuns, - analyzer_naive.naive_square_accumulator_); -} - -// This is a synthetic example that is just outside the dynamic range of a -// double accumulator. Doubles have 53 significand bits. When cubed, the -// difference between the small and big values below require just over 53 bits. -TEST(FrameMetricsWindowedAnalyzerTest, NoAccumulatedPrecisionErrorBasic) { - constexpr uint32_t big = 1 << 19; - constexpr uint32_t small = 2; - TestNoAccumulatedPrecisionError(big, small, 5e-8, 60); -} - -// This is a more realistic scenario with orders of magnitude we are likely -// to see in actual data. The error is small, but can become significant over -// time. -TEST(FrameMetricsWindowedAnalyzerTest, NoAccumulatedPrecisionErrorBig) { - constexpr uint32_t big = 1 * base::TimeTicks::kMicrosecondsPerSecond; - constexpr uint32_t small = 1 * base::TimeTicks::kMicrosecondsPerMillisecond; - TestNoAccumulatedPrecisionError(big, small, 7e-8, 256); -} - -// This is a scenario with orders of magnitude that we can see in our data, -// but that will be rare. Even after a single bad pattern, the error is -// significant. -TEST(FrameMetricsWindowedAnalyzerTest, NoAccumulatedPrecisionErrorBigger) { - constexpr uint32_t big = 20 * base::TimeTicks::kMicrosecondsPerSecond; - constexpr uint32_t small = 1 * base::TimeTicks::kMicrosecondsPerMillisecond; - TestNoAccumulatedPrecisionError(big, small, 2e-5, 1e6); -} - -} // namespace -} // namespace frame_metrics -} // namespace ui |