From be388f301b9fef2e4ecd0cd9612dfe9216b5b266 Mon Sep 17 00:00:00 2001 From: Bruno Rocha Date: Tue, 2 Aug 2011 12:40:17 -0700 Subject: [PATCH] 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 Reviewed-by: Darin Petkov Tested-by: Bruno Pontes Soares Rocha Reviewed-by: Bruno Pontes Soares Rocha --- metrics/Makefile | 17 +++-- metrics/timer.cc | 77 ++++++++++++++++++++ metrics/timer.h | 140 ++++++++++++++++++++++++++++++++++++ metrics/timer_mock.h | 45 ++++++++++++ metrics/timer_test.cc | 162 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 437 insertions(+), 4 deletions(-) create mode 100644 metrics/timer.cc create mode 100644 metrics/timer.h create mode 100644 metrics/timer_mock.h create mode 100644 metrics/timer_test.cc diff --git a/metrics/Makefile b/metrics/Makefile index 2920547ea..072fb048e 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 @@ LIB = libmetrics.a 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 @@ TESTDAEMON_OBJS = \ 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 @@ POLICY_LIBS = -lpolicy 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 @@ $(CLIENT): $(CLIENT_OBJS) $(SHAREDLIB) $(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 @@ $(LIB_TEST): $(TESTLIB_OBJS) $(SHAREDLIB) 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 000000000..67b0fd14b --- /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 + +#include +#include + +#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 000000000..1d3197672 --- /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 + +#include +#include +#include // 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 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 000000000..ef29191a2 --- /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 + +#include +#include +#include + +#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 000000000..445aeeb0e --- /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 +#include +#include +#include + +#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 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 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(); +}