Added a timer-based report to the metrics library.

Timer encapsulates a timer with basic functionality.
TimerReporter subclasses it for also sending UMA reports.

BUG=chromium-os:18800
TEST=Unit test 'timer_test' has been included

Change-Id: I9de9a2a7388721ba1476fe706a8d12788d2176ad
Reviewed-on: http://gerrit.chromium.org/gerrit/5161
Reviewed-by: Gaurav Shah <gauravsh@chromium.org>
Reviewed-by: Darin Petkov <petkov@chromium.org>
Tested-by: Bruno Pontes Soares Rocha <bpontes@google.com>
Reviewed-by: Bruno Pontes Soares Rocha <bpontes@google.com>
diff --git a/metrics/Makefile b/metrics/Makefile
index 2920547..072fb04 100644
--- a/metrics/Makefile
+++ b/metrics/Makefile
@@ -1,4 +1,4 @@
-# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+# Copyright (c) 2011 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.
 #
@@ -17,10 +17,14 @@
 SHAREDLIB = libmetrics.so
 LIB_TEST = metrics_library_test
 COUNTER_TEST = counter_test
+TIMER_TEST = timer_test
 
 TESTCOUNTER_OBJS = \
 	counter.o \
 	counter_test.o
+TESTTIMER_OBJS = \
+	timer.o \
+	timer_test.o
 CLIENT_OBJS = \
 	metrics_client.o
 DAEMON_OBJS = \
@@ -33,11 +37,13 @@
 	metrics_daemon_test.o
 LIB_OBJS = \
 	c_metrics_library.o \
-	metrics_library.o
+	metrics_library.o \
+	timer.o
 TESTLIB_OBJS = \
 	metrics_library_test.o
 
 TESTCOUNTER_LIBS = -lgmock -lgtest -lbase -lrt -lpthread -lglib-2.0
+TESTTIMER_LIBS = -lgmock -lgtest -lbase -lrt -lpthread -lglib-2.0
 DAEMON_LDFLAGS = $(LDFLAGS) $(LDCONFIG) -lrt -lbase -lpthread -lgflags \
 		 -lglib-2.0 -lrootdev -lpolicy
 TESTDAEMON_LIBS = -lgmock -lgtest
@@ -46,7 +52,7 @@
 
 all: $(LIB) $(SHAREDLIB) $(CLIENT) $(DAEMON)
 
-tests: $(COUNTER_TEST) $(DAEMON_TEST) $(LIB_TEST)
+tests: $(COUNTER_TEST) $(DAEMON_TEST) $(LIB_TEST) $(TIMER_TEST)
 
 $(CLIENT): $(CLIENT_OBJS) $(SHAREDLIB)
 	$(CXX) $(LDFLAGS) $(POLICY_LIBS) -lrt $^ -o $@
@@ -54,6 +60,9 @@
 $(COUNTER_TEST): $(TESTCOUNTER_OBJS)
 	$(CXX) -o $@ $^ $(TESTCOUNTER_LIBS)
 
+$(TIMER_TEST): $(TESTTIMER_OBJS)
+	$(CXX) -o $@ $^ $(TESTTIMER_LIBS)
+
 $(DAEMON): $(DAEMON_OBJS) $(SHAREDLIB)
 	$(CXX) -o $@ $^ $(DAEMON_LDFLAGS)
 
@@ -74,4 +83,4 @@
 
 clean:
 	rm -f $(CLIENT) $(DAEMON) $(LIB) $(SHAREDLIB) *.o
