Write "logout-started" event on next boot.

Writing logout-started on process exit is unstable.
So moving it to next boot.

BUG=352130
TEST=manual

Review URL: https://codereview.chromium.org/303233004

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@274649 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/chromeos/boot_times_loader.cc b/chrome/browser/chromeos/boot_times_loader.cc
index 94a675d..db542f0 100644
--- a/chrome/browser/chromeos/boot_times_loader.cc
+++ b/chrome/browser/chromeos/boot_times_loader.cc
@@ -10,11 +10,14 @@
 #include "base/command_line.h"
 #include "base/file_util.h"
 #include "base/files/file_path.h"
+#include "base/json/json_reader.h"
+#include "base/json/json_writer.h"
 #include "base/lazy_instance.h"
 #include "base/location.h"
 #include "base/message_loop/message_loop.h"
 #include "base/message_loop/message_loop_proxy.h"
 #include "base/metrics/histogram.h"
+#include "base/prefs/pref_service.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
@@ -29,6 +32,7 @@
 #include "chrome/browser/ui/browser_iterator.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
 #include "content/public/browser/browser_thread.h"
 #include "content/public/browser/navigation_controller.h"
 #include "content/public/browser/notification_service.h"
@@ -43,6 +47,9 @@
 
 namespace {
 
+const char kUptime[] = "uptime";
+const char kDisk[] = "disk";
+
 RenderWidgetHost* GetRenderWidgetHost(NavigationController* tab) {
   WebContents* web_contents = tab->GetWebContents();
   if (web_contents) {
@@ -70,6 +77,19 @@
   return std::string();
 }
 
+// Appends the given buffer into the file. Returns the number of bytes
+// written, or -1 on error.
+// TODO(satorux): Move this to file_util.
+int AppendFile(const base::FilePath& file_path, const char* data, int size) {
+  FILE* file = base::OpenFile(file_path, "a");
+  if (!file)
+    return -1;
+
+  const int num_bytes_written = fwrite(data, 1, size, file);
+  base::CloseFile(file);
+  return num_bytes_written;
+}
+
 }  // namespace
 
 namespace chromeos {
@@ -112,6 +132,109 @@
 static base::LazyInstance<BootTimesLoader> g_boot_times_loader =
     LAZY_INSTANCE_INITIALIZER;
 
+// static
+BootTimesLoader::Stats BootTimesLoader::Stats::GetCurrentStats() {
+  const base::FilePath kProcUptime(FPL("/proc/uptime"));
+  const base::FilePath kDiskStat(FPL("/sys/block/sda/stat"));
+  Stats stats;
+  // Callers of this method expect synchronous behavior.
+  // It's safe to allow IO here, because only virtual FS are accessed.
+  base::ThreadRestrictions::ScopedAllowIO allow_io;
+  base::ReadFileToString(kProcUptime, &stats.uptime_);
+  base::ReadFileToString(kDiskStat, &stats.disk_);
+  return stats;
+}
+
+std::string BootTimesLoader::Stats::SerializeToString() const {
+  if (uptime_.empty() || disk_.empty())
+    return std::string();
+  base::DictionaryValue dictionary;
+  dictionary.SetString(kUptime, uptime_);
+  dictionary.SetString(kDisk, disk_);
+
+  std::string result;
+  if (!base::JSONWriter::Write(&dictionary, &result)) {
+    LOG(WARNING) << "BootTimesLoader::Stats::SerializeToString(): failed.";
+    return std::string();
+  }
+
+  return result;
+}
+
+// static
+BootTimesLoader::Stats BootTimesLoader::Stats::DeserializeFromString(
+    const std::string& source) {
+  if (source.empty())
+    return Stats();
+
+  scoped_ptr<base::Value> value(base::JSONReader::Read(source));
+  base::DictionaryValue* dictionary;
+  if (!value || !value->GetAsDictionary(&dictionary)) {
+    LOG(ERROR) << "BootTimesLoader::Stats::DeserializeFromString(): not a "
+                  "dictionary: '" << source << "'";
+    return Stats();
+  }
+
+  Stats result;
+  if (!dictionary->GetString(kUptime, &result.uptime_) ||
+      !dictionary->GetString(kDisk, &result.disk_)) {
+    LOG(ERROR)
+        << "BootTimesLoader::Stats::DeserializeFromString(): format error: '"
+        << source << "'";
+    return Stats();
+  }
+
+  return result;
+}
+
+bool BootTimesLoader::Stats::UptimeDouble(double* result) const {
+  std::string uptime = uptime_;
+  const size_t space_at = uptime.find_first_of(' ');
+  if (space_at == std::string::npos)
+    return false;
+
+  uptime.resize(space_at);
+
+  if (base::StringToDouble(uptime, result))
+    return true;
+
+  return false;
+}
+
+void BootTimesLoader::Stats::RecordStats(const std::string& name) const {
+  BrowserThread::PostBlockingPoolTask(
+      FROM_HERE,
+      base::Bind(&BootTimesLoader::Stats::RecordStatsImpl,
+                 base::Owned(new Stats(*this)),
+                 name));
+}
+
+void BootTimesLoader::Stats::RecordStatsWithCallback(
+    const std::string& name,
+    const base::Closure& callback) const {
+  BrowserThread::PostBlockingPoolTaskAndReply(
+      FROM_HERE,
+      base::Bind(&BootTimesLoader::Stats::RecordStatsImpl,
+                 base::Owned(new Stats(*this)),
+                 name),
+      callback);
+}
+
+void BootTimesLoader::Stats::RecordStatsImpl(
+    const base::FilePath::StringType& name) const {
+  DCHECK(content::BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread());
+
+  const base::FilePath log_path(kLogPath);
+  const base::FilePath uptime_output =
+      log_path.Append(base::FilePath(kUptimePrefix + name));
+  const base::FilePath disk_output =
+      log_path.Append(base::FilePath(kDiskPrefix + name));
+
+  // Append numbers to the files.
+  AppendFile(uptime_output, uptime_.data(), uptime_.size());
+  AppendFile(disk_output, disk_.data(), disk_.size());
+}
+
 BootTimesLoader::BootTimesLoader()
     : backend_(new Backend()),
       have_registered_(false),
@@ -121,42 +244,14 @@
   logout_time_markers_.reserve(30);
 }
 
-BootTimesLoader::~BootTimesLoader() {}
+BootTimesLoader::~BootTimesLoader() {
+}
 
 // static
 BootTimesLoader* BootTimesLoader::Get() {
   return g_boot_times_loader.Pointer();
 }
 
-// Appends the given buffer into the file. Returns the number of bytes
-// written, or -1 on error.
-// TODO(satorux): Move this to file_util.
-static int AppendFile(const base::FilePath& file_path,
-                      const char* data,
-                      int size) {
-  FILE* file = base::OpenFile(file_path, "a");
-  if (!file) {
-    return -1;
-  }
-  const int num_bytes_written = fwrite(data, 1, size, file);
-  base::CloseFile(file);
-  return num_bytes_written;
-}
-
-static void RecordStatsDelayed(const base::FilePath::StringType& name,
-                               const std::string& uptime,
-                               const std::string& disk) {
-  const base::FilePath log_path(kLogPath);
-  const base::FilePath uptime_output =
-      log_path.Append(base::FilePath(kUptimePrefix + name));
-  const base::FilePath disk_output =
-      log_path.Append(base::FilePath(kDiskPrefix + name));
-
-  // Append numbers to the files.
-  AppendFile(uptime_output, uptime.data(), uptime.size());
-  AppendFile(disk_output, disk.data(), disk.size());
-}
-
 // static
 void BootTimesLoader::WriteTimes(
     const std::string base_name,
@@ -265,32 +360,63 @@
              logout_time_markers_);
 }
 
