blob: 55f43a7aecfe69cb2f6239a9bf4b1741a2f22ff2 [file] [log] [blame]
// 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