-	rm -f $(COUNTER_TEST) $(DAEMON_TEST) $(LIB_TEST)
+	rm -f $(COUNTER_TEST) $(DAEMON_TEST) $(LIB_TEST) $(TIMER_TEST)
diff --git a/metrics/timer.cc b/metrics/timer.cc
new file mode 100644
index 0000000..67b0fd1
--- /dev/null
+++ b/metrics/timer.cc
@@ -0,0 +1,77 @@
+// Copyright (c) 2011 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 "timer.h"
+
+#include <string>
+
+#include <base/memory/scoped_ptr.h>
+#include <base/time.h>
+
+#include "metrics_library.h"
+
+namespace chromeos_metrics {
+
+base::TimeTicks ClockWrapper::GetCurrentTime() const {
+  return base::TimeTicks::Now();
+}
+
+Timer::Timer()
+    : is_started_(false),
+      clock_wrapper_(new ClockWrapper()) {}
+
+bool Timer::Start() {
+  start_time_ = clock_wrapper_->GetCurrentTime();
+  is_started_ = true;
+  return true;
+}
+
+bool Timer::Stop() {
+  // Check if the timer has been started.
+  if (!is_started_) return false;
+  is_started_ = false;
+  elapsed_time_ = clock_wrapper_->GetCurrentTime() - start_time_;
+  return true;
+}
+
+bool Timer::Reset() {
+  is_started_ = false;
+  return true;
+}
+
+bool Timer::HasStarted() const {
+  return is_started_;
+}
+
+bool Timer::GetElapsedTime(base::TimeDelta* elapsed_time) const {
+  if (start_time_.is_null() || !elapsed_time) return false;
+  if (is_started_) {
+    *elapsed_time = clock_wrapper_->GetCurrentTime() - start_time_;
+  } else {
+    *elapsed_time = elapsed_time_;
+  }
+  return true;
+}
+
+// static
+MetricsLibraryInterface* TimerReporter::metrics_lib_ = NULL;
+
+TimerReporter::TimerReporter(const std::string& histogram_name, int min,
+                             int max, int num_buckets)
+    : histogram_name_(histogram_name),
+      min_(min),
+      max_(max),
+      num_buckets_(num_buckets) {}
+
+bool TimerReporter::ReportMilliseconds() const {
+  base::TimeDelta elapsed_time;
+  if (!metrics_lib_ || !GetElapsedTime(&elapsed_time)) return false;
+  return metrics_lib_->SendToUMA(histogram_name_,
+                                 elapsed_time.InMilliseconds(),
+                                 min_,
+                                 max_,
+                                 num_buckets_);
+}
+
+}  // namespace chromeos_metrics
diff --git a/metrics/timer.h b/metrics/timer.h
new file mode 100644
index 0000000..1d31976
--- /dev/null
+++ b/metrics/timer.h
@@ -0,0 +1,140 @@
+// Copyright (c) 2011 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.
+
+// Timer - class that provides timer tracking.
+
+#ifndef METRICS_TIMER_H_
+#define METRICS_TIMER_H_
+
+#include <string>
+
+#include <base/memory/scoped_ptr.h>
+#include <base/time.h>
+#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+
+class MetricsLibraryInterface;
+
+namespace chromeos_metrics {
+
+class TimerInterface {
+ public:
+  virtual ~TimerInterface() {}
+
+  virtual bool Start() = 0;
+  virtual bool Stop() = 0;
+  virtual bool Reset() = 0;
+  virtual bool HasStarted() const = 0;
+};
+
+// Wrapper for calls to the system clock.
+class ClockWrapper {
+ public:
+  ClockWrapper() {}
+  virtual ~ClockWrapper() {}
+
+  // Returns the current time from the system.
+  virtual base::TimeTicks GetCurrentTime() const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ClockWrapper);
+};
+
+// Implements a Timer.
+class Timer : public TimerInterface {
+ public:
+  Timer();
+  virtual ~Timer() {}
+
+  // Starts the timer. If a timer is already running, also resets current
+  // timer. Always returns true.
+  virtual bool Start();
+
+  // Stops the timer and calculates the total time elapsed between now and when
+  // Start() was called. Note that this method needs a prior call to Start().
+  // Otherwise, it fails (returns false).
+  virtual bool Stop();
+
+  // Resets the timer, erasing the current duration being tracked. Always
+  // returns true.
+  virtual bool Reset();
+
+  // Returns whether the timer has started or not.
+  virtual bool HasStarted() const;
+
+  // Stores the current elapsed time in |elapsed_time|. If timer is stopped,
+  // stores the elapsed time from when Stop() was last called. Otherwise,
+  // calculates and stores the elapsed time since the last Start().
+  // Returns false if the timer was never Start()'ed or if called with a null
+  // pointer argument.
+  virtual bool GetElapsedTime(base::TimeDelta* elapsed_time) const;
+
+ private:
+  friend class TimerTest;
+  friend class TimerReporterTest;
+  FRIEND_TEST(TimerTest, StartStop);
+  FRIEND_TEST(TimerTest, ReStart);
+  FRIEND_TEST(TimerTest, Reset);
+  FRIEND_TEST(TimerTest, SeparatedTimers);
+  FRIEND_TEST(TimerTest, InvalidStop);
+  FRIEND_TEST(TimerTest, InvalidElapsedTime);
+  FRIEND_TEST(TimerReporterTest, StartStopReport);
+
+  // Elapsed time of the last use of the timer.
+  base::TimeDelta elapsed_time_;
+
+  // Starting time value.
+  base::TimeTicks start_time_;
+
+  // Whether the timer has started or not.
+  bool is_started_;
+
+  // Wrapper for the calls to the system clock.
+  scoped_ptr<ClockWrapper> clock_wrapper_;
+
+  DISALLOW_COPY_AND_ASSIGN(Timer);
+};
+
+// Extends the Timer class to report the elapsed time in milliseconds through
+// the UMA metrics library.
+class TimerReporter : public Timer {
+ public:
+  // Initializes the timer by providing a |histogram_name| to report to with
+  // |min|, |max| and |num_buckets| attributes for the histogram.
+  TimerReporter(const std::string& histogram_name, int min, int max,
+                int num_buckets);
+  virtual ~TimerReporter() {}
+
+  // Sets the metrics library used by all instances of this class.
+  static void set_metrics_lib(MetricsLibraryInterface* metrics_lib) {
+    metrics_lib_ = metrics_lib;
+  }
+
+  // Reports the current duration to UMA, in milliseconds. Returns false if
+  // there is nothing to report, i.e. the timer was not started or a metrics
+  // library is not set.
+  virtual bool ReportMilliseconds() const;
+
+  // Accessor methods.
+  const std::string& histogram_name() const { return histogram_name_; }
+  int min() const { return min_; }
+  int max() const { return max_; }
+  int num_buckets() const { return num_buckets_; }
+
+ private:
+  friend class TimerReporterTest;
+  FRIEND_TEST(TimerReporterTest, StartStopReport);
+  FRIEND_TEST(TimerReporterTest, InvalidReport);
+
+  static MetricsLibraryInterface* metrics_lib_;
+  std::string histogram_name_;
+  int min_;
+  int max_;
+  int num_buckets_;
+
+  DISALLOW_COPY_AND_ASSIGN(TimerReporter);
+};
+
+}  // namespace chromeos_metrics
+
+#endif  // METRICS_TIMER_H_
diff --git a/metrics/timer_mock.h b/metrics/timer_mock.h
new file mode 100644
index 0000000..ef29191
--- /dev/null
+++ b/metrics/timer_mock.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2011 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_TIMER_MOCK_H_
+#define METRICS_TIMER_MOCK_H_
+
+
+#include <string>
+
+#include <base/basictypes.h>
+#include <base/time.h>
+#include <gmock/gmock.h>
+
+#include "timer.h"
+
+namespace chromeos_metrics {
+
+class TimerMock : public Timer {
+ public:
+  MOCK_METHOD0(Start, bool());
+  MOCK_METHOD0(Stop, bool());
+  MOCK_METHOD0(Reset, bool());
+  MOCK_CONST_METHOD0(HasStarted, bool());
+  MOCK_CONST_METHOD1(GetElapsedTime, bool(base::TimeDelta* elapsed_time));
+};
+
+class TimerReporterMock : public TimerReporter {
+ public:
+  MOCK_METHOD0(Start, bool());
+  MOCK_METHOD0(Stop, bool());
+  MOCK_METHOD0(Reset, bool());
+  MOCK_CONST_METHOD0(HasStarted, bool());
+  MOCK_CONST_METHOD0(GetElapsedTime, base::TimeDelta());
+  MOCK_CONST_METHOD0(ReportMilliseconds, bool());
+};
+
+class ClockWrapperMock : public ClockWrapper {
+ public:
+  MOCK_CONST_METHOD0(GetCurrentTime, base::TimeTicks());
+};
+
+}  // namespace chromeos_metrics
+
+#endif  // METRICS_TIMER_MOCK_H_
diff --git a/metrics/timer_test.cc b/metrics/timer_test.cc
new file mode 100644
index 0000000..445aeeb
--- /dev/null
+++ b/metrics/timer_test.cc
@@ -0,0 +1,162 @@
+// Copyright (c) 2011 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 <base/memory/scoped_ptr.h>
+#include <base/time.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "metrics_library_mock.h"
+#include "timer.h"
+#include "timer_mock.h"
+
+using ::testing::_;
+using ::testing::Return;
+
+namespace chromeos_metrics {
+
+class TimerTest : public testing::Test {
+ public:
+  TimerTest() : clock_wrapper_mock_(new ClockWrapperMock()) {}
+
+ protected:
+  virtual void SetUp() {
+    EXPECT_FALSE(timer_.is_started_);
+    stime += base::TimeDelta::FromMilliseconds(1500);
+    etime += base::TimeDelta::FromMilliseconds(3000);
+  }
+
+  virtual void TearDown() {}
+
+  Timer timer_;
+  scoped_ptr<ClockWrapperMock> clock_wrapper_mock_;
+  base::TimeTicks stime, etime;
+};
+
+TEST_F(TimerTest, StartStop) {
+  EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+      .WillOnce(Return(stime))
+      .WillOnce(Return(etime));
+  timer_.clock_wrapper_.reset(clock_wrapper_mock_.release());
+  ASSERT_TRUE(timer_.Start());
+  ASSERT_TRUE(timer_.start_time_ == stime);
+  ASSERT_TRUE(timer_.HasStarted());
+  ASSERT_TRUE(timer_.Stop());
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), 1500);
+  ASSERT_FALSE(timer_.HasStarted());
+}
+
+TEST_F(TimerTest, ReStart) {
+  EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+      .WillOnce(Return(stime))
+      .WillOnce(Return(etime));
+  timer_.clock_wrapper_.reset(clock_wrapper_mock_.release());
+  timer_.Start();
+  base::TimeTicks buffer = timer_.start_time_;
+  timer_.Start();
+  ASSERT_FALSE(timer_.start_time_ == buffer);
+}
+
+TEST_F(TimerTest, Reset) {
+  EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+      .WillOnce(Return(stime));
+  timer_.clock_wrapper_.reset(clock_wrapper_mock_.release());
+  timer_.Start();
+  ASSERT_TRUE(timer_.Reset());
+  ASSERT_FALSE(timer_.HasStarted());
+}
+
+TEST_F(TimerTest, SeparatedTimers) {
+  base::TimeTicks stime2, etime2;
+  stime2 += base::TimeDelta::FromMilliseconds(4200);
+  etime2 += base::TimeDelta::FromMilliseconds(5000);
+  EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+      .WillOnce(Return(stime))
+      .WillOnce(Return(etime))
+      .WillOnce(Return(stime2))
+      .WillOnce(Return(etime2));
+  timer_.clock_wrapper_.reset(clock_wrapper_mock_.release());
+  ASSERT_TRUE(timer_.Start());
+  ASSERT_TRUE(timer_.Stop());
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), 1500);
+  ASSERT_TRUE(timer_.Start());
+  ASSERT_TRUE(timer_.start_time_ == stime2);
+  ASSERT_TRUE(timer_.Stop());
+  ASSERT_EQ(timer_.elapsed_time_.InMilliseconds(), 800);
+  ASSERT_FALSE(timer_.HasStarted());
+}
+
+TEST_F(TimerTest, InvalidStop) {
+  EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+      .WillOnce(Return(stime))
+      .WillOnce(Return(etime));
+  timer_.clock_wrapper_.reset(clock_wrapper_mock_.release());
+  ASSERT_FALSE(timer_.Stop());
+  // Now we try it again, but after a valid start/stop.
+  timer_.Start();
+  timer_.Stop();
+  base::TimeDelta elapsed_time = timer_.elapsed_time_;
+  ASSERT_FALSE(timer_.Stop());
+  ASSERT_TRUE(elapsed_time == timer_.elapsed_time_);
+}
+
+TEST_F(TimerTest, InvalidElapsedTime) {
+  base::TimeDelta elapsed_time;
+  ASSERT_FALSE(timer_.GetElapsedTime(&elapsed_time));
+}
+
+static const char kMetricName[] = "test-timer";
+static const int kMinSample = 0;
+static const int kMaxSample = 120 * 1E6;
+static const int kNumBuckets = 50;
+
+class TimerReporterTest : public testing::Test {
+ public:
+  TimerReporterTest() : timer_reporter_(kMetricName, kMinSample, kMaxSample,
+                                        kNumBuckets),
+                        clock_wrapper_mock_(new ClockWrapperMock()) {}
+
+ protected:
+  virtual void SetUp() {
+    timer_reporter_.set_metrics_lib(&lib_);
+    EXPECT_EQ(timer_reporter_.histogram_name_, kMetricName);
+    EXPECT_EQ(timer_reporter_.min_, kMinSample);
+    EXPECT_EQ(timer_reporter_.max_, kMaxSample);
+    EXPECT_EQ(timer_reporter_.num_buckets_, kNumBuckets);
+    stime += base::TimeDelta::FromMilliseconds(1500);
+    etime += base::TimeDelta::FromMilliseconds(3000);
+  }
+
+  virtual void TearDown() {
+    timer_reporter_.set_metrics_lib(NULL);
+  }
+
+  TimerReporter timer_reporter_;
+  MetricsLibraryMock lib_;
+  scoped_ptr<ClockWrapperMock> clock_wrapper_mock_;
+  base::TimeTicks stime, etime;
+};
+
+TEST_F(TimerReporterTest, StartStopReport) {
+  EXPECT_CALL(*clock_wrapper_mock_, GetCurrentTime())
+      .WillOnce(Return(stime))
+      .WillOnce(Return(etime));
+  timer_reporter_.clock_wrapper_.reset(clock_wrapper_mock_.release());
+  EXPECT_CALL(lib_, SendToUMA(kMetricName, 1500, kMinSample, kMaxSample,
+                              kNumBuckets)).WillOnce(Return(true));
+  ASSERT_TRUE(timer_reporter_.Start());
+  ASSERT_TRUE(timer_reporter_.Stop());
+  ASSERT_TRUE(timer_reporter_.ReportMilliseconds());
+}
+
+TEST_F(TimerReporterTest, InvalidReport) {
+  ASSERT_FALSE(timer_reporter_.ReportMilliseconds());
+}
+
+}  // namespace chromeos_metrics
+
+int main(int argc, char **argv) {
+  testing::InitGoogleTest(&argc, argv);
+  return RUN_ALL_TESTS();
+}