blob: 91595fd2e27486091b7d93adacda025cae1125f1 [file] [log] [blame]
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/scheduler/responsiveness/calculator.h"
#include <optional>
#include "base/test/metrics/histogram_tester.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "content/public/browser/responsiveness_calculator_delegate.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace responsiveness {
using CongestionType = Calculator::CongestionType;
using StartupStage = Calculator::StartupStage;
using ::testing::_;
namespace {
// Copied from calculator.cc.
constexpr int kMeasurementIntervalInMs = 30 * 1000;
constexpr int kCongestionThresholdInMs = 100;
class FakeCalculator : public Calculator {
public:
using Calculator::Calculator;
MOCK_METHOD3(EmitResponsivenessMock,
void(CongestionType congestion_type,
size_t congested_slices,
StartupStage startup_stage));
void EmitResponsiveness(CongestionType congestion_type,
size_t congested_slices,
StartupStage startup_stage,
uint64_t event_id) override {
EmitResponsivenessMock(congestion_type, congested_slices, startup_stage);
// Emit the histograms anyways for verification in some tests.
Calculator::EmitResponsiveness(congestion_type, congested_slices,
startup_stage, event_id);
}
MOCK_METHOD5(EmitCongestedIntervalsMeasurementTraceEvent,
void(StartupStage startup_stage,
base::TimeTicks start_time,
base::TimeTicks end_time,
size_t num_congested_slices,
uint64_t event_id));
MOCK_METHOD3(EmitCongestedIntervalTraceEvent,
void(CongestionType congestion_type,
base::TimeTicks start_time,
base::TimeTicks end_time));
using Calculator::EmitResponsivenessTraceEvents;
using Calculator::GetLastCalculationTime;
};
} // namespace
class MockDelegate : public ResponsivenessCalculatorDelegate {
public:
MockDelegate() = default;
~MockDelegate() override = default;
// ResponsivenessCalculatorDelegate:
MOCK_METHOD(void, OnMeasurementIntervalEnded, (), (override));
MOCK_METHOD(void,
OnResponsivenessEmitted,
(int sample,
int min,
int exclusive_max,
size_t buckets),
(override));
};
class ResponsivenessCalculatorTest : public testing::Test {
public:
void SetUp() override {
auto delegate = std::make_unique<
testing::NiceMock<MockDelegate>>(); // NiceMock because the delegate is
// only verified in one test.
delegate_ = delegate.get();
calculator_ = std::make_unique<testing::StrictMock<FakeCalculator>>(
std::move(delegate));
last_calculation_time_ = calculator_->GetLastCalculationTime();
#if BUILDFLAG(IS_ANDROID)
base::android::ApplicationStatusListener::NotifyApplicationStateChange(
base::android::APPLICATION_STATE_HAS_RUNNING_ACTIVITIES);
base::RunLoop().RunUntilIdle();
#endif
}
void AddEventUI(int queue_time_in_ms,
int execution_start_time_in_ms,
int execution_finish_time_in_ms) {
calculator_->TaskOrEventFinishedOnUIThread(
last_calculation_time_ + base::Milliseconds(queue_time_in_ms),
last_calculation_time_ + base::Milliseconds(execution_start_time_in_ms),
last_calculation_time_ +
base::Milliseconds(execution_finish_time_in_ms));
}
void AddEventIO(int queue_time_in_ms,
int execution_start_time_in_ms,
int execution_finish_time_in_ms) {
calculator_->TaskOrEventFinishedOnIOThread(
last_calculation_time_ + base::Milliseconds(queue_time_in_ms),
last_calculation_time_ + base::Milliseconds(execution_start_time_in_ms),
last_calculation_time_ +
base::Milliseconds(execution_finish_time_in_ms));
}
void TriggerCalculation() {
AddEventUI(kMeasurementIntervalInMs + 1, kMeasurementIntervalInMs + 1,
kMeasurementIntervalInMs + 1);
last_calculation_time_ = calculator_->GetLastCalculationTime();
}
protected:
// This member sets up BrowserThread::IO and BrowserThread::UI. It must be the
// first member, as other members may depend on these abstractions.
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<FakeCalculator> calculator_;
raw_ptr<MockDelegate> delegate_;
base::TimeTicks last_calculation_time_;
};
#define EXPECT_EXECUTION_CONGESTED_SLICES(num_slices, phase) \
EXPECT_CALL(*calculator_, \
EmitResponsivenessMock(CongestionType::kExecutionOnly, \
num_slices, phase));
#define EXPECT_CONGESTED_SLICES(num_slices, phase) \
EXPECT_CALL(*calculator_, \
EmitResponsivenessMock(CongestionType::kQueueAndExecution, \
num_slices, phase));
// A single event executing slightly longer than kCongestionThresholdInMs.
TEST_F(ResponsivenessCalculatorTest, ShortExecutionCongestion) {
constexpr int kQueueTime = 35;
constexpr int kStartTime = 40;
constexpr int kFinishTime = kStartTime + kCongestionThresholdInMs + 5;
AddEventUI(kQueueTime, kStartTime, kFinishTime);
EXPECT_EXECUTION_CONGESTED_SLICES(1u, StartupStage::kFirstInterval);
EXPECT_CONGESTED_SLICES(1u, StartupStage::kFirstInterval);
TriggerCalculation();
}
// A single event queued slightly longer than kCongestionThresholdInMs.
TEST_F(ResponsivenessCalculatorTest, ShortQueueCongestion) {
constexpr int kQueueTime = 35;
constexpr int kStartTime = kQueueTime + kCongestionThresholdInMs + 5;
constexpr int kFinishTime = kStartTime + 5;
AddEventUI(kQueueTime, kStartTime, kFinishTime);
EXPECT_EXECUTION_CONGESTED_SLICES(0u, StartupStage::kFirstInterval);
EXPECT_CONGESTED_SLICES(1u, StartupStage::kFirstInterval);
TriggerCalculation();
}
// A single event whose queuing and execution time together take longer than
// kCongestionThresholdInMs.
TEST_F(ResponsivenessCalculatorTest, ShortCombinedQueueAndExecutionCongestion) {
constexpr int kQueueTime = 35;
constexpr int kStartTime = kQueueTime + (kCongestionThresholdInMs / 2);
constexpr int kFinishTime = kStartTime + (kCongestionThresholdInMs / 2) + 1;
AddEventUI(kQueueTime, kStartTime, kFinishTime);
EXPECT_EXECUTION_CONGESTED_SLICES(0u, StartupStage::kFirstInterval);
EXPECT_CONGESTED_SLICES(1u, StartupStage::kFirstInterval);
TriggerCalculation();
}
// A single event executing slightly longer than 10 * kCongestionThresholdInMs.
TEST_F(ResponsivenessCalculatorTest, LongExecutionCongestion) {
constexpr int kQueueTime = 35;
constexpr int kStartTime = 40;
constexpr int kFinishTime = kStartTime + 10 * kCongestionThresholdInMs + 5;
AddEventUI(kQueueTime, kStartTime, kFinishTime);
EXPECT_EXECUTION_CONGESTED_SLICES(10, StartupStage::kFirstInterval);
EXPECT_CONGESTED_SLICES(10u, StartupStage::kFirstInterval);
TriggerCalculation();
}
// A single event executing slightly longer than 10 * kCongestionThresholdInMs.
TEST_F(ResponsivenessCalculatorTest, LongQueueCongestion) {
constexpr int kQueueTime = 35;
constexpr int kStartTime = kQueueTime + 10 * kCongestionThresholdInMs + 5;
constexpr int kFinishTime = kStartTime + 5;
AddEventUI(kQueueTime, kStartTime, kFinishTime);
EXPECT_EXECUTION_CONGESTED_SLICES(0u, StartupStage::kFirstInterval);
EXPECT_CONGESTED_SLICES(10u, StartupStage::kFirstInterval);
TriggerCalculation();
}
// Events that execute in less than 100ms are not congested, regardless of start
// time.
TEST_F(ResponsivenessCalculatorTest, NoExecutionCongestion) {
int base_time = 30;
for (int i = 0; i < kCongestionThresholdInMs; ++i) {
AddEventUI(base_time, base_time, base_time + i);
}
base_time += kCongestionThresholdInMs;
for (int i = 0; i < kCongestionThresholdInMs; ++i) {
AddEventUI(base_time + i, base_time + i, base_time + 2 * i);
}
EXPECT_EXECUTION_CONGESTED_SLICES(0u, StartupStage::kFirstInterval);
EXPECT_CONGESTED_SLICES(0u, StartupStage::kFirstInterval);
TriggerCalculation();
}
// Events that are queued and execute in less than 100ms are not congested,
// regardless of start time.
TEST_F(ResponsivenessCalculatorTest, NoQueueCongestion) {
int base_time = 30;
for (int i = 0; i < kCongestionThresholdInMs; ++i) {
AddEventUI(base_time, base_time + i, base_time + i);
}
base_time += kCongestionThresholdInMs;
for (int i = 0; i < kCongestionThresholdInMs; ++i) {
AddEventUI(base_time + i, base_time + 2 * i, base_time + 2 * i);
}
EXPECT_EXECUTION_CONGESTED_SLICES(0u, StartupStage::kFirstInterval);
EXPECT_CONGESTED_SLICES(0u, StartupStage::kFirstInterval);
TriggerCalculation();
}
// 10 execution congestion events, but very closely overlapping. Time slices are
// discretized and fixed, e.g. [0 100] [100 200] [200 300]. In this test, the
// events all start in the [0 100] slice and end in the [100 200] slice. All of
// them end up marking the [100 200] slice as congested.
TEST_F(ResponsivenessCalculatorTest, OverlappingExecutionCongestion) {
int base_time = 30;
for (int i = 0; i < 10; ++i) {
const int queue_time = base_time;
const int start_time = base_time;
const int finish_time = start_time + kCongestionThresholdInMs + i;
AddEventUI(queue_time, start_time, finish_time);
}
EXPECT_EXECUTION_CONGESTED_SLICES(1u, StartupStage::kFirstInterval);
EXPECT_CONGESTED_SLICES(1u, StartupStage::kFirstInterval);
TriggerCalculation();
}
// 10 queue congestion events, but very closely overlapping. Time slices are
// discretized and fixed, e.g. [0 100] [100 200] [200 300]. In this test, the
// events are all queued in the [0 100] slice and start executing in the [100
// 200] slice. All of them end up marking the [100 200] slice as congested.
TEST_F(ResponsivenessCalculatorTest, OverlappingQueueCongestion) {
int base_time = 30;
for (int i = 0; i < 10; ++i) {
const int queue_time = base_time;
const int start_time = base_time + kCongestionThresholdInMs + i;
const int finish_time = start_time + 1;
AddEventUI(queue_time, start_time, finish_time);
}
EXPECT_EXECUTION_CONGESTED_SLICES(0u, StartupStage::kFirstInterval);
EXPECT_CONGESTED_SLICES(1u, StartupStage::kFirstInterval);
TriggerCalculation();
}
// UI thread has 3 execution congestion events on slices 1, 2, 3
// IO thread has 3 execution congestion events on slices 3, 4, 5,
// There should be a total of 5 congestion events.
TEST_F(ResponsivenessCalculatorTest,
OverlappingExecutionCongestionMultipleThreads) {
int base_time = 105;
for (int i = 0; i < 3; ++i) {
const int queue_time = base_time + i * kCongestionThresholdInMs;
const int start_time = queue_time;
const int finish_time = start_time + kCongestionThresholdInMs + 10;
AddEventUI(queue_time, start_time, finish_time);
}
base_time = 305;
for (int i = 0; i < 3; ++i) {
const int queue_time = base_time + i * kCongestionThresholdInMs;
const int start_time = queue_time;
const int finish_time = start_time + kCongestionThresholdInMs + 10;
AddEventIO(queue_time, start_time, finish_time);
}
EXPECT_EXECUTION_CONGESTED_SLICES(5u, StartupStage::kFirstInterval);
EXPECT_CONGESTED_SLICES(5u, StartupStage::kFirstInterval);
TriggerCalculation();
}
// UI thread has 3 queue congestion events on slices 1, 2, 3
// IO thread has 3 queue congestion events on slices 3, 4, 5,
// There should be a total of 5 congestion events.
TEST_F(ResponsivenessCalculatorTest,
OverlappingQueueCongestionMultipleThreads) {
int base_time = 105;
for (int i = 0; i < 3; ++i) {
const int queue_time = base_time + i * kCongestionThresholdInMs;
const int start_time = queue_time + kCongestionThresholdInMs + 10;
const int finish_time = start_time;
AddEventUI(queue_time, start_time, finish_time);
}
base_time = 305;
for (int i = 0; i < 3; ++i) {
const int queue_time = base_time + i * kCongestionThresholdInMs;
const int start_time = queue_time + kCongestionThresholdInMs + 10;
const int finish_time = start_time;
AddEventIO(queue_time, start_time, finish_time);
}
EXPECT_EXECUTION_CONGESTED_SLICES(0u, StartupStage::kFirstInterval);
EXPECT_CONGESTED_SLICES(5u, StartupStage::kFirstInterval);
TriggerCalculation();
}
// Three execution congestions, each of length 2, separated by some shorter
// events.
TEST_F(ResponsivenessCalculatorTest, SeparatedExecutionCongestions) {
int base_time = 105;
for (int i = 0; i < 3; ++i) {
{
const int queue_time = base_time;
const int start_time = base_time;
const int finish_time = base_time + 1;
AddEventUI(queue_time, start_time, finish_time);
}
{
const int queue_time = base_time;
const int start_time = base_time;
const int finish_time = base_time + 2 * kCongestionThresholdInMs + 1;
AddEventUI(queue_time, start_time, finish_time);
}
base_time += 10 * kCongestionThresholdInMs;
}
EXPECT_EXECUTION_CONGESTED_SLICES(6u, StartupStage::kFirstInterval);
EXPECT_CONGESTED_SLICES(6u, StartupStage::kFirstInterval);
TriggerCalculation();
}
// Three queue congestions, each of length 2, separated by some shorter events.
TEST_F(ResponsivenessCalculatorTest, SeparatedQueueCongestions) {
int base_time = 105;
for (int i = 0; i < 3; ++i) {
{
const int queue_time = base_time;
const int start_time = base_time + 1;
const int finish_time = start_time;
AddEventUI(queue_time, start_time, finish_time);
}
{
const int queue_time = base_time;
const int start_time = base_time + 2 * kCongestionThresholdInMs + 1;
const int finish_time = start_time;
AddEventUI(queue_time, start_time, finish_time);
}
base_time += 10 * kCongestionThresholdInMs;
}
EXPECT_EXECUTION_CONGESTED_SLICES(0u, StartupStage::kFirstInterval);
EXPECT_CONGESTED_SLICES(6u, StartupStage::kFirstInterval);
TriggerCalculation();
}
TEST_F(ResponsivenessCalculatorTest, MultipleTrigger) {
int base_time = 105;
// 3 Congestions, then trigger, then repeat.
for (int i = 0; i < 10; ++i) {
for (int j = 0; j < 3; ++j) {
AddEventUI(base_time, base_time,
base_time + 3 * kCongestionThresholdInMs + 1);
base_time += 3 * kCongestionThresholdInMs;
}
EXPECT_EXECUTION_CONGESTED_SLICES(
9u, i == 0 ? StartupStage::kFirstInterval
: StartupStage::kFirstIntervalDoneWithoutFirstIdle);
EXPECT_CONGESTED_SLICES(
9u, i == 0 ? StartupStage::kFirstInterval
: StartupStage::kFirstIntervalDoneWithoutFirstIdle);
TriggerCalculation();
testing::Mock::VerifyAndClear(calculator_.get());
}
}
// A long delay means that the machine likely went to sleep.
TEST_F(ResponsivenessCalculatorTest, LongDelay) {
int base_time = 105;
AddEventUI(base_time, base_time,
base_time + 3 * kCongestionThresholdInMs + 1);
base_time += 10 * kMeasurementIntervalInMs;
AddEventUI(base_time, base_time, base_time + 1);
EXPECT_CALL(*calculator_, EmitResponsivenessMock(_, _, _)).Times(0);
}
// A long event means that the machine likely went to sleep.
TEST_F(ResponsivenessCalculatorTest, LongEvent) {
int base_time = 105;
AddEventUI(base_time, base_time, base_time + 10 * kMeasurementIntervalInMs);
EXPECT_CALL(*calculator_, EmitResponsivenessMock(_, _, _)).Times(0);
}
#if BUILDFLAG(IS_ANDROID)
// Metric should not be recorded when application is in background.
TEST_F(ResponsivenessCalculatorTest, ApplicationInBackground) {
constexpr int kQueueTime = 35;
constexpr int kStartTime = 40;
constexpr int kFinishTime = kStartTime + kCongestionThresholdInMs + 5;
AddEventUI(kQueueTime, kStartTime, kFinishTime);
base::android::ApplicationStatusListener::NotifyApplicationStateChange(
base::android::APPLICATION_STATE_HAS_STOPPED_ACTIVITIES);
base::RunLoop().RunUntilIdle();
AddEventUI(kQueueTime, kStartTime + 1, kFinishTime + 1);
EXPECT_CALL(*calculator_, EmitResponsivenessMock(_, _, _)).Times(0);
TriggerCalculation();
}
#endif
TEST_F(ResponsivenessCalculatorTest, StartupStages) {
constexpr int kQueueTime = 35;
constexpr int kStartTime = kQueueTime + 10 * kCongestionThresholdInMs + 5;
constexpr int kFinishTime = kStartTime + 5;
std::optional<base::HistogramTester> histograms;
// Queue congestion event during the first kMeasurementInterval.
AddEventUI(kQueueTime, kStartTime, kFinishTime);
EXPECT_CALL(*calculator_,
EmitResponsivenessMock(CongestionType::kExecutionOnly, 0,
StartupStage::kFirstInterval));
EXPECT_CALL(*calculator_,
EmitResponsivenessMock(CongestionType::kQueueAndExecution, 10u,
StartupStage::kFirstInterval));
histograms.emplace();
TriggerCalculation();
histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.RunningOnly", 0,
1);
histograms->ExpectUniqueSample(
"Browser.MainThreadsCongestion.RunningOnly.Initial", 0, 1);
histograms->ExpectTotalCount(
"Browser.MainThreadsCongestion.RunningOnly.Periodic", 0);
histograms->ExpectTotalCount("Browser.MainThreadsCongestion", 0);
histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Initial", 0);
histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Periodic", 0);
// Queue congestion event during a few kMeasurementInterval (without having
// seen OnFirstIdle()). Neither .Initial nor .Periodic
AddEventUI(kQueueTime, kStartTime, kFinishTime);
EXPECT_CALL(
*calculator_,
EmitResponsivenessMock(CongestionType::kExecutionOnly, 0,
StartupStage::kFirstIntervalDoneWithoutFirstIdle));
EXPECT_CALL(
*calculator_,
EmitResponsivenessMock(CongestionType::kQueueAndExecution, 10u,
StartupStage::kFirstIntervalDoneWithoutFirstIdle));
histograms.emplace();
TriggerCalculation();
histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.RunningOnly", 0,
1);
histograms->ExpectTotalCount(
"Browser.MainThreadsCongestion.RunningOnly.Initial", 0);
histograms->ExpectTotalCount(
"Browser.MainThreadsCongestion.RunningOnly.Periodic", 0);
histograms->ExpectTotalCount("Browser.MainThreadsCongestion", 0);
histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Initial", 0);
histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Periodic", 0);
AddEventUI(kQueueTime, kStartTime, kFinishTime);
EXPECT_CALL(
*calculator_,
EmitResponsivenessMock(CongestionType::kExecutionOnly, 0,
StartupStage::kFirstIntervalDoneWithoutFirstIdle));
EXPECT_CALL(
*calculator_,
EmitResponsivenessMock(CongestionType::kQueueAndExecution, 10u,
StartupStage::kFirstIntervalDoneWithoutFirstIdle));
histograms.emplace();
TriggerCalculation();
histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.RunningOnly", 0,
1);
histograms->ExpectTotalCount(
"Browser.MainThreadsCongestion.RunningOnly.Initial", 0);
histograms->ExpectTotalCount(
"Browser.MainThreadsCongestion.RunningOnly.Periodic", 0);
histograms->ExpectTotalCount("Browser.MainThreadsCongestion", 0);
histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Initial", 0);
histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Periodic", 0);
// OnFirstIdle() eventually during a kMeasurementInterval. Same as above, last
// one of these.
AddEventUI(kQueueTime, kStartTime, kFinishTime);
calculator_->OnFirstIdle();
EXPECT_CALL(
*calculator_,
EmitResponsivenessMock(CongestionType::kExecutionOnly, 0,
StartupStage::kFirstIntervalDoneWithoutFirstIdle));
EXPECT_CALL(
*calculator_,
EmitResponsivenessMock(CongestionType::kQueueAndExecution, 10u,
StartupStage::kFirstIntervalDoneWithoutFirstIdle));
histograms.emplace();
TriggerCalculation();
histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.RunningOnly", 0,
1);
histograms->ExpectTotalCount(
"Browser.MainThreadsCongestion.RunningOnly.Initial", 0);
histograms->ExpectTotalCount(
"Browser.MainThreadsCongestion.RunningOnly.Periodic", 0);
histograms->ExpectTotalCount("Browser.MainThreadsCongestion", 0);
histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Initial", 0);
histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Periodic", 0);
// Events in intervals after OnFirstIdle(). congested3.Initial still no
// .Periodic.
AddEventUI(kQueueTime, kStartTime, kFinishTime);
EXPECT_CALL(*calculator_, EmitResponsivenessMock(
CongestionType::kExecutionOnly, 0,
StartupStage::kFirstIntervalAfterFirstIdle));
EXPECT_CALL(*calculator_, EmitResponsivenessMock(
CongestionType::kQueueAndExecution, 10u,
StartupStage::kFirstIntervalAfterFirstIdle));
histograms.emplace();
TriggerCalculation();
histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.RunningOnly", 0,
1);
histograms->ExpectTotalCount(
"Browser.MainThreadsCongestion.RunningOnly.Initial", 0);
histograms->ExpectTotalCount(
"Browser.MainThreadsCongestion.RunningOnly.Periodic", 0);
histograms->ExpectUniqueSample("Browser.MainThreadsCongestion", 10, 1);
histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.Initial", 10,
1);
histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Periodic", 0);
AddEventUI(kQueueTime, kStartTime, kFinishTime);
EXPECT_CALL(*calculator_,
EmitResponsivenessMock(CongestionType::kExecutionOnly, 0,
StartupStage::kPeriodic));
EXPECT_CALL(*calculator_,
EmitResponsivenessMock(CongestionType::kQueueAndExecution, 10u,
StartupStage::kPeriodic));
histograms.emplace();
TriggerCalculation();
histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.RunningOnly", 0,
1);
histograms->ExpectTotalCount(
"Browser.MainThreadsCongestion.RunningOnly.Initial", 0);
histograms->ExpectUniqueSample(
"Browser.MainThreadsCongestion.RunningOnly.Periodic", 0, 1);
histograms->ExpectUniqueSample("Browser.MainThreadsCongestion", 10, 1);
histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Initial", 0);
histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.Periodic", 10,
1);
}
TEST_F(ResponsivenessCalculatorTest, FastStartupStages) {
constexpr int kQueueTime = 35;
constexpr int kStartTime = kQueueTime + 10 * kCongestionThresholdInMs + 5;
constexpr int kFinishTime = kStartTime + 5;
std::optional<base::HistogramTester> histograms;
// OnFirstIdle() right away during the first kMeasurementInterval. Still
// considered as kFirstInterval, but second interval will go straight
// to kFirstIntervalAfterFirstIdle.
AddEventUI(kQueueTime, kStartTime, kFinishTime);
calculator_->OnFirstIdle();
EXPECT_CALL(*calculator_,
EmitResponsivenessMock(CongestionType::kExecutionOnly, 0,
StartupStage::kFirstInterval));
EXPECT_CALL(*calculator_,
EmitResponsivenessMock(CongestionType::kQueueAndExecution, 10u,
StartupStage::kFirstInterval));
histograms.emplace();
TriggerCalculation();
histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.RunningOnly", 0,
1);
histograms->ExpectUniqueSample(
"Browser.MainThreadsCongestion.RunningOnly.Initial", 0, 1);
histograms->ExpectTotalCount(
"Browser.MainThreadsCongestion.RunningOnly.Periodic", 0);
histograms->ExpectTotalCount("Browser.MainThreadsCongestion", 0);
histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Initial", 0);
histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Periodic", 0);
AddEventUI(kQueueTime, kStartTime, kFinishTime);
EXPECT_CALL(*calculator_, EmitResponsivenessMock(
CongestionType::kExecutionOnly, 0,
StartupStage::kFirstIntervalAfterFirstIdle));
EXPECT_CALL(*calculator_, EmitResponsivenessMock(
CongestionType::kQueueAndExecution, 10u,
StartupStage::kFirstIntervalAfterFirstIdle));
histograms.emplace();
TriggerCalculation();
histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.RunningOnly", 0,
1);
histograms->ExpectTotalCount(
"Browser.MainThreadsCongestion.RunningOnly.Initial", 0);
histograms->ExpectTotalCount(
"Browser.MainThreadsCongestion.RunningOnly.Periodic", 0);
histograms->ExpectUniqueSample("Browser.MainThreadsCongestion", 10, 1);
histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.Initial", 10,
1);
histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Periodic", 0);
AddEventUI(kQueueTime, kStartTime, kFinishTime);
EXPECT_CALL(*calculator_,
EmitResponsivenessMock(CongestionType::kExecutionOnly, 0,
StartupStage::kPeriodic));
EXPECT_CALL(*calculator_,
EmitResponsivenessMock(CongestionType::kQueueAndExecution, 10u,
StartupStage::kPeriodic));
histograms.emplace();
TriggerCalculation();
histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.RunningOnly", 0,
1);
histograms->ExpectTotalCount(
"Browser.MainThreadsCongestion.RunningOnly.Initial", 0);
histograms->ExpectUniqueSample(
"Browser.MainThreadsCongestion.RunningOnly.Periodic", 0, 1);
histograms->ExpectUniqueSample("Browser.MainThreadsCongestion", 10, 1);
histograms->ExpectTotalCount("Browser.MainThreadsCongestion.Initial", 0);
histograms->ExpectUniqueSample("Browser.MainThreadsCongestion.Periodic", 10,
1);
}
// An event execution that crosses a measurement interval boundary should count
// towards both measurement intervals.
TEST_F(ResponsivenessCalculatorTest, ExecutionCrossesBoundary) {
// Dummy event so that Calculator doesn't think the process is suspended.
{
const int kTime = 0.5 * kMeasurementIntervalInMs;
AddEventUI(kTime, kTime, kTime);
}
// The event goes from [29801, 30150]. It should count as 1 congestion in the
// first measurement interval and 2 in the second.
{
EXPECT_EXECUTION_CONGESTED_SLICES(1u, StartupStage::kFirstInterval);
EXPECT_CONGESTED_SLICES(1u, StartupStage::kFirstInterval);
const int queue_time =
kMeasurementIntervalInMs - 2 * kCongestionThresholdInMs + 1;
const int start_time = queue_time;
const int finish_time =
kMeasurementIntervalInMs + 1.5 * kCongestionThresholdInMs;
AddEventUI(queue_time, start_time, finish_time);
}
// Dummy event so that Calculator doesn't think the process is suspended.
{
const int kTime = 1.5 * kMeasurementIntervalInMs;
AddEventUI(kTime, kTime, kTime);
}
// Trigger another calculation.
EXPECT_EXECUTION_CONGESTED_SLICES(
2u, StartupStage::kFirstIntervalDoneWithoutFirstIdle);
EXPECT_CONGESTED_SLICES(2u, StartupStage::kFirstIntervalDoneWithoutFirstIdle);
const int kTime = 2 * kMeasurementIntervalInMs + 1;
AddEventUI(kTime, kTime, kTime);
}
// An event queuing that crosses a measurement interval boundary should count
// towards both measurement intervals.
TEST_F(ResponsivenessCalculatorTest, QueuingCrossesBoundary) {
// Dummy event so that Calculator doesn't think the process is suspended.
{
const int kTime = 0.5 * kMeasurementIntervalInMs;
AddEventUI(kTime, kTime, kTime);
}
// The event goes from [29801, 30150]. It should count as 1 congestion in the
// first measurement interval and 2 in the second.
{
EXPECT_EXECUTION_CONGESTED_SLICES(0u, StartupStage::kFirstInterval);
EXPECT_CONGESTED_SLICES(1u, StartupStage::kFirstInterval);
const int queue_time =
kMeasurementIntervalInMs - 2 * kCongestionThresholdInMs + 1;
const int start_time =
kMeasurementIntervalInMs + 1.5 * kCongestionThresholdInMs;
const int finish_time = start_time;
AddEventUI(queue_time, start_time, finish_time);
}
// Dummy event so that Calculator doesn't think the process is suspended.
{
const int kTime = 1.5 * kMeasurementIntervalInMs;
AddEventUI(kTime, kTime, kTime);
}
// Trigger another calculation.
EXPECT_EXECUTION_CONGESTED_SLICES(
0u, StartupStage::kFirstIntervalDoneWithoutFirstIdle);
EXPECT_CONGESTED_SLICES(2u, StartupStage::kFirstIntervalDoneWithoutFirstIdle);
const int kTime = 2 * kMeasurementIntervalInMs + 1;
AddEventUI(kTime, kTime, kTime);
}
// Events may not be ordered by start or end time.
TEST_F(ResponsivenessCalculatorTest, UnorderedEvents) {
// We add the following tasks:
// [100, 100, 250]
// [150, 150, 300]
// [50, 50, 200]
// [50, 50, 390] <- A
//
// [1100, 1250, 1251]
// [1150, 1300, 1301]
// [1050, 1200, 1201]
// [1050, 1390, 1391] <- B
//
// The execution congestion in A subsumes all other execution congestions. The
// queue congestion in B subsumes all other queue congestions.
AddEventUI(100, 100, 250);
AddEventUI(150, 150, 300);
AddEventUI(50, 50, 200);
AddEventUI(50, 50, 390);
AddEventUI(1100, 1250, 1251);
AddEventUI(1150, 1300, 1301);
AddEventUI(1050, 1200, 1201);
AddEventUI(1050, 1390, 1391);
EXPECT_EXECUTION_CONGESTED_SLICES(3u, StartupStage::kFirstInterval);
EXPECT_CONGESTED_SLICES(6u, StartupStage::kFirstInterval);
TriggerCalculation();
}
TEST_F(ResponsivenessCalculatorTest, EmitResponsivenessTraceEventsEmpty) {
constexpr base::TimeTicks kStartTime = base::TimeTicks();
constexpr base::TimeTicks kFinishTime =
kStartTime + base::Milliseconds(kMeasurementIntervalInMs);
const std::set<int> congested_slices;
uint64_t event_id = 42;
EXPECT_CALL(*calculator_, EmitCongestedIntervalsMeasurementTraceEvent(
StartupStage::kPeriodic, _, _, _, event_id))
.Times(0);
calculator_->EmitResponsivenessTraceEvents(
CongestionType::kQueueAndExecution, StartupStage::kPeriodic, kStartTime,
kFinishTime, congested_slices, event_id);
}
TEST_F(ResponsivenessCalculatorTest,
EmitResponsivenessTraceEventsExecutionOnly) {
constexpr base::TimeDelta kSliceInterval =
base::Milliseconds(kCongestionThresholdInMs);
constexpr base::TimeTicks kStartTime = base::TimeTicks();
constexpr base::TimeTicks kFinishTime =
kStartTime + base::Milliseconds(kMeasurementIntervalInMs);
const std::set<int> congested_slices = {1};
uint64_t event_id = 42;
EXPECT_CALL(*calculator_, EmitCongestedIntervalsMeasurementTraceEvent(
StartupStage::kPeriodic, _, _, _, event_id))
.Times(0);
EXPECT_CALL(*calculator_,
EmitCongestedIntervalTraceEvent(CongestionType::kExecutionOnly,
kStartTime + 1 * kSliceInterval,
kStartTime + 2 * kSliceInterval));
calculator_->EmitResponsivenessTraceEvents(
CongestionType::kExecutionOnly, StartupStage::kPeriodic, kStartTime,
kFinishTime, congested_slices, event_id);
}
TEST_F(ResponsivenessCalculatorTest, EmitResponsivenessTraceEvents) {
constexpr base::TimeDelta kSliceInterval =
base::Milliseconds(kCongestionThresholdInMs);
constexpr base::TimeTicks kStartTime = base::TimeTicks();
constexpr base::TimeTicks kFinishTime =
kStartTime + base::Milliseconds(kMeasurementIntervalInMs);
const std::set<int> congested_slices = {3, 4, 5, 12, 15};
uint64_t event_id = 42;
EXPECT_CALL(*calculator_,
EmitCongestedIntervalsMeasurementTraceEvent(
StartupStage::kPeriodic, kStartTime, kFinishTime, 5, 42));
EXPECT_CALL(*calculator_, EmitCongestedIntervalTraceEvent(
CongestionType::kQueueAndExecution,
kStartTime + 3 * kSliceInterval,
kStartTime + 6 * kSliceInterval));
EXPECT_CALL(*calculator_, EmitCongestedIntervalTraceEvent(
CongestionType::kQueueAndExecution,
kStartTime + 12 * kSliceInterval,
kStartTime + 13 * kSliceInterval));
EXPECT_CALL(*calculator_, EmitCongestedIntervalTraceEvent(
CongestionType::kQueueAndExecution,
kStartTime + 15 * kSliceInterval,
kStartTime + 16 * kSliceInterval));
calculator_->EmitResponsivenessTraceEvents(
CongestionType::kQueueAndExecution, StartupStage::kPeriodic, kStartTime,
kFinishTime, congested_slices, event_id);
}
TEST_F(ResponsivenessCalculatorTest, Delegate) {
calculator_->OnFirstIdle();
// To have a valid interval, there needs to be at least 30 seconds that
// passed, during which there was at least one event.
int interval_start = 0;
int interval_mid = 15000;
int interval_end = kMeasurementIntervalInMs + 1000;
EXPECT_EXECUTION_CONGESTED_SLICES(1u, StartupStage::kFirstInterval);
EXPECT_CONGESTED_SLICES(1u, StartupStage::kFirstInterval);
// OnResponsivenessEmitted() is not invoked for the first interval.
EXPECT_CALL(*delegate_, OnMeasurementIntervalEnded());
EXPECT_CALL(*delegate_, OnResponsivenessEmitted(_, _, _, _)).Times(0);
AddEventUI(interval_start, interval_start, interval_start);
AddEventUI(interval_mid, interval_mid,
interval_mid + kCongestionThresholdInMs + 20);
AddEventUI(interval_end, interval_end, interval_end);
// Repeat the same interval immediately following the first one.
interval_start += interval_end;
interval_mid += interval_end;
interval_end += interval_end;
EXPECT_EXECUTION_CONGESTED_SLICES(1u,
StartupStage::kFirstIntervalAfterFirstIdle);
EXPECT_CONGESTED_SLICES(1u, StartupStage::kFirstIntervalAfterFirstIdle);
// OnResponsivenessEmitted() is invoked for subsequent intervals.
{
testing::InSequence in_sequence;
EXPECT_CALL(*delegate_, OnMeasurementIntervalEnded());
EXPECT_CALL(*delegate_, OnResponsivenessEmitted(_, _, _, _));
}
AddEventUI(interval_start, interval_start, interval_start);
AddEventUI(interval_mid, interval_mid,
interval_mid + kCongestionThresholdInMs + 20);
AddEventUI(interval_end, interval_end, interval_end);
}
} // namespace responsiveness
} // namespace content