Implement a persistent storage aggregation counter class.

This class is currently used to aggregate the active daily use time
but can also be used to aggregate other data (e.g., active use time
between crashes) before sending to UMA. Abstracting this in a separate
class also simplifies the daemon unit tests.

An alternative design would store the data on shutdown (but may slow
down shutdown a little). This should do it for now.

BUG=none
TEST=gmerged on device,inspected logs,about:histograms,etc.

Review URL: http://codereview.chromium.org/2731008
diff --git a/metrics/Makefile b/metrics/Makefile
index e0c3741..c8ba545 100644
--- a/metrics/Makefile
+++ b/metrics/Makefile
@@ -16,32 +16,42 @@
 LIB = libmetrics.a
 SHAREDLIB = libmetrics.so
 LIB_TEST = metrics_library_test
+COUNTER_TEST = counter_test
 
+TESTCOUNTER_OBJS = \
+	counter.o \
+	counter_test.o
 CLIENT_OBJS = \
 	metrics_client.o
+DAEMON_OBJS = \
+	counter.o \
+	metrics_daemon.o \
+	metrics_daemon_main.o
+TESTDAEMON_OBJS = \
+	counter.o \
+	metrics_daemon.o \
+	metrics_daemon_test.o
 LIB_OBJS = \
 	metrics_library.o
 TESTLIB_OBJS = \
 	metrics_library.o \
 	metrics_library_test.o
-DAEMON_OBJS = \
-	metrics_daemon.o \
-	metrics_daemon_main.o
-TESTDAEMON_OBJS = \
-	metrics_daemon.o \
-	metrics_daemon_test.o
 
+TESTCOUNTER_LIBS = -lgmock -lgtest -lbase -lrt -lpthread
 DAEMON_LDFLAGS = $(LDFLAGS) $(LDCONFIG) -lrt -lbase -lpthread -lgflags
 TESTDAEMON_LIBS = -lgmock -lgtest
 TESTLIB_LIBS = -lgtest -lbase -lrt -lpthread
 
 all: $(LIB) $(SHAREDLIB) $(CLIENT) $(DAEMON)
 
-tests: $(DAEMON_TEST) $(LIB_TEST)
+tests: $(COUNTER_TEST) $(DAEMON_TEST) $(LIB_TEST)
 
 $(CLIENT): $(CLIENT_OBJS) $(SHAREDLIB)
 	$(CXX) $(LDFLAGS) $^ -o $@
 
+$(COUNTER_TEST): $(TESTCOUNTER_OBJS)
+	$(CXX) -o $@ $^ $(TESTCOUNTER_LIBS)
+
 $(DAEMON): $(DAEMON_OBJS) $(SHAREDLIB)
 	$(CXX) -o $@ $^ $(DAEMON_LDFLAGS)
 
@@ -73,4 +83,4 @@
 
 clean:
 	rm -f $(CLIENT) $(DAEMON) $(LIB) $(SHAREDLIB) *.o
