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

#include "components/metrics/stability_metrics_helper.h"

#include <stdint.h>

#include <vector>

#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "components/metrics/metrics_pref_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/variations/hashing.h"
#include "extensions/buildflags/buildflags.h"
#include "third_party/metrics_proto/system_profile.pb.h"

#if defined(OS_WIN)
#include <windows.h>  // Needed for STATUS_* codes
#endif

#if defined(OS_CHROMEOS)
#include "components/metrics/system_memory_stats_recorder.h"
#endif

namespace metrics {

namespace {

enum RendererType {
  RENDERER_TYPE_RENDERER = 1,
  RENDERER_TYPE_EXTENSION,
  // NOTE: Add new action types only immediately above this line. Also,
  // make sure the enum list in tools/metrics/histograms/histograms.xml is
  // updated with any change in here.
  RENDERER_TYPE_COUNT
};

// Converts an exit code into something that can be inserted into our
// histograms (which expect non-negative numbers less than MAX_INT).
int MapCrashExitCodeForHistogram(int exit_code) {
#if defined(OS_WIN)
  // Since |abs(STATUS_GUARD_PAGE_VIOLATION) == MAX_INT| it causes problems in
  // histograms.cc. Solve this by remapping it to a smaller value, which
  // hopefully doesn't conflict with other codes.
  if (static_cast<DWORD>(exit_code) == STATUS_GUARD_PAGE_VIOLATION)
    return 0x1FCF7EC3;  // Randomly picked number.
#endif

  return std::abs(exit_code);
}

void RecordChildKills(RendererType histogram_type) {
  UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildKills",
                            histogram_type, RENDERER_TYPE_COUNT);
}

// Macro for logging the age of a crashed process.
//
// Notes:
// - IMPORTANT: When changing the constants below, please change the names of
//   the histograms logged via UMA_HISTOGRAM_CRASHED_PROCESS_AGE.
// - 99th percentile of Memory.Experimental.Renderer.Uptime hovers around 17h.
// - |kCrashedProcessAgeMin| is as low as possible, so that we may with
//   high-confidence categorize crashes that occur during early startup (e.g.
//   crashes that end up with STATUS_DLL_INIT_FAILED or STATUS_DLL_NOT_FOUND).
// - Note that even with just 50 buckets, we still get narrow and accurate
//   buckets at the lower end: 0ms, 1ms, 2ms, 3ms, 4-5ms, 6-8ms, 9-12ms, ...
constexpr auto kCrashedProcessAgeMin = base::TimeDelta::FromMilliseconds(1);
constexpr auto kCrashedProcessAgeMax = base::TimeDelta::FromHours(48);
constexpr uint32_t kCrashedProcessAgeCount = 50;
#define UMA_HISTOGRAM_CRASHED_PROCESS_AGE(histogram_name, uptime)           \
  UMA_HISTOGRAM_CUSTOM_TIMES(histogram_name, uptime, kCrashedProcessAgeMin, \
                             kCrashedProcessAgeMax, kCrashedProcessAgeCount)

}  // namespace

StabilityMetricsHelper::StabilityMetricsHelper(PrefService* local_state)
    : local_state_(local_state) {
  DCHECK(local_state_);
}

StabilityMetricsHelper::~StabilityMetricsHelper() {}

void StabilityMetricsHelper::ProvideStabilityMetrics(
    SystemProfileProto* system_profile_proto) {
  SystemProfileProto_Stability* stability_proto =
      system_profile_proto->mutable_stability();

  int count = local_state_->GetInteger(prefs::kStabilityPageLoadCount);
  if (count) {
    stability_proto->set_page_load_count(count);
    local_state_->SetInteger(prefs::kStabilityPageLoadCount, 0);
  }

  count = local_state_->GetInteger(prefs::kStabilityChildProcessCrashCount);
  if (count) {
    stability_proto->set_child_process_crash_count(count);
    local_state_->SetInteger(prefs::kStabilityChildProcessCrashCount, 0);
  }

  count = local_state_->GetInteger(prefs::kStabilityRendererCrashCount);
  if (count) {
    stability_proto->set_renderer_crash_count(count);
    local_state_->SetInteger(prefs::kStabilityRendererCrashCount, 0);
  }

  count = local_state_->GetInteger(prefs::kStabilityRendererFailedLaunchCount);
  if (count) {
    stability_proto->set_renderer_failed_launch_count(count);
    local_state_->SetInteger(prefs::kStabilityRendererFailedLaunchCount, 0);
  }

  count = local_state_->GetInteger(prefs::kStabilityRendererLaunchCount);
  if (count) {
    stability_proto->set_renderer_launch_count(count);
    local_state_->SetInteger(prefs::kStabilityRendererLaunchCount, 0);
  }

  count =
      local_state_->GetInteger(prefs::kStabilityExtensionRendererCrashCount);
  if (count) {
    stability_proto->set_extension_renderer_crash_count(count);
    local_state_->SetInteger(prefs::kStabilityExtensionRendererCrashCount, 0);
  }

  count = local_state_->GetInteger(
      prefs::kStabilityExtensionRendererFailedLaunchCount);
  if (count) {
    stability_proto->set_extension_renderer_failed_launch_count(count);
    local_state_->SetInteger(
        prefs::kStabilityExtensionRendererFailedLaunchCount, 0);
  }

  count = local_state_->GetInteger(prefs::kStabilityRendererHangCount);
  if (count) {
    stability_proto->set_renderer_hang_count(count);
    local_state_->SetInteger(prefs::kStabilityRendererHangCount, 0);
  }

  count =
      local_state_->GetInteger(prefs::kStabilityExtensionRendererLaunchCount);
  if (count) {
    stability_proto->set_extension_renderer_launch_count(count);
    local_state_->SetInteger(prefs::kStabilityExtensionRendererLaunchCount, 0);
  }
}

