// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <utility>

#include "base/metrics/persistent_histogram_allocator.h"
#include "base/process/current_process.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/threading/thread.h"
#include "content/common/android/cpu_time_metrics_internal.h"
#include "content/common/process_priority_tracker.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace content {
namespace internal {
namespace {

void WorkForOneCpuSec(base::WaitableEvent* event) {
  auto initial_ticks = base::ThreadTicks::Now();
  while (!event->IsSignaled()) {
    if (base::ThreadTicks::Now() > initial_ticks + base::Seconds(1)) {
      event->Signal();
    }
  }
}

class CpuTimeMetricsTest : public ::testing::Test {
 protected:
  CpuTimeMetricsTest() {
    base::CurrentProcess::GetInstance().SetProcessType(
        base::CurrentProcessType::PROCESS_BROWSER);
  }
};

TEST_F(CpuTimeMetricsTest, RecordsMetricsForeground) {
  // Ensure the visibility tracker is created on the test runner thread.
  ProcessPriorityTracker::GetInstance();
  base::test::TaskEnvironment task_environment;

  base::HistogramTester histograms;
  base::Thread thread1("StackSamplingProfiler");

  thread1.StartAndWaitForTesting();
  ASSERT_TRUE(thread1.IsRunning());

  base::WaitableEvent event;

  // Create the ProcessCpuTimeMetrics instance and register it as the process
  // visibility observer.
  ProcessCpuTimeMetrics::SetIgnoreHistogramAllocatorForTesting(true);
  std::unique_ptr<ProcessCpuTimeMetrics> metrics =
      ProcessCpuTimeMetrics::CreateForTesting();
  metrics->WaitForCollectionForTesting();

  // Start out in the foreground and spend one CPU second there.
  ProcessPriorityTracker::GetInstance()->OnProcessPriorityChanged(
      base::Process::Priority::kUserBlocking);
  metrics->WaitForCollectionForTesting();

  thread1.task_runner()->PostTask(
      FROM_HERE, BindOnce(&WorkForOneCpuSec, base::Unretained(&event)));

  // Wait until the thread has consumed one second of CPU time.
  event.Wait();

  // Update the state to background to trigger the collection of high level
  // metrics.
  ProcessPriorityTracker::GetInstance()->OnProcessPriorityChanged(
      base::Process::Priority::kBestEffort);
  metrics->WaitForCollectionForTesting();

  // The test process is recognized as the browser process. The thread created
  // above is named like a sampling profiler thread.
  static constexpr int kBrowserProcessBucket =
      std::to_underlying(base::ShortProcessType::kBrowser);
  static constexpr int kSamplingProfilerThreadBucket = 24;

  // Expect that the CPU second spent by the thread above is represented in the
  // metrics.
  int browser_cpu_seconds = histograms.GetBucketCount(
      "Power.CpuTimeSecondsPerProcessType2", kBrowserProcessBucket);
  EXPECT_GE(browser_cpu_seconds, 1) << histograms.GetAllHistogramsRecorded();

  int browser_cpu_seconds_foreground = histograms.GetBucketCount(
      "Power.CpuTimeSecondsPerProcessType2.Foreground", kBrowserProcessBucket);
  EXPECT_GE(browser_cpu_seconds_foreground, 1)
      << histograms.GetAllHistogramsRecorded();

  int thread_cpu_seconds =
      histograms.GetBucketCount("Power.CpuTimeSecondsPerThreadType.Browser",
                                kSamplingProfilerThreadBucket);
  EXPECT_GE(thread_cpu_seconds, 1) << histograms.GetAllHistogramsRecorded();

  thread1.Stop();

  ProcessCpuTimeMetrics::SetIgnoreHistogramAllocatorForTesting(false);
}

TEST_F(CpuTimeMetricsTest, RecordsMetricsBackground) {
  // Ensure the visibility tracker is created on the test runner thread.
  ProcessPriorityTracker::GetInstance();
  base::test::TaskEnvironment task_environment;

  base::HistogramTester histograms;
  base::Thread thread1("StackSamplingProfiler");

  thread1.StartAndWaitForTesting();
  ASSERT_TRUE(thread1.IsRunning());

  base::WaitableEvent event;

  // Create the ProcessCpuTimeMetrics instance and register it as the process
  // visibility observer.
  ProcessCpuTimeMetrics::SetIgnoreHistogramAllocatorForTesting(true);
  std::unique_ptr<ProcessCpuTimeMetrics> metrics =
      ProcessCpuTimeMetrics::CreateForTesting();
  metrics->WaitForCollectionForTesting();

  // Start out in the background and spend one CPU second there.
  ProcessPriorityTracker::GetInstance()->OnProcessPriorityChanged(
      base::Process::Priority::kBestEffort);
  metrics->WaitForCollectionForTesting();

  thread1.task_runner()->PostTask(
      FROM_HERE, BindOnce(&WorkForOneCpuSec, base::Unretained(&event)));

  // Wait until the thread has consumed one second of CPU time.
  event.Wait();

  // Update the state to foreground to trigger the collection of high level
  // metrics.
  ProcessPriorityTracker::GetInstance()->OnProcessPriorityChanged(
      base::Process::Priority::kUserBlocking);
  metrics->WaitForCollectionForTesting();

  // The test process is recognized as the browser process. The thread created
  // above is named like a sampling profiler thread.
  static constexpr int kBrowserProcessBucket =
      std::to_underlying(base::ShortProcessType::kBrowser);
  static constexpr int kSamplingProfilerThreadBucket = 24;

  // Expect that the CPU second spent by the thread above is represented in the
  // metrics.
  int browser_cpu_seconds = histograms.GetBucketCount(
      "Power.CpuTimeSecondsPerProcessType2", kBrowserProcessBucket);
  EXPECT_GE(browser_cpu_seconds, 1) << histograms.GetAllHistogramsRecorded();

  int browser_cpu_seconds_background = histograms.GetBucketCount(
      "Power.CpuTimeSecondsPerProcessType2.Background", kBrowserProcessBucket);
  EXPECT_GE(browser_cpu_seconds_background, 1)
      << histograms.GetAllHistogramsRecorded();

  int thread_cpu_seconds =
      histograms.GetBucketCount("Power.CpuTimeSecondsPerThreadType.Browser",
                                kSamplingProfilerThreadBucket);
  EXPECT_GE(thread_cpu_seconds, 1) << histograms.GetAllHistogramsRecorded();

  thread1.Stop();

  ProcessCpuTimeMetrics::SetIgnoreHistogramAllocatorForTesting(false);
}

}  // namespace
}  // namespace internal
}  // namespace content
