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

#include "components/startup_metric_utils/browser/startup_metric_utils.h"

#include <stddef.h>
#include <stdint.h>

#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>

#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/dcheck_is_on.h"
#include "base/location.h"
#include "base/memory/page_size.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/strcat.h"
#include "base/threading/scoped_thread_priority.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "components/privacy_sandbox/privacy_sandbox_attestations/privacy_sandbox_attestations_histograms.h"

#if BUILDFLAG(IS_WIN)
#include <windows.h>
#include <winternl.h>

#include "base/byte_size.h"
#include "base/win/windows_handle_util.h"
#elif BUILDFLAG(IS_MAC)
#include <mach/mach.h>
#include <mach/task.h>
#elif BUILDFLAG(IS_LINUX)
#include <sys/resource.h>
#endif

#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
namespace {

// These values are taken from the
// Startup.BrowserMessageLoopStartHardFaultCount histogram. The latest
// revision landed on <5 and >3500 for a good split of warm/cold. In between
// being considered "lukewarm". Full analysis @
// https://docs.google.com/document/d/1haXFN1cQ6XE-NfhKgww-rOP-Wi-gK6AczP3gT4M5_kI
// These values should be reconsidered if either .WarmStartup or .ColdStartup
// distributions of a suffixed histogram becomes unexplainably bimodal.
//
// Maximum number of hard faults tolerated for a startup to be classified as a
// warm start.
constexpr uint32_t kWarmStartHardFaultCountThreshold = 5;

// Minimum number of hard faults (of 4KB pages) expected for a startup to be
// classified as a cold start. The right value for this seems to be between
// 10% and 15% of chrome.dll's size (from anecdata of the two times we did
// this analysis... it was 1200 in M47 back when chrome.dll was 35MB (32-bit
// and split from chrome_child.dll) and was made 3500 in M81 when chrome.dll
// was 126MB).
constexpr uint32_t kColdStartHardFaultCountThreshold = 3500;

// Range and bucket count for the HardFaultBytes histograms.
constexpr int kHardFaultBytesMin = 1024;
constexpr int kHardFaultBytesMax = 1073741824;  // 1 GiB
constexpr int kHardFaultBytesBucketCount = 50;

}  // namespace
#endif

#if BUILDFLAG(IS_WIN)
namespace {

// The struct used to return system process information via the NT internal
// QuerySystemInformation call. This is partially documented at
// http://goo.gl/Ja9MrH and fully documented at http://goo.gl/QJ70rn
// This structure is laid out in the same format on both 32-bit and 64-bit
// systems, but has a different size due to the various pointer-sized fields.
struct SYSTEM_PROCESS_INFORMATION_EX {
  ULONG NextEntryOffset;
  ULONG NumberOfThreads;
  LARGE_INTEGER WorkingSetPrivateSize;
  ULONG HardFaultCount;
  BYTE Reserved1[36];
  PVOID Reserved2[3];
  // This is labeled a handle so that it expands to the correct size for
  // 32-bit and 64-bit operating systems. However, under the hood it's a
  // 32-bit DWORD containing the process ID.
  HANDLE UniqueProcessId;
  PVOID Reserved3;
  ULONG HandleCount;
  BYTE Reserved4[4];
  PVOID Reserved5[11];
  SIZE_T PeakPagefileUsage;
  SIZE_T PrivatePageCount;
  LARGE_INTEGER Reserved6[6];
  // Array of SYSTEM_THREAD_INFORMATION structs follows.
};

}  // namespace
#endif

namespace {
const char kProcessType[] = "type";

startup_metric_utils::StartupTemperature g_startup_temperature =
    startup_metric_utils::UNDETERMINED_STARTUP_TEMPERATURE;

// Helper function for splitting out an UMA histogram based on startup
// temperature. |histogram_function| is the histogram type, and corresponds to
// an UMA function like base::UmaHistogramLongTimes. It must itself be a
// function that only takes two parameters.
// |histogram_basename| is the basename of the histogram. A histogram of this
// name will always be recorded to. If the startup temperature is known then a
// value will also be recorded to the histogram with name |histogram_basename|
// and suffix ".ColdStartup", ".LukewarmStartup", or ".WarmStartup" as
// appropriate.
// |value| is the value to be recorded. A metric logged using this function
// must have an affected-histogram entry in the definition of the
// StartupTemperature variants in histograms.xml.
// This function must only be used in code that runs after
// |g_startup_temperature| has been initialized.
template <typename T>
void EmitHistogramWithTemperature(void (*histogram_function)(std::string_view,
                                                             T),
                                  std::string_view histogram_basename,
                                  T value) {
  // Always record to the base histogram.
  (*histogram_function)(histogram_basename, value);
  // Record to the cold/warm/lukewarm suffixed histogram as appropriate.
  switch (g_startup_temperature) {
    case startup_metric_utils::COLD_STARTUP_TEMPERATURE:
      (*histogram_function)(base::StrCat({histogram_basename, ".ColdStartup"}),
                            value);
      break;
    case startup_metric_utils::WARM_STARTUP_TEMPERATURE:
      (*histogram_function)(base::StrCat({histogram_basename, ".WarmStartup"}),
                            value);
      break;
    case startup_metric_utils::LUKEWARM_STARTUP_TEMPERATURE:
      (*histogram_function)(
          base::StrCat({histogram_basename, ".LukewarmStartup"}), value);
      break;
    case startup_metric_utils::UNDETERMINED_STARTUP_TEMPERATURE:
      break;
    case startup_metric_utils::STARTUP_TEMPERATURE_COUNT:
      NOTREACHED();
  }
}

}  // namespace

namespace startup_metric_utils {

BrowserStartupMetricRecorder::BrowserStartupMetricRecorder() = default;

void BrowserStartupMetricRecorder::EmitHistogramWithTemperatureAndTraceEvent(
    void (*histogram_function)(std::string_view, base::TimeDelta),
    const char* histogram_basename,
    base::TimeTicks begin_ticks,
    base::TimeTicks end_ticks) {
  EmitHistogramWithTemperature(histogram_function, histogram_basename,
                               end_ticks - begin_ticks);
  GetCommon().EmitTraceEvent(histogram_basename, begin_ticks, end_ticks);
}

void BrowserStartupMetricRecorder::EmitBrowserWindowDisplayHistogram() {
  if (is_browser_window_display_metric_emitted_) {
    return;
  }

  // The metric requires the message loop to have started so that the startup
  // temperature evaluation has run.
  if (browser_window_display_ticks_.is_null() ||
      message_loop_start_ticks_.is_null()) {
    return;
  }

  // Skip logging if the main window startup was interrupted, e.g., by
  // --silent-launch, profile picker, or bad flags prompt.
  if (!ShouldLogStartupHistogram()) {
    return;
  }

  is_browser_window_display_metric_emitted_ = true;

  EmitHistogramWithTemperatureAndTraceEvent(
      &base::UmaHistogramLongTimes, "Startup.BrowserWindowDisplay",
      GetCommon().application_start_ticks_, browser_window_display_ticks_);
}

BrowserStartupMetricRecorder& GetBrowser() {
  // If this ceases to be true, Get{Common,Browser} need to be changed to use
  // base::NoDestructor.
  static_assert(
      std::is_trivially_destructible<BrowserStartupMetricRecorder>::value,
      "Startup metric recorder classes must be trivially destructible.");

  // This guard prevents non-browser processes from reporting browser process
  // metrics.
  CHECK(!base::CommandLine::ForCurrentProcess()->HasSwitch(kProcessType));
  static BrowserStartupMetricRecorder instance;
  return instance;
}

#if BUILDFLAG(IS_WIN)
// Returns the hard fault count of the current process, or nullopt if it can't
// be determined.
std::optional<uint32_t>
BrowserStartupMetricRecorder::GetHardFaultCountForCurrentProcess() {
  // The output of this system call depends on the number of threads and
  // processes on the entire system, and this can change between calls. Retry
  // a small handful of times growing the buffer along the way.
  // NOTE: The actual required size depends entirely on the number of
  // processes and threads running on the system. The initial guess suffices for
  // ~100s of processes and ~1000s of threads.
  std::vector<uint8_t> buffer(base::KiB(32).InBytes());
  constexpr int kMaxNumBufferResize = 2;
  int num_buffer_resize = 0;
  for (;;) {
    ULONG return_length = 0;
    const NTSTATUS status = ::NtQuerySystemInformation(
        SystemProcessInformation, buffer.data(),
        static_cast<ULONG>(buffer.size()), &return_length);

    // NtQuerySystemInformation succeeded.
    if (NT_SUCCESS(status)) {
      DCHECK_LE(return_length, buffer.size());
      break;
    }

    // NtQuerySystemInformation failed due to insufficient buffer length.
    if (return_length > buffer.size()) {
      // Abort if a large size is required for the buffer. It is undesirable
      // to fill a large buffer just to record histograms.
#if defined(_WIN64)
      constexpr ULONG kMaxLength =
          base::MiB(2).InBytes();  // 2 MB for 64-bit systems
#else
      constexpr ULONG kMaxLength =
          base::KiB(512).InBytes();  // 512 KB for 32-bit systems
#endif
      if (return_length >= kMaxLength) {
        return std::nullopt;
      }

      // Resize the buffer and retry, if the buffer hasn't already been
      // resized too many times. Use double the return length to have padding
      // for new threads spawned in the meantime.
      if (num_buffer_resize < kMaxNumBufferResize) {
        ++num_buffer_resize;
        buffer.resize(std::min(return_length * 2, kMaxLength));
        continue;
      }
    }

    // Abort if NtQuerySystemInformation failed for another reason than
    // insufficient buffer length, or if the buffer was resized too many
    // times.
    DCHECK(return_length <= buffer.size() ||
           num_buffer_resize >= kMaxNumBufferResize);
    return std::nullopt;
  }

  // Look for the struct housing information for the current process.
  const DWORD proc_id = ::GetCurrentProcessId();
  size_t index = 0;
  while (index < buffer.size()) {
    DCHECK_LE(index + sizeof(SYSTEM_PROCESS_INFORMATION_EX), buffer.size());
    SYSTEM_PROCESS_INFORMATION_EX* proc_info =
        UNSAFE_TODO(reinterpret_cast<SYSTEM_PROCESS_INFORMATION_EX*>(
            buffer.data() + index));
    if (base::win::HandleToUint32(proc_info->UniqueProcessId) == proc_id) {
      return proc_info->HardFaultCount;
    }
    // The list ends when NextEntryOffset is zero. This also prevents busy
    // looping if the data is in fact invalid.
    if (proc_info->NextEntryOffset <= 0) {
      return std::nullopt;
    }
    index += proc_info->NextEntryOffset;
  }

  return std::nullopt;
}
#elif BUILDFLAG(IS_MAC)
std::optional<uint32_t>
BrowserStartupMetricRecorder::GetHardFaultCountForCurrentProcess() {
  task_events_info_data_t events_info;
  mach_msg_type_number_t count = TASK_EVENTS_INFO_COUNT;
  // TASK_EVENTS_INFO is passed to task_info() to retrieve event
  // statistics for a task (such as page faults and pageins). See:
  // https://github.com/apple-oss-distributions/xnu/blob/main/osfmk/mach/task_info.h
  kern_return_t kr =
      task_info(mach_task_self(), TASK_EVENTS_INFO,
                reinterpret_cast<task_info_t>(&events_info), &count);
  if (kr != KERN_SUCCESS) {
    return std::nullopt;
  }
  return base::saturated_cast<uint32_t>(events_info.pageins);
}
#elif BUILDFLAG(IS_LINUX)
std::optional<uint32_t>
BrowserStartupMetricRecorder::GetHardFaultCountForCurrentProcess() {
  struct rusage usage;
  if (getrusage(RUSAGE_SELF, &usage) != 0) {
    return std::nullopt;
  }
  return base::saturated_cast<uint32_t>(usage.ru_majflt);
}
#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)

void BrowserStartupMetricRecorder::ResetSessionForTesting() {
  GetCommon().ResetSessionForTesting();
  // Reset global ticks that will be recorded multiple times when multiple
  // tests run in the same process.
  main_window_startup_interrupted_ = false;
  message_loop_start_ticks_ = base::TimeTicks();
  browser_window_display_ticks_ = base::TimeTicks();
  browser_window_first_paint_ticks_ = base::TimeTicks();
  is_privacy_sandbox_attestations_component_ready_recorded_ = false;
  is_privacy_sandbox_attestations_first_check_recorded_ = false;
  is_first_run_ = false;
  is_browser_window_display_metric_emitted_ = false;
  did_record_startup_fcp_ = false;
  did_record_startup_lcp_ = false;
}

bool BrowserStartupMetricRecorder::WasMainWindowStartupInterrupted() const {
  return main_window_startup_interrupted_;
}

void BrowserStartupMetricRecorder::SetNonBrowserUIDisplayed() {
  main_window_startup_interrupted_ = true;
}

void BrowserStartupMetricRecorder::SetBackgroundModeEnabled() {
  main_window_startup_interrupted_ = true;
}

void BrowserStartupMetricRecorder::RecordMessageLoopStartTicks(
    base::TimeTicks ticks) {
  DCHECK(message_loop_start_ticks_.is_null());
  message_loop_start_ticks_ = ticks;
  DCHECK(!message_loop_start_ticks_.is_null());
}

base::TimeTicks BrowserStartupMetricRecorder::GetWebContentsStartTicks() const {
  return web_contents_start_ticks_.is_null()
             ? GetCommon().application_start_ticks_
             : web_contents_start_ticks_;
}

void BrowserStartupMetricRecorder::RecordBrowserMainMessageLoopStart(
    base::TimeTicks ticks,
    bool is_first_run) {
  DCHECK(!GetCommon().application_start_ticks_.is_null());
  is_first_run_ = is_first_run;

  RecordMessageLoopStartTicks(ticks);

  // Keep RecordHardFaultHistogram() near the top of this method (as much as
  // possible) as many other histograms depend on it setting
  // |g_startup_temperature|.
  RecordHardFaultHistogram();

  // Record timing of the browser message-loop start time.
  if (is_first_run) {
    EmitHistogramWithTemperatureAndTraceEvent(
        &base::UmaHistogramLongTimes100,
        "Startup.BrowserMessageLoopStartTime.FirstRun",
        GetCommon().application_start_ticks_, ticks);
  } else {
    EmitHistogramWithTemperatureAndTraceEvent(
        &base::UmaHistogramLongTimes100, "Startup.BrowserMessageLoopStartTime",
        GetCommon().application_start_ticks_, ticks);
  }
  GetCommon().AddStartupEventsForTelemetry();

  // Record values stored prior to startup temperature evaluation.
  EmitBrowserWindowDisplayHistogram();

  // Process creation to application start. See comment above
  // RecordApplicationStart().
  if (!GetCommon().process_creation_ticks_.is_null()) {
    EmitHistogramWithTemperatureAndTraceEvent(
        &base::UmaHistogramLongTimes,
        "Startup.LoadTime.ProcessCreateToApplicationStart",
        GetCommon().process_creation_ticks_,
        GetCommon().application_start_ticks_);

    // Application start to ChromeMain().
    DCHECK(!GetCommon().chrome_main_entry_ticks_.is_null());
    EmitHistogramWithTemperatureAndTraceEvent(
        &base::UmaHistogramLongTimes,
        "Startup.LoadTime.ApplicationStartToChromeMain",
        GetCommon().application_start_ticks_,
        GetCommon().chrome_main_entry_ticks_);
  }

  // PreReadFile time.
  if (!GetCommon().preread_end_ticks_.is_null() &&
      !GetCommon().preread_begin_ticks_.is_null()) {
    EmitHistogramWithTemperatureAndTraceEvent(
        &base::UmaHistogramLongTimes, "Startup.Browser.LoadTime.PreReadFile",
        GetCommon().preread_begin_ticks_, GetCommon().preread_end_ticks_);
  }
}

void BrowserStartupMetricRecorder::RecordBrowserMainLoopFirstIdle(
    base::TimeTicks ticks) {
  DCHECK(!GetCommon().application_start_ticks_.is_null());
  GetCommon().AssertFirstCallInSession(FROM_HERE);

  if (!ShouldLogStartupHistogram()) {
    return;
  }

  EmitHistogramWithTemperatureAndTraceEvent(
      &base::UmaHistogramLongTimes100, "Startup.BrowserMessageLoopFirstIdle",
      GetCommon().application_start_ticks_, ticks);
}

void BrowserStartupMetricRecorder::RecordBrowserWindowDisplay(
    base::TimeTicks ticks) {
  DCHECK(!ticks.is_null());

  // Return if it has already been recorded.
  if (!browser_window_display_ticks_.is_null()) {
    return;
  }

  // The value will be recorded in appropriate histograms after the startup
  // temperature is evaluated.
  //
  // Note: In some cases (e.g. launching with --silent-launch), the first
  // browser window is displayed after the startup temperature is evaluated. In
  // these cases, the value will not be recorded, which is the desired behavior
  // for a non-conventional launch.
  browser_window_display_ticks_ = ticks;

  EmitBrowserWindowDisplayHistogram();
}

void BrowserStartupMetricRecorder::RecordBrowserWindowFirstPaintTicks(
    base::TimeTicks ticks) {
  DCHECK(!ticks.is_null());

  if (!browser_window_first_paint_ticks_.is_null()) {
    return;
  }

  browser_window_first_paint_ticks_ = ticks;
}

void BrowserStartupMetricRecorder::RecordFirstWebContentsNonEmptyPaint(
    base::TimeTicks now,
    base::TimeTicks render_process_host_init_time) {
  const base::TimeTicks web_contents_start_ticks = GetWebContentsStartTicks();
  DCHECK(!web_contents_start_ticks.is_null());
  GetCommon().AssertFirstCallInSession(FROM_HERE);

  if (!ShouldLogStartupHistogram()) {
    return;
  }

  EmitHistogramWithTemperatureAndTraceEvent(
      &base::UmaHistogramLongTimes100,
      "Startup.FirstWebContents.NonEmptyPaint3", web_contents_start_ticks, now);

  EmitHistogramWithTemperature(
      &base::UmaHistogramLongTimes100,
      "Startup.BrowserMessageLoopStart.To.NonEmptyPaint2",
      now - message_loop_start_ticks_);
}

void BrowserStartupMetricRecorder::
    RecordFirstWebContentsNonEmptyPaintForOsLaunch(base::TimeTicks now) {
  const base::TimeTicks web_contents_start_ticks = GetWebContentsStartTicks();
  DCHECK(!web_contents_start_ticks.is_null());
  GetCommon().AssertFirstCallInSession(FROM_HERE);

  if (!ShouldLogStartupHistogram()) {
    return;
  }

  base::UmaHistogramLongTimes100(
      "Startup.FirstWebContents.NonEmptyPaint3.AutoLaunchByOs",
      now - web_contents_start_ticks);
}

void BrowserStartupMetricRecorder::RecordFirstWebContentsFirstContentfulPaint(
    base::TimeTicks fcp_ticks) {
  if (did_record_startup_fcp_) {
    return;
  }

  const base::TimeTicks web_contents_start_ticks = GetWebContentsStartTicks();
  if (web_contents_start_ticks.is_null()) {
    return;
  }

  if (!ShouldLogStartupHistogram()) {
    return;
  }

  did_record_startup_fcp_ = true;

  EmitHistogramWithTemperatureAndTraceEvent(
      &base::UmaHistogramLongTimes100,
      "Startup.FirstWebContents.FirstContentfulPaint", web_contents_start_ticks,
      fcp_ticks);
}

void BrowserStartupMetricRecorder::RecordFirstWebContentsLargestContentfulPaint(
    base::TimeTicks lcp_ticks) {
  // Only record LCP if FCP was already recorded for the same page load.
  if (!did_record_startup_fcp_ || did_record_startup_lcp_) {
    return;
  }

  const base::TimeTicks web_contents_start_ticks = GetWebContentsStartTicks();
  if (web_contents_start_ticks.is_null()) {
    return;
  }

  if (!ShouldLogStartupHistogram()) {
    return;
  }

  did_record_startup_lcp_ = true;

  EmitHistogramWithTemperatureAndTraceEvent(
      &base::UmaHistogramLongTimes100,
      "Startup.FirstWebContents.LargestContentfulPaint",
      web_contents_start_ticks, lcp_ticks);
}

void BrowserStartupMetricRecorder::RecordFirstWebContentsMainNavigationStart(
    base::TimeTicks ticks) {
  const base::TimeTicks web_contents_start_ticks = GetWebContentsStartTicks();
  DCHECK(!web_contents_start_ticks.is_null());
  GetCommon().AssertFirstCallInSession(FROM_HERE);

  if (!ShouldLogStartupHistogram()) {
    return;
  }

  EmitHistogramWithTemperatureAndTraceEvent(
      &base::UmaHistogramLongTimes100,
      "Startup.FirstWebContents.MainNavigationStart", web_contents_start_ticks,
      ticks);
}

void BrowserStartupMetricRecorder::RecordFirstWebContentsMainNavigationFinished(
    base::TimeTicks ticks) {
  const base::TimeTicks web_contents_start_ticks = GetWebContentsStartTicks();
  DCHECK(!web_contents_start_ticks.is_null());
  GetCommon().AssertFirstCallInSession(FROM_HERE);

  if (!ShouldLogStartupHistogram()) {
    return;
  }

  EmitHistogramWithTemperatureAndTraceEvent(
      &base::UmaHistogramLongTimes100,
      "Startup.FirstWebContents.MainNavigationFinished",
      web_contents_start_ticks, ticks);
}

void BrowserStartupMetricRecorder::RecordBrowserWindowFirstPaint(
    base::TimeTicks ticks) {
  static bool is_first_call = true;
  if (!is_first_call || ticks.is_null()) {
    return;
  }
  is_first_call = false;
  RecordBrowserWindowFirstPaintTicks(ticks);
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
  // Mirror Startup.BrowserMessageLoopStartHardFault{Count,Bytes} at the first
  // paint checkpoint (using a higher 1M cap for Count to avoid saturation).
  // We record this unconditionally (even if ShouldLogStartupHistogram() is
  // false) so experiments altering page-in behavior (e.g., MacFrameworkPreRead)
  // can compare all launches. See histogram descriptions for details.
  if (const std::optional<uint32_t> hard_fault_count =
          GetHardFaultCountForCurrentProcess()) {
    base::UmaHistogramCounts1M("Startup.BrowserWindowFirstPaintHardFaultCount",
                               *hard_fault_count);
    base::UmaHistogramCustomCounts(
        "Startup.BrowserWindowFirstPaintHardFaultBytes",
        base::saturated_cast<int>(static_cast<uint64_t>(*hard_fault_count) *
                                  base::GetPageSize()),
        kHardFaultBytesMin, kHardFaultBytesMax, kHardFaultBytesBucketCount);
  }
#endif
  if (!ShouldLogStartupHistogram()) {
    return;
  }

  base::TimeTicks latency_origin = GetApplicationStartTicksForStartup();
  if (latency_origin.is_null()) {
    return;
  }
  DCHECK(!latency_origin.is_null());

  EmitHistogramWithTemperatureAndTraceEvent(&base::UmaHistogramLongTimes100,
                                            "Startup.BrowserWindow.FirstPaint",
                                            latency_origin, ticks);
}

void BrowserStartupMetricRecorder::RecordFirstRunSentinelCreation(
    FirstRunSentinelCreationResult result) {
  base::UmaHistogramEnumeration("FirstRun.Sentinel.Created", result);
}

void BrowserStartupMetricRecorder::RecordHardFaultHistogram() {
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
  DCHECK_EQ(UNDETERMINED_STARTUP_TEMPERATURE, g_startup_temperature);

  const std::optional<uint32_t> hard_fault_count =
      GetHardFaultCountForCurrentProcess();

  if (hard_fault_count.has_value()) {
    // Hard fault counts are expected to be in the thousands range,
    // corresponding to faulting in ~10s of MBs of code ~10s of KBs at a time.
    // (Observed to vary from 1000 to 10000 on various test machines and
    // platforms.)
    base::UmaHistogramCustomCounts(
        "Startup.BrowserMessageLoopStartHardFaultCount",
        hard_fault_count.value(), 1, 40000, 50);
    int hard_fault_bytes = base::saturated_cast<int>(
        static_cast<uint64_t>(hard_fault_count.value()) * base::GetPageSize());
    base::UmaHistogramCustomCounts(
        "Startup.BrowserMessageLoopStartHardFaultBytes", hard_fault_bytes,
        kHardFaultBytesMin, kHardFaultBytesMax, kHardFaultBytesBucketCount);

    // Determine the startup type based on the number of observed hard faults.
    if (hard_fault_count < kWarmStartHardFaultCountThreshold) {
      g_startup_temperature = WARM_STARTUP_TEMPERATURE;
      GetCommon().EmitInstantEvent("Startup.Temperature.Warm");
    } else if (hard_fault_count >= kColdStartHardFaultCountThreshold) {
      g_startup_temperature = COLD_STARTUP_TEMPERATURE;
      GetCommon().EmitInstantEvent("Startup.Temperature.Cold");
    } else {
      g_startup_temperature = LUKEWARM_STARTUP_TEMPERATURE;
      GetCommon().EmitInstantEvent("Startup.Temperature.Lukewarm");
    }
  } else {
    // |g_startup_temperature| remains
    // UNDETERMINED_STARTUP_TEMPERATURE if the number of hard faults could not
    // be determined.
  }

  // Record the startup 'temperature'.
  base::UmaHistogramEnumeration("Startup.Temperature", g_startup_temperature,
                                STARTUP_TEMPERATURE_COUNT);
#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || BUILDFLAG(IS_LINUX)
}

bool BrowserStartupMetricRecorder::ShouldLogStartupHistogram() const {
  return !WasMainWindowStartupInterrupted();
}

StartupTemperature BrowserStartupMetricRecorder::GetStartupTemperature() const {
  return g_startup_temperature;
}

bool BrowserStartupMetricRecorder::IsFirstRun() const {
  return is_first_run_;
}

base::TimeTicks
BrowserStartupMetricRecorder::GetApplicationStartTicksForStartup() const {
#if BUILDFLAG(IS_CHROMEOS)
  // `application_start_ticks_` is inappropriate since the device often boots
  // to a login screen, and an indefinite amount of time can elapse before a
  // browser window is opened. Even when restoring a session after a crash
  // (which has no login screen), the session is not restored automatically.
  // The user must click a notification first before browser windows are
  // created and restored, so using `application_start_ticks_` would have the
  // same issue.
  //
  // If `web_contents_start_ticks_` is not set here, that could be intentional
  // as this metric should not be recorded in certain cases (ex: a manually
  // opened browser window).
  if (web_contents_start_ticks_.is_null()) {
    return base::TimeTicks();
  }
  return web_contents_start_ticks_;
#else
  return GetCommon().application_start_ticks_;
#endif  // BUILDFLAG(IS_CHROMEOS)
}

#if BUILDFLAG(IS_CHROMEOS)
void BrowserStartupMetricRecorder::RecordWebContentsStartTime(
    base::TimeTicks ticks) {
  if (web_contents_start_ticks_.is_null()) {
    web_contents_start_ticks_ = ticks;
    DCHECK(!web_contents_start_ticks_.is_null());
  }
}
#endif

void BrowserStartupMetricRecorder::RecordExternalStartupMetric(
    const char* histogram_name,
    base::TimeTicks completion_ticks,
    bool set_non_browser_ui_displayed) {
  DCHECK(!GetCommon().application_start_ticks_.is_null());

  if (!ShouldLogStartupHistogram()) {
    return;
  }

  EmitHistogramWithTemperatureAndTraceEvent(
      &base::UmaHistogramMediumTimes, histogram_name,
      GetCommon().application_start_ticks_, completion_ticks);

  if (set_non_browser_ui_displayed) {
    SetNonBrowserUIDisplayed();
  }
}

// There are two possible callers of `ComponentReady()`:
// a) Component registration, when there is existing component file on disk.
// b) Component installation, when the component is downloaded.
//
// The following factors affect the timing of `ComponentReady()`:
// Non-browser UI during startup, for example, profile picker.
// - When the user stays at the profile picker indefinitely. The registration
// takes place in around 4 minutes after opening the browser.
//
// The purpose of this metric is to understand the time gap between the time
// users are able to navigate and the time the Privacy Sandbox attestations map
// is ready. If navigation to sites that use Privacy Sandbox APIs takes place
// during this gap, the API calls may be rejected because the attestations map
// has not been ready yet.
//
// To reduce the noise introduced by non-browser UI, we measure from the first
// browser window paint if it has been recorded. If it is not recorded, the
// measurement is taken from application start.
// TODO(crbug.com/329235182): The Privacy Sandbox Attestation start up related
// histograms are not using the temperature breakouts. The logic for all these
// histograms could just live in the privacy sandbox component itself, which
// pulls from startup code just to get the application start timeticks.
void BrowserStartupMetricRecorder::RecordPrivacySandboxAttestationsFirstReady(
    base::TimeTicks ticks) {
  DCHECK(!ticks.is_null());

  // This metric should be recorded at most once for each Chrome session.
  if (is_privacy_sandbox_attestations_component_ready_recorded_) {
    return;
  }

  // The first browser window paint has been recorded.
  if (!browser_window_first_paint_ticks_.is_null()) {
    is_privacy_sandbox_attestations_component_ready_recorded_ = true;
    base::UmaHistogramLongTimes100(
        privacy_sandbox::kComponentReadyFromBrowserWindowFirstPaintUMA,
        ticks - browser_window_first_paint_ticks_);
    return;
  }

  // Otherwise, this implies the component is installed before first browser
  // window paint.
  is_privacy_sandbox_attestations_component_ready_recorded_ = true;
  if (WasMainWindowStartupInterrupted()) {
    // The durations should be a few minutes.
    base::UmaHistogramLongTimes100(
        privacy_sandbox::kComponentReadyFromApplicationStartWithInterruptionUMA,
        ticks - GetCommon().application_start_ticks_);
  } else {
    // The durations should be a few milliseconds.
    base::UmaHistogramLongTimes100(
        privacy_sandbox::kComponentReadyFromApplicationStartUMA,
        ticks - GetCommon().application_start_ticks_);
  }
}

void BrowserStartupMetricRecorder::RecordPrivacySandboxAttestationFirstCheck(
    base::TimeTicks ticks) {
  DCHECK(!ticks.is_null());

  // This metric should be recorded at most once for each Chrome session.
  if (is_privacy_sandbox_attestations_first_check_recorded_) {
    return;
  }

  is_privacy_sandbox_attestations_first_check_recorded_ = true;

  // Record the first time a Privacy Sandbox API is checked for attestation.
  base::UmaHistogramLongTimes100(privacy_sandbox::kAttestationFirstCheckTimeUMA,
                                 ticks - GetCommon().application_start_ticks_);
}

}  // namespace startup_metric_utils