-	rm -f $(DAEMON_TEST) $(LIB_TEST)
+	rm -f $(COUNTER_TEST) $(DAEMON_TEST) $(LIB_TEST)
diff --git a/metrics/counter.cc b/metrics/counter.cc
new file mode 100644
index 0000000..34fcf71
--- /dev/null
+++ b/metrics/counter.cc
@@ -0,0 +1,176 @@
+// Copyright (c) 2010 The Chromium OS 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 "counter.h"
+
+#include <sys/file.h>
+
+#include <base/eintr_wrapper.h>
+#include <base/logging.h>
+
+namespace chromeos_metrics {
+
+// TaggedCounter::Record implementation.
+void TaggedCounter::Record::Init(int tag, int count) {
+  tag_ = tag;
+  count_ = (count > 0) ? count : 0;
+}
+
+void TaggedCounter::Record::Add(int count) {
+  if (count <= 0)
+    return;
+
+  count_ += count;
+
+  // Saturates on postive overflow.
+  if (count_ < 0) {
+    count_ = INT_MAX;
+  }
+}
+
+// TaggedCounter implementation.
+TaggedCounter::TaggedCounter()
+    : filename_(NULL), reporter_(NULL), reporter_handle_(NULL),
+      record_state_(kRecordInvalid) {}
+
+TaggedCounter::~TaggedCounter() {}
+
+void TaggedCounter::Init(const char* filename,
+                         Reporter reporter, void* reporter_handle) {
+  DCHECK(filename);
+  filename_ = filename;
+  reporter_ = reporter;
+  reporter_handle_ = reporter_handle;
+  record_state_ = kRecordInvalid;
+}
+
+void TaggedCounter::Update(int tag, int count) {
+  UpdateInternal(tag,
+                 count,
+                 false);  // No flush.
+}
+
+void TaggedCounter::Flush() {
+  UpdateInternal(0,  // tag
+                 0,  // count
+                 true);  // Do flush.
+}
+
+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;
+
+  DLOG(INFO) << "tag: " << tag << " count: " << count << " flush: " << flush;
+  DCHECK(filename_);
+
+  // NOTE: The assumption is that this TaggedCounter object is the
+  // sole owner of the persistent storage file so no locking is
+  // necessary.
+  int fd = HANDLE_EINTR(open(filename_, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR));
+  if (fd < 0) {
+    PLOG(WARNING) << "Unable to open the persistent counter file";
+    return;
+  }
+
+  ReadRecord(fd);
+  ReportRecord(tag, flush);
+  UpdateRecord(tag, count);
+  WriteRecord(fd);
+
+  HANDLE_EINTR(close(fd));
+}
+
+void TaggedCounter::ReadRecord(int fd) {
+  if (record_state_ != kRecordInvalid)
+    return;
+
+  if (HANDLE_EINTR(read(fd, &record_, sizeof(record_))) == sizeof(record_)) {
+    if (record_.count() > 0) {
+      record_state_ = kRecordValid;
+      return;
+    }
+    // This shouldn't happen normally unless somebody messed with the
+    // persistent storage file.
+    NOTREACHED();
+    record_state_ = kRecordNullDirty;
+    return;
+  }
+
+  record_state_ = kRecordNull;
+}
+
+void TaggedCounter::ReportRecord(int tag, bool flush) {
+  // If no valid record, there's nothing to report.
+  if (record_state_ != kRecordValid) {
+    DCHECK(record_state_ == kRecordNull);
+    return;
+  }
+
+  // If the current record has the same tag as the new tag, it's not
+  // ready to be reported yet.
+  if (!flush && record_.tag() == tag)
+    return;
+
+  if (reporter_) {
+    reporter_(reporter_handle_, record_.tag(), record_.count());
+  }
+  record_state_ = kRecordNullDirty;
+}
+
+void TaggedCounter::UpdateRecord(int tag, int count) {
+  if (count <= 0)
+    return;
+
+  switch (record_state_) {
+    case kRecordNull:
+    case kRecordNullDirty:
+      // Current record is null, starting a new record.
+      record_.Init(tag, count);
+      record_state_ = kRecordValidDirty;
+      break;
+
+    case kRecordValid:
+      // If there's an existing record for the current tag,
+      // accumulates the counts.
+      DCHECK_EQ(record_.tag(), tag);
+      record_.Add(count);
+      record_state_ = kRecordValidDirty;
+      break;
+
+    default:
+      NOTREACHED();
+  }
+}
+
+void TaggedCounter::WriteRecord(int fd) {
+  switch (record_state_) {
+    case kRecordNullDirty:
+      // Truncates the aggregation file to discard the record.
+      PLOG_IF(WARNING, HANDLE_EINTR(ftruncate(fd, 0)) != 0);
+      record_state_ = kRecordNull;
+      break;
+
+    case kRecordValidDirty:
+      // Updates the accumulator record in the file if there's new data.
+      PLOG_IF(WARNING, HANDLE_EINTR(lseek(fd, 0, SEEK_SET)) != 0);
+      PLOG_IF(WARNING,
+              HANDLE_EINTR(write(fd, &record_, sizeof(record_))) !=
+              sizeof(record_));
+      record_state_ = kRecordValid;
+      break;
+
+    case kRecordNull:
+    case kRecordValid:
+      // Nothing to do.
+      break;
+
+    default:
+      NOTREACHED();
+  }
+}
+
+}  // namespace chromeos_metrics
diff --git a/metrics/counter.h b/metrics/counter.h
new file mode 100644
index 0000000..aac00af
--- /dev/null
+++ b/metrics/counter.h
@@ -0,0 +1,151 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_COUNTER_H_
+#define METRICS_COUNTER_H_
+
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+namespace chromeos_metrics {
+
+// TaggedCounter maintains a persistent storage (i.e., a file)
+// aggregation counter for a given tag (e.g., day, hour) that survives
+// system shutdowns, reboots and crashes, as well as daemon process
+// restarts. The counter object is initialized by pointing to the
+// persistent storage file and providing a callback used for reporting
+// aggregated data.  The counter can then be updated with additional
+// event counts.  The aggregated count is reported through the
+// callback when the counter is explicitly flushed or when data for a
+// new tag arrives.
+class TaggedCounterInterface {
+ 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.
+  //
+  // |handle| is the |reporter_handle| pointer passed through Init.
+  // |tag| is the tag associated with the aggregated count.
+  // |count| is aggregated count.
+  typedef void (*Reporter)(void* handle, int tag, int count);
+
+  virtual ~TaggedCounterInterface() {}
+
+  // Initializes the counter by providing the persistent storage
+  // location |filename| and a |reporter| callback for reporting
+  // aggregated counts. |reporter_handle| is sent to the |reporter|
+  // along with the aggregated counts.
+  //
+  // NOTE: The assumption is that this object is the sole owner of the
+  // persistent storage file so no locking is currently implemented.
+  virtual void Init(const char* filename,
+                    Reporter reporter, void* reporter_handle) = 0;
+
+  // Adds |count| of events for the given |tag|. If there's an
+  // existing aggregated count for a different tag, it's reported
+  // through the reporter callback and discarded.
+  virtual void Update(int tag, int count) = 0;
+
+  // Reports the current aggregated count (if any) through the
+  // reporter callback and discards it.
+  virtual void Flush() = 0;
+};
+
+class TaggedCounter : public TaggedCounterInterface {
+ public:
+  TaggedCounter();
+  ~TaggedCounter();
+
+  // Implementation of interface methods.
+  void Init(const char* filename, Reporter reporter, void* reporter_handle);
+  void Update(int tag, int count);
+  void Flush();
+
+ private:
+  friend class RecordTest;
+  friend class TaggedCounterTest;
+  FRIEND_TEST(TaggedCounterTest, BadFileLocation);
+  FRIEND_TEST(TaggedCounterTest, Flush);
+  FRIEND_TEST(TaggedCounterTest, InitFromFile);
+  FRIEND_TEST(TaggedCounterTest, Update);
+
+  // The current tag/count record is cached by the counter object to
+  // avoid potentially unnecessary I/O. The cached record can be in
+  // one of the following states:
+  enum RecordState {
+    kRecordInvalid,    // Invalid record, sync from persistent storage needed.
+    kRecordNull,       // No current record, persistent storage synced.
+    kRecordNullDirty,  // No current record, persistent storage is invalid.
+    kRecordValid,      // Current record valid, persistent storage synced.
+    kRecordValidDirty  // Current record valid, persistent storage is invalid.
+  };
+
+  // Defines the tag/count record. Objects of this class are synced
+  // with the persistent storage through binary reads/writes.
+  class Record {
+   public:
+    // Creates a new Record with |tag_| and |count_| reset to 0.
+    Record() : tag_(0), count_(0) {}
+
+    // Initializes with |tag| and |count|. If |count| is negative,
+    // |count_| is set to 0.
+    void Init(int tag, int count);
+
+    // Adds |count| to the current |count_|. Negative |count| is
+    // ignored. In case of positive overflow, |count_| is saturated to
+    // INT_MAX.
+    void Add(int count);
+
+    int tag() const { return tag_; }
+    int count() const { return count_; }
+
+   private:
+    int tag_;
+    int count_;
+  };
+
+  // Implementation of the Update and Flush methods. Goes through the
+  // necessary steps to read, report, update, and sync the aggregated
+  // record.
+  void UpdateInternal(int tag, int count, bool flush);
+
+  // If the current cached record is invalid, reads it from persistent
+  // storage specified through file descriptor |fd| and updates the
+  // cached record state to either null, or valid depending on the
+  // persistent storage contents.
+  void ReadRecord(int fd);
+
+  // If there's an existing valid record and either |flush| is true,
+  // or the new |tag| is different than the old one, reports the
+  // aggregated data through the reporter callback and resets the
+  // cached record.
+  void ReportRecord(int tag, bool flush);
+
+  // 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);
+
+  // If the cached record state is dirty, updates the persistent
+  // storage specified through file descriptor |fd| and switches the
+  // record state to non-dirty.
+  void WriteRecord(int fd);
+
+  // Persistent storage file path.
+  const char* filename_;
+
+  // Aggregated data reporter callback and handle to pass-through.
+  Reporter reporter_;
+  void* reporter_handle_;
+
+  // Current cached aggregation record.
+  Record record_;
+
+  // Current cached aggregation record state.
+  RecordState record_state_;
+};
+
+}  // namespace chromeos_metrics
+
+#endif  // METRICS_COUNTER_H_
diff --git a/metrics/counter_mock.h b/metrics/counter_mock.h
new file mode 100644
index 0000000..22327ac
--- /dev/null
+++ b/metrics/counter_mock.h
@@ -0,0 +1,26 @@
+// Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef METRICS_COUNTER_MOCK_H_
+#define METRICS_COUNTER_MOCK_H_
+
+#include <string>
+
+#include <gmock/gmock.h>
+
+#include "counter.h"
+
+namespace chromeos_metrics {
+
+class TaggedCounterMock : public TaggedCounterInterface {
+ public:
+  MOCK_METHOD3(Init, void(const char* filename,
+                          Reporter reporter, void* reporter_handle));
+  MOCK_METHOD2(Update, void(int tag, int count));
+  MOCK_METHOD0(Flush, void());
+};
+
+}  // namespace chromeos_metrics
+
+#endif  // METRICS_COUNTER_MOCK_H_
diff --git a/metrics/counter_test.cc b/metrics/counter_test.cc
new file mode 100644
index 0000000..f9a191f
--- /dev/null
+++ b/metrics/counter_test.cc
@@ -0,0 +1,262 @@
+// Copyright (c) 2010 The Chromium OS 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 <sys/file.h>
+
+#include <base/eintr_wrapper.h>
+#include <base/file_util.h>
+#include <base/logging.h>
+#include <base/string_util.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "counter.h"
+
+using ::testing::_;
+using ::testing::MockFunction;
+using ::testing::StrictMock;
+
+namespace chromeos_metrics {
+
+static const char kTestRecordFile[] = "record-file";
+static const char kDoesNotExistFile[] = "/does/not/exist";
+
+class RecordTest : public testing::Test {
+ protected:
+  virtual void SetUp() {
+    EXPECT_EQ(0, record_.tag());
+    EXPECT_EQ(0, record_.count());
+  }
+
+  // The record under test.
+  TaggedCounter::Record record_;
+};
+
+class TaggedCounterTest : public testing::Test {
+ protected:
+  virtual void SetUp() {
+    EXPECT_EQ(NULL, counter_.filename_);
+    EXPECT_TRUE(NULL == counter_.reporter_);
+    EXPECT_EQ(NULL, counter_.reporter_handle_);
+    EXPECT_EQ(TaggedCounter::kRecordInvalid, counter_.record_state_);
+
+    counter_.Init(kTestRecordFile, &Reporter, this);
+    EXPECT_TRUE(AssertNoOrEmptyRecordFile());
+    EXPECT_EQ(kTestRecordFile, counter_.filename_);
+    EXPECT_TRUE(Reporter == counter_.reporter_);
+    EXPECT_EQ(this, counter_.reporter_handle_);
+    EXPECT_EQ(TaggedCounter::kRecordInvalid, counter_.record_state_);
+
+    // The test fixture object will be used by the log message handler.
+    test_ = this;
+    logging::SetLogMessageHandler(HandleLogMessages);
+  }
+
+  virtual void TearDown() {
+    logging::SetLogMessageHandler(NULL);
+    test_ = NULL;
+    file_util::Delete(FilePath(kTestRecordFile), false);
+  }
+
+  // Asserts that the record file contains the specified contents.
+  testing::AssertionResult AssertRecord(const char* expr_tag,
+                                        const char* expr_count,
+                                        int expected_tag,
+                                        int expected_count) {
+    int fd = HANDLE_EINTR(open(kTestRecordFile, O_RDONLY));
+    if (fd < 0) {
+      testing::Message msg;
+      msg << "Unable to open " << kTestRecordFile;
+      return testing::AssertionFailure(msg);
+    }
+
+    TaggedCounter::Record record;
+    if (!file_util::ReadFromFD(fd, reinterpret_cast<char*>(&record),
+                               sizeof(record))) {
+      testing::Message msg;
+      msg << "Unable to read " << sizeof(record) << " bytes from "
+          << kTestRecordFile;
+      HANDLE_EINTR(close(fd));
+      return testing::AssertionFailure(msg);
+    }
+
+    if (record.tag() != expected_tag || record.count() != expected_count) {
+      testing::Message msg;
+      msg << "actual record (" << record.tag() << ", " << record.count()
+          << ") expected (" << expected_tag << ", " << expected_count << ")";
+      HANDLE_EINTR(close(fd));
+      return testing::AssertionFailure(msg);
+    }
+
+    HANDLE_EINTR(close(fd));
+    return testing::AssertionSuccess();
+  }
+
+  // Returns true if the persistent record file does not exist or is
+  // empty, false otherwise.
+  bool AssertNoOrEmptyRecordFile() {
+    FilePath record_file(counter_.filename_);
+    int64 record_file_size;
+    return !file_util::PathExists(record_file) ||
+        (file_util::GetFileSize(record_file, &record_file_size) &&
+         record_file_size == 0);
+  }
+
+  // Adds a reporter call expectation that the specified tag/count
+  // callback will be generated.
+  void ExpectReporterCall(int tag, int count) {
+    EXPECT_CALL(reporter_, Call(_, tag, count))
+        .Times(1)
+        .RetiresOnSaturation();
+  }
+
+  // The reporter callback forwards the call to the reporter mock so
+  // that we can set call expectations.
+  static void Reporter(void* handle, int tag, int count) {
+    TaggedCounterTest* test = static_cast<TaggedCounterTest*>(handle);
+    ASSERT_FALSE(NULL == test);
+    test->reporter_.Call(handle, tag, count);
+  }
+
+  // Collects log messages in the |log_| member string so that they
+  // can be analyzed for errors and expected behavior.
+  static bool HandleLogMessages(int severity, const std::string& str) {
+    test_->log_.append(str);
+    test_->log_.append("\n");
+
+    // Returning true would mute the log.
+    return false;
+  }
+
+  // Returns true if the counter log contains |pattern|, false otherwise.
+  bool LogContains(const std::string& pattern) {
+    return log_.find(pattern) != std::string::npos;
+  }
+
+  // The TaggedCounter object under test.
+  TaggedCounter counter_;
+
+  // The accumulated counter log.
+  std::string log_;
+
+  // Reporter mock to set callback expectations on.
+  StrictMock<MockFunction<void(void* handle, int tag, int count)> > reporter_;
+
+  // Pointer to the current test fixture.
+  static TaggedCounterTest* test_;
+};
+
+// static
+TaggedCounterTest* TaggedCounterTest::test_ = NULL;
+
+TEST_F(RecordTest, Init) {
+  record_.Init(/* tag */ 5, /* count */ -1);
+  EXPECT_EQ(5, record_.tag());
+  EXPECT_EQ(0, record_.count());
+
+  record_.Init(/* tag */ -2, /* count */ 10);
+  EXPECT_EQ(-2, record_.tag());
+  EXPECT_EQ(10, record_.count());
+}
+
+TEST_F(RecordTest, Add) {
+  record_.Add(/* count */ -1);
+  EXPECT_EQ(0, record_.count());
+
+  record_.Add(/* count */ 5);
+  EXPECT_EQ(5, record_.count());
+
+  record_.Add(/* count */ 10);
+  EXPECT_EQ(15, record_.count());
+
+  record_.Add(/* count */ -2);
+  EXPECT_EQ(15, record_.count());
+
+  record_.Add(/* count */ INT_MAX);
+  EXPECT_EQ(INT_MAX, record_.count());
+
+  record_.Add(/* count */ 1);
+  EXPECT_EQ(INT_MAX, record_.count());
+}
+
+TEST_F(TaggedCounterTest, BadFileLocation) {
+  // Checks that the counter doesn't die badly if the file can't be
+  // created.
+  counter_.Init(kDoesNotExistFile,
+                /* reporter */ NULL, /* reporter_handle */ NULL);
+  counter_.Update(/* tag */ 10, /* count */ 20);
+  EXPECT_TRUE(LogContains("Unable to open the persistent counter file: "
+                          "No such file or directory"));
+  EXPECT_EQ(TaggedCounter::kRecordInvalid, counter_.record_state_);
+  file_util::Delete(FilePath(kDoesNotExistFile), false);
+}
+
+TEST_F(TaggedCounterTest, Flush) {
+  counter_.Flush();
+  EXPECT_EQ(TaggedCounter::kRecordNull, counter_.record_state_);
+
+  counter_.Update(/* tag */ 40, /* count */ 60);
+  ExpectReporterCall(/* tag */ 40, /* count */ 60);
+  counter_.Flush();
+  EXPECT_TRUE(AssertNoOrEmptyRecordFile());
+  EXPECT_EQ(TaggedCounter::kRecordNull, counter_.record_state_);
+
+  counter_.Update(/* tag */ 41, /* count */ 70);
+  counter_.record_.Init(/* tag */ 0, /* count */ 0);
+  counter_.record_state_ = TaggedCounter::kRecordInvalid;
+  ExpectReporterCall(/* tag */ 41, /* count */ 70);
+  counter_.Flush();
+  EXPECT_TRUE(AssertNoOrEmptyRecordFile());
+  EXPECT_EQ(TaggedCounter::kRecordNull, counter_.record_state_);
+}
+
+TEST_F(TaggedCounterTest, InitFromFile) {
+  counter_.Update(/* tag */ 30, /* count */ 50);
+  EXPECT_PRED_FORMAT2(AssertRecord, /* day */ 30, /* seconds */ 50);
+  EXPECT_EQ(TaggedCounter::kRecordValid, counter_.record_state_);
+
+  counter_.Init(kTestRecordFile, &Reporter, this);
+  counter_.Update(/* tag */ 30, /* count */ 40);
+  EXPECT_PRED_FORMAT2(AssertRecord, /* day */ 30, /* seconds */ 90);
+  EXPECT_EQ(TaggedCounter::kRecordValid, counter_.record_state_);
+
+  counter_.Init(kTestRecordFile, &Reporter, this);
+  ExpectReporterCall(/* tag */ 30, /* count */ 90);
+  counter_.Update(/* tag */ 31, /* count */ 60);
+  EXPECT_PRED_FORMAT2(AssertRecord, /* day */ 31, /* seconds */ 60);
+  EXPECT_EQ(TaggedCounter::kRecordValid, counter_.record_state_);
+
+  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_);
+}
+
+TEST_F(TaggedCounterTest, Update) {
+  counter_.Update(/* tag */ 20, /* count */ 30);
+  EXPECT_PRED_FORMAT2(AssertRecord, /* day */ 20, /* seconds */ 30);
+  EXPECT_EQ(TaggedCounter::kRecordValid, counter_.record_state_);
+
+  counter_.Update(/* tag */ 20, /* count */ 40);
+  EXPECT_PRED_FORMAT2(AssertRecord, /* day */ 20, /* seconds */ 70);
+  EXPECT_EQ(TaggedCounter::kRecordValid, counter_.record_state_);
+
+  ExpectReporterCall(/* tag */ 20, /* count */ 70);
+  counter_.Update(/* tag */ 21, /* count */ 15);
+  EXPECT_PRED_FORMAT2(AssertRecord, /* day */ 21, /* seconds */ 15);
+  EXPECT_EQ(TaggedCounter::kRecordValid, counter_.record_state_);
+
+  ExpectReporterCall(/* tag */ 21, /* count */ 15);
+  counter_.Update(/* tag */ 22, /* count */ 0);
+  EXPECT_TRUE(AssertNoOrEmptyRecordFile());
+  EXPECT_EQ(TaggedCounter::kRecordNull, counter_.record_state_);
+}
+
+}  // namespace chromeos_metrics
+
+int main(int argc, char** argv) {
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}
diff --git a/metrics/metrics_daemon.cc b/metrics/metrics_daemon.cc
index 70c0748..e93c9a8 100644
--- a/metrics/metrics_daemon.cc
+++ b/metrics/metrics_daemon.cc
@@ -5,11 +5,11 @@
 #include "metrics_daemon.h"
 
 #include <dbus/dbus-glib-lowlevel.h>
