Measure and report time between user-space process crashes.

BUG=none
TEST=unit tests, gmerged on the device and inspected
logs, about:histograms,etc.

Review URL: http://codereview.chromium.org/2736008
diff --git a/metrics/counter.cc b/metrics/counter.cc
index 34fcf71..58dbae4 100644
--- a/metrics/counter.cc
+++ b/metrics/counter.cc
@@ -58,11 +58,16 @@
 }
 
 void TaggedCounter::UpdateInternal(int tag, int count, bool flush) {
-  // If there's no new data and the last record in the aggregation
-  // file is with the same tag, there's nothing to do.
-  if (!flush && count <= 0 &&
-      record_state_ == kRecordValid && record_.tag() == tag)
-    return;
+  if (flush) {
+    // Flushing but record is null, so nothing to do.
+    if (record_state_ == kRecordNull)
+      return;
+  } else {
+    // If there's no new data and the last record in the aggregation
+    // file is with the same tag, there's nothing to do.
+    if (count <= 0 && record_state_ == kRecordValid && record_.tag() == tag)
+      return;
+  }
 
   DLOG(INFO) << "tag: " << tag << " count: " << count << " flush: " << flush;
   DCHECK(filename_);
@@ -78,7 +83,7 @@
 
   ReadRecord(fd);
   ReportRecord(tag, flush);
-  UpdateRecord(tag, count);
+  UpdateRecord(tag, count, flush);
   WriteRecord(fd);
 
   HANDLE_EINTR(close(fd));
@@ -89,7 +94,7 @@
     return;
 
   if (HANDLE_EINTR(read(fd, &record_, sizeof(record_))) == sizeof(record_)) {
-    if (record_.count() > 0) {
+    if (record_.count() >= 0) {
       record_state_ = kRecordValid;
       return;
     }
@@ -106,7 +111,7 @@
 void TaggedCounter::ReportRecord(int tag, bool flush) {
   // If no valid record, there's nothing to report.
   if (record_state_ != kRecordValid) {
-    DCHECK(record_state_ == kRecordNull);
+    DCHECK_EQ(record_state_, kRecordNull);
     return;
   }
 
@@ -121,9 +126,11 @@
   record_state_ = kRecordNullDirty;
 }
 
-void TaggedCounter::UpdateRecord(int tag, int count) {
-  if (count <= 0)
+void TaggedCounter::UpdateRecord(int tag, int count, bool flush) {
+  if (flush) {
+    DCHECK(record_state_ == kRecordNull || record_state_ == kRecordNullDirty);
     return;
+  }
 
   switch (record_state_) {
     case kRecordNull:
@@ -137,8 +144,10 @@
       // If there's an existing record for the current tag,
       // accumulates the counts.
       DCHECK_EQ(record_.tag(), tag);
-      record_.Add(count);
-      record_state_ = kRecordValidDirty;
+      if (count > 0) {
+        record_.Add(count);
+        record_state_ = kRecordValidDirty;
+      }
       break;
 
     default:
diff --git a/metrics/counter.h b/metrics/counter.h
index aac00af..876b107 100644
--- a/metrics/counter.h
+++ b/metrics/counter.h
@@ -22,8 +22,7 @@
  public:
   // Callback type used for reporting aggregated or flushed data.
   // Once this callback is invoked by the counter, the reported
-  // aggregated data is discarded. Only aggregated data with positive
-  // counts is reported.
+  // aggregated data is discarded.
   //
   // |handle| is the |reporter_handle| pointer passed through Init.
   // |tag| is the tag associated with the aggregated count.
@@ -124,8 +123,9 @@
 
   // Updates the cached record given the new |tag| and |count|. This
   // method expects either a null cached record, or a valid cached
-  // record with the same tag as |tag|.
-  void UpdateRecord(int tag, int count);
+  // record with the same tag as |tag|. If |flush| is true, the method
+  // asserts that the cached record is null and returns.
+  void UpdateRecord(int tag, int count, bool flush);
 
   // If the cached record state is dirty, updates the persistent
   // storage specified through file descriptor |fd| and switches the
diff --git a/metrics/counter_test.cc b/metrics/counter_test.cc
index f9a191f..605e859 100644
--- a/metrics/counter_test.cc
+++ b/metrics/counter_test.cc
@@ -230,8 +230,8 @@
   ExpectReporterCall(/* tag */ 31, /* count */ 60);
   counter_.Init(kTestRecordFile, &Reporter, this);
   counter_.Update(/* tag */ 32, /* count */ 0);
-  EXPECT_TRUE(AssertNoOrEmptyRecordFile());
-  EXPECT_EQ(TaggedCounter::kRecordNull, counter_.record_state_);
+  EXPECT_PRED_FORMAT2(AssertRecord, /* day */ 32, /* seconds */ 0);
+  EXPECT_EQ(TaggedCounter::kRecordValid, counter_.record_state_);
 }
 
 TEST_F(TaggedCounterTest, Update) {
@@ -250,8 +250,8 @@
 
   ExpectReporterCall(/* tag */ 21, /* count */ 15);
   counter_.Update(/* tag */ 22, /* count */ 0);
-  EXPECT_TRUE(AssertNoOrEmptyRecordFile());
-  EXPECT_EQ(TaggedCounter::kRecordNull, counter_.record_state_);
+  EXPECT_PRED_FORMAT2(AssertRecord, /* day */ 22, /* seconds */ 0);
+  EXPECT_EQ(TaggedCounter::kRecordValid, counter_.record_state_);
 }
 
 }  // namespace chromeos_metrics
diff --git a/metrics/metrics_daemon.cc b/metrics/metrics_daemon.cc
index e93c9a8..6cbe8e8 100644
--- a/metrics/metrics_daemon.cc
+++ b/metrics/metrics_daemon.cc
@@ -15,6 +15,7 @@
 using base::TimeTicks;
 
 #define SAFE_MESSAGE(e) (e.message ? e.message : "unknown error")
+#define DBUS_IFACE_CRASH_REPORTER "org.chromium.CrashReporter"
 #define DBUS_IFACE_FLIMFLAM_MANAGER "org.chromium.flimflam.Manager"
 #define DBUS_IFACE_POWER_MANAGER "org.chromium.PowerManager"
 #define DBUS_IFACE_SESSION_MANAGER "org.chromium.SessionManagerInterface"
@@ -23,11 +24,16 @@
 // TODO(petkov): This file should probably live in a user-specific stateful
 // location, e.g., /home/chronos/user.
 static const char kDailyUseRecordFile[] = "/var/log/metrics/daily-usage";
+static const char kUserCrashIntervalRecordFile[] =
+    "/var/log/metrics/user-crash-interval";
 
 static const int kSecondsPerMinute = 60;
 static const int kMinutesPerHour = 60;
 static const int kHoursPerDay = 24;
 static const int kMinutesPerDay = kHoursPerDay * kMinutesPerHour;
+static const int kSecondsPerDay = kSecondsPerMinute * kMinutesPerDay;
+static const int kDaysPerWeek = 7;
+static const int kSecondsPerWeek = kSecondsPerDay * kDaysPerWeek;
 
 // The daily use monitor is scheduled to a 1-minute interval after
 // initial user activity and then it's exponentially backed off to
@@ -52,9 +58,20 @@
     8 /* hours */ * kMinutesPerHour * kSecondsPerMinute;
 const int MetricsDaemon::kMetricTimeToNetworkDropBuckets = 50;
 
+const char MetricsDaemon::kMetricUserCrashIntervalName[] =
+    "Logging.UserCrashInterval";
+const int MetricsDaemon::kMetricUserCrashIntervalMin = 1;
+const int MetricsDaemon::kMetricUserCrashIntervalMax = 4 * kSecondsPerWeek;
+const int MetricsDaemon::kMetricUserCrashIntervalBuckets = 50;
+
 // static
 const char* MetricsDaemon::kDBusMatches_[] = {
   "type='signal',"
+  "interface='" DBUS_IFACE_CRASH_REPORTER "',"
+  "path='/',"
+  "member='UserCrash'",
+
+  "type='signal',"
   "sender='org.chromium.flimflam',"
   "interface='" DBUS_IFACE_FLIMFLAM_MANAGER "',"
   "path='/',"
@@ -111,6 +128,9 @@
   metrics_lib_ = metrics_lib;
   daily_use_.reset(new chromeos_metrics::TaggedCounter());
   daily_use_->Init(kDailyUseRecordFile, &DailyUseReporter, this);
+  user_crash_interval_.reset(new chromeos_metrics::TaggedCounter());
+  user_crash_interval_->Init(kUserCrashIntervalRecordFile,
+                             &UserCrashIntervalReporter, this);
 
   // Don't setup D-Bus and GLib in test mode.
   if (testing)
@@ -130,7 +150,7 @@
   dbus_connection_setup_with_g_main(connection, NULL);
 
   // Registers D-Bus matches for the signals we would like to catch.
-  for (unsigned int m = 0; m < sizeof(kDBusMatches_) / sizeof(char*); m++) {
+  for (unsigned int m = 0; m < arraysize(kDBusMatches_); m++) {
     const char* match = kDBusMatches_[m];
     DLOG(INFO) << "adding dbus match: " << match;
     dbus_bus_add_match(connection, match, &error);
@@ -171,7 +191,11 @@
 
   DBusMessageIter iter;
   dbus_message_iter_init(message, &iter);
-  if (strcmp(interface, DBUS_IFACE_FLIMFLAM_MANAGER) == 0) {
+  if (strcmp(interface, DBUS_IFACE_CRASH_REPORTER) == 0) {
+    CHECK(strcmp(dbus_message_get_member(message),
+                 "UserCrash") == 0);
+    daemon->ProcessUserCrash();
+  } else if (strcmp(interface, DBUS_IFACE_FLIMFLAM_MANAGER) == 0) {
     CHECK(strcmp(dbus_message_get_member(message),
                  "StateChanged") == 0);
 
@@ -295,6 +319,7 @@
   TimeDelta since_epoch = now - Time();
   int day = since_epoch.InDays();
   daily_use_->Update(day, seconds);
+  user_crash_interval_->Update(0, seconds);
 
   // Schedules a use monitor on inactive->active transitions and
   // unschedules it on active->inactive transitions.
@@ -309,6 +334,14 @@
   user_active_last_ = now;
 }
 
+void MetricsDaemon::ProcessUserCrash() {
+  // Counts the active use time up to now.
+  SetUserActiveState(user_active_, Time::Now());
+
+  // Reports the active use time since the last crash and resets it.
+  user_crash_interval_->Flush();
+}
+
 // static
 gboolean MetricsDaemon::UseMonitorStatic(gpointer data) {
   return static_cast<MetricsDaemon*>(data)->UseMonitor() ? TRUE : FALSE;
@@ -371,6 +404,9 @@
 
 // static
 void MetricsDaemon::DailyUseReporter(void* handle, int tag, int count) {
+  if (count <= 0)
+    return;
+
   MetricsDaemon* daemon = static_cast<MetricsDaemon*>(handle);
   int minutes = (count + kSecondsPerMinute / 2) / kSecondsPerMinute;
   daemon->SendMetric(kMetricDailyUseTimeName, minutes,
@@ -379,6 +415,16 @@
                      kMetricDailyUseTimeBuckets);
 }
 
+// static
+void MetricsDaemon::UserCrashIntervalReporter(void* handle,
+                                              int tag, int count) {
+  MetricsDaemon* daemon = static_cast<MetricsDaemon*>(handle);
+  daemon->SendMetric(kMetricUserCrashIntervalName, count,
+                     kMetricUserCrashIntervalMin,
+                     kMetricUserCrashIntervalMax,
+                     kMetricUserCrashIntervalBuckets);
+}
+
 void MetricsDaemon::SendMetric(const std::string& name, int sample,
                                int min, int max, int nbuckets) {
   DLOG(INFO) << "received metric: " << name << " " << sample << " "
diff --git a/metrics/metrics_daemon.h b/metrics/metrics_daemon.h
index ee80596..50958b8 100644
--- a/metrics/metrics_daemon.h
+++ b/metrics/metrics_daemon.h
@@ -40,11 +40,13 @@
   FRIEND_TEST(MetricsDaemonTest, NetStateChangedSimpleDrop);
   FRIEND_TEST(MetricsDaemonTest, NetStateChangedSuspend);
   FRIEND_TEST(MetricsDaemonTest, PowerStateChanged);
+  FRIEND_TEST(MetricsDaemonTest, ProcessUserCrash);
   FRIEND_TEST(MetricsDaemonTest, ScreenSaverStateChanged);
   FRIEND_TEST(MetricsDaemonTest, SendMetric);
   FRIEND_TEST(MetricsDaemonTest, SessionStateChanged);
   FRIEND_TEST(MetricsDaemonTest, SetUserActiveState);
   FRIEND_TEST(MetricsDaemonTest, SetUserActiveStateTimeJump);
+  FRIEND_TEST(MetricsDaemonTest, UserCrashIntervalReporter);
 
   // The network states (see network_states.h).
   enum NetworkState {
@@ -87,6 +89,10 @@
   static const int kMetricTimeToNetworkDropMin;
   static const int kMetricTimeToNetworkDropMax;
   static const int kMetricTimeToNetworkDropBuckets;
+  static const char kMetricUserCrashIntervalName[];
+  static const int kMetricUserCrashIntervalMin;
+  static const int kMetricUserCrashIntervalMax;
+  static const int kMetricUserCrashIntervalBuckets;
 
   // D-Bus message match strings.
   static const char* kDBusMatches_[];
@@ -142,6 +148,10 @@
   // the usage file, the |seconds| are accumulated.
   void LogDailyUseRecord(int day, int seconds);
 
+  // Updates the active use time and logs time between user-space
+  // process crashes.
+  void ProcessUserCrash();
+
   // Callbacks for the daily use monitor. The daily use monitor uses
   // LogDailyUseRecord to aggregate current usage data and send it to
   // UMA, if necessary. It also reschedules itself using an
@@ -169,8 +179,14 @@
   void SendMetric(const std::string& name, int sample,
                   int min, int max, int nbuckets);
 
+  // TaggedCounter callback to process aggregated daily usage data and
+  // send to UMA.
   static void DailyUseReporter(void* data, int tag, int count);
 
+  // TaggedCounter callback to process time between user-space process
+  // crashes and send to UMA.
+  static void UserCrashIntervalReporter(void* data, int tag, int count);
+
   // Test mode.
   bool testing_;
 
@@ -195,13 +211,17 @@
   // started, screen is not locked.
   bool user_active_;
 
-  // Timestamps last user active update.  Active use time is
-  // aggregated each day before sending to UMA so using time since the
-  // epoch as the timestamp.
+  // Timestamps last user active update. Active use time is aggregated
+  // each day before sending to UMA so using time since the epoch as
+  // the timestamp.
   base::Time user_active_last_;
 
+  // Daily active use time in seconds.
   scoped_ptr<chromeos_metrics::TaggedCounterInterface> daily_use_;
 
+  // Active use time between user-space process crashes.
+  scoped_ptr<chromeos_metrics::TaggedCounterInterface> user_crash_interval_;
+
   // Sleep period until the next daily usage aggregation performed by
   // the daily use monitor (see ScheduleUseMonitor).
   int usemon_interval_;
diff --git a/metrics/metrics_daemon_test.cc b/metrics/metrics_daemon_test.cc
index 7119c85..1f2c0fa 100644
--- a/metrics/metrics_daemon_test.cc
+++ b/metrics/metrics_daemon_test.cc
@@ -42,12 +42,19 @@
  protected:
   virtual void SetUp() {
     EXPECT_EQ(NULL, daemon_.daily_use_.get());
+    EXPECT_EQ(NULL, daemon_.user_crash_interval_.get());
     daemon_.Init(true, &metrics_lib_);
 
     // Tests constructor initialization. Switches to mock counters.
     EXPECT_TRUE(NULL != daemon_.daily_use_.get());
+    EXPECT_TRUE(NULL != daemon_.user_crash_interval_.get());
+
+    // Allocates mock counter and transfers ownership.
     daily_use_ = new StrictMock<TaggedCounterMock>();
-    daemon_.daily_use_.reset(daily_use_);  // Transfers ownership.
+    daemon_.daily_use_.reset(daily_use_);
+    user_crash_interval_ = new StrictMock<TaggedCounterMock>();
+    daemon_.user_crash_interval_.reset(user_crash_interval_);
+
     EXPECT_FALSE(daemon_.user_active_);
     EXPECT_TRUE(daemon_.user_active_last_.is_null());
     EXPECT_EQ(MetricsDaemon::kUnknownNetworkState, daemon_.network_state_);
@@ -58,10 +65,24 @@
 
   virtual void TearDown() {}
 
-  // Adds a daily use aggregation counter expectation that the
+  // Adds active use aggregation counters update expectations that the
   // specified tag/count update will be generated.
-  void ExpectDailyUseUpdate(int tag, int count) {
-    EXPECT_CALL(*daily_use_, Update(tag, count))
+  void ExpectActiveUseUpdate(int daily_tag, int count) {
+    EXPECT_CALL(*daily_use_, Update(daily_tag, count))
+        .Times(1)
+        .RetiresOnSaturation();
+    EXPECT_CALL(*user_crash_interval_, Update(0, count))
+        .Times(1)
+        .RetiresOnSaturation();
+  }
+
+  // Adds active use aggregation counters update expectations that
+  // ignore the update arguments.
+  void IgnoreActiveUseUpdate() {
+    EXPECT_CALL(*daily_use_, Update(_, _))
+        .Times(1)
+        .RetiresOnSaturation();
+    EXPECT_CALL(*user_crash_interval_, Update(_, _))
         .Times(1)
         .RetiresOnSaturation();
   }
@@ -134,11 +155,11 @@
   // metric generation calls are marked as failures.
   StrictMock<MetricsLibraryMock> metrics_lib_;
 
-  // Daily use time aggregation counter mock. It's a strict mock so
-  // that all unexpected update calls are marked as failures. It's a
-  // pointer so that it can replace the scoped_ptr allocated by the
-  // daemon.
+  // Counter mocks. They are strict mocks so that all unexpected
+  // update calls are marked as failures. They are pointers so that
+  // they can replace the scoped_ptr's allocated by the daemon.
   StrictMock<TaggedCounterMock>* daily_use_;
+  StrictMock<TaggedCounterMock>* user_crash_interval_;
 };
 
 TEST_F(MetricsDaemonTest, DailyUseReporter) {
@@ -147,6 +168,10 @@
 
   ExpectDailyUseTimeMetric(/* sample */ 1);
   MetricsDaemon::DailyUseReporter(&daemon_, /* tag */ 23, /* count */ 89);
+
+  // There should be no metrics generated for the calls below.
+  MetricsDaemon::DailyUseReporter(&daemon_, /* tag */ 50, /* count */ 0);
+  MetricsDaemon::DailyUseReporter(&daemon_, /* tag */ 60, /* count */ -5);
 }
 
 TEST_F(MetricsDaemonTest, LookupNetworkState) {
@@ -183,6 +208,18 @@
   EXPECT_EQ(DBUS_HANDLER_RESULT_NOT_YET_HANDLED, res);
   DeleteDBusMessage(msg);
 
+  IgnoreActiveUseUpdate();
+  EXPECT_CALL(*user_crash_interval_, Flush())
+      .Times(1)
+      .RetiresOnSaturation();
+  msg = NewDBusSignalString("/",
+                            "org.chromium.CrashReporter",
+                            "UserCrash",
+                            "");
+  res = MetricsDaemon::MessageFilter(/* connection */ NULL, msg, &daemon_);
+  EXPECT_EQ(DBUS_HANDLER_RESULT_HANDLED, res);
+  DeleteDBusMessage(msg);
+
   msg = NewDBusSignalString("/",
                             "org.chromium.flimflam.Manager",
                             "StateChanged",
@@ -203,9 +240,7 @@
   EXPECT_EQ(DBUS_HANDLER_RESULT_HANDLED, res);
   DeleteDBusMessage(msg);
 
-  EXPECT_CALL(*daily_use_, Update(_, 0))
-      .Times(1)
-      .RetiresOnSaturation();
+  IgnoreActiveUseUpdate();
   msg = NewDBusSignalString("/",
                             "org.chromium.PowerManager",
                             "ScreenIsUnlocked",
@@ -216,9 +251,7 @@
   EXPECT_EQ(DBUS_HANDLER_RESULT_HANDLED, res);
   DeleteDBusMessage(msg);
 
-  EXPECT_CALL(*daily_use_, Update(_, 0))
-      .Times(1)
-      .RetiresOnSaturation();
+  IgnoreActiveUseUpdate();
   msg = NewDBusSignalString("/org/chromium/SessionManager",
                             "org.chromium.SessionManagerInterface",
                             "SessionStateChanged",
@@ -283,13 +316,13 @@
 }
 
 TEST_F(MetricsDaemonTest, PowerStateChanged) {
-  ExpectDailyUseUpdate(7, 0);
+  ExpectActiveUseUpdate(7, 0);
   daemon_.SetUserActiveState(/* active */ true,
                              TestTime(7 * kSecondsPerDay + 15));
   EXPECT_TRUE(daemon_.user_active_);
   EXPECT_EQ(TestTime(7 * kSecondsPerDay + 15), daemon_.user_active_last_);
 
-  ExpectDailyUseUpdate(7, 30);
+  ExpectActiveUseUpdate(7, 30);
   daemon_.PowerStateChanged("mem", TestTime(7 * kSecondsPerDay + 45));
   EXPECT_EQ(MetricsDaemon::kPowerStateMem, daemon_.power_state_);
   EXPECT_FALSE(daemon_.user_active_);
@@ -300,13 +333,21 @@
   EXPECT_FALSE(daemon_.user_active_);
   EXPECT_EQ(TestTime(7 * kSecondsPerDay + 45), daemon_.user_active_last_);
 
-  ExpectDailyUseUpdate(7, 0);
+  ExpectActiveUseUpdate(7, 0);
   daemon_.PowerStateChanged("otherstate", TestTime(7 * kSecondsPerDay + 185));
   EXPECT_EQ(MetricsDaemon::kUnknownPowerState, daemon_.power_state_);
   EXPECT_FALSE(daemon_.user_active_);
   EXPECT_EQ(TestTime(7 * kSecondsPerDay + 185), daemon_.user_active_last_);
 }
 
+TEST_F(MetricsDaemonTest, ProcessUserCrash) {
+  IgnoreActiveUseUpdate();
+  EXPECT_CALL(*user_crash_interval_, Flush())
+      .Times(1)
+      .RetiresOnSaturation();
+  daemon_.ProcessUserCrash();
+}
+
 TEST_F(MetricsDaemonTest, SendMetric) {
   ExpectMetric("Dummy.Metric", 3, 1, 100, 50);
   daemon_.SendMetric("Dummy.Metric", /* sample */ 3,
@@ -314,19 +355,19 @@
 }
 
 TEST_F(MetricsDaemonTest, SessionStateChanged) {
-  ExpectDailyUseUpdate(15, 0);
+  ExpectActiveUseUpdate(15, 0);
   daemon_.SessionStateChanged("started", TestTime(15 * kSecondsPerDay + 20));
   EXPECT_EQ(MetricsDaemon::kSessionStateStarted, daemon_.session_state_);
   EXPECT_TRUE(daemon_.user_active_);
   EXPECT_EQ(TestTime(15 * kSecondsPerDay + 20), daemon_.user_active_last_);
 
-  ExpectDailyUseUpdate(15, 130);
+  ExpectActiveUseUpdate(15, 130);
   daemon_.SessionStateChanged("stopped", TestTime(15 * kSecondsPerDay + 150));
   EXPECT_EQ(MetricsDaemon::kSessionStateStopped, daemon_.session_state_);
   EXPECT_FALSE(daemon_.user_active_);
   EXPECT_EQ(TestTime(15 * kSecondsPerDay + 150), daemon_.user_active_last_);
 
-  ExpectDailyUseUpdate(15, 0);
+  ExpectActiveUseUpdate(15, 0);
   daemon_.SessionStateChanged("otherstate",
                               TestTime(15 * kSecondsPerDay + 300));
   EXPECT_EQ(MetricsDaemon::kUnknownSessionState, daemon_.session_state_);
@@ -335,31 +376,31 @@
 }
 
 TEST_F(MetricsDaemonTest, SetUserActiveState) {
-  ExpectDailyUseUpdate(5, 0);
+  ExpectActiveUseUpdate(5, 0);
   daemon_.SetUserActiveState(/* active */ false,
                              TestTime(5 * kSecondsPerDay + 10));
   EXPECT_FALSE(daemon_.user_active_);
   EXPECT_EQ(TestTime(5 * kSecondsPerDay + 10), daemon_.user_active_last_);
 
-  ExpectDailyUseUpdate(6, 0);
+  ExpectActiveUseUpdate(6, 0);
   daemon_.SetUserActiveState(/* active */ true,
                              TestTime(6 * kSecondsPerDay + 20));
   EXPECT_TRUE(daemon_.user_active_);
   EXPECT_EQ(TestTime(6 * kSecondsPerDay + 20), daemon_.user_active_last_);
 
-  ExpectDailyUseUpdate(6, 100);
+  ExpectActiveUseUpdate(6, 100);
   daemon_.SetUserActiveState(/* active */ true,
                              TestTime(6 * kSecondsPerDay + 120));
   EXPECT_TRUE(daemon_.user_active_);
   EXPECT_EQ(TestTime(6 * kSecondsPerDay + 120), daemon_.user_active_last_);
 
-  ExpectDailyUseUpdate(6, 110);
+  ExpectActiveUseUpdate(6, 110);
   daemon_.SetUserActiveState(/* active */ false,
                              TestTime(6 * kSecondsPerDay + 230));
   EXPECT_FALSE(daemon_.user_active_);
   EXPECT_EQ(TestTime(6 * kSecondsPerDay + 230), daemon_.user_active_last_);
 
-  ExpectDailyUseUpdate(6, 0);
+  ExpectActiveUseUpdate(6, 0);
   daemon_.SetUserActiveState(/* active */ false,
                              TestTime(6 * kSecondsPerDay + 260));
   EXPECT_FALSE(daemon_.user_active_);
@@ -367,25 +408,33 @@
 }
 
 TEST_F(MetricsDaemonTest, SetUserActiveStateTimeJump) {
-  ExpectDailyUseUpdate(10, 0);
+  ExpectActiveUseUpdate(10, 0);
   daemon_.SetUserActiveState(/* active */ true,
                              TestTime(10 * kSecondsPerDay + 500));
   EXPECT_TRUE(daemon_.user_active_);
   EXPECT_EQ(TestTime(10 * kSecondsPerDay + 500), daemon_.user_active_last_);
 
-  ExpectDailyUseUpdate(10, 0);
+  ExpectActiveUseUpdate(10, 0);
   daemon_.SetUserActiveState(/* active */ true,
                              TestTime(10 * kSecondsPerDay + 300));
   EXPECT_TRUE(daemon_.user_active_);
   EXPECT_EQ(TestTime(10 * kSecondsPerDay + 300), daemon_.user_active_last_);
 
-  ExpectDailyUseUpdate(10, 0);
+  ExpectActiveUseUpdate(10, 0);
   daemon_.SetUserActiveState(/* active */ true,
                              TestTime(10 * kSecondsPerDay + 1000));
   EXPECT_TRUE(daemon_.user_active_);
   EXPECT_EQ(TestTime(10 * kSecondsPerDay + 1000), daemon_.user_active_last_);
 }
 
+TEST_F(MetricsDaemonTest, UserCrashIntervalReporter) {
+  ExpectMetric(MetricsDaemon::kMetricUserCrashIntervalName, 50,
+               MetricsDaemon::kMetricUserCrashIntervalMin,
+               MetricsDaemon::kMetricUserCrashIntervalMax,
+               MetricsDaemon::kMetricUserCrashIntervalBuckets);
+  MetricsDaemon::UserCrashIntervalReporter(&daemon_, 0, 50);
+}
+
 int main(int argc, char** argv) {
   testing::InitGoogleTest(&argc, argv);
   return RUN_ALL_TESTS();