-void BootTimesLoader::RecordStats(const std::string& name, const Stats& stats) {
-  BrowserThread::PostTask(
-      BrowserThread::FILE, FROM_HERE,
-      base::Bind(&RecordStatsDelayed, name, stats.uptime, stats.disk));
+// static
+void BootTimesLoader::ClearLogoutStartedLastPreference() {
+  PrefService* local_state = g_browser_process->local_state();
+  local_state->ClearPref(prefs::kLogoutStartedLast);
 }
 
-BootTimesLoader::Stats BootTimesLoader::GetCurrentStats() {
-  const base::FilePath kProcUptime(FPL("/proc/uptime"));
-  const base::FilePath kDiskStat(FPL("/sys/block/sda/stat"));
-  Stats stats;
-  base::ThreadRestrictions::ScopedAllowIO allow_io;
-  base::ReadFileToString(kProcUptime, &stats.uptime);
-  base::ReadFileToString(kDiskStat, &stats.disk);
-  return stats;
+void BootTimesLoader::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 Stats logout_started_last_stats =
+      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) ||
+      !Stats::GetCurrentStats().UptimeDouble(&uptime)) {
+    return;
+  }
+
+  if (logout_started_last >= uptime) {
+    // Reboot happened.
+    ClearLogoutStartedLastPreference();
+    return;
+  }
+
+  // Write /tmp/uptime-logout-started as well.
+  const char kLogoutStarted[] = "logout-started";
+  logout_started_last_stats.RecordStatsWithCallback(
+      kLogoutStarted,
+      base::Bind(&BootTimesLoader::ClearLogoutStartedLastPreference));
+}
+
+void BootTimesLoader::OnLogoutStarted(PrefService* state) {
+  const std::string uptime = Stats::GetCurrentStats().SerializeToString();
+  if (!uptime.empty())
+    state->SetString(prefs::kLogoutStartedLast, uptime);
 }
 
 void BootTimesLoader::RecordCurrentStats(const std::string& name) {
-  RecordStats(name, GetCurrentStats());
+  Stats::GetCurrentStats().RecordStats(name);
 }
 
 void BootTimesLoader::SaveChromeMainStats() {
-  chrome_main_stats_ = GetCurrentStats();
+  chrome_main_stats_ = Stats::GetCurrentStats();
 }
 
 void BootTimesLoader::RecordChromeMainStats() {
-  RecordStats(kChromeMain, chrome_main_stats_);
+  chrome_main_stats_.RecordStats(kChromeMain);
 }
 
 void BootTimesLoader::RecordLoginAttempted() {
diff --git a/chrome/browser/chromeos/boot_times_loader.h b/chrome/browser/chromeos/boot_times_loader.h
index 854dccd..1df7e1c 100644
--- a/chrome/browser/chromeos/boot_times_loader.h
+++ b/chrome/browser/chromeos/boot_times_loader.h
@@ -17,6 +17,8 @@
 #include "content/public/browser/notification_registrar.h"
 #include "content/public/browser/render_widget_host.h"
 
+class PrefService;
+
 namespace chromeos {
 
 // BootTimesLoader loads the bootimes of Chrome OS from the file system.
@@ -82,6 +84,12 @@
   // Mark that WriteLogoutTimes should handle restart.
   void set_restart_requested() { restart_requested_ = true; }
 
+  // This is called on Chrome process startup to write saved logout stats.
+  void OnChromeProcessStart();
+
+  // This saves logout-started metric to Local State.
+  void OnLogoutStarted(PrefService* state);
+
  private:
   // BootTimesLoader calls into the Backend on the file thread to load
   // the boot times.
@@ -119,21 +127,46 @@
     bool send_to_uma_;
   };
 
-  struct Stats {
+  class Stats {
    public:
-    std::string uptime;
-    std::string disk;
+    // Initializes stats with current /proc values.
+    static Stats GetCurrentStats();
+
+    // Returns JSON representation.
+    std::string SerializeToString() const;
+
+    // Creates new object from JSON representation.
+    static Stats DeserializeFromString(const std::string& value);
+
+    const std::string& uptime() const { return uptime_; }
+    const std::string& disk() const { return disk_; }
+
+    // Writes "uptime in seconds" to result. (This is first field in uptime_.)
+    // Returns true on successful conversion.
+    bool UptimeDouble(double* result) const;
+
+    void RecordStats(const std::string& name) const;
+    void RecordStatsWithCallback(const std::string& name,
+                                 const base::Closure& callback) const;
+
+   private:
+    // Runs on BlockingPool
+    void RecordStatsImpl(const std::string& name) const;
+
+    std::string uptime_;
+    std::string disk_;
   };
 
-  static void RecordStats(
-      const std::string& name, const Stats& stats);
-  static Stats GetCurrentStats();
   static void WriteTimes(const std::string base_name,
                          const std::string uma_name,
                          const std::string uma_prefix,
                          std::vector<TimeMarker> login_times);
   static void AddMarker(std::vector<TimeMarker>* vector, TimeMarker marker);
 
+  // Clear saved logout-started metric in Local State.
+  // This method is called when logout-state was writen to file.
+  static void ClearLogoutStartedLastPreference();
+
   // Used to hold the stats at main().
   Stats chrome_main_stats_;
   scoped_refptr<Backend> backend_;
diff --git a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
index ea9f3ba..e73622f 100644
--- a/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
+++ b/chrome/browser/chromeos/chrome_browser_main_chromeos.cc
@@ -613,6 +613,8 @@
   // -- This used to be in ChromeBrowserMainParts::PreMainMessageLoopRun()
   // -- just after CreateProfile().
 
+  BootTimesLoader::Get()->OnChromeProcessStart();
+
   // Restarting Chrome inside existing user session. Possible cases:
   // 1. Chrome is restarted after crash.
   // 2. Chrome is started in browser_tests skipping the login flow
diff --git a/chrome/browser/chromeos/preferences.cc b/chrome/browser/chromeos/preferences.cc
index 2fa7fe0..d0e04df 100644
--- a/chrome/browser/chromeos/preferences.cc
+++ b/chrome/browser/chromeos/preferences.cc
@@ -83,6 +83,7 @@
   registry->RegisterBooleanPref(prefs::kOwnerPrimaryMouseButtonRight, false);
   registry->RegisterBooleanPref(prefs::kOwnerTapToClickEnabled, true);
   registry->RegisterBooleanPref(prefs::kVirtualKeyboardEnabled, false);
+  registry->RegisterStringPref(prefs::kLogoutStartedLast, std::string());
 }
 
 // static
diff --git a/chrome/browser/lifetime/application_lifetime.cc b/chrome/browser/lifetime/application_lifetime.cc
index 6731cb5..7e377a8 100644
--- a/chrome/browser/lifetime/application_lifetime.cc
+++ b/chrome/browser/lifetime/application_lifetime.cc
@@ -143,13 +143,12 @@
 #if defined(OS_CHROMEOS)
   StartShutdownTracing();
   chromeos::BootTimesLoader::Get()->AddLogoutTimeMarker("LogoutStarted", false);
-  // Write /tmp/uptime-logout-started as well.
-  const char kLogoutStarted[] = "logout-started";
-  chromeos::BootTimesLoader::Get()->RecordCurrentStats(kLogoutStarted);
 
-  // Login screen should show up in owner's locale.
   PrefService* state = g_browser_process->local_state();
   if (state) {
+    chromeos::BootTimesLoader::Get()->OnLogoutStarted(state);
+
+    // Login screen should show up in owner's locale.
     std::string owner_locale = state->GetString(prefs::kOwnerLocale);
     if (!owner_locale.empty() &&
         state->GetString(prefs::kApplicationLocale) != owner_locale &&