-#include <sys/file.h>
 
-#include <base/eintr_wrapper.h>
 #include <base/logging.h>
 
+#include "counter.h"
+
 using base::Time;
 using base::TimeDelta;
 using base::TimeTicks;
@@ -89,6 +89,16 @@
 #include "session_states.h"
 };
 
+MetricsDaemon::MetricsDaemon()
+    : network_state_(kUnknownNetworkState),
+      power_state_(kUnknownPowerState),
+      session_state_(kUnknownSessionState),
+      user_active_(false),
+      usemon_interval_(0),
+      usemon_source_(NULL) {}
+
+MetricsDaemon::~MetricsDaemon() {}
+
 void MetricsDaemon::Run(bool run_as_daemon) {
   if (!run_as_daemon || daemon(0, 0) == 0) {
     Loop();
@@ -99,7 +109,8 @@
   testing_ = testing;
   DCHECK(metrics_lib != NULL);
   metrics_lib_ = metrics_lib;
-  daily_use_record_file_ = kDailyUseRecordFile;
+  daily_use_.reset(new chromeos_metrics::TaggedCounter());
+  daily_use_->Init(kDailyUseRecordFile, &DailyUseReporter, this);
 
   // Don't setup D-Bus and GLib in test mode.
   if (testing)
@@ -283,7 +294,7 @@
   }
   TimeDelta since_epoch = now - Time();
   int day = since_epoch.InDays();
-  LogDailyUseRecord(day, seconds);
+  daily_use_->Update(day, seconds);
 
   // Schedules a use monitor on inactive->active transitions and
   // unschedules it on active->inactive transitions.
@@ -298,70 +309,6 @@
   user_active_last_ = now;
 }
 
