blob: 07745b7cd01185ca31f8876072bed1845be5506f [file] [log] [blame]
// Copyright (c) 2019 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 "gpu/ipc/service/gpu_watchdog_thread_v2.h"
#include "base/power_monitor/power_monitor.h"
#include "base/power_monitor/power_monitor_source.h"
#include "base/test/power_monitor_test_base.h"
#include "base/threading/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace gpu {
namespace {
constexpr auto kGpuWatchdogTimeout = base::TimeDelta::FromMilliseconds(1000);
// This task will run for duration_ms milliseconds.
void SimpleTask(base::TimeDelta duration) {
base::PlatformThread::Sleep(duration);
}
} // namespace
class GpuWatchdogTest : public testing::Test {
public:
GpuWatchdogTest() {}
void LongTaskWithReportProgress(base::TimeDelta duration,
base::TimeDelta report_delta);
void LongTaskFromBackgroundToForeground(
base::TimeDelta duration,
base::TimeDelta time_to_switch_to_foreground);
// Implements testing::Test
void SetUp() override;
protected:
~GpuWatchdogTest() override = default;
base::MessageLoop main_loop;
base::RunLoop run_loop;
std::unique_ptr<gpu::GpuWatchdogThread> watchdog_thread_;
};
class GpuWatchdogPowerTest : public GpuWatchdogTest {
public:
GpuWatchdogPowerTest() {}
void LongTaskOnResume(base::TimeDelta duration,
base::TimeDelta time_to_power_resume);
// Implements testing::Test
void SetUp() override;
void TearDown() override;
protected:
~GpuWatchdogPowerTest() override = default;
base::PowerMonitorTestSource* power_monitor_source_ = nullptr;
};
void GpuWatchdogTest::SetUp() {
ASSERT_TRUE(base::ThreadTaskRunnerHandle::IsSet());
ASSERT_TRUE(base::MessageLoopCurrent::IsSet());
// Set watchdog timeout to 1000 milliseconds
watchdog_thread_ = gpu::GpuWatchdogThreadImplV2::Create(
/*start_backgrounded*/ false,
/*timeout*/ kGpuWatchdogTimeout,
/*test_mode*/ true);
}
void GpuWatchdogPowerTest::SetUp() {
GpuWatchdogTest::SetUp();
// Report GPU init complete.
watchdog_thread_->OnInitComplete();
// Create a power monitor test source.
auto power_monitor_source = std::make_unique<base::PowerMonitorTestSource>();
power_monitor_source_ = power_monitor_source.get();
base::PowerMonitor::Initialize(std::move(power_monitor_source));
watchdog_thread_->AddPowerObserver();
// Wait until the power observer is added on the watchdog thread
watchdog_thread_->WaitForPowerObserverAddedForTesting();
}
void GpuWatchdogPowerTest::TearDown() {
GpuWatchdogTest::TearDown();
watchdog_thread_.reset();
base::PowerMonitor::ShutdownForTesting();
}
// This task will run for duration_ms milliseconds. It will also call watchdog
// ReportProgress() every report_delta_ms milliseconds.
void GpuWatchdogTest::LongTaskWithReportProgress(base::TimeDelta duration,
base::TimeDelta report_delta) {
base::TimeTicks start = base::TimeTicks::Now();
base::TimeTicks end;
do {
base::PlatformThread::Sleep(report_delta);
watchdog_thread_->ReportProgress();
end = base::TimeTicks::Now();
} while (end - start <= duration);
}
void GpuWatchdogTest::LongTaskFromBackgroundToForeground(
base::TimeDelta duration,
base::TimeDelta time_to_switch_to_foreground) {
// Chrome is running in the background first.
watchdog_thread_->OnBackgrounded();
base::PlatformThread::Sleep(time_to_switch_to_foreground);
// Now switch Chrome to the foreground after the specified time
watchdog_thread_->OnForegrounded();
base::PlatformThread::Sleep(duration - time_to_switch_to_foreground);
}
void GpuWatchdogPowerTest::LongTaskOnResume(
base::TimeDelta duration,
base::TimeDelta time_to_power_resume) {
// Stay in power suspension mode first.
power_monitor_source_->GenerateSuspendEvent();
base::PlatformThread::Sleep(time_to_power_resume);
// Now wake up on power resume.
power_monitor_source_->GenerateResumeEvent();
// Continue the GPU task for the remaining time.
base::PlatformThread::Sleep(duration - time_to_power_resume);
}
// GPU Hang In Initialization
TEST_F(GpuWatchdogTest, GpuInitializationHang) {
// Gpu init (5000 ms) takes longer than timeout (2000 ms).
SimpleTask(kGpuWatchdogTimeout * kGpuWatchdogInitFactor +
base::TimeDelta::FromMilliseconds(3000));
// Gpu hangs. OnInitComplete() is not called
bool result = watchdog_thread_->IsGpuHangDetectedForTesting();
EXPECT_TRUE(result);
}
// Normal GPU Initialization and Running Task
TEST_F(GpuWatchdogTest, GpuInitializationAndRunningTasks) {
// Assume GPU initialization takes 300 milliseconds.
SimpleTask(base::TimeDelta::FromMilliseconds(300));
watchdog_thread_->OnInitComplete();
// Start running GPU tasks. Watchdog function WillProcessTask(),
// DidProcessTask() and ReportProgress() are tested.
main_loop.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&SimpleTask, base::TimeDelta::FromMilliseconds(500)));
main_loop.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&SimpleTask, base::TimeDelta::FromMilliseconds(500)));
// This long task takes 3000 milliseconds to finish, longer than timeout.
// But it reports progress every 500 milliseconds
main_loop.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(
&GpuWatchdogTest::LongTaskWithReportProgress, base::Unretained(this),
kGpuWatchdogTimeout + base::TimeDelta::FromMilliseconds(2000),
base::TimeDelta::FromMilliseconds(500)));
main_loop.task_runner()->PostTask(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
// Everything should be fine. No GPU hang detected.
bool result = watchdog_thread_->IsGpuHangDetectedForTesting();
EXPECT_FALSE(result);
}
// GPU Hang when running a task
TEST_F(GpuWatchdogTest, GpuRunningATaskHang) {
// Report gpu init complete
watchdog_thread_->OnInitComplete();
// Start running a GPU task. This long task takes 6000 milliseconds to finish.
main_loop.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&SimpleTask, kGpuWatchdogTimeout * 2 +
base::TimeDelta::FromMilliseconds(4000)));
main_loop.task_runner()->PostTask(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
// This GPU task takes too long. A GPU hang should be detected.
bool result = watchdog_thread_->IsGpuHangDetectedForTesting();
EXPECT_TRUE(result);
}
TEST_F(GpuWatchdogTest, ChromeInBackground) {
// Chrome starts in the background.
watchdog_thread_->OnBackgrounded();
// Gpu init (3000 ms) takes longer than timeout (2000 ms).
SimpleTask(kGpuWatchdogTimeout * kGpuWatchdogInitFactor +
base::TimeDelta::FromMilliseconds(1000));
// Report GPU init complete.
watchdog_thread_->OnInitComplete();
// Run a task that takes longer (3000 milliseconds) than timeout.
main_loop.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&SimpleTask, kGpuWatchdogTimeout * 2 +
base::TimeDelta::FromMilliseconds(1000)));
main_loop.task_runner()->PostTask(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
// The gpu might be slow when running in the background. This is ok.
bool result = watchdog_thread_->IsGpuHangDetectedForTesting();
EXPECT_FALSE(result);
}
TEST_F(GpuWatchdogTest, GpuSwitchingToForegroundHang) {
// Report GPU init complete.
watchdog_thread_->OnInitComplete();
// A task stays in the background for 200 milliseconds, and then
// switches to the foreground and runs for 6000 milliseconds. This is longer
// than the first-time foreground watchdog timeout (2000 ms).
main_loop.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&GpuWatchdogTest::LongTaskFromBackgroundToForeground,
base::Unretained(this),
/*duration*/ kGpuWatchdogTimeout * 2 +
base::TimeDelta::FromMilliseconds(4200),
/*time_to_switch_to_foreground*/
base::TimeDelta::FromMilliseconds(200)));
main_loop.task_runner()->PostTask(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
// It takes too long to finish a task after switching to the foreground.
// A GPU hang should be detected.
bool result = watchdog_thread_->IsGpuHangDetectedForTesting();
EXPECT_TRUE(result);
}
TEST_F(GpuWatchdogPowerTest, GpuOnSuspend) {
// watchdog_thread_->OnInitComplete() is called in SetUp
// Enter power suspension mode.
power_monitor_source_->GenerateSuspendEvent();
// Run a task that takes longer (5000 milliseconds) than timeout.
main_loop.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&SimpleTask, kGpuWatchdogTimeout * 2 +
base::TimeDelta::FromMilliseconds(3000)));
main_loop.task_runner()->PostTask(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
// A task might take long time to finish after entering suspension mode.
// This one is not a GPU hang.
bool result = watchdog_thread_->IsGpuHangDetectedForTesting();
EXPECT_FALSE(result);
}
TEST_F(GpuWatchdogPowerTest, GpuOnResumeHang) {
// watchdog_thread_->OnInitComplete() is called in SetUp
// This task stays in the suspension mode for 200 milliseconds, and it
// wakes up on power resume and then runs for 6000 milliseconds. This is
// longer than the watchdog resume timeout (2000 ms).
main_loop.task_runner()->PostTask(
FROM_HERE, base::BindOnce(&GpuWatchdogPowerTest::LongTaskOnResume,
base::Unretained(this),
/*duration*/ kGpuWatchdogTimeout * 2 +
base::TimeDelta::FromMilliseconds(4200),
/*time_to_power_resume*/
base::TimeDelta::FromMilliseconds(200)));
main_loop.task_runner()->PostTask(FROM_HERE, run_loop.QuitClosure());
run_loop.Run();
// It takes too long to finish this task after power resume. A GPU hang should
// be detected.
bool result = watchdog_thread_->IsGpuHangDetectedForTesting();
EXPECT_TRUE(result);
}
} // namespace gpu