void StabilityMetricsHelper::ClearSavedStabilityMetrics() {
  // Clear all the prefs used in this class in UMA reports (which doesn't
  // include |kUninstallMetricsPageLoadCount| as it's not sent up by UMA).
  local_state_->SetInteger(prefs::kStabilityChildProcessCrashCount, 0);
  local_state_->SetInteger(prefs::kStabilityExtensionRendererCrashCount, 0);
  local_state_->SetInteger(prefs::kStabilityExtensionRendererFailedLaunchCount,
                           0);
  local_state_->SetInteger(prefs::kStabilityExtensionRendererLaunchCount, 0);
  local_state_->SetInteger(prefs::kStabilityPageLoadCount, 0);
  local_state_->SetInteger(prefs::kStabilityRendererCrashCount, 0);
  local_state_->SetInteger(prefs::kStabilityRendererFailedLaunchCount, 0);
  local_state_->SetInteger(prefs::kStabilityRendererHangCount, 0);
  local_state_->SetInteger(prefs::kStabilityRendererLaunchCount, 0);
}

// static
void StabilityMetricsHelper::RegisterPrefs(PrefRegistrySimple* registry) {
  registry->RegisterIntegerPref(prefs::kStabilityChildProcessCrashCount, 0);
  registry->RegisterIntegerPref(prefs::kStabilityExtensionRendererCrashCount,
                                0);
  registry->RegisterIntegerPref(
      prefs::kStabilityExtensionRendererFailedLaunchCount, 0);
  registry->RegisterIntegerPref(prefs::kStabilityExtensionRendererLaunchCount,
                                0);
  registry->RegisterIntegerPref(prefs::kStabilityPageLoadCount, 0);
  registry->RegisterIntegerPref(prefs::kStabilityRendererCrashCount, 0);
  registry->RegisterIntegerPref(prefs::kStabilityRendererFailedLaunchCount, 0);
  registry->RegisterIntegerPref(prefs::kStabilityRendererHangCount, 0);
  registry->RegisterIntegerPref(prefs::kStabilityRendererLaunchCount, 0);

  registry->RegisterInt64Pref(prefs::kUninstallMetricsPageLoadCount, 0);
}

void StabilityMetricsHelper::IncreaseRendererCrashCount() {
  IncrementPrefValue(prefs::kStabilityRendererCrashCount);
}

void StabilityMetricsHelper::BrowserUtilityProcessLaunched(
    const std::string& metrics_name) {
  uint32_t hash = variations::HashName(metrics_name);
  base::UmaHistogramSparse("ChildProcess.Launched.UtilityProcessHash", hash);
}

void StabilityMetricsHelper::BrowserUtilityProcessCrashed(
    const std::string& metrics_name,
    int exit_code) {
  // TODO(wfh): there doesn't appear to be a good way to log these exit_codes
  // without adding something into the stability proto, so for now only log the
  // crash and if the numbers are high enough, logging exit codes can be added
  // later.
  uint32_t hash = variations::HashName(metrics_name);
  base::UmaHistogramSparse("ChildProcess.Crashed.UtilityProcessHash", hash);
}

void StabilityMetricsHelper::BrowserChildProcessCrashed() {
  IncrementPrefValue(prefs::kStabilityChildProcessCrashCount);
}