-void MetricsDaemon::LogDailyUseRecord(int day, int seconds) {
-  // If there's no new active use today and the last record in the
-  // usage aggregation file is today, there's nothing to do.
-  if (seconds == 0 && day == daily_use_day_last_)
-    return;
-
-  DLOG(INFO) << "day: " << day << " usage: " << seconds << " seconds";
-  int fd = HANDLE_EINTR(open(daily_use_record_file_,
-                             O_RDWR | O_CREAT,
-                             S_IRUSR | S_IWUSR));
-  if (fd < 0) {
-    PLOG(WARNING) << "Unable to open the daily use file";
-    return;
-  }
-
-  bool same_day = false;
-  UseRecord record;
-  if (HANDLE_EINTR(read(fd, &record, sizeof(record))) == sizeof(record)) {
-    if (record.day_ == day) {
-      // If there's an existing record for today, aggregates the usage
-      // time.
-      same_day = true;
-      record.seconds_ += seconds;
-    } else {
-      // If there's an existing record for a day in the past, rounds
-      // the usage to the nearest minute and sends it to UMA.
-      int minutes =
-          (record.seconds_ + kSecondsPerMinute / 2) / kSecondsPerMinute;
-      SendMetric(kMetricDailyUseTimeName, minutes,
-                 kMetricDailyUseTimeMin,
-                 kMetricDailyUseTimeMax,
-                 kMetricDailyUseTimeBuckets);
-
-      // Truncates the usage file to ensure that no duplicate usage is
-      // sent to UMA.
-      PLOG_IF(WARNING, HANDLE_EINTR(ftruncate(fd, 0)) != 0);
-    }
-  }
-
-  // Updates the use record in the daily usage file if there's new
-  // usage today.
-  if (seconds > 0) {
-    if (!same_day) {
-      record.day_ = day;
-      record.seconds_ = seconds;
-    }
-    // else an already existing record for the same day will be
-    // overwritten with updated usage below.
-
-    PLOG_IF(WARNING, HANDLE_EINTR(lseek(fd, 0, SEEK_SET)) != 0);
-    PLOG_IF(WARNING,
-            HANDLE_EINTR(write(fd, &record, sizeof(record))) !=
-            sizeof(record));
-  }
-
-  HANDLE_EINTR(close(fd));
-
-  // Remembers the day of the use record in the usage aggregation file
-  // to reduce file I/O. This is not really useful now but potentially
-  // allows frequent LogDailyUseRecord calls with no unnecessary I/O
-  // overhead.
-  daily_use_day_last_ = day;
-}
-
 // static
 gboolean MetricsDaemon::UseMonitorStatic(gpointer data) {
   return static_cast<MetricsDaemon*>(data)->UseMonitor() ? TRUE : FALSE;
@@ -422,6 +369,16 @@
   usemon_interval_ = 0;
 }
 
