blob: 458fa838f4ed3f87244af90e8f9b03413c0c3b68 [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 "third_party/blink/renderer/core/frame/local_frame_ukm_aggregator.h"
#include "base/test/test_mock_time_task_runner.h"
#include "cc/metrics/begin_main_frame_metrics.h"
#include "components/ukm/test_ukm_recorder.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace blink {
class LocalFrameUkmAggregatorTest : public testing::Test {
public:
LocalFrameUkmAggregatorTest() = default;
~LocalFrameUkmAggregatorTest() override = default;
void SetUp() override {
test_task_runner_ = base::MakeRefCounted<base::TestMockTimeTaskRunner>();
aggregator_ = base::MakeRefCounted<LocalFrameUkmAggregator>(
ukm::UkmRecorder::GetNewSourceID(), &recorder_);
aggregator_->SetTickClockForTesting(test_task_runner_->GetMockTickClock());
}
void TearDown() override {
aggregator_.reset();
}
LocalFrameUkmAggregator& aggregator() {
CHECK(aggregator_);
return *aggregator_;
}
ukm::TestUkmRecorder& recorder() { return recorder_; }
void ResetAggregator() { aggregator_.reset(); }
std::string GetPrimaryMetricName() {
return LocalFrameUkmAggregator::primary_metric_name().Utf8();
}
std::string GetMetricName(int index) {
return LocalFrameUkmAggregator::metrics_data()[index].name.Utf8();
}
std::string GetPercentageMetricName(int index) {
return LocalFrameUkmAggregator::metrics_data()[index].name.Utf8() +
"Percentage";
}
void FramesToNextEventForTest(unsigned delta) {
aggregator().FramesToNextEventForTest(delta);
}
base::TimeTicks Now() { return test_task_runner_->NowTicks(); }
protected:
scoped_refptr<base::TestMockTimeTaskRunner> test_task_runner_;
void VerifyEntries(unsigned expected_num_entries,
unsigned expected_primary_metric,
unsigned expected_sub_metric,
unsigned expected_percentage) {
EXPECT_EQ(recorder().entries_count(), expected_num_entries);
auto entries = recorder().GetEntriesByName("Blink.UpdateTime");
EXPECT_EQ(entries.size(), expected_num_entries);
for (auto* entry : entries) {
EXPECT_TRUE(
ukm::TestUkmRecorder::EntryHasMetric(entry, GetPrimaryMetricName()));
const int64_t* primary_metric_value =
ukm::TestUkmRecorder::GetEntryMetric(entry, GetPrimaryMetricName());
EXPECT_NEAR(*primary_metric_value / 1e3, expected_primary_metric, 0.001);
for (int i = 0; i < LocalFrameUkmAggregator::kCount; ++i) {
EXPECT_TRUE(
ukm::TestUkmRecorder::EntryHasMetric(entry, GetMetricName(i)));
const int64_t* metric_value =
ukm::TestUkmRecorder::GetEntryMetric(entry, GetMetricName(i));
EXPECT_NEAR(*metric_value / 1e3, expected_sub_metric, 0.001);
EXPECT_TRUE(ukm::TestUkmRecorder::EntryHasMetric(
entry, GetPercentageMetricName(i)));
const int64_t* metric_percentage = ukm::TestUkmRecorder::GetEntryMetric(
entry, GetPercentageMetricName(i));
EXPECT_NEAR(*metric_percentage, expected_percentage, 0.001);
}
}
}
private:
scoped_refptr<LocalFrameUkmAggregator> aggregator_;
ukm::TestUkmRecorder recorder_;
};
TEST_F(LocalFrameUkmAggregatorTest, EmptyEventsNotRecorded) {
// Although the tests use a mock clock, the UKM aggregator checks if the
// system has a high resolution clock before recording results. As a result,
// the tests will fail if the system does not have a high resolution clock.
if (!base::TimeTicks::IsHighResolution())
return;
// There is no BeginMainFrame, so no metrics get recorded.
test_task_runner_->FastForwardBy(base::TimeDelta::FromSeconds(10));
ResetAggregator();
EXPECT_EQ(recorder().sources_count(), 0u);
EXPECT_EQ(recorder().entries_count(), 0u);
}
TEST_F(LocalFrameUkmAggregatorTest, FirstFrameIsRecorded) {
// Although the tests use a mock clock, the UKM aggregator checks if the
// system has a high resolution clock before recording results. As a result,
// the tests will fail if the system does not have a high resolution clock.
if (!base::TimeTicks::IsHighResolution())
return;
// The initial interval is always zero, so we should see one set of metrics
// for the initial frame, regardless of the initial interval.
base::TimeTicks start_time = Now();
FramesToNextEventForTest(1);
unsigned millisecond_for_step = 1;
aggregator().BeginMainFrame();
for (int i = 0; i < LocalFrameUkmAggregator::kCount; ++i) {
auto timer =
aggregator().GetScopedTimer(i % LocalFrameUkmAggregator::kCount);
test_task_runner_->FastForwardBy(
base::TimeDelta::FromMilliseconds(millisecond_for_step));
}
aggregator().RecordEndOfFrameMetrics(start_time, Now());
EXPECT_EQ(recorder().entries_count(), 1u);
float expected_primary_metric =
millisecond_for_step * LocalFrameUkmAggregator::kCount;
float expected_sub_metric = millisecond_for_step;
float expected_percentage =
floor(100.0 / (float)LocalFrameUkmAggregator::kCount);
VerifyEntries(1u, expected_primary_metric, expected_sub_metric,
expected_percentage);
// Reset the aggregator. Should not record any more.
ResetAggregator();
VerifyEntries(1u, expected_primary_metric, expected_sub_metric,
expected_percentage);
}
TEST_F(LocalFrameUkmAggregatorTest, EventsRecordedAtIntervals) {
// Although the tests use a mock clock, the UKM aggregator checks if the
// system has a high resolution clock before recording results. As a result,
// the tests will fail if the system does not have a high resolution clock.
if (!base::TimeTicks::IsHighResolution())
return;
// The records should be recorded in the first frame after every interval,
// and no sooner.
// Set the first sample interval to 2.
FramesToNextEventForTest(2);
unsigned millisecond_per_step = 50 / (LocalFrameUkmAggregator::kCount + 1);
unsigned millisecond_per_frame =
millisecond_per_step * (LocalFrameUkmAggregator::kCount + 1);
base::TimeTicks start_time = Now();
aggregator().BeginMainFrame();
for (int i = 0; i < LocalFrameUkmAggregator::kCount; ++i) {
auto timer = aggregator().GetScopedTimer(i);
test_task_runner_->FastForwardBy(
base::TimeDelta::FromMilliseconds(millisecond_per_step));
}
test_task_runner_->FastForwardBy(
base::TimeDelta::FromMilliseconds(millisecond_per_step));
aggregator().RecordEndOfFrameMetrics(start_time, Now());
// We should have a sample after the very first step, regardless of the
// interval. The FirstFrameIsRecorded test above also tests this.
float expected_percentage =
floor(millisecond_per_step * 100.0 / (float)millisecond_per_frame);
VerifyEntries(1u, millisecond_per_frame, millisecond_per_step,
expected_percentage);
// Another step does not get us past the sample interval.
start_time = Now();
aggregator().BeginMainFrame();
for (int i = 0; i < LocalFrameUkmAggregator::kCount; ++i) {
auto timer = aggregator().GetScopedTimer(i);
test_task_runner_->FastForwardBy(
base::TimeDelta::FromMilliseconds(millisecond_per_step));
}
test_task_runner_->FastForwardBy(
base::TimeDelta::FromMilliseconds(millisecond_per_step));
aggregator().RecordEndOfFrameMetrics(start_time, Now());
VerifyEntries(1u, millisecond_per_frame, millisecond_per_step,
expected_percentage);
// Another step should tick us past the sample interval.
// Note that the sample is a single frame, so even if we've taken
// multiple steps we should see just one frame's time.
start_time = Now();
aggregator().BeginMainFrame();
for (int i = 0; i < LocalFrameUkmAggregator::kCount; ++i) {
auto timer = aggregator().GetScopedTimer(i);
test_task_runner_->FastForwardBy(
base::TimeDelta::FromMilliseconds(millisecond_per_step));
}
test_task_runner_->FastForwardBy(
base::TimeDelta::FromMilliseconds(millisecond_per_step));
aggregator().RecordEndOfFrameMetrics(start_time, Now());
VerifyEntries(2u, millisecond_per_frame, millisecond_per_step,
expected_percentage);
// Step one more frame so we don't sample again.
start_time = Now();
aggregator().BeginMainFrame();
for (int i = 0; i < LocalFrameUkmAggregator::kCount; ++i) {
auto timer = aggregator().GetScopedTimer(i);
test_task_runner_->FastForwardBy(
base::TimeDelta::FromMilliseconds(millisecond_per_step));
}
test_task_runner_->FastForwardBy(
base::TimeDelta::FromMilliseconds(millisecond_per_step));
aggregator().RecordEndOfFrameMetrics(start_time, Now());
// Should be no more samples.
VerifyEntries(2u, millisecond_per_frame, millisecond_per_step,
expected_percentage);
// And one more step to generate one more sample
start_time = Now();
aggregator().BeginMainFrame();
for (int i = 0; i < LocalFrameUkmAggregator::kCount; ++i) {
auto timer = aggregator().GetScopedTimer(i);
test_task_runner_->FastForwardBy(
base::TimeDelta::FromMilliseconds(millisecond_per_step));
}
test_task_runner_->FastForwardBy(
base::TimeDelta::FromMilliseconds(millisecond_per_step));
aggregator().RecordEndOfFrameMetrics(start_time, Now());
// We should have 3 more events, once for the prior interval and 2 for the
// new interval.
VerifyEntries(3u, millisecond_per_frame, millisecond_per_step,
expected_percentage);
}
TEST_F(LocalFrameUkmAggregatorTest, LatencyDataIsPopulated) {
// Although the tests use a mock clock, the UKM aggregator checks if the
// system has a high resolution clock before recording results. As a result,
// the tests will fail if the system does not have a high resolution clock.
if (!base::TimeTicks::IsHighResolution())
return;
// The initial interval is always zero, so we should see one set of metrics
// for the initial frame, regardless of the initial interval.
FramesToNextEventForTest(1);
unsigned millisecond_for_step = 1;
aggregator().BeginMainFrame();
for (int i = 0; i < LocalFrameUkmAggregator::kCount; ++i) {
auto timer =
aggregator().GetScopedTimer(i % LocalFrameUkmAggregator::kCount);
test_task_runner_->FastForwardBy(
base::TimeDelta::FromMilliseconds(millisecond_for_step));
}
// Need to populate before the end of the frame.
std::unique_ptr<cc::BeginMainFrameMetrics> metrics_data =
aggregator().GetBeginMainFrameMetrics();
EXPECT_EQ(metrics_data->handle_input_events.InMillisecondsF(),
millisecond_for_step);
EXPECT_EQ(metrics_data->animate.InMillisecondsF(), millisecond_for_step);
EXPECT_EQ(metrics_data->style_update.InMillisecondsF(), millisecond_for_step);
EXPECT_EQ(metrics_data->layout_update.InMillisecondsF(),
millisecond_for_step);
EXPECT_EQ(metrics_data->prepaint.InMillisecondsF(), millisecond_for_step);
EXPECT_EQ(metrics_data->composite.InMillisecondsF(), millisecond_for_step);
EXPECT_EQ(metrics_data->paint.InMillisecondsF(), millisecond_for_step);
EXPECT_EQ(metrics_data->scrolling_coordinator.InMillisecondsF(),
millisecond_for_step);
EXPECT_EQ(metrics_data->composite_commit.InMillisecondsF(),
millisecond_for_step);
// Do not check the value in metrics_data.update_layers because it
// is not set by the aggregator.
}
} // namespace blink