chromium/chrome/browser/ash/boot_times_recorder/boot_times_recorder.cc

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

#include "chrome/browser/ash/boot_times_recorder/boot_times_recorder.h"

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

#include <vector>

#include "base/check.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/lazy_instance.h"
#include "base/location.h"
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/metrics/login_event_recorder.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"

namespace ash {

namespace {

using ::content::BrowserThread;
using ::content::NavigationController;
using ::content::RenderWidgetHost;
using ::content::RenderWidgetHostView;
using ::content::WebContents;

const std::string GetTabUrl(RenderWidgetHost* rwh) {
  for (Browser* browser : *BrowserList::GetInstance()) {
    for (int i = 0, tab_count = browser->tab_strip_model()->count();
         i < tab_count;
         ++i) {
      WebContents* tab = browser->tab_strip_model()->GetWebContentsAt(i);
      if (tab->GetPrimaryMainFrame()->GetRenderWidgetHost() == rwh) {
        return tab->GetLastCommittedURL().spec();
      }
    }
  }
  return std::string();
}

}  // namespace

#define FPL(value) FILE_PATH_LITERAL(value)

// Name of the time that Chrome's main() is called.
constexpr base::FilePath::CharType kChromeMain[] = FPL("chrome-main");

// Names of login stats files.
constexpr base::FilePath::CharType kChromeFirstRender[] =
    FPL("chrome-first-render");

// Names of login UMA values.
static const char kUmaLogin[] = "BootTime.Login2";
static const char kUmaLoginNewUser[] = "BootTime.LoginNewUser";
constexpr char kUmaLogout[] = "ShutdownTime.Logout";
constexpr char kUmaRestart[] = "ShutdownTime.Restart";

// Name of file collecting login times.
constexpr base::FilePath::CharType kLoginTimes[] = FPL("login-times");

// Name of file collecting logout times.
constexpr char kLogoutTimes[] = "logout-times";

static base::LazyInstance<BootTimesRecorder>::DestructorAtExit
    g_boot_times_recorder = LAZY_INSTANCE_INITIALIZER;

BootTimesRecorder::BootTimesRecorder() = default;

BootTimesRecorder::~BootTimesRecorder() = default;

// static
BootTimesRecorder* BootTimesRecorder::Get() {
  return g_boot_times_recorder.Pointer();
}

// static
BootTimesRecorder* BootTimesRecorder::GetIfCreated() {
  if (!g_boot_times_recorder.IsCreated()) {
    return nullptr;
  }
  return g_boot_times_recorder.Pointer();
}

void BootTimesRecorder::LoginDone(bool is_user_new) {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (login_done_)
    return;

  login_done_ = true;
  login_started_ = false;
  AddLoginTimeMarker("LoginDone", false);
  RecordCurrentStats(kChromeFirstRender);
  LoginEventRecorder::Get()->ScheduleWriteLoginTimes(
      kLoginTimes, (is_user_new ? kUmaLoginNewUser : kUmaLogin),
      kUmaLoginPrefix);
}

void BootTimesRecorder::WriteLogoutTimes() {
  // Either we're on the browser thread, or (more likely) Chrome is in the
  // process of shutting down and we're on the main thread but the message loop
  // has already been terminated.
  DCHECK(!BrowserThread::IsThreadInitialized(BrowserThread::UI) ||
         BrowserThread::CurrentlyOn(BrowserThread::UI));
  LoginEventRecorder::Get()->WriteLogoutTimes(
      kLogoutTimes, (restart_requested_ ? kUmaRestart : kUmaLogout),
      kUmaLogoutPrefix);
}

void BootTimesRecorder::AddLoginTimeMarkerWithURL(const char* marker_name,
                                                  const std::string& url) {
  LoginEventRecorder::Get()->AddLoginTimeMarkerWithURL(marker_name, url, false);
}

// static
void BootTimesRecorder::ClearLogoutStartedLastPreference() {
  PrefService* local_state = g_browser_process->local_state();
  local_state->ClearPref(prefs::kLogoutStartedLast);
}

void BootTimesRecorder::OnChromeProcessStart() {
  PrefService* local_state = g_browser_process->local_state();
  const std::string logout_started_last_str =
      local_state->GetString(prefs::kLogoutStartedLast);
  if (logout_started_last_str.empty())
    return;

  // Note that kLogoutStartedLast is not cleared on format error to stay in
  // logs in case of other fatal system errors.

  const LoginEventRecorder::Stats logout_started_last_stats =
      LoginEventRecorder::Stats::DeserializeFromString(logout_started_last_str);
  if (logout_started_last_stats.uptime().empty())
    return;

  double logout_started_last;
  double uptime;
  if (!logout_started_last_stats.UptimeDouble(&logout_started_last) ||
      !LoginEventRecorder::Stats::GetCurrentStats().UptimeDouble(&uptime)) {
    return;
  }

  if (logout_started_last >= uptime) {
    // Reboot happened.
    ClearLogoutStartedLastPreference();
    return;
  }

  // Write /run/bootstat/uptime-logout-started as well.
  constexpr char kLogoutStarted[] = "logout-started";
  logout_started_last_stats.RecordStatsWithCallback(
      kLogoutStarted, /*write_flag_file=*/true,
      base::BindOnce(&BootTimesRecorder::ClearLogoutStartedLastPreference));
}

void BootTimesRecorder::OnLogoutStarted(PrefService* state) {
  const std::string uptime =
      LoginEventRecorder::Stats::GetCurrentStats().SerializeToString();
  if (!uptime.empty())
    state->SetString(prefs::kLogoutStartedLast, uptime);
}

void BootTimesRecorder::RecordCurrentStats(const std::string& name) {
  LoginEventRecorder::Get()->RecordCurrentStats(name);
}

void BootTimesRecorder::SaveChromeMainStats() {
  chrome_main_stats_ = LoginEventRecorder::Stats::GetCurrentStats();
}

void BootTimesRecorder::RecordChromeMainStats() {
  chrome_main_stats_.RecordStats(kChromeMain, /*write_flag_file=*/false);
}

void BootTimesRecorder::RecordLoginAttempted() {
  DCHECK_CURRENTLY_ON(BrowserThread::UI);
  if (login_done_)
    return;

  LoginEventRecorder::Get()->ClearLoginTimeMarkers();
  AddLoginTimeMarker("LoginStarted", false);
  login_started_ = true;
}

void BootTimesRecorder::AddLoginTimeMarker(const char* marker_name,
                                           bool send_to_uma) {
  LoginEventRecorder::Get()->AddLoginTimeMarker(marker_name, send_to_uma);
}

void BootTimesRecorder::AddLogoutTimeMarker(const char* marker_name,
                                            bool send_to_uma) {
  LoginEventRecorder::Get()->AddLogoutTimeMarker(marker_name, send_to_uma);
}

void BootTimesRecorder::TabLoadStart(WebContents* web_contents) {
  if (!login_started_ || login_done_) {
    return;
  }

  AddLoginTimeMarkerWithURL("TabLoad-Start",
                            web_contents->GetLastCommittedURL().spec());

  RenderWidgetHost* rwh =
      web_contents->GetPrimaryMainFrame()->GetRenderWidgetHost();
  if (render_widget_host_observations_.IsObservingSource(rwh)) {
    return;
  }

  render_widget_host_observations_.AddObservation(rwh);
}

void BootTimesRecorder::TabLoadEnd(WebContents* web_contents) {
  if (!login_started_ || login_done_) {
    return;
  }

  RenderWidgetHost* rwh =
      web_contents->GetPrimaryMainFrame()->GetRenderWidgetHost();
  if (!render_widget_host_observations_.IsObservingSource(rwh)) {
    return;
  }

  AddLoginTimeMarkerWithURL("TabLoad-End",
                            web_contents->GetLastCommittedURL().spec());
}

void BootTimesRecorder::RenderFrameHostChanged(
    content::RenderFrameHost* old_host,
    content::RenderFrameHost* new_host) {
  // `old_host` may be null when creating the initial RenderFrameHost in a new
  // tab.
  if (!old_host) {
    return;
  }

  RenderWidgetHost* old_rwh = old_host->GetRenderWidgetHost();
  if (render_widget_host_observations_.IsObservingSource(old_rwh)) {
    render_widget_host_observations_.RemoveObservation(old_rwh);
    render_widget_host_observations_.AddObservation(
        new_host->GetRenderWidgetHost());
  }
}

void BootTimesRecorder::RenderWidgetHostDidUpdateVisualProperties(
    content::RenderWidgetHost* widget_host) {
  AddLoginTimeMarkerWithURL("TabPaint", GetTabUrl(widget_host));
}

void BootTimesRecorder::RenderWidgetHostDestroyed(
    content::RenderWidgetHost* widget_host) {
  render_widget_host_observations_.RemoveObservation(widget_host);
}

}  // namespace ash