+// static
+void MetricsDaemon::DailyUseReporter(void* handle, int tag, int count) {
+  MetricsDaemon* daemon = static_cast<MetricsDaemon*>(handle);
+  int minutes = (count + kSecondsPerMinute / 2) / kSecondsPerMinute;
+  daemon->SendMetric(kMetricDailyUseTimeName, minutes,
+                     kMetricDailyUseTimeMin,
+                     kMetricDailyUseTimeMax,
+                     kMetricDailyUseTimeBuckets);
+}
+
 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 ea4771f..ee80596 100644
--- a/metrics/metrics_daemon.h
+++ b/metrics/metrics_daemon.h
@@ -8,24 +8,19 @@
 #include <dbus/dbus.h>
 #include <glib.h>
 
+#include <base/scoped_ptr.h>
+#include <base/time.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
 #include "metrics_library.h"
 
-#include <gtest/gtest_prod.h>  // for FRIEND_TEST
-#include <base/time.h>
+namespace chromeos_metrics { class TaggedCounterInterface; }
 
 class MetricsDaemon {
 
  public:
-  MetricsDaemon()
-      : daily_use_record_file_(NULL),
-        network_state_(kUnknownNetworkState),
-        power_state_(kUnknownPowerState),
-        session_state_(kUnknownSessionState),
-        user_active_(false),
-        daily_use_day_last_(0),
-        usemon_interval_(0),
-        usemon_source_(NULL) {}
-  ~MetricsDaemon() {}
+  MetricsDaemon();
+  ~MetricsDaemon();
 
   // Initializes.
   void Init(bool testing, MetricsLibraryInterface* metrics_lib);
@@ -36,10 +31,7 @@
 
  private:
   friend class MetricsDaemonTest;
-  FRIEND_TEST(MetricsDaemonTest, LogDailyUseRecordBadFileLocation);
-  FRIEND_TEST(MetricsDaemonTest, LogDailyUseRecordOnLogin);
-  FRIEND_TEST(MetricsDaemonTest, LogDailyUseRecordRoundDown);
-  FRIEND_TEST(MetricsDaemonTest, LogDailyUseRecordRoundUp);
+  FRIEND_TEST(MetricsDaemonTest, DailyUseReporter);
   FRIEND_TEST(MetricsDaemonTest, LookupNetworkState);
   FRIEND_TEST(MetricsDaemonTest, LookupPowerState);
   FRIEND_TEST(MetricsDaemonTest, LookupScreenSaverState);
@@ -51,8 +43,7 @@
   FRIEND_TEST(MetricsDaemonTest, ScreenSaverStateChanged);
   FRIEND_TEST(MetricsDaemonTest, SendMetric);
   FRIEND_TEST(MetricsDaemonTest, SessionStateChanged);
-  FRIEND_TEST(MetricsDaemonTest, SetUserActiveStateSendOnLogin);
-  FRIEND_TEST(MetricsDaemonTest, SetUserActiveStateSendOnMonitor);
+  FRIEND_TEST(MetricsDaemonTest, SetUserActiveState);
   FRIEND_TEST(MetricsDaemonTest, SetUserActiveStateTimeJump);
 
   // The network states (see network_states.h).
@@ -178,14 +169,14 @@
   void SendMetric(const std::string& name, int sample,
                   int min, int max, int nbuckets);
 
+  static void DailyUseReporter(void* data, int tag, int count);
+
   // Test mode.
   bool testing_;
 
   // The metrics library handle.
   MetricsLibraryInterface* metrics_lib_;
 
-  const char* daily_use_record_file_;
-
   // Current network state.
   NetworkState network_state_;
 
@@ -209,8 +200,7 @@
   // epoch as the timestamp.
   base::Time user_active_last_;
 
-  // Last stored daily use day (since the epoch).
-  int daily_use_day_last_;
+  scoped_ptr<chromeos_metrics::TaggedCounterInterface> daily_use_;
 
   // Sleep period until the next daily usage aggregation performed by
   // the daily use monitor (see ScheduleUseMonitor).
diff --git a/metrics/metrics_daemon_test.cc b/metrics/metrics_daemon_test.cc
index 49acd31..7119c85 100644
--- a/metrics/metrics_daemon_test.cc
+++ b/metrics/metrics_daemon_test.cc
@@ -2,26 +2,19 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include <gtest/gtest.h>
+
+#include "counter_mock.h"
 #include "metrics_daemon.h"
 #include "metrics_library_mock.h"
 
-#include <sys/file.h>
-
-#include <base/eintr_wrapper.h>
-#include <base/file_util.h>
-#include <base/logging.h>
-#include <base/string_util.h>
-#include <gtest/gtest.h>
-
 using base::Time;
 using base::TimeTicks;
-using ::testing::Mock;
+using chromeos_metrics::TaggedCounterMock;
+using ::testing::_;
 using ::testing::Return;
 using ::testing::StrictMock;
 
-static const char kTestDailyUseRecordFile[] = "daily-usage-test";
-static const char kDoesNotExistFile[] = "/does/not/exist";
-
 static const int kSecondsPerDay = 24 * 60 * 60;
 
 // This class allows a TimeTicks object to be initialized with seconds
@@ -48,46 +41,29 @@
 class MetricsDaemonTest : public testing::Test {
  protected:
   virtual void SetUp() {
-    EXPECT_EQ(NULL, daemon_.daily_use_record_file_);
+    EXPECT_EQ(NULL, daemon_.daily_use_.get());
     daemon_.Init(true, &metrics_lib_);
 
-    // Tests constructor initialization. Switches to a test daily use
-    // record file.
-    EXPECT_TRUE(NULL != daemon_.daily_use_record_file_);
-    daemon_.daily_use_record_file_ = kTestDailyUseRecordFile;
-    EXPECT_TRUE(AssertNoOrEmptyUseRecordFile());
-    EXPECT_EQ(0, daemon_.daily_use_day_last_);
+    // Tests constructor initialization. Switches to mock counters.
+    EXPECT_TRUE(NULL != daemon_.daily_use_.get());
+    daily_use_ = new StrictMock<TaggedCounterMock>();
+    daemon_.daily_use_.reset(daily_use_);  // Transfers ownership.
     EXPECT_FALSE(daemon_.user_active_);
     EXPECT_TRUE(daemon_.user_active_last_.is_null());
     EXPECT_EQ(MetricsDaemon::kUnknownNetworkState, daemon_.network_state_);
     EXPECT_TRUE(daemon_.network_state_last_.is_null());
     EXPECT_EQ(MetricsDaemon::kUnknownPowerState, daemon_.power_state_);
     EXPECT_EQ(MetricsDaemon::kUnknownSessionState, daemon_.session_state_);
-
-    // The test fixture object will be used by the log message handler.
-    daemon_test_ = this;
-    logging::SetLogMessageHandler(HandleLogMessages);
   }
 
-  virtual void TearDown() {
-    logging::SetLogMessageHandler(NULL);
-    daemon_test_ = NULL;
-    file_util::Delete(FilePath(kTestDailyUseRecordFile), false);
-  }
+  virtual void TearDown() {}
 
-  // Collects log messages in the |daemon_log_| member string so that
-  // they can be analyzed for errors and expected behavior.
-  static bool HandleLogMessages(int severity, const std::string& str) {
-    daemon_test_->daemon_log_.append(str);
-    daemon_test_->daemon_log_.append("\n");
-
-    // Returning true would mute the log.
-    return false;
-  }
-
-  // Returns true if the daemon log contains |pattern|, false otherwise.
-  bool LogContains(const std::string& pattern) {
-    return daemon_log_.find(pattern) != std::string::npos;
+  // Adds a daily use aggregation counter expectation that the
+  // specified tag/count update will be generated.
+  void ExpectDailyUseUpdate(int tag, int count) {
+    EXPECT_CALL(*daily_use_, Update(tag, count))
+        .Times(1)
+        .RetiresOnSaturation();
   }
 
   // Adds a metrics library mock expectation that the specified metric
@@ -118,55 +94,11 @@
                  MetricsDaemon::kMetricTimeToNetworkDropBuckets);
   }
 
+  // Converts from seconds to a Time object.
   Time TestTime(int64 seconds) {
     return Time::FromInternalValue(seconds * Time::kMicrosecondsPerSecond);
   }
 
-  // Asserts that the daily use record file contains the specified
-  // contents.
-  testing::AssertionResult AssertDailyUseRecord(const char* expr_day,
-                                                const char* expr_seconds,
-                                                int expected_day,
-                                                int expected_seconds) {
-    int fd = HANDLE_EINTR(open(daemon_.daily_use_record_file_, O_RDONLY));
-    if (fd < 0) {
-      testing::Message msg;
-      msg << "Unable to open " << daemon_.daily_use_record_file_;
-      return testing::AssertionFailure(msg);
-    }
-
-    MetricsDaemon::UseRecord record;
-    if (!file_util::ReadFromFD(fd, reinterpret_cast<char*>(&record),
-                               sizeof(record))) {
-      testing::Message msg;
-      msg << "Unable to read " << sizeof(record) << " bytes from "
-          << daemon_.daily_use_record_file_;
-      HANDLE_EINTR(close(fd));
-      return testing::AssertionFailure(msg);
-    }
-
-    if (record.day_ != expected_day || record.seconds_ != expected_seconds) {
-      testing::Message msg;
-      msg << "actual use record (" << record.day_ << ", " << record.seconds_
-          << ") expected (" << expected_day << ", " << expected_seconds << ")";
-      HANDLE_EINTR(close(fd));
-      return testing::AssertionFailure(msg);
-    }
-
-    HANDLE_EINTR(close(fd));
-    return testing::AssertionSuccess();
-  }
-
-  // Returns true if the daily use record file does not exist or is
-  // empty, false otherwise.
-  bool AssertNoOrEmptyUseRecordFile() {
-    FilePath record_file(daemon_.daily_use_record_file_);
-    int64 record_file_size;
-    return !file_util::PathExists(record_file) ||
-        (file_util::GetFileSize(record_file, &record_file_size) &&
-         record_file_size == 0);
-  }
-
   // Creates a new DBus signal message with a single string
   // argument. The message can be deallocated through
   // DeleteDBusMessage.
@@ -195,9 +127,6 @@
     dbus_message_unref(msg);
   }
 
