Commit 2b021ec7 authored by Brian Anderson's avatar Brian Anderson Committed by Commit Bot

ui: Add FrameMetrics histogram helpers.

Adds a Histogram interface with two implementations. One for
ratios with most of it's precision just above 1 and another
for latency with higher precision near vsync intervals.

The Histogram can be queried for approximate percentiles,
which will be useful for UKM based frame metrics.

Bug: 807463
Change-Id: I83b251e2d50166cd4c376a10050ee344e312368f
Reviewed-on: https://chromium-review.googlesource.com/972194Reviewed-by: default avatarTimothy Dresser <tdresser@chromium.org>
Commit-Queue: Brian Anderson <brianderson@chromium.org>
Cr-Commit-Position: refs/heads/master@{#544754}
parent 748162fa
......@@ -9,6 +9,8 @@ jumbo_source_set("latency") {
sources = [
"fixed_point.cc",
"fixed_point.h",
"histograms.cc",
"histograms.h",
"latency_histogram_macros.h",
"latency_info.cc",
"latency_info.h",
......@@ -40,6 +42,9 @@ jumbo_source_set("test_support") {
test("latency_unittests") {
sources = [
"fixed_point_unittest.cc",
"histograms_test_common.cc",
"histograms_test_common.h",
"histograms_unittest.cc",
"latency_info_unittest.cc",
]
......@@ -66,3 +71,21 @@ test("latency_unittests") {
]
}
}
test("latency_perftests") {
sources = [
"histograms_perftest.cc",
"histograms_test_common.cc",
"histograms_test_common.h",
]
deps = [
":latency",
"//base",
"//base/test:test_support",
"//mojo/edk/test:run_all_unittests",
"//testing/gmock",
"//testing/gtest",
"//testing/perf",
]
}
This diff is collapsed.
// 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/macros.h"
namespace ui {
// Used to communicate percentile results to clients.
struct PercentileResults {
static constexpr double kPercentiles[] = {.50, .99};
static constexpr size_t kCount = arraysize(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;
// Calculates and returns the approximate percentiles based on the
// histogram distribution.
virtual PercentileResults CalculatePercentiles() 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 CalculatePercentiles() 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 CalculatePercentiles() 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_
// 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_test.h"
#include "ui/latency/fixed_point.h"
#include "ui/latency/histograms_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 CalculatePercentiles() const override {
return PercentileResults();
}
void Reset() override {}
private:
TestRatioBoundaries ratio_boundaries_;
base::BucketRanges bucket_ranges_;
base::SampleVector sample_vector_;
DISALLOW_COPY_AND_ASSIGN(RatioHistogramBaseline);
};
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 X = base_time.InSecondsF() / impl_time.InSecondsF();
perf_test::PrintResult(__FUNCTION__, "", __FUNCTION__, X, "x", true);
}
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 X = base_time.InSecondsF() / impl_time.InSecondsF();
perf_test::PrintResult(__FUNCTION__, "", __FUNCTION__, X, "x", true);
}
// 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 elemet 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 CalculatePercentiles() 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 X = base_time.InSecondsF() / impl_time.InSecondsF();
perf_test::PrintResult(__FUNCTION__, "", __FUNCTION__, X, "x", true);
}
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 X = base_time.InSecondsF() / impl_time.InSecondsF();
perf_test::PrintResult(__FUNCTION__, "", __FUNCTION__, X, "x", true);
}
} // namespace frame_metrics
} // namespace ui
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ui/latency/histograms_test_common.h"
#include "base/logging.h"
namespace ui {
namespace frame_metrics {
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);
}
} // namespace frame_metrics
} // namespace ui
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_LATENCY_HISTOGRAMS_TEST_COMMON_H_
#define UI_LATENCY_HISTOGRAMS_TEST_COMMON_H_
#include "ui/latency/fixed_point.h"
#include <array>
namespace ui {
namespace frame_metrics {
// 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}};
} // namespace frame_metrics
} // namespace ui
#endif // UI_LATENCY_HISTOGRAMS_TEST_COMMON_H_
// 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/histograms_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());
}
}
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->CalculatePercentiles();
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->CalculatePercentiles();
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->CalculatePercentiles();
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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment