chromium/components/startup_metric_utils/browser/startup_metric_utils.cc

// 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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

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

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

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

#include "base/command_line.h"
#include "base/dcheck_is_on.h"
#include "base/location.h"
#include "base/metrics/histogram_functions.h"
#include "base/notreached.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/win/win_util.h"

namespace {

// The signature of the NtQuerySystemInformation function.
typedef NTSTATUS(WINAPI* NtQuerySystemInformationPtr)(SYSTEM_INFORMATION_CLASS,
                                                      PVOID,
                                                      ULONG,
                                                      PULONG);

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

// 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[] =;

// An enumeration of startup temperatures. This must be kept in sync with
// the UMA StartupType enumeration defined in histograms.xml.
enum StartupTemperature {};

StartupTemperature g_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.
// |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 |basename| and suffix
// ".ColdStart", ".WarmStart" as appropriate.
// |value_expr| is an expression evaluating to the value to be recorded. This
// will be evaluated exactly once and cached, so side effects are not an
// issue. A metric logged using this function must have an affected-histogram
// entry in the definition of the StartupTemperature suffix in histograms.xml.
// This function must only be used in code that runs after
// |g_startup_temperature| has been initialized.
template <typename T>
void UmaHistogramWithTemperature(
    void (*histogram_function)(const std::string& name, T),
    const std::string& histogram_basename,
    T value) {}

void UmaHistogramWithTraceAndTemperature(
    void (*histogram_function)(const std::string& name, base::TimeDelta),
    const char* histogram_basename,
    base::TimeTicks begin_ticks,
    base::TimeTicks end_ticks) {}

}  // namespace

namespace startup_metric_utils {

BrowserStartupMetricRecorder& GetBrowser() {}

#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() {
  // Get the function pointer.
  static const NtQuerySystemInformationPtr query_sys_info =
      reinterpret_cast<NtQuerySystemInformationPtr>(::GetProcAddress(
          GetModuleHandle(L"ntdll.dll"), "NtQuerySystemInformation"));
  if (query_sys_info == nullptr) {
    return std::nullopt;
  }

  // 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(32 * 1024);
  constexpr int kMaxNumBufferResize = 2;
  int num_buffer_resize = 0;
  for (;;) {
    ULONG return_length = 0;
    const NTSTATUS status =
        query_sys_info(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.
      constexpr ULONG kMaxLength = 512 * 1024;
      if (return_length >= kMaxLength) {
        return std::nullopt;
      }

      // Resize the buffer and retry, if the buffer hasn't already been
      // resized too many times.
      if (num_buffer_resize < kMaxNumBufferResize) {
        ++num_buffer_resize;
        buffer.resize(return_length);
        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 =
        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;
}
#endif  // BUILDFLAG(IS_WIN)

void BrowserStartupMetricRecorder::ResetSessionForTesting() {}

bool BrowserStartupMetricRecorder::WasMainWindowStartupInterrupted() const {}

void BrowserStartupMetricRecorder::SetNonBrowserUIDisplayed() {}

void BrowserStartupMetricRecorder::SetBackgroundModeEnabled() {}

void BrowserStartupMetricRecorder::RecordMessageLoopStartTicks(
    base::TimeTicks ticks) {}

base::TimeTicks BrowserStartupMetricRecorder::GetWebContentsStartTicks() const {}

void BrowserStartupMetricRecorder::RecordBrowserMainMessageLoopStart(
    base::TimeTicks ticks,
    bool is_first_run) {}

void BrowserStartupMetricRecorder::RecordBrowserMainLoopFirstIdle(
    base::TimeTicks ticks) {}

void BrowserStartupMetricRecorder::RecordBrowserWindowDisplay(
    base::TimeTicks ticks) {}

void BrowserStartupMetricRecorder::RecordBrowserWindowFirstPaintTicks(
    base::TimeTicks ticks) {}

void BrowserStartupMetricRecorder::RecordFirstWebContentsNonEmptyPaint(
    base::TimeTicks now,
    base::TimeTicks render_process_host_init_time) {}

void BrowserStartupMetricRecorder::RecordFirstWebContentsMainNavigationStart(
    base::TimeTicks ticks) {}

void BrowserStartupMetricRecorder::RecordFirstWebContentsMainNavigationFinished(
    base::TimeTicks ticks) {}

void BrowserStartupMetricRecorder::RecordBrowserWindowFirstPaint(
    base::TimeTicks ticks) {}

void BrowserStartupMetricRecorder::RecordFirstRunSentinelCreation(
    FirstRunSentinelCreationResult result) {}

void BrowserStartupMetricRecorder::RecordHardFaultHistogram() {}

bool BrowserStartupMetricRecorder::ShouldLogStartupHistogram() const {}

#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) {}

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

void BrowserStartupMetricRecorder::RecordPrivacySandboxAttestationFirstCheck(
    base::TimeTicks ticks) {}

}  // namespace startup_metric_utils