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") { ...@@ -9,6 +9,8 @@ jumbo_source_set("latency") {
sources = [ sources = [
"fixed_point.cc", "fixed_point.cc",
"fixed_point.h", "fixed_point.h",
"histograms.cc",
"histograms.h",
"latency_histogram_macros.h", "latency_histogram_macros.h",
"latency_info.cc", "latency_info.cc",
"latency_info.h", "latency_info.h",
...@@ -40,6 +42,9 @@ jumbo_source_set("test_support") { ...@@ -40,6 +42,9 @@ jumbo_source_set("test_support") {
test("latency_unittests") { test("latency_unittests") {
sources = [ sources = [
"fixed_point_unittest.cc", "fixed_point_unittest.cc",
"histograms_test_common.cc",
"histograms_test_common.h",
"histograms_unittest.cc",
"latency_info_unittest.cc", "latency_info_unittest.cc",
] ]
...@@ -66,3 +71,21 @@ test("latency_unittests") { ...@@ -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",
]
}
// 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();
}
NOTREACHED();
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::CalculatePercentiles() 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::CalculatePercentiles() 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
// 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