-  // Pointer to the current test fixture.
-  static MetricsDaemonTest* daemon_test_;
-
   // The MetricsDaemon under test.
   MetricsDaemon daemon_;
 
@@ -205,67 +134,19 @@
   // metric generation calls are marked as failures.
   StrictMock<MetricsLibraryMock> metrics_lib_;
 
-  // The accumulated metrics daemon log.
-  std::string daemon_log_;
+  // 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.
+  StrictMock<TaggedCounterMock>* daily_use_;
 };
 
-// static
-MetricsDaemonTest* MetricsDaemonTest::daemon_test_ = NULL;
-
-TEST_F(MetricsDaemonTest, LogDailyUseRecordBadFileLocation) {
-  // Checks that the daemon doesn't die badly if the file can't be
-  // created.
-  daemon_.daily_use_record_file_ = kDoesNotExistFile;
-  daemon_.LogDailyUseRecord(10, 20);
-  EXPECT_TRUE(LogContains("Unable to open the daily use file: "
-                          "No such file or directory"));
-  EXPECT_EQ(0, daemon_.daily_use_day_last_);
-  file_util::Delete(FilePath(kDoesNotExistFile), false);
-}
-
-TEST_F(MetricsDaemonTest, LogDailyUseRecordOnLogin) {
-  daemon_.LogDailyUseRecord(/* day */ 5, /* seconds */ 120);
-  EXPECT_PRED_FORMAT2(AssertDailyUseRecord, /* day */ 5, /* seconds */ 120);
-  EXPECT_EQ(5, daemon_.daily_use_day_last_);
-
-  daemon_.LogDailyUseRecord(/* day */ 5, /* seconds */ 0);
-  EXPECT_PRED_FORMAT2(AssertDailyUseRecord, /* day */ 5, /* seconds */ 120);
-  EXPECT_EQ(5, daemon_.daily_use_day_last_);
-
-  daemon_.LogDailyUseRecord(/* day */ 5, /* seconds */ 240);
-  EXPECT_PRED_FORMAT2(AssertDailyUseRecord, /* day */ 5, /* seconds */ 360);
-  EXPECT_EQ(5, daemon_.daily_use_day_last_);
-
-  ExpectDailyUseTimeMetric(/* sample */ 6);
-  daemon_.LogDailyUseRecord(/* day */ 6, /* seconds */ 0);
-  EXPECT_TRUE(AssertNoOrEmptyUseRecordFile());
-  EXPECT_EQ(6, daemon_.daily_use_day_last_);
-}
-
-TEST_F(MetricsDaemonTest, LogDailyUseRecordRoundDown) {
-  daemon_.LogDailyUseRecord(/* day */ 7, /* seconds */ 89);
-  EXPECT_PRED_FORMAT2(AssertDailyUseRecord, /* day */ 7, /* seconds */ 89);
-  EXPECT_EQ(7, daemon_.daily_use_day_last_);
+TEST_F(MetricsDaemonTest, DailyUseReporter) {
+  ExpectDailyUseTimeMetric(/* sample */ 2);
+  MetricsDaemon::DailyUseReporter(&daemon_, /* tag */ 20, /* count */ 90);
 
   ExpectDailyUseTimeMetric(/* sample */ 1);
-  daemon_.LogDailyUseRecord(/* day */ 6, /* seconds */ 15);
-  EXPECT_PRED_FORMAT2(AssertDailyUseRecord, /* day */ 6, /* seconds */ 15);
-  EXPECT_EQ(6, daemon_.daily_use_day_last_);
-}
-
-TEST_F(MetricsDaemonTest, LogDailyUseRecordRoundUp) {
-  daemon_.LogDailyUseRecord(/* day */ 6, /* seconds */ 0);
-  EXPECT_EQ(6, daemon_.daily_use_day_last_);
-
-  // Tests rounding use time to the closest minute.
-  daemon_.LogDailyUseRecord(/* day */ 6, /* seconds */ 90);
-  EXPECT_PRED_FORMAT2(AssertDailyUseRecord, /* day */ 6, /* seconds */ 90);
-  EXPECT_EQ(6, daemon_.daily_use_day_last_);
-
-  ExpectDailyUseTimeMetric(/* sample */ 2);
-  daemon_.LogDailyUseRecord(/* day */ 7, /* seconds */ 89);
-  EXPECT_PRED_FORMAT2(AssertDailyUseRecord, /* day */ 7, /* seconds */ 89);
-  EXPECT_EQ(7, daemon_.daily_use_day_last_);
+  MetricsDaemon::DailyUseReporter(&daemon_, /* tag */ 23, /* count */ 89);
 }
 
 TEST_F(MetricsDaemonTest, LookupNetworkState) {
@@ -322,6 +203,9 @@
   EXPECT_EQ(DBUS_HANDLER_RESULT_HANDLED, res);
   DeleteDBusMessage(msg);
 
+  EXPECT_CALL(*daily_use_, Update(_, 0))
+      .Times(1)
+      .RetiresOnSaturation();
   msg = NewDBusSignalString("/",
                             "org.chromium.PowerManager",
                             "ScreenIsUnlocked",
@@ -332,6 +216,9 @@
   EXPECT_EQ(DBUS_HANDLER_RESULT_HANDLED, res);
   DeleteDBusMessage(msg);
 
+  EXPECT_CALL(*daily_use_, Update(_, 0))
+      .Times(1)
+      .RetiresOnSaturation();
   msg = NewDBusSignalString("/org/chromium/SessionManager",
                             "org.chromium.SessionManagerInterface",
                             "SessionStateChanged",
@@ -396,30 +283,28 @@
 }
 
 TEST_F(MetricsDaemonTest, PowerStateChanged) {
+  ExpectDailyUseUpdate(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_);
-  EXPECT_EQ(7, daemon_.daily_use_day_last_);
-  EXPECT_TRUE(AssertNoOrEmptyUseRecordFile());
 
+  ExpectDailyUseUpdate(7, 30);
   daemon_.PowerStateChanged("mem", TestTime(7 * kSecondsPerDay + 45));
   EXPECT_EQ(MetricsDaemon::kPowerStateMem, daemon_.power_state_);
   EXPECT_FALSE(daemon_.user_active_);
   EXPECT_EQ(TestTime(7 * kSecondsPerDay + 45), daemon_.user_active_last_);
-  EXPECT_PRED_FORMAT2(AssertDailyUseRecord, /* day */ 7, /* seconds */ 30);
 
   daemon_.PowerStateChanged("on", TestTime(7 * kSecondsPerDay + 85));
   EXPECT_EQ(MetricsDaemon::kPowerStateOn, daemon_.power_state_);
   EXPECT_FALSE(daemon_.user_active_);
   EXPECT_EQ(TestTime(7 * kSecondsPerDay + 45), daemon_.user_active_last_);
-  EXPECT_PRED_FORMAT2(AssertDailyUseRecord, /* day */ 7, /* seconds */ 30);
 
+  ExpectDailyUseUpdate(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_);
-  EXPECT_PRED_FORMAT2(AssertDailyUseRecord, /* day */ 7, /* seconds */ 30);
 }
 
 TEST_F(MetricsDaemonTest, SendMetric) {
@@ -429,117 +314,76 @@
 }
 
 TEST_F(MetricsDaemonTest, SessionStateChanged) {
+  ExpectDailyUseUpdate(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_);
-  EXPECT_EQ(15, daemon_.daily_use_day_last_);
-  EXPECT_TRUE(AssertNoOrEmptyUseRecordFile());
 
+  ExpectDailyUseUpdate(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_);
-  EXPECT_PRED_FORMAT2(AssertDailyUseRecord, /* day */ 15, /* seconds */ 130);
 
+  ExpectDailyUseUpdate(15, 0);
   daemon_.SessionStateChanged("otherstate",
                               TestTime(15 * kSecondsPerDay + 300));
   EXPECT_EQ(MetricsDaemon::kUnknownSessionState, daemon_.session_state_);
   EXPECT_FALSE(daemon_.user_active_);
   EXPECT_EQ(TestTime(15 * kSecondsPerDay + 300), daemon_.user_active_last_);
-  EXPECT_PRED_FORMAT2(AssertDailyUseRecord, /* day */ 15, /* seconds */ 130);
 }
 
-TEST_F(MetricsDaemonTest, SetUserActiveStateSendOnLogin) {
+TEST_F(MetricsDaemonTest, SetUserActiveState) {
+  ExpectDailyUseUpdate(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_);
-  EXPECT_EQ(5, daemon_.daily_use_day_last_);
-  EXPECT_TRUE(AssertNoOrEmptyUseRecordFile());
 
+  ExpectDailyUseUpdate(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_);
-  EXPECT_EQ(6, daemon_.daily_use_day_last_);
-  EXPECT_TRUE(AssertNoOrEmptyUseRecordFile());
 
+  ExpectDailyUseUpdate(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_);
-  EXPECT_EQ(6, daemon_.daily_use_day_last_);
-  EXPECT_PRED_FORMAT2(AssertDailyUseRecord, /* day */ 6, /* seconds */ 100);
 
+  ExpectDailyUseUpdate(6, 110);
   daemon_.SetUserActiveState(/* active */ false,
-                             TestTime(6 * kSecondsPerDay + 220));
+                             TestTime(6 * kSecondsPerDay + 230));
   EXPECT_FALSE(daemon_.user_active_);
-  EXPECT_EQ(TestTime(6 * kSecondsPerDay + 220), daemon_.user_active_last_);
-  EXPECT_EQ(6, daemon_.daily_use_day_last_);
-  EXPECT_PRED_FORMAT2(AssertDailyUseRecord, /* day */ 6, /* seconds */ 200);
+  EXPECT_EQ(TestTime(6 * kSecondsPerDay + 230), daemon_.user_active_last_);
 
-  ExpectDailyUseTimeMetric(/* sample */ 3);
-  daemon_.SetUserActiveState(/* active */ true,
-                             TestTime(8 * kSecondsPerDay - 300));
-  EXPECT_TRUE(daemon_.user_active_);
-  EXPECT_EQ(TestTime(8 * kSecondsPerDay - 300), daemon_.user_active_last_);
-  EXPECT_EQ(7, daemon_.daily_use_day_last_);
-  EXPECT_TRUE(AssertNoOrEmptyUseRecordFile());
-}
-
-TEST_F(MetricsDaemonTest, SetUserActiveStateSendOnMonitor) {
-  daemon_.SetUserActiveState(/* active */ true,
-                             TestTime(8 * kSecondsPerDay - 300));
-  EXPECT_TRUE(daemon_.user_active_);
-  EXPECT_EQ(TestTime(8 * kSecondsPerDay - 300), daemon_.user_active_last_);
-  EXPECT_EQ(7, daemon_.daily_use_day_last_);
-  EXPECT_TRUE(AssertNoOrEmptyUseRecordFile());
-
+  ExpectDailyUseUpdate(6, 0);
   daemon_.SetUserActiveState(/* active */ false,
-                             TestTime(8 * kSecondsPerDay + 300));
+                             TestTime(6 * kSecondsPerDay + 260));
   EXPECT_FALSE(daemon_.user_active_);
-  EXPECT_EQ(TestTime(8 * kSecondsPerDay + 300), daemon_.user_active_last_);
-  EXPECT_EQ(8, daemon_.daily_use_day_last_);
-  EXPECT_PRED_FORMAT2(AssertDailyUseRecord, /* day */ 8, /* seconds */ 600);
-
-  daemon_.SetUserActiveState(/* active */ true,
-                             TestTime(9 * kSecondsPerDay - 200));
-  EXPECT_TRUE(daemon_.user_active_);
-  EXPECT_EQ(TestTime(9 * kSecondsPerDay - 200), daemon_.user_active_last_);
-  EXPECT_EQ(8, daemon_.daily_use_day_last_);
-  EXPECT_PRED_FORMAT2(AssertDailyUseRecord, /* day */ 8, /* seconds */ 600);
-
-  ExpectDailyUseTimeMetric(/* sample */ 10);
-  daemon_.SetUserActiveState(/* active */ true,
-                             TestTime(9 * kSecondsPerDay + 200));
-  EXPECT_TRUE(daemon_.user_active_);
-  EXPECT_EQ(TestTime(9 * kSecondsPerDay + 200), daemon_.user_active_last_);
-  EXPECT_EQ(9, daemon_.daily_use_day_last_);
-  EXPECT_PRED_FORMAT2(AssertDailyUseRecord, /* day */ 9, /* seconds */ 400);
+  EXPECT_EQ(TestTime(6 * kSecondsPerDay + 260), daemon_.user_active_last_);
 }
 
 TEST_F(MetricsDaemonTest, SetUserActiveStateTimeJump) {
+  ExpectDailyUseUpdate(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_);
-  EXPECT_EQ(10, daemon_.daily_use_day_last_);
-  EXPECT_TRUE(AssertNoOrEmptyUseRecordFile());
 
+  ExpectDailyUseUpdate(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_);
-  EXPECT_EQ(10, daemon_.daily_use_day_last_);
-  EXPECT_TRUE(AssertNoOrEmptyUseRecordFile());
 
+  ExpectDailyUseUpdate(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_);
-  EXPECT_EQ(10, daemon_.daily_use_day_last_);
-  EXPECT_TRUE(AssertNoOrEmptyUseRecordFile());
 }
 
 int main(int argc, char** argv) {
diff --git a/metrics/metrics_library_mock.h b/metrics/metrics_library_mock.h
index ffc9342..ba59c8f 100644
--- a/metrics/metrics_library_mock.h
+++ b/metrics/metrics_library_mock.h
@@ -20,4 +20,4 @@
                                    int max));
 };
 
-#endif /* METRICS_LIBRARY_MOCK_H_ */
+#endif  // METRICS_LIBRARY_MOCK_H_