void StabilityMetricsHelper::LogLoadStarted(bool is_incognito) {
  base::RecordAction(base::UserMetricsAction("PageLoad"));
  if (is_incognito)
    base::RecordAction(base::UserMetricsAction("PageLoadInIncognito"));
  IncrementPrefValue(prefs::kStabilityPageLoadCount);
  IncrementLongPrefsValue(prefs::kUninstallMetricsPageLoadCount);
  // We need to save the prefs, as page load count is a critical stat, and it
  // might be lost due to a crash :-(.
}

void StabilityMetricsHelper::LogRendererCrash(
    bool was_extension_process,
    base::TerminationStatus status,
    int exit_code,
    base::Optional<base::TimeDelta> uptime) {
  RendererType histogram_type =
      was_extension_process ? RENDERER_TYPE_EXTENSION : RENDERER_TYPE_RENDERER;

  switch (status) {
    case base::TERMINATION_STATUS_NORMAL_TERMINATION:
      break;
    case base::TERMINATION_STATUS_PROCESS_CRASHED:
    case base::TERMINATION_STATUS_ABNORMAL_TERMINATION:
    case base::TERMINATION_STATUS_OOM:
      if (was_extension_process) {
#if !BUILDFLAG(ENABLE_EXTENSIONS)
        NOTREACHED();
#endif
        IncrementPrefValue(prefs::kStabilityExtensionRendererCrashCount);

        base::UmaHistogramSparse("CrashExitCodes.Extension",
                                 MapCrashExitCodeForHistogram(exit_code));
        if (uptime.has_value()) {
          UMA_HISTOGRAM_CRASHED_PROCESS_AGE(
              "Stability.CrashedProcessAge.Extension", uptime.value());
        }
      } else {
        IncrementPrefValue(prefs::kStabilityRendererCrashCount);

        base::UmaHistogramSparse("CrashExitCodes.Renderer",
                                 MapCrashExitCodeForHistogram(exit_code));
        if (uptime.has_value()) {
          UMA_HISTOGRAM_CRASHED_PROCESS_AGE(
              "Stability.CrashedProcessAge.Renderer", uptime.value());
        }
      }

      UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildCrashes",
                                histogram_type, RENDERER_TYPE_COUNT);
      break;
    case base::TERMINATION_STATUS_PROCESS_WAS_KILLED:
      RecordChildKills(histogram_type);
      break;
#if defined(OS_ANDROID)
    case base::TERMINATION_STATUS_OOM_PROTECTED:
      // TODO(wfh): Check if this should be a Kill or a Crash on Android.
      break;
#endif
#if defined(OS_CHROMEOS)
    case base::TERMINATION_STATUS_PROCESS_WAS_KILLED_BY_OOM:
      RecordChildKills(histogram_type);
      UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildKills.OOM",
                                was_extension_process ? 2 : 1, 3);
      RecordMemoryStats(was_extension_process
                            ? RECORD_MEMORY_STATS_EXTENSIONS_OOM_KILLED
                            : RECORD_MEMORY_STATS_CONTENTS_OOM_KILLED);
      break;
#endif
    case base::TERMINATION_STATUS_STILL_RUNNING:
      UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.DisconnectedAlive",
                                histogram_type, RENDERER_TYPE_COUNT);
      break;
    case base::TERMINATION_STATUS_LAUNCH_FAILED:
      UMA_HISTOGRAM_ENUMERATION("BrowserRenderProcessHost.ChildLaunchFailures",
                                histogram_type, RENDERER_TYPE_COUNT);
      base::UmaHistogramSparse(
          "BrowserRenderProcessHost.ChildLaunchFailureCodes", exit_code);
      if (was_extension_process)
        IncrementPrefValue(prefs::kStabilityExtensionRendererFailedLaunchCount);
      else
        IncrementPrefValue(prefs::kStabilityRendererFailedLaunchCount);
      break;
    case base::TERMINATION_STATUS_MAX_ENUM:
      NOTREACHED();
      break;
  }
}

void StabilityMetricsHelper::LogRendererLaunched(bool was_extension_process) {
  if (was_extension_process)
    IncrementPrefValue(prefs::kStabilityExtensionRendererLaunchCount);
  else
    IncrementPrefValue(prefs::kStabilityRendererLaunchCount);
}

void StabilityMetricsHelper::IncrementPrefValue(const char* path) {
  int value = local_state_->GetInteger(path);
  local_state_->SetInteger(path, value + 1);
}

void StabilityMetricsHelper::IncrementLongPrefsValue(const char* path) {
  int64_t value = local_state_->GetInt64(path);
  local_state_->SetInt64(path, value + 1);
}

void StabilityMetricsHelper::LogRendererHang() {
  IncrementPrefValue(prefs::kStabilityRendererHangCount);
}

}  // namespace metrics
