// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <vector>
#include "base/functional/bind.h"
#include "base/no_destructor.h"
#include "base/profiler/stack_sampling_profiler.h"
#include "base/run_loop.h"
#include "base/synchronization/lock.h"
#include "base/test/scoped_run_loop_timeout.h"
#include "base/thread_annotations.h"
#include "base/threading/platform_thread.h"
#include "build/build_config.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/platform_browser_test.h"
#include "components/metrics/call_stacks/call_stack_profile_metrics_provider.h"
#include "components/version_info/channel.h"
#include "content/public/test/browser_test.h"
#include "third_party/metrics_proto/sampled_profile.pb.h"
#if BUILDFLAG(IS_MAC)
#include "base/mac/mac_util.h"
#endif
namespace {
// Class that intercepts and stores profiles provided to the
// CallStackProfileMetricsProvider. Intercept() is invoked on the profiler
// thread while FetchProfiles() is invoked on the main thread.
class ProfileInterceptor {
public:
using Predicate =
base::RepeatingCallback<bool(const metrics::SampledProfile&)>;
// Get the static object instance. This object must leak because there is no
// synchronization between it and the profiler thread which can invoke
// Intercept at any time.
static ProfileInterceptor& GetInstance() {
static base::NoDestructor<ProfileInterceptor> instance;
return *instance;
}
void SetFoundClosure(const base::RepeatingClosure& found_closure) {
base::AutoLock lock(lock_);
found_closure_ = found_closure;
}
void SetPredicate(const Predicate& predicate) {
base::AutoLock lock(lock_);
predicate_ = predicate;
}
bool ProfileWasFound() {
base::AutoLock lock(lock_);
return found_profile_;
}
void Intercept(metrics::SampledProfile profile) {
base::AutoLock lock(lock_);
if (predicate_.is_null()) {
pending_profiles_.push_back(profile);
} else {
CHECK(!found_closure_.is_null());
if (predicate_.Run(profile)) {
OnProfileFound();
return;
}
for (const auto& pending_profile : pending_profiles_) {
if (predicate_.Run(pending_profile)) {
OnProfileFound();
break;
}
}
pending_profiles_.clear();
}
}
private:
void OnProfileFound() EXCLUSIVE_LOCKS_REQUIRED(lock_) {
found_profile_ = true;
found_closure_.Run();
}
base::Lock lock_;
base::RepeatingClosure found_closure_ GUARDED_BY(lock_);
Predicate predicate_ GUARDED_BY(lock_);
std::vector<metrics::SampledProfile> pending_profiles_ GUARDED_BY(lock_);
bool found_profile_ GUARDED_BY(lock_) = false;
};
// Returns true if |profile| has the specified properties |trigger_event|,
// |process| and |thread|. Returns false otherwise.
bool MatchesProfile(metrics::SampledProfile::TriggerEvent trigger_event,
metrics::Process process,
metrics::Thread thread,
const metrics::SampledProfile& profile) {
return profile.trigger_event() == trigger_event &&
profile.process() == process && profile.thread() == thread;
}
class ThreadProfilerBrowserTest : public PlatformBrowserTest {
public:
void SetUp() override {
// Arrange to intercept the CPU profiles at the time they're provided to the
// metrics component.
metrics::CallStackProfileMetricsProvider::
SetCpuInterceptorCallbackForTesting(base::BindRepeating(
&ProfileInterceptor::Intercept,
base::Unretained(&ProfileInterceptor::GetInstance())));
PlatformBrowserTest::SetUp();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
// Enable the special browser test mode.
command_line->AppendSwitchASCII(switches::kStartStackProfiler,
switches::kStartStackProfilerBrowserTest);
}
};
// Wait for a profile with the specified properties.
bool WaitForProfile(metrics::SampledProfile::TriggerEvent trigger_event,
metrics::Process process,
metrics::Thread thread) {
// Profiling is only enabled for trunk builds and non-stable channels.
// Perform an early return and pass the test for the other channels.
switch (chrome::GetChannel()) {
case version_info::Channel::UNKNOWN:
case version_info::Channel::CANARY:
case version_info::Channel::DEV:
case version_info::Channel::BETA:
break;
default:
return true;
}
auto predicate =
base::BindRepeating(&MatchesProfile, trigger_event, process, thread);
base::RunLoop run_loop;
ProfileInterceptor::GetInstance().SetFoundClosure(run_loop.QuitClosure());
ProfileInterceptor::GetInstance().SetPredicate(predicate);
base::test::ScopedRunLoopTimeout timeout(FROM_HERE, base::Seconds(30));
run_loop.Run();
return ProfileInterceptor::GetInstance().ProfileWasFound();
}
} // namespace
#if BUILDFLAG(IS_ANDROID) && defined(ARCH_CPU_ARMEL)
// Android doesn't have a network service process.
#define MAYBE_NetworkServiceProcessIOThread \
DISABLED_NetworkServiceProcessIOThread
#else
#define MAYBE_NetworkServiceProcessIOThread NetworkServiceProcessIOThread
#endif
// Check that we receive startup profiles in the browser process for profiled
// processes/threads. We've seen multiple breakages previously where profiles
// were dropped as a result of bugs introduced by mojo refactorings.
IN_PROC_BROWSER_TEST_F(ThreadProfilerBrowserTest, BrowserProcessMainThread) {
EXPECT_TRUE(WaitForProfile(metrics::SampledProfile::PROCESS_STARTUP,
metrics::BROWSER_PROCESS, metrics::MAIN_THREAD));
}
IN_PROC_BROWSER_TEST_F(ThreadProfilerBrowserTest, BrowserProcessIOThread) {
EXPECT_TRUE(WaitForProfile(metrics::SampledProfile::PROCESS_STARTUP,
metrics::BROWSER_PROCESS, metrics::IO_THREAD));
}
IN_PROC_BROWSER_TEST_F(ThreadProfilerBrowserTest, GpuProcessMainThread) {
EXPECT_TRUE(WaitForProfile(metrics::SampledProfile::PROCESS_STARTUP,
metrics::GPU_PROCESS, metrics::MAIN_THREAD));
}
IN_PROC_BROWSER_TEST_F(ThreadProfilerBrowserTest, GpuProcessIOThread) {
EXPECT_TRUE(WaitForProfile(metrics::SampledProfile::PROCESS_STARTUP,
metrics::GPU_PROCESS, metrics::IO_THREAD));
}
IN_PROC_BROWSER_TEST_F(ThreadProfilerBrowserTest, GpuProcessCompositorThread) {
EXPECT_TRUE(WaitForProfile(metrics::SampledProfile::PROCESS_STARTUP,
metrics::GPU_PROCESS, metrics::COMPOSITOR_THREAD));
}
IN_PROC_BROWSER_TEST_F(ThreadProfilerBrowserTest, RendererProcessMainThread) {
EXPECT_TRUE(WaitForProfile(metrics::SampledProfile::PROCESS_STARTUP,
metrics::RENDERER_PROCESS, metrics::MAIN_THREAD));
}
IN_PROC_BROWSER_TEST_F(ThreadProfilerBrowserTest, RendererProcessIOThread) {
EXPECT_TRUE(WaitForProfile(metrics::SampledProfile::PROCESS_STARTUP,
metrics::RENDERER_PROCESS, metrics::IO_THREAD));
}
IN_PROC_BROWSER_TEST_F(ThreadProfilerBrowserTest,
RendererProcessCompositorThread) {
EXPECT_TRUE(WaitForProfile(metrics::SampledProfile::PROCESS_STARTUP,
metrics::RENDERER_PROCESS,
metrics::COMPOSITOR_THREAD));
}
IN_PROC_BROWSER_TEST_F(ThreadProfilerBrowserTest,
MAYBE_NetworkServiceProcessIOThread) {
EXPECT_TRUE(WaitForProfile(metrics::SampledProfile::PROCESS_STARTUP,
metrics::NETWORK_SERVICE_PROCESS,
metrics::IO_THREAD));
}