chromium/chrome/common/profiler/thread_profiler_browsertest.cc

// 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));
}