diff --git a/metrics/counter.cc b/metrics/counter.cc deleted file mode 100644 index f71b4fbf6..000000000 --- a/metrics/counter.cc +++ /dev/null @@ -1,307 +0,0 @@ -// 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 - -#include -#include - -#include "metrics_library.h" - -namespace chromeos_metrics { - -// TaggedCounter::Record implementation. -void TaggedCounter::Record::Init(uint32 report_tag, - uint32 reset_tag, - int32 count) { - report_tag_ = report_tag; - reset_tag_ = reset_tag; - count_ = (count > 0) ? count : 0; -} - -void TaggedCounter::Record::Add(int32 count) { - if (count <= 0) - return; - - // Saturates on positive overflow. - int64 new_count = static_cast(count_) + static_cast(count); - if (new_count > kint32max) - count_ = kint32max; - else - count_ = static_cast(new_count); -} - -// TaggedCounter implementation. -TaggedCounter::TaggedCounter() - : 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(uint32 report_tag, uint32 reset_tag, int32 count) { - UpdateInternal(report_tag, - reset_tag, - count, - false); // No flush. -} - -void TaggedCounter::Flush() { - UpdateInternal(0, // report_tag - 0, // reset_tag - 0, // count - true); // Do flush. -} - -void TaggedCounter::UpdateInternal(uint32 report_tag, - uint32 reset_tag, - int32 count, - bool flush) { - 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_.report_tag() == report_tag && - record_.reset_tag() == reset_tag) - return; - } - - DLOG(INFO) << "report_tag: " << report_tag << " reset_tag: " << reset_tag - << " count: " << count << " flush: " << flush; - DCHECK(!filename_.empty()); - - // 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_.c_str(), - O_RDWR | O_CREAT, S_IRUSR | S_IWUSR)); - if (fd < 0) { - PLOG(WARNING) << "Unable to open the persistent counter file"; - return; - } - ReadRecord(fd); - ReportRecord(report_tag, reset_tag, flush); - UpdateRecord(report_tag, reset_tag, count, flush); - WriteRecord(fd); - - HANDLE_EINTR(close(fd)); -} - -void TaggedCounter::ReadRecord(int fd) { - if (record_state_ != kRecordInvalid) - return; - - // Three cases: 1. empty file (first time), 2. size of file == size of record - // (normal case), 3. size of file != size of record (new version). We treat - // cases 1 and 3 identically. - 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(uint32 report_tag, - uint32 reset_tag, - bool flush) { - // If no valid record, there's nothing to report. - if (record_state_ != kRecordValid) { - DCHECK_EQ(record_state_, kRecordNull); - return; - } - - // If the current record has the same tags as the new ones, it's - // not ready to be reported yet. - if (!flush && - record_.report_tag() == report_tag && - record_.reset_tag() == reset_tag) - return; - - if (reporter_) { - reporter_(reporter_handle_, record_.count()); - } - // If the report tag has changed, update it to the current one. - if (record_.report_tag() != report_tag) { - record_.set_report_tag(report_tag); - record_state_ = kRecordValidDirty; - } - // If the reset tag has changed, the new state is NullDirty, no - // matter whether the report tag has changed or not. - if (record_.reset_tag() != reset_tag) { - record_state_ = kRecordNullDirty; - } -} - -void TaggedCounter::UpdateRecord(uint32 report_tag, - uint32 reset_tag, - int32 count, - bool flush) { - if (flush && - (record_state_ == kRecordNull || record_state_ == kRecordNullDirty)) - return; - - switch (record_state_) { - case kRecordNull: - case kRecordNullDirty: - // Current record is null, starting a new record. - record_.Init(report_tag, reset_tag, count); - record_state_ = kRecordValidDirty; - break; - - case kRecordValid: - case kRecordValidDirty: - // If there's an existing record for the current tag, - // accumulates the counts. - DCHECK_EQ(record_.report_tag(), report_tag); - DCHECK_EQ(record_.reset_tag(), reset_tag); - if (count > 0) { - 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(); - } -} - -MetricsLibraryInterface* TaggedCounterReporter::metrics_lib_ = NULL; - -TaggedCounterReporter::TaggedCounterReporter() - : tagged_counter_(new TaggedCounter()), - min_(0), - max_(0), - buckets_(0) { -} - -TaggedCounterReporter::~TaggedCounterReporter() { -} - -void TaggedCounterReporter::Init(const char* filename, - const char* histogram_name, - int min, - int max, - int buckets) { - tagged_counter_->Init(filename, Report, this); - histogram_name_ = histogram_name; - min_ = min; - max_ = max; - buckets_ = buckets; - CHECK(min_ >= 0); - CHECK(max_ > min_); - CHECK(buckets_ > 0); -} - -void TaggedCounterReporter::Report(void* handle, int32 count) { - TaggedCounterReporter* this_reporter = - reinterpret_cast(handle); - DLOG(INFO) << "received metric: " << this_reporter->histogram_name_ - << " " << count << " " << this_reporter->min_ << " " - << this_reporter->max_ << " " << this_reporter->buckets_; - CHECK(metrics_lib_ != NULL); - CHECK(this_reporter->buckets_ > 0); - metrics_lib_->SendToUMA(this_reporter->histogram_name_, - count, - this_reporter->min_, - this_reporter->max_, - this_reporter->buckets_); -} - -FrequencyCounter::FrequencyCounter() : cycle_duration_(1) { -} - -FrequencyCounter::~FrequencyCounter() { -} - -void FrequencyCounter::Init(TaggedCounterInterface* tagged_counter, - time_t cycle_duration) { - tagged_counter_.reset(tagged_counter); - DCHECK_GT(cycle_duration, 0); - cycle_duration_ = cycle_duration; -} - -void FrequencyCounter::UpdateInternal(int32 count, time_t now) { - DCHECK(tagged_counter_.get()); - tagged_counter_->Update(0, GetCycleNumber(now), count); -} - -int32 FrequencyCounter::GetCycleNumber(time_t now) { - return now / cycle_duration_; -} - -VersionCounter::VersionCounter() : cycle_duration_(1) { -} - -VersionCounter::~VersionCounter() { -} - -void VersionCounter::Init(TaggedCounterInterface* tagged_counter, - time_t cycle_duration) { - tagged_counter_.reset(tagged_counter); - DCHECK_GT(cycle_duration, 0); - cycle_duration_ = cycle_duration; -} - -void VersionCounter::UpdateInternal(int32 count, - time_t now, - uint32 version_hash) { - DCHECK(tagged_counter_.get()); - tagged_counter_->Update(GetCycleNumber(now), version_hash, count); -} - -int32 VersionCounter::GetCycleNumber(time_t now) { - return now / cycle_duration_; -} - -} // namespace chromeos_metrics diff --git a/metrics/counter.h b/metrics/counter.h deleted file mode 100644 index 679aaafc9..000000000 --- a/metrics/counter.h +++ /dev/null @@ -1,345 +0,0 @@ -// 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 -#include - -#include -#include -#include // for FRIEND_TEST - -class MetricsLibraryInterface; - -namespace chromeos_metrics { - -// Constants useful for frequency statistics. -const int kSecondsPerDay = 60 * 60 * 24; -const int kSecondsPerWeek = kSecondsPerDay * 7; - -// TaggedCounter maintains a persistent storage (i.e., a file) aggregation -// counter for given tags (e.g., day, hour, version number) 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. -// -// The primary reason for using an interface is to allow easier unit -// testing in clients through mocking thus avoiding file access and -// callbacks. Of course, it also enables alternative implementations -// of the counter with additional features. -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. - // - // |handle| is the |reporter_handle| pointer passed through Init. - // |count| is aggregated count. - typedef void (*Reporter)(void* handle, int32 count); - - virtual ~TaggedCounterInterface() {} - - // Adds |count| of events for the given tags. If there's an existing - // aggregated count for different tags, it's reported through the reporter - // callback, and optionally discarded, depending on whether |report_tag| - // changed or |reset_tag| changed. - virtual void Update(uint32 report_tag, uint32 reset_tag, int32 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(); - virtual ~TaggedCounter(); - - // 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); - - // Implementation of interface methods. - virtual void Update(uint32 report_tag, uint32 reset_tag, int32 count); - virtual 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 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 record. Objects of this class are synced - // with the persistent storage through binary reads/writes. - class Record { - public: - // Creates a new Record with all fields reset to 0. - Record() : report_tag_(0), reset_tag_(0), count_(0) {} - - // Initializes with |report_tag|, |reset_tag| and |count|. - // If |count| is negative, |count_| is set to 0. - void Init(uint32 report_tag, uint32 reset_tag, int32 count); - - // Adds |count| to the current |count_|. Negative |count| is - // ignored. In case of positive overflow, |count_| is saturated to - // kint32max. - void Add(int32 count); - - uint32 report_tag() const { return report_tag_; } - void set_report_tag(uint32 report_tag) { report_tag_ = report_tag; } - uint32 reset_tag() const { return reset_tag_; } - int32 count() const { return count_; } - - private: - // When |report_tag_| changes, the counter is reported as a UMA sample. - // When |reset_tag_| changes, the counter is both reported and reset. - uint32 report_tag_; - uint32 reset_tag_; - int32 count_; - }; - - // Implementation of the Update and Flush methods. Goes through the - // necessary steps to read, report, update, and sync the aggregated - // record. - void UpdateInternal(uint32 report_tag, - uint32 reset_tag, - int32 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 either - // new tag is different than the old one, reports the aggregated data through - // the reporter callback, and possibly resets the cached record. - void ReportRecord(uint32 report_tag, uint32 reset_tag, bool flush); - - // Updates the cached record given the new tags and |count|. This - // method expects either a null cached record, or a valid cached - // record with the same tags as given. If |flush| is true, the method - // asserts that the cached record is null and returns. - void UpdateRecord(uint32 report_tag, - uint32 reset_tag, - int32 count, - bool flush); - - // 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. - std::string 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_; -}; - -// TaggedCounterReporter provides a TaggedCounterInterface which both -// counts tagged events and reports them up through the metrics -// library to UMA. -class TaggedCounterReporter : public TaggedCounterInterface { - public: - TaggedCounterReporter(); - virtual ~TaggedCounterReporter(); - - // Set the metrics library used by all TaggedCounterReporter - // instances. We assume there is only one metrics library - // shared amongst all reporters. - static void SetMetricsLibraryInterface(MetricsLibraryInterface* metrics_lib) { - metrics_lib_ = metrics_lib; - } - - // Initializes the counter by providing the persistent storage - // location |filename|, a |histogram_name| (a linear histogram) to - // report to with |min|, |max|, and |buckets| attributes for the - // histogram. - virtual void Init(const char* filename, - const char* histogram_name, - int min, - int max, - int buckets); - - // Implementation of interface method. - virtual void Update(uint32 report_tag, uint32 reset_tag, int32 count) { - tagged_counter_->Update(report_tag, reset_tag, count); - } - // Implementation of interface method. - virtual void Flush() { - tagged_counter_->Flush(); - } - - // Accessor functions. - const std::string& histogram_name() const { - return histogram_name_; - } - - int min() const { - return min_; - } - - int max() const { - return max_; - } - - int buckets() const { - return buckets_; - } - - protected: - friend class TaggedCounterReporterTest; - FRIEND_TEST(TaggedCounterReporterTest, Report); - - static void Report(void* handle, int32 count); - - static MetricsLibraryInterface* metrics_lib_; - scoped_ptr tagged_counter_; - std::string histogram_name_; - int min_; - int max_; - int buckets_; -}; - -// FrequencyCounter uses TaggedCounter to maintain a persistent -// storage of the number of events that occur in a given cycle -// duration (in other words, a frequency count). For example, to -// count the number of blips per day, initialize |cycle_duration| to -// chromeos_metrics::kSecondsPerDay, and call Update with the number -// of blips that happen concurrently (usually 1). Reporting of the -// value is done through TaggedCounter's reporter function. -class FrequencyCounter { - public: - // Create a new frequency counter. - FrequencyCounter(); - virtual ~FrequencyCounter(); - - // Initialize a frequency counter, which is necessary before first - // use. |tagged_counter| is used to store the counts, its memory - // will be managed by this FrequencyCounter. |cycle_duration| is - // the number of seconds in a cycle. - virtual void Init(TaggedCounterInterface* tagged_counter, - time_t cycle_duration); - // Record that an event occurred. |count| is the number of concurrent - // events that have occurred. The time is implicitly assumed to be the - // time of the call. - virtual void Update(int32 count) { - UpdateInternal(count, time(NULL)); - } - - // Update the frequency counter based on the current time. If a - // cycle has finished, this will have the effect of flushing the - // cycle's count, without first requiring another update to the - // frequency counter. The more often this is called, the lower the - // latency to have a new sample submitted. - virtual void FlushFinishedCycles() { - Update(0); - } - - // Accessor function. - const TaggedCounterInterface& tagged_counter() const { - return *tagged_counter_; - } - - time_t cycle_duration() const { - return cycle_duration_; - } - - private: - friend class FrequencyCounterTest; - FRIEND_TEST(FrequencyCounterTest, UpdateInternal); - FRIEND_TEST(FrequencyCounterTest, GetCycleNumberForWeek); - FRIEND_TEST(FrequencyCounterTest, GetCycleNumberForDay); - - void UpdateInternal(int32 count, time_t now); - int32 GetCycleNumber(time_t now); - - time_t cycle_duration_; - scoped_ptr tagged_counter_; -}; - -// VersionCounter is like a FrequencyCounter, but it exposes -// separate "report" and "reset" tags, for counters that should -// be reported more often than they are reset. -class VersionCounter { - public: - VersionCounter(); - virtual ~VersionCounter(); - - // Initialize a version counter, which is necessary before first use. - // |tagged_counter| is used to store the counts. Its memory is managed - // by this FrequencyCounter. |cycle_duration| is the number of seconds in a - // cycle. - virtual void Init(TaggedCounterInterface* tagged_counter, - time_t cycle_duration); - // Record that |count| events have occurred. The - // time is implicitly assumed to be the time of the call. - // The version hash is passed. - virtual void Update(int32 count, uint32 version_hash) { - UpdateInternal(count, time(NULL), version_hash); - } - - // Reports the counter if enough time has passed, and also resets it if the - // version number has changed. - virtual void FlushOnChange(uint32 version_hash) { - UpdateInternal(0, time(NULL), version_hash); - } - - // Accessor function. - const TaggedCounterInterface& tagged_counter() const { - return *tagged_counter_; - } - - time_t cycle_duration() const { - return cycle_duration_; - } - - private: - friend class VersionCounterTest; - FRIEND_TEST(VersionCounterTest, UpdateInternal); - - void UpdateInternal(int32 count, time_t now, uint32 version_hash); - // TODO(semenzato): it's generally better to use base::TimeTicks (for - // monotonically-increasing timestamps) or base::Time (for wall time) - int32 GetCycleNumber(time_t now); - time_t cycle_duration_; - scoped_ptr tagged_counter_; -}; - -} // namespace chromeos_metrics - -#endif // METRICS_COUNTER_H_ diff --git a/metrics/counter_mock.h b/metrics/counter_mock.h deleted file mode 100644 index 77272a18f..000000000 --- a/metrics/counter_mock.h +++ /dev/null @@ -1,45 +0,0 @@ -// 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 - -#include - -#include "counter.h" - -namespace chromeos_metrics { - -class TaggedCounterMock : public TaggedCounter { - public: - MOCK_METHOD3(Init, void(const char* filename, - Reporter reporter, void* reporter_handle)); - MOCK_METHOD3(Update, void(uint32 report_tag, uint32 reset_tag, int32 count)); - MOCK_METHOD0(Flush, void()); -}; - -class TaggedCounterReporterMock : public TaggedCounterReporter { - public: - MOCK_METHOD5(Init, void(const char* filename, - const char* histogram_name, - int min, - int max, - int nbuckets)); - MOCK_METHOD3(Update, void(uint32 report_tag, uint32 reset_tag, int32 count)); - MOCK_METHOD0(Flush, void()); -}; - -class FrequencyCounterMock : public FrequencyCounter { - public: - MOCK_METHOD2(Init, void(TaggedCounterInterface* tagged_counter, - time_t cycle_duration)); - MOCK_METHOD1(Update, void(int32 count)); - MOCK_METHOD0(FlushFinishedCycles, void()); -}; - -} // namespace chromeos_metrics - -#endif // METRICS_COUNTER_MOCK_H_ diff --git a/metrics/counter_test.cc b/metrics/counter_test.cc deleted file mode 100644 index 90c3cd8ce..000000000 --- a/metrics/counter_test.cc +++ /dev/null @@ -1,461 +0,0 @@ -// 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 - -#include -#include -#include -#include -#include -#include - -#include "counter.h" -#include "counter_mock.h" // For TaggedCounterMock. -#include "metrics_library_mock.h" - -using base::FilePath; -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_.report_tag()); - EXPECT_EQ(0, record_.reset_tag()); - EXPECT_EQ(0, record_.count()); - } - - // The record under test. - TaggedCounter::Record record_; -}; - -class TaggedCounterTest : public testing::Test { - protected: - virtual void SetUp() { - EXPECT_TRUE(counter_.filename_.empty()); - 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; - base::DeleteFile(FilePath(kTestRecordFile), false); - } - - testing::AssertionResult AssertRecord(const char* expr_reset_tag, - const char* expr_count, - uint32 expected_reset_tag, - int32 expected_count) { - return AssertRecordFull(12345, expected_reset_tag, expected_count, false); - } - - // Asserts that the record file contains the specified contents. - testing::AssertionResult AssertRecord3(const char* expr_report_tag, - const char* expr_reset_tag, - const char* expr_count, - uint32 expected_report_tag, - uint32 expected_reset_tag, - int32 expected_count) { - return AssertRecordFull(expected_report_tag, expected_reset_tag, - expected_count, true); - } - - testing::AssertionResult AssertRecordFull(uint32 expected_report_tag, - uint32 expected_reset_tag, - int32 expected_count, - bool check_report_tag) { - 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 (!base::ReadFromFD(fd, reinterpret_cast(&record), - sizeof(record))) { - testing::Message msg; - msg << "Unable to read " << sizeof(record) << " bytes from " - << kTestRecordFile; - HANDLE_EINTR(close(fd)); - return testing::AssertionFailure(msg); - } - - if ((check_report_tag && (record.report_tag() != expected_report_tag)) || - record.reset_tag() != expected_reset_tag || - record.count() != expected_count) { - testing::Message msg; - msg << "actual record (" << record.report_tag() << ", " - << record.reset_tag() << ", " << record.count() - << ") expected (" << expected_report_tag << ", " - << expected_reset_tag << ", " << expected_count << ")"; - if (!check_report_tag) - msg << "\n(ignore differences in the first field)"; - 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() { - base::FilePath record_file(counter_.filename_); - int64 record_file_size; - return !base::PathExists(record_file) || - (base::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(int32 count) { - EXPECT_CALL(reporter_, Call(_, 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, int32 count) { - TaggedCounterTest* test = static_cast(handle); - ASSERT_FALSE(NULL == test); - test->reporter_.Call(handle, 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 char* file, - int line, - size_t message_start, - 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) const { - 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 > reporter_; - - // Pointer to the current test fixture. - static TaggedCounterTest* test_; -}; - -// static -TaggedCounterTest* TaggedCounterTest::test_ = NULL; - -TEST_F(RecordTest, Init) { - record_.Init(/* report_tag */ 8, /* reset_tag */ 5, /* count */ -1); - EXPECT_EQ(8, record_.report_tag()); - EXPECT_EQ(5, record_.reset_tag()); - EXPECT_EQ(0, record_.count()); - - record_.Init(/* report_tag */ -8, /* reset_tag */ -2, /* count */ 10); - EXPECT_EQ(-8, record_.report_tag()); - EXPECT_EQ(-2, record_.reset_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 */ kint32max); - EXPECT_EQ(kint32max, record_.count()); - - record_.Add(/* count */ 1); - EXPECT_EQ(kint32max, 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(/* report_tag */ 0, /* reset_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_); - base::DeleteFile(FilePath(kDoesNotExistFile), false); -} - -TEST_F(TaggedCounterTest, Flush) { - counter_.Flush(); - EXPECT_EQ(TaggedCounter::kRecordNull, counter_.record_state_); - - counter_.Update(/* report_tag */ 0, /* reset_tag */ 40, /* count */ 60); - ExpectReporterCall(/* count */ 60); - counter_.Flush(); - EXPECT_TRUE(AssertNoOrEmptyRecordFile()); - EXPECT_EQ(TaggedCounter::kRecordNull, counter_.record_state_); - - counter_.Update(/* report_tag */ 0, /* reset_tag */ 41, /* count */ 70); - counter_.record_.Init(/* report_tag */ 0, /* reset_tag */ 0, /* count */ 0); - counter_.record_state_ = TaggedCounter::kRecordInvalid; - ExpectReporterCall(/* count */ 70); - counter_.Flush(); - EXPECT_TRUE(AssertNoOrEmptyRecordFile()); - EXPECT_EQ(TaggedCounter::kRecordNull, counter_.record_state_); -} - -TEST_F(TaggedCounterTest, InitFromFile) { - counter_.Update(/* report_tag */ 0, /* reset_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(/* report_tag */ 0, /* reset_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(/* count */ 90); - counter_.Update(/* report_tag */ 0, /* reset_tag */ 31, /* count */ 60); - EXPECT_PRED_FORMAT2(AssertRecord, /* day */ 31, /* seconds */ 60); - EXPECT_EQ(TaggedCounter::kRecordValid, counter_.record_state_); - - ExpectReporterCall(/* count */ 60); - counter_.Init(kTestRecordFile, &Reporter, this); - counter_.Update(/* report_tag */ 0, /* reset_tag */ 32, /* count */ 0); - EXPECT_PRED_FORMAT2(AssertRecord, /* day */ 32, /* seconds */ 0); - EXPECT_EQ(TaggedCounter::kRecordValid, counter_.record_state_); -} - -TEST_F(TaggedCounterTest, Update) { - counter_.Update(/* report_tag */ 0, /* reset_tag */ 20, /* count */ 30); - EXPECT_PRED_FORMAT2(AssertRecord, /* day */ 20, /* seconds */ 30); - EXPECT_EQ(TaggedCounter::kRecordValid, counter_.record_state_); - - counter_.Update(/* report_tag */ 0, /* reset_tag */ 20, /* count */ 40); - EXPECT_PRED_FORMAT2(AssertRecord, /* day */ 20, /* seconds */ 70); - EXPECT_EQ(TaggedCounter::kRecordValid, counter_.record_state_); - - ExpectReporterCall(/* count */ 70); - counter_.Update(/* report_tag */ 0, /* reset_tag */ 21, /* count */ 15); - EXPECT_PRED_FORMAT2(AssertRecord, /* day */ 21, /* seconds */ 15); - EXPECT_EQ(TaggedCounter::kRecordValid, counter_.record_state_); - - ExpectReporterCall(/* count */ 15); - counter_.Update(/* report_tag */ 0, /* reset_tag */ 22, /* count */ 0); - EXPECT_PRED_FORMAT2(AssertRecord, /* day */ 22, /* seconds */ 0); - EXPECT_EQ(TaggedCounter::kRecordValid, counter_.record_state_); - - ExpectReporterCall(/* count */ 33); - counter_.Update(/* report_tag */ 0, /* reset_tag */ 22, /* count */ 33); - EXPECT_PRED_FORMAT2(AssertRecord, /* day */ 22, /* seconds */ 33); - EXPECT_EQ(TaggedCounter::kRecordValid, counter_.record_state_); - // Check that changing the report tag does not reset the counter. - counter_.Update(/* report_tag */ 1, /* reset_tag */ 22, /* count */ 0); - EXPECT_PRED_FORMAT3(AssertRecord3, /* version */ 1, - /* day */ 22, /* seconds */ 33); - EXPECT_EQ(TaggedCounter::kRecordValid, counter_.record_state_); -} - -static const char kTestFilename[] = "test_filename"; -static const char kTestHistogram[] = "test_histogram"; -const int kHistogramMin = 15; -const int kHistogramMax = 1024; -const int kHistogramBuckets = 23; - -class TaggedCounterReporterTest : public testing::Test { - protected: - virtual void SetUp() { - tagged_counter_ = new StrictMock(); - reporter_.tagged_counter_.reset(tagged_counter_); - metrics_lib_.reset(new StrictMock); - reporter_.SetMetricsLibraryInterface(metrics_lib_.get()); - ASSERT_TRUE(metrics_lib_.get() == reporter_.metrics_lib_); - } - virtual void TearDown() { - reporter_.SetMetricsLibraryInterface(NULL); - } - - void DoInit(); - StrictMock* tagged_counter_; - TaggedCounterReporter reporter_; - scoped_ptr metrics_lib_; -}; - -void TaggedCounterReporterTest::DoInit() { - EXPECT_CALL(*tagged_counter_, - Init(kTestFilename, - TaggedCounterReporter::Report, - &reporter_)) - .Times(1) - .RetiresOnSaturation(); - reporter_.Init(kTestFilename, - kTestHistogram, - kHistogramMin, - kHistogramMax, - kHistogramBuckets); - EXPECT_EQ(kTestHistogram, reporter_.histogram_name_); - EXPECT_EQ(kHistogramBuckets, reporter_.buckets_); - EXPECT_EQ(kHistogramMax, reporter_.max_); - EXPECT_EQ(kHistogramMin, reporter_.min_); -} - -TEST_F(TaggedCounterReporterTest, Init) { - DoInit(); -} - -TEST_F(TaggedCounterReporterTest, Update) { - DoInit(); - EXPECT_CALL(*tagged_counter_, Update(1, 0, 2)) - .Times(1) - .RetiresOnSaturation(); - reporter_.Update(1, 0, 2); -} - -TEST_F(TaggedCounterReporterTest, Flush) { - DoInit(); - EXPECT_CALL(*tagged_counter_, Flush()) - .Times(1) - .RetiresOnSaturation(); - reporter_.Flush(); -} - -TEST_F(TaggedCounterReporterTest, Report) { - DoInit(); - EXPECT_CALL(*metrics_lib_, SendToUMA(kTestHistogram, - 301, - kHistogramMin, - kHistogramMax, - kHistogramBuckets)) - .Times(1) - .RetiresOnSaturation(); - reporter_.Report(&reporter_, 301); -} - -class FrequencyCounterTest : public testing::Test { - protected: - virtual void SetUp() { - tagged_counter_ = NULL; - } - - void CheckInit(int32 cycle_duration); - void CheckCycleNumber(int32 cycle_duration); - - FrequencyCounter frequency_counter_; - StrictMock* tagged_counter_; - - TaggedCounter::Reporter reporter_; -}; - -void FrequencyCounterTest::CheckInit(int32 cycle_duration) { - tagged_counter_ = new StrictMock; - frequency_counter_.Init(tagged_counter_, cycle_duration); - EXPECT_EQ(cycle_duration, frequency_counter_.cycle_duration_); - EXPECT_EQ(tagged_counter_, frequency_counter_.tagged_counter_.get()); -} - -TEST_F(FrequencyCounterTest, Init) { - CheckInit(100); -} - -void FrequencyCounterTest::CheckCycleNumber(int32 cycle_duration) { - CheckInit(cycle_duration); - EXPECT_EQ(150, frequency_counter_.GetCycleNumber( - cycle_duration * 150)); - EXPECT_EQ(150, frequency_counter_.GetCycleNumber( - cycle_duration * 150 + cycle_duration - 1)); - EXPECT_EQ(151, frequency_counter_.GetCycleNumber( - cycle_duration * 151 + 1)); - EXPECT_EQ(0, frequency_counter_.GetCycleNumber(0)); -} - - -TEST_F(FrequencyCounterTest, GetCycleNumberForWeek) { - CheckCycleNumber(kSecondsPerWeek); -} - -TEST_F(FrequencyCounterTest, GetCycleNumberForDay) { - CheckCycleNumber(kSecondsPerDay); -} - -TEST_F(FrequencyCounterTest, UpdateInternal) { - CheckInit(kSecondsPerWeek); - EXPECT_CALL(*tagged_counter_, Update(0, 150, 2)) - .Times(1) - .RetiresOnSaturation(); - frequency_counter_.UpdateInternal(2, kSecondsPerWeek * 150); -} - -class VersionCounterTest : public testing::Test { - protected: - virtual void SetUp() { - tagged_counter_ = NULL; - } - void Init(); - - VersionCounter version_counter_; - StrictMock* tagged_counter_; - - TaggedCounter::Reporter reporter_; -}; - -void VersionCounterTest::Init() { - tagged_counter_ = new StrictMock; - version_counter_.Init(tagged_counter_, 1); - EXPECT_EQ(tagged_counter_, version_counter_.tagged_counter_.get()); -} - -TEST_F(VersionCounterTest, UpdateInternal) { - Init(); - EXPECT_CALL(*tagged_counter_, Update(0, 150, 2)) - .Times(1) - .RetiresOnSaturation(); - version_counter_.UpdateInternal(2, 0, 150); -} - -} // namespace chromeos_metrics - -int main(int argc, char** argv) { - testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/metrics/metrics.gyp b/metrics/metrics.gyp index 0a5ac48ba..2906a64c6 100644 --- a/metrics/metrics.gyp +++ b/metrics/metrics.gyp @@ -34,7 +34,7 @@ ], }, 'sources': [ - 'counter.cc', + 'persistent_integer.cc', 'metrics_daemon.cc', 'metrics_daemon_main.cc', ] @@ -73,15 +73,6 @@ 'metrics_library_test.cc', ] }, - { - 'target_name': 'counter_test', - 'type': 'executable', - 'includes': ['../common-mk/common_test.gypi'], - 'sources': [ - 'counter.cc', - 'counter_test.cc', - ] - }, { 'target_name': 'timer_test', 'type': 'executable', diff --git a/metrics/metrics_daemon.cc b/metrics/metrics_daemon.cc index d6eb8a302..883c132b8 100644 --- a/metrics/metrics_daemon.cc +++ b/metrics/metrics_daemon.cc @@ -22,13 +22,12 @@ #include #include -#include "counter.h" - using base::FilePath; using base::StringPrintf; using base::Time; using base::TimeDelta; using base::TimeTicks; +using chromeos_metrics::PersistentInteger; using std::map; using std::string; using std::vector; @@ -58,54 +57,7 @@ static const int kUseMonitorIntervalMax = 10 * kSecondsPerMinute; const char kKernelCrashDetectedFile[] = "/var/run/kernel-crash-detected"; static const char kUncleanShutdownDetectedFile[] = - "/var/run/unclean-shutdown-detected"; - -// static metrics parameters -const char MetricsDaemon::kMetricDailyUseTimeName[] = - "Logging.DailyUseTime"; -const int MetricsDaemon::kMetricDailyUseTimeMin = 1; -const int MetricsDaemon::kMetricDailyUseTimeMax = kMinutesPerDay; -const int MetricsDaemon::kMetricDailyUseTimeBuckets = 50; - -// crash interval metrics -const char MetricsDaemon::kMetricKernelCrashIntervalName[] = - "Logging.KernelCrashInterval"; -const char MetricsDaemon::kMetricUncleanShutdownIntervalName[] = - "Logging.UncleanShutdownInterval"; -const char MetricsDaemon::kMetricUserCrashIntervalName[] = - "Logging.UserCrashInterval"; - -const int MetricsDaemon::kMetricCrashIntervalMin = 1; -const int MetricsDaemon::kMetricCrashIntervalMax = - 4 * kSecondsPerWeek; -const int MetricsDaemon::kMetricCrashIntervalBuckets = 50; - -// crash frequency metrics -const char MetricsDaemon::kMetricAnyCrashesDailyName[] = - "Logging.AnyCrashesDaily"; -const char MetricsDaemon::kMetricAnyCrashesWeeklyName[] = - "Logging.AnyCrashesWeekly"; -const char MetricsDaemon::kMetricKernelCrashesDailyName[] = - "Logging.KernelCrashesDaily"; -const char MetricsDaemon::kMetricKernelCrashesWeeklyName[] = - "Logging.KernelCrashesWeekly"; -const char MetricsDaemon::kMetricKernelCrashesVersionName[] = - "Logging.KernelCrashesSinceUpdate"; -const int MetricsDaemon::kMetricCumulativeCrashCountMin = 1; -const int MetricsDaemon::kMetricCumulativeCrashCountMax = 500; -const int MetricsDaemon::kMetricCumulativeCrashCountBuckets = 100; - -const char MetricsDaemon::kMetricUncleanShutdownsDailyName[] = - "Logging.UncleanShutdownsDaily"; -const char MetricsDaemon::kMetricUncleanShutdownsWeeklyName[] = - "Logging.UncleanShutdownsWeekly"; -const char MetricsDaemon::kMetricUserCrashesDailyName[] = - "Logging.UserCrashesDaily"; -const char MetricsDaemon::kMetricUserCrashesWeeklyName[] = - "Logging.UserCrashesWeekly"; -const int MetricsDaemon::kMetricCrashFrequencyMin = 1; -const int MetricsDaemon::kMetricCrashFrequencyMax = 100; -const int MetricsDaemon::kMetricCrashFrequencyBuckets = 50; + "/var/run/unclean-shutdown-detected"; // disk stats metrics @@ -159,13 +111,6 @@ const char MetricsDaemon::kMetricSwapOutShortName[] = const char MetricsDaemon::kMetricScaledCpuFrequencyName[] = "Platform.CpuFrequencyThermalScaling"; -// persistent metrics path -const char MetricsDaemon::kMetricsPath[] = "/var/log/metrics"; - -// file containing OS version string -const char MetricsDaemon::kLsbReleasePath[] = "/etc/lsb-release"; - - // static const char* MetricsDaemon::kPowerStates_[] = { #define STATE(name, capname) #name, @@ -205,7 +150,6 @@ MetricsDaemon::MetricsDaemon() stats_initial_time_(0) {} MetricsDaemon::~MetricsDaemon() { - DeleteFrequencyCounters(); } double MetricsDaemon::GetActiveTime() { @@ -219,14 +163,6 @@ double MetricsDaemon::GetActiveTime() { } } -void MetricsDaemon::DeleteFrequencyCounters() { - for (FrequencyCounters::iterator i = frequency_counters_.begin(); - i != frequency_counters_.end(); ++i) { - delete i->second; - i->second = NULL; - } -} - void MetricsDaemon::Run(bool run_as_daemon) { base::AtExitManager at_exit_manager; @@ -236,72 +172,22 @@ void MetricsDaemon::Run(bool run_as_daemon) { if (CheckSystemCrash(kKernelCrashDetectedFile)) { ProcessKernelCrash(); } - kernel_crash_version_counter_->FlushOnChange(GetOsVersionHash()); if (CheckSystemCrash(kUncleanShutdownDetectedFile)) { ProcessUncleanShutdown(); } + // On OS version change, clear version stats (which are reported daily). + int32 version = GetOsVersionHash(); + if (version_cycle_->Get() != version) { + version_cycle_->Set(version); + SendKernelCrashesCumulativeCountSample(); + kernel_crashes_version_count_->Set(0); + } + Loop(); } -FilePath MetricsDaemon::GetHistogramPath(const char* histogram_name) { - return FilePath(kMetricsPath).Append(histogram_name); -} - -void MetricsDaemon::ConfigureCrashIntervalReporter( - const char* histogram_name, - scoped_ptr* reporter) { - reporter->reset(new chromeos_metrics::TaggedCounterReporter()); - FilePath file_path = GetHistogramPath(histogram_name); - (*reporter)->Init(file_path.value().c_str(), - histogram_name, - kMetricCrashIntervalMin, - kMetricCrashIntervalMax, - kMetricCrashIntervalBuckets); -} - -void MetricsDaemon::ConfigureCrashFrequencyReporter( - const char* histogram_name) { - scoped_ptr reporter( - new chromeos_metrics::TaggedCounterReporter()); - FilePath file_path = GetHistogramPath(histogram_name); - reporter->Init(file_path.value().c_str(), - histogram_name, - kMetricCrashFrequencyMin, - kMetricCrashFrequencyMax, - kMetricCrashFrequencyBuckets); - scoped_ptr new_counter( - new chromeos_metrics::FrequencyCounter()); - time_t cycle_duration = strstr(histogram_name, "Weekly") != NULL ? - chromeos_metrics::kSecondsPerWeek : - chromeos_metrics::kSecondsPerDay; - new_counter->Init( - static_cast( - reporter.release()), - cycle_duration); - frequency_counters_[histogram_name] = new_counter.release(); -} - -void MetricsDaemon::ConfigureCrashVersionReporter( - const char* histogram_name) { - scoped_ptr reporter( - new chromeos_metrics::TaggedCounterReporter()); - FilePath file_path = GetHistogramPath(histogram_name); - reporter->Init(file_path.value().c_str(), - histogram_name, - kMetricCumulativeCrashCountMin, - kMetricCumulativeCrashCountMax, - kMetricCumulativeCrashCountBuckets); - scoped_ptr new_counter( - new chromeos_metrics::VersionCounter()); - new_counter->Init( - static_cast( - reporter.release()), - chromeos_metrics::kSecondsPerDay); - kernel_crash_version_counter_ = new_counter.release(); -} - uint32 MetricsDaemon::GetOsVersionHash() { static uint32 cached_version_hash = 0; static bool version_hash_is_cached = false; @@ -328,31 +214,39 @@ void MetricsDaemon::Init(bool testing, MetricsLibraryInterface* metrics_lib, testing_ = testing; DCHECK(metrics_lib != NULL); metrics_lib_ = metrics_lib; - chromeos_metrics::TaggedCounterReporter:: - SetMetricsLibraryInterface(metrics_lib); - static const char kDailyUseRecordFile[] = "/var/log/metrics/daily-usage"; - daily_use_.reset(new chromeos_metrics::TaggedCounter()); - daily_use_->Init(kDailyUseRecordFile, &ReportDailyUse, this); + daily_use_.reset( + new PersistentInteger("Logging.DailyUseTime")); - ConfigureCrashIntervalReporter(kMetricKernelCrashIntervalName, - &kernel_crash_interval_); - ConfigureCrashIntervalReporter(kMetricUncleanShutdownIntervalName, - &unclean_shutdown_interval_); - ConfigureCrashIntervalReporter(kMetricUserCrashIntervalName, - &user_crash_interval_); + kernel_crash_interval_.reset( + new PersistentInteger("Logging.KernelCrashInterval")); + unclean_shutdown_interval_.reset( + new PersistentInteger("Logging.UncleanShutdownInterval")); + user_crash_interval_.reset( + new PersistentInteger("Logging.UserCrashInterval")); - DeleteFrequencyCounters(); - ConfigureCrashFrequencyReporter(kMetricAnyCrashesDailyName); - ConfigureCrashFrequencyReporter(kMetricAnyCrashesWeeklyName); - ConfigureCrashFrequencyReporter(kMetricKernelCrashesDailyName); - ConfigureCrashFrequencyReporter(kMetricKernelCrashesWeeklyName); - ConfigureCrashFrequencyReporter(kMetricUncleanShutdownsDailyName); - ConfigureCrashFrequencyReporter(kMetricUncleanShutdownsWeeklyName); - ConfigureCrashFrequencyReporter(kMetricUserCrashesDailyName); - ConfigureCrashFrequencyReporter(kMetricUserCrashesWeeklyName); + any_crashes_daily_count_.reset( + new PersistentInteger("Logging.AnyCrashesDaily")); + any_crashes_weekly_count_.reset( + new PersistentInteger("Logging.AnyCrashesWeekly")); + user_crashes_daily_count_.reset( + new PersistentInteger("Logging.UserCrashesDaily")); + user_crashes_weekly_count_.reset( + new PersistentInteger("Logging.UserCrashesWeekly")); + kernel_crashes_daily_count_.reset( + new PersistentInteger("Logging.KernelCrashesDaily")); + kernel_crashes_weekly_count_.reset( + new PersistentInteger("Logging.KernelCrashesWeekly")); + kernel_crashes_version_count_.reset( + new PersistentInteger("Logging.KernelCrashesSinceUpdate")); + unclean_shutdowns_daily_count_.reset( + new PersistentInteger("Logging.UncleanShutdownsDaily")); + unclean_shutdowns_weekly_count_.reset( + new PersistentInteger("Logging.UncleanShutdownsWeekly")); - ConfigureCrashVersionReporter(kMetricKernelCrashesVersionName); + daily_cycle_.reset(new PersistentInteger("daily.cycle")); + weekly_cycle_.reset(new PersistentInteger("weekly.cycle")); + version_cycle_.reset(new PersistentInteger("version.cycle")); diskstats_path_ = diskstats_path; vmstats_path_ = vmstats_path; @@ -504,6 +398,37 @@ MetricsDaemon::LookupSessionState(const char* state_name) { return kUnknownSessionState; } +void MetricsDaemon::ReportStats(Time now) { + TimeDelta since_epoch = now - Time::UnixEpoch(); + int day = since_epoch.InDays(); + int week = day / 7; + + if (daily_cycle_->Get() == day) { + // We did today already. + return; + } + daily_cycle_->Set(day); + + // Daily stats. + SendCrashFrequencySample(any_crashes_daily_count_); + SendCrashFrequencySample(user_crashes_daily_count_); + SendCrashFrequencySample(kernel_crashes_daily_count_); + SendCrashFrequencySample(unclean_shutdowns_daily_count_); + SendKernelCrashesCumulativeCountSample(); + + if (weekly_cycle_->Get() == week) { + // We did this week already. + return; + } + weekly_cycle_->Set(week); + + // Weekly stats. + SendCrashFrequencySample(any_crashes_weekly_count_); + SendCrashFrequencySample(user_crashes_weekly_count_); + SendCrashFrequencySample(kernel_crashes_weekly_count_); + SendCrashFrequencySample(unclean_shutdowns_weekly_count_); +} + void MetricsDaemon::SetUserActiveState(bool active, Time now) { DLOG(INFO) << "user: " << (active ? "active" : "inactive"); @@ -519,21 +444,12 @@ void MetricsDaemon::SetUserActiveState(bool active, Time now) { seconds = static_cast(since_active.InSeconds()); } } - TimeDelta since_epoch = now - Time(); - int day = since_epoch.InDays(); - daily_use_->Update(day, seconds, 0); - user_crash_interval_->Update(0, seconds, 0); - kernel_crash_interval_->Update(0, seconds, 0); + daily_use_->Add(seconds); + user_crash_interval_->Add(seconds); + kernel_crash_interval_->Add(seconds); - // Flush finished cycles of all frequency counters. - for (FrequencyCounters::iterator i = frequency_counters_.begin(); - i != frequency_counters_.end(); ++i) { - i->second->FlushFinishedCycles(); - } - // Report count if we're on a new cycle. FlushOnChange can also reset the - // counter, but not when called from here, because any version change has - // already been processed during initialization. - kernel_crash_version_counter_->FlushOnChange(GetOsVersionHash()); + // Report daily and weekly stats as needed. + ReportStats(now); // Schedules a use monitor on inactive->active transitions and // unschedules it on active->inactive transitions. @@ -553,12 +469,12 @@ void MetricsDaemon::ProcessUserCrash() { SetUserActiveState(user_active_, Time::Now()); // Reports the active use time since the last crash and resets it. - user_crash_interval_->Flush(); + SendCrashIntervalSample(user_crash_interval_); - frequency_counters_[kMetricUserCrashesDailyName]->Update(1); - frequency_counters_[kMetricUserCrashesWeeklyName]->Update(1); - frequency_counters_[kMetricAnyCrashesDailyName]->Update(1); - frequency_counters_[kMetricAnyCrashesWeeklyName]->Update(1); + any_crashes_daily_count_->Add(1); + any_crashes_weekly_count_->Add(1); + user_crashes_daily_count_->Add(1); + user_crashes_weekly_count_->Add(1); } void MetricsDaemon::ProcessKernelCrash() { @@ -566,14 +482,14 @@ void MetricsDaemon::ProcessKernelCrash() { SetUserActiveState(user_active_, Time::Now()); // Reports the active use time since the last crash and resets it. - kernel_crash_interval_->Flush(); + SendCrashIntervalSample(kernel_crash_interval_); - frequency_counters_[kMetricKernelCrashesDailyName]->Update(1); - frequency_counters_[kMetricKernelCrashesWeeklyName]->Update(1); - frequency_counters_[kMetricAnyCrashesDailyName]->Update(1); - frequency_counters_[kMetricAnyCrashesWeeklyName]->Update(1); + any_crashes_daily_count_->Add(1); + any_crashes_weekly_count_->Add(1); + kernel_crashes_daily_count_->Add(1); + kernel_crashes_weekly_count_->Add(1); - kernel_crash_version_counter_->Update(1, GetOsVersionHash()); + kernel_crashes_version_count_->Add(1); } void MetricsDaemon::ProcessUncleanShutdown() { @@ -581,12 +497,12 @@ void MetricsDaemon::ProcessUncleanShutdown() { SetUserActiveState(user_active_, Time::Now()); // Reports the active use time since the last crash and resets it. - unclean_shutdown_interval_->Flush(); + SendCrashIntervalSample(unclean_shutdown_interval_); - frequency_counters_[kMetricUncleanShutdownsDailyName]->Update(1); - frequency_counters_[kMetricUncleanShutdownsWeeklyName]->Update(1); - frequency_counters_[kMetricAnyCrashesDailyName]->Update(1); - frequency_counters_[kMetricAnyCrashesWeeklyName]->Update(1); + unclean_shutdowns_daily_count_->Add(1); + unclean_shutdowns_weekly_count_->Add(1); + any_crashes_daily_count_->Add(1); + any_crashes_weekly_count_->Add(1); } bool MetricsDaemon::CheckSystemCrash(const string& crash_file) { @@ -830,7 +746,7 @@ void MetricsDaemon::SendCpuThrottleMetrics() { // scaled_freq is not the actual turbo frequency. We indicate this situation // with a 101% value. int percent = scaled_freq > max_freq ? 101 : scaled_freq / (max_freq / 100); - SendLinearMetric(kMetricScaledCpuFrequencyName, percent, 101, 102); + SendLinearSample(kMetricScaledCpuFrequencyName, percent, 101, 102); } // static @@ -868,29 +784,29 @@ void MetricsDaemon::StatsCallback() { switch (stats_state_) { case kStatsShort: if (diskstats_success) { - SendMetric(kMetricReadSectorsShortName, + SendSample(kMetricReadSectorsShortName, read_sectors_per_second, 1, kMetricSectorsIOMax, kMetricSectorsBuckets); - SendMetric(kMetricWriteSectorsShortName, + SendSample(kMetricWriteSectorsShortName, write_sectors_per_second, 1, kMetricSectorsIOMax, kMetricSectorsBuckets); } if (vmstats_success) { - SendMetric(kMetricPageFaultsShortName, + SendSample(kMetricPageFaultsShortName, page_faults_per_second, 1, kMetricPageFaultsMax, kMetricPageFaultsBuckets); - SendMetric(kMetricSwapInShortName, + SendSample(kMetricSwapInShortName, swap_in_per_second, 1, kMetricPageFaultsMax, kMetricPageFaultsBuckets); - SendMetric(kMetricSwapOutShortName, + SendSample(kMetricSwapOutShortName, swap_out_per_second, 1, kMetricPageFaultsMax, @@ -903,12 +819,12 @@ void MetricsDaemon::StatsCallback() { break; case kStatsLong: if (diskstats_success) { - SendMetric(kMetricReadSectorsLongName, + SendSample(kMetricReadSectorsLongName, read_sectors_per_second, 1, kMetricSectorsIOMax, kMetricSectorsBuckets); - SendMetric(kMetricWriteSectorsLongName, + SendSample(kMetricWriteSectorsLongName, write_sectors_per_second, 1, kMetricSectorsIOMax, @@ -918,17 +834,17 @@ void MetricsDaemon::StatsCallback() { write_sectors_ = write_sectors_now; } if (vmstats_success) { - SendMetric(kMetricPageFaultsLongName, + SendSample(kMetricPageFaultsLongName, page_faults_per_second, 1, kMetricPageFaultsMax, kMetricPageFaultsBuckets); - SendMetric(kMetricSwapInLongName, + SendSample(kMetricSwapInLongName, swap_in_per_second, 1, kMetricPageFaultsMax, kMetricPageFaultsBuckets); - SendMetric(kMetricSwapOutLongName, + SendSample(kMetricSwapOutLongName, swap_out_per_second, 1, kMetricPageFaultsMax, @@ -1018,11 +934,11 @@ bool MetricsDaemon::ProcessMeminfo(const string& meminfo_raw) { case kMeminfoOp_HistPercent: // report value as percent of total memory percent = fields[i].value * 100 / total_memory; - SendLinearMetric(metrics_name, percent, 100, 101); + SendLinearSample(metrics_name, percent, 100, 101); break; case kMeminfoOp_HistLog: // report value in kbytes, log scale, 4Gb max - SendMetric(metrics_name, fields[i].value, 1, 4 * 1000 * 1000, 100); + SendSample(metrics_name, fields[i].value, 1, 4 * 1000 * 1000, 100); break; case kMeminfoOp_SwapTotal: swap_total = fields[i].value; @@ -1034,8 +950,8 @@ bool MetricsDaemon::ProcessMeminfo(const string& meminfo_raw) { if (swap_total > 0) { int swap_used = swap_total - swap_free; int swap_used_percent = swap_used * 100 / swap_total; - SendMetric("Platform.MeminfoSwapUsed", swap_used, 1, 8 * 1000 * 1000, 100); - SendLinearMetric("Platform.MeminfoSwapUsedPercent", swap_used_percent, + SendSample("Platform.MeminfoSwapUsed", swap_used, 1, 8 * 1000 * 1000, 100); + SendLinearSample("Platform.MeminfoSwapUsedPercent", swap_used_percent, 100, 101); } return true; @@ -1142,7 +1058,7 @@ bool MetricsDaemon::ProcessMemuse(const string& meminfo_raw) { } string metrics_name = base::StringPrintf("Platform.MemuseAnon%d", memuse_interval_index_); - SendLinearMetric(metrics_name, (active_anon + inactive_anon) * 100 / total, + SendLinearSample(metrics_name, (active_anon + inactive_anon) * 100 / total, 100, 101); return true; } @@ -1154,20 +1070,49 @@ void MetricsDaemon::ReportDailyUse(void* handle, int count) { MetricsDaemon* daemon = static_cast(handle); int minutes = (count + kSecondsPerMinute / 2) / kSecondsPerMinute; - daemon->SendMetric(kMetricDailyUseTimeName, minutes, - kMetricDailyUseTimeMin, - kMetricDailyUseTimeMax, - kMetricDailyUseTimeBuckets); + daemon->SendSample("Logging.DailyUseTime", + minutes, + 1, + kMinutesPerDay, + 50); } -void MetricsDaemon::SendMetric(const string& name, int sample, +void MetricsDaemon::SendSample(const string& name, int sample, int min, int max, int nbuckets) { DLOG(INFO) << "received metric: " << name << " " << sample << " " << min << " " << max << " " << nbuckets; metrics_lib_->SendToUMA(name, sample, min, max, nbuckets); } -void MetricsDaemon::SendLinearMetric(const string& name, int sample, +void MetricsDaemon::SendKernelCrashesCumulativeCountSample() { + // Report the number of crashes for this OS version, but don't clear the + // counter. It is cleared elsewhere on version change. + SendSample(kernel_crashes_version_count_->Name(), + kernel_crashes_version_count_->Get(), + 1, // value of first bucket + 500, // value of last bucket + 100); // number of buckets +} + +void MetricsDaemon::SendCrashIntervalSample( + const scoped_ptr& interval) { + SendSample(interval->Name(), + interval->GetAndClear(), + 1, // value of first bucket + 4 * kSecondsPerWeek, // value of last bucket + 50); // number of buckets +} + +void MetricsDaemon::SendCrashFrequencySample( + const scoped_ptr& frequency) { + SendSample(frequency->Name(), + frequency->GetAndClear(), + 1, // value of first bucket + 100, // value of last bucket + 50); // number of buckets +} + +void MetricsDaemon::SendLinearSample(const string& name, int sample, int max, int nbuckets) { DLOG(INFO) << "received linear metric: " << name << " " << sample << " " << max << " " << nbuckets; diff --git a/metrics/metrics_daemon.h b/metrics/metrics_daemon.h index 6d8f81b1b..e4bf853ac 100644 --- a/metrics/metrics_daemon.h +++ b/metrics/metrics_daemon.h @@ -15,13 +15,9 @@ #include // for FRIEND_TEST #include "metrics_library.h" +#include "persistent_integer.h" -namespace chromeos_metrics { -class FrequencyCounter; -class VersionCounter; -class TaggedCounter; -class TaggedCounterReporter; -} +using chromeos_metrics::PersistentInteger; class MetricsDaemon { @@ -66,7 +62,7 @@ class MetricsDaemon { FRIEND_TEST(MetricsDaemonTest, ReportUncleanShutdownInterval); FRIEND_TEST(MetricsDaemonTest, ReportUserCrashInterval); FRIEND_TEST(MetricsDaemonTest, ScreenSaverStateChanged); - FRIEND_TEST(MetricsDaemonTest, SendMetric); + FRIEND_TEST(MetricsDaemonTest, SendSample); FRIEND_TEST(MetricsDaemonTest, SendCpuThrottleMetrics); FRIEND_TEST(MetricsDaemonTest, SessionStateChanged); FRIEND_TEST(MetricsDaemonTest, SetUserActiveState); @@ -128,37 +124,7 @@ class MetricsDaemon { uint64_t swap_out_; // pages swapped out }; - typedef std::map - FrequencyCounters; - // Metric parameters. - static const char kMetricAnyCrashesDailyName[]; - static const char kMetricAnyCrashesWeeklyName[]; - static const int kMetricCrashFrequencyBuckets; - static const int kMetricCrashFrequencyMax; - static const int kMetricCrashFrequencyMin; - static const int kMetricCrashIntervalBuckets; - static const int kMetricCrashIntervalMax; - static const int kMetricCrashIntervalMin; - static const int kMetricCumulativeCrashCountBuckets; - static const int kMetricCumulativeCrashCountMax; - static const int kMetricCumulativeCrashCountMin; - static const int kMetricDailyUseTimeBuckets; - static const int kMetricDailyUseTimeMax; - static const int kMetricDailyUseTimeMin; - static const char kMetricDailyUseTimeName[]; - static const char kMetricKernelCrashesDailyName[]; - static const char kMetricKernelCrashesWeeklyName[]; - static const char kMetricKernelCrashesVersionName[]; - static const char kMetricKernelCrashIntervalName[]; - static const char kMetricsPath[]; - static const char kLsbReleasePath[]; - static const char kMetricUncleanShutdownIntervalName[]; - static const char kMetricUncleanShutdownsDailyName[]; - static const char kMetricUncleanShutdownsWeeklyName[]; - static const char kMetricUserCrashesDailyName[]; - static const char kMetricUserCrashesWeeklyName[]; - static const char kMetricUserCrashIntervalName[]; static const char kMetricReadSectorsLongName[]; static const char kMetricReadSectorsShortName[]; static const char kMetricWriteSectorsLongName[]; @@ -189,23 +155,6 @@ class MetricsDaemon { // Returns the active time since boot (uptime minus sleep time) in seconds. double GetActiveTime(); - // Clears and deletes the data contained in frequency_counters_. - void DeleteFrequencyCounters(); - - // Configures the given crash interval reporter. - void ConfigureCrashIntervalReporter( - const char* histogram_name, - scoped_ptr* reporter); - - // Configures the given frequency counter reporter. - void ConfigureCrashFrequencyReporter(const char* histogram_name); - - // Configures the given version counter reporter. - void ConfigureCrashVersionReporter(const char* histogram_name); - - // Returns file path to persistent file for generating given histogram. - base::FilePath GetHistogramPath(const char* histogram_name); - // Creates the event loop and enters it. void Loop(); @@ -285,15 +234,26 @@ class MetricsDaemon { // Sends a regular (exponential) histogram sample to Chrome for // transport to UMA. See MetricsLibrary::SendToUMA in // metrics_library.h for a description of the arguments. - void SendMetric(const std::string& name, int sample, + void SendSample(const std::string& name, int sample, int min, int max, int nbuckets); // Sends a linear histogram sample to Chrome for transport to UMA. See // MetricsLibrary::SendToUMA in metrics_library.h for a description of the // arguments. - void SendLinearMetric(const std::string& name, int sample, + void SendLinearSample(const std::string& name, int sample, int max, int nbuckets); + // Sends a histogram sample with the total number of kernel crashes since the + // last version update. + void SendKernelCrashesCumulativeCountSample(); + + // Sends a sample representing a time interval between two crashes of the + // same type. + void SendCrashIntervalSample(const scoped_ptr& interval); + + // Sends a sample representing a frequency of crashes of some type. + void SendCrashFrequencySample(const scoped_ptr& frequency); + // Initializes vm and disk stats reporting. void StatsReporterInit(); @@ -361,6 +321,9 @@ class MetricsDaemon { // Reads an integer CPU frequency value from sysfs. bool ReadFreqToInt(const std::string& sysfs_file_name, int* value); + // Report UMA stats when cycles (daily or weekly) have changed. + void ReportStats(base::Time now); + // Reads the current OS version from /etc/lsb-release and hashes it // to a unsigned 32-bit int. uint32 GetOsVersionHash(); @@ -391,26 +354,6 @@ class MetricsDaemon { // the timestamp. base::Time user_active_last_; - // Daily active use time in seconds. - scoped_ptr daily_use_; - - // Active use time between user-space process crashes. - scoped_ptr user_crash_interval_; - - // Active use time between kernel crashes. - scoped_ptr kernel_crash_interval_; - - // Active use time between unclean shutdowns crashes. - scoped_ptr - unclean_shutdown_interval_; - - // Map of all frequency counters, to simplify flushing them. - FrequencyCounters frequency_counters_; - - // This contains a cumulative number of kernel crashes since the latest - // version update. - chromeos_metrics::VersionCounter* kernel_crash_version_counter_; - // Sleep period until the next daily usage aggregation performed by // the daily use monitor (see ScheduleUseMonitor). int usemon_interval_; @@ -432,6 +375,27 @@ class MetricsDaemon { StatsState stats_state_; double stats_initial_time_; + // Persistent counters for crash statistics. + scoped_ptr daily_cycle_; + scoped_ptr weekly_cycle_; + scoped_ptr version_cycle_; + + scoped_ptr daily_use_; + + scoped_ptr user_crash_interval_; + scoped_ptr kernel_crash_interval_; + scoped_ptr unclean_shutdown_interval_; + + scoped_ptr any_crashes_daily_count_; + scoped_ptr any_crashes_weekly_count_; + scoped_ptr user_crashes_daily_count_; + scoped_ptr user_crashes_weekly_count_; + scoped_ptr kernel_crashes_daily_count_; + scoped_ptr kernel_crashes_weekly_count_; + scoped_ptr kernel_crashes_version_count_; + scoped_ptr unclean_shutdowns_daily_count_; + scoped_ptr unclean_shutdowns_weekly_count_; + std::string diskstats_path_; std::string vmstats_path_; std::string scaling_max_freq_path_; diff --git a/metrics/metrics_daemon_test.cc b/metrics/metrics_daemon_test.cc index 255f26070..aea0f4bd6 100644 --- a/metrics/metrics_daemon_test.cc +++ b/metrics/metrics_daemon_test.cc @@ -13,25 +13,21 @@ #include #include -#include "counter_mock.h" #include "metrics_daemon.h" #include "metrics_library_mock.h" +#include "persistent_integer_mock.h" using base::FilePath; using base::StringPrintf; using base::Time; using base::TimeTicks; -using chromeos_metrics::FrequencyCounter; -using chromeos_metrics::FrequencyCounterMock; -using chromeos_metrics::TaggedCounterMock; -using chromeos_metrics::TaggedCounterReporter; -using chromeos_metrics::TaggedCounterReporterMock; using std::string; using std::vector; using ::testing::_; using ::testing::Return; using ::testing::StrictMock; using ::testing::AtLeast; +using chromeos_metrics::PersistentIntegerMock; static const int kSecondsPerDay = 24 * 60 * 60; @@ -61,9 +57,6 @@ class TestTicks : public TimeTicks { class MetricsDaemonTest : public testing::Test { protected: virtual void SetUp() { - EXPECT_EQ(NULL, daemon_.daily_use_.get()); - EXPECT_EQ(NULL, daemon_.kernel_crash_interval_.get()); - EXPECT_EQ(NULL, daemon_.user_crash_interval_.get()); kFakeDiskStats[0] = base::StringPrintf(kFakeDiskStatsFormat, kFakeReadSectors[0], kFakeWriteSectors[0]); @@ -74,64 +67,10 @@ class MetricsDaemonTest : public testing::Test { CreateFakeCpuFrequencyFile(kFakeCpuinfoMaxFreqPath, 10000000); CreateFakeCpuFrequencyFile(kFakeScalingMaxFreqPath, 10000000); + chromeos_metrics::PersistentInteger::SetTestingMode(true); daemon_.Init(true, &metrics_lib_, kFakeDiskStatsPath, kFakeVmStatsPath, kFakeScalingMaxFreqPath, kFakeCpuinfoMaxFreqPath); - // Check configuration of a few histograms. - FrequencyCounter* frequency_counter = - daemon_.frequency_counters_[MetricsDaemon::kMetricAnyCrashesDailyName]; - const TaggedCounterReporter* reporter = GetReporter(frequency_counter); - EXPECT_EQ(MetricsDaemon::kMetricAnyCrashesDailyName, - reporter->histogram_name()); - EXPECT_EQ(chromeos_metrics::kSecondsPerDay, - frequency_counter->cycle_duration()); - EXPECT_EQ(MetricsDaemon::kMetricCrashFrequencyMin, reporter->min()); - EXPECT_EQ(MetricsDaemon::kMetricCrashFrequencyMax, reporter->max()); - EXPECT_EQ(MetricsDaemon::kMetricCrashFrequencyBuckets, reporter->buckets()); - - frequency_counter = - daemon_.frequency_counters_[MetricsDaemon::kMetricAnyCrashesWeeklyName]; - reporter = GetReporter(frequency_counter); - EXPECT_EQ(MetricsDaemon::kMetricAnyCrashesWeeklyName, - reporter->histogram_name()); - EXPECT_EQ(chromeos_metrics::kSecondsPerWeek, - frequency_counter->cycle_duration()); - - EXPECT_EQ(MetricsDaemon::kMetricKernelCrashIntervalName, - daemon_.kernel_crash_interval_->histogram_name()); - EXPECT_EQ(MetricsDaemon::kMetricCrashIntervalMin, - daemon_.kernel_crash_interval_->min()); - EXPECT_EQ(MetricsDaemon::kMetricCrashIntervalMax, - daemon_.kernel_crash_interval_->max()); - EXPECT_EQ(MetricsDaemon::kMetricCrashIntervalBuckets, - daemon_.kernel_crash_interval_->buckets()); - - EXPECT_EQ(MetricsDaemon::kMetricUncleanShutdownIntervalName, - daemon_.unclean_shutdown_interval_->histogram_name()); - - // Tests constructor initialization. Switches to mock counters. - EXPECT_TRUE(NULL != daemon_.daily_use_.get()); - EXPECT_TRUE(NULL != daemon_.kernel_crash_interval_.get()); - EXPECT_TRUE(NULL != daemon_.user_crash_interval_.get()); - - // Allocates mock counter and transfers ownership. - daily_use_ = new StrictMock(); - daemon_.daily_use_.reset(daily_use_); - kernel_crash_interval_ = new StrictMock(); - daemon_.kernel_crash_interval_.reset(kernel_crash_interval_); - user_crash_interval_ = new StrictMock(); - daemon_.user_crash_interval_.reset(user_crash_interval_); - unclean_shutdown_interval_ = new StrictMock(); - daemon_.unclean_shutdown_interval_.reset(unclean_shutdown_interval_); - - // Reset all frequency counter reporters to mocks for further testing. - MetricsDaemon::FrequencyCounters::iterator i; - for (i = daemon_.frequency_counters_.begin(); - i != daemon_.frequency_counters_.end(); ++i) { - delete i->second; - i->second = new StrictMock(); - } - EXPECT_FALSE(daemon_.user_active_); EXPECT_TRUE(daemon_.user_active_last_.is_null()); EXPECT_EQ(MetricsDaemon::kUnknownPowerState, daemon_.power_state_); @@ -139,6 +78,24 @@ class MetricsDaemonTest : public testing::Test { base::DeleteFile(FilePath(kTestDir), true); base::CreateDirectory(FilePath(kTestDir)); + + // Replace original persistent values with mock ones. + daily_use_mock_ = + new StrictMock("1.mock"); + daemon_.daily_use_.reset(daily_use_mock_); + + kernel_crash_interval_mock_ = + new StrictMock("2.mock"); + daemon_.kernel_crash_interval_.reset(kernel_crash_interval_mock_); + + user_crash_interval_mock_ = + new StrictMock("3.mock"); + daemon_.user_crash_interval_.reset(user_crash_interval_mock_); + + unclean_shutdown_interval_mock_ = + new StrictMock("4.mock"); + daemon_.unclean_shutdown_interval_.reset(unclean_shutdown_interval_mock_); + } virtual void TearDown() { @@ -147,57 +104,37 @@ class MetricsDaemonTest : public testing::Test { EXPECT_EQ(0, unlink(kFakeCpuinfoMaxFreqPath)); } - const TaggedCounterReporter* - GetReporter(FrequencyCounter* frequency_counter) const { - return static_cast( - &frequency_counter->tagged_counter()); - } - - void ExpectFrequencyFlushCalls() { - MetricsDaemon::FrequencyCounters::iterator i; - for (i = daemon_.frequency_counters_.begin(); - i != daemon_.frequency_counters_.end(); ++i) { - FrequencyCounterMock* mock = - static_cast(i->second); - EXPECT_CALL(*mock, FlushFinishedCycles()); - } - } - // Adds active use aggregation counters update expectations that the - // specified tag/count update will be generated. - void ExpectActiveUseUpdate(int daily_tag, int count) { - EXPECT_CALL(*daily_use_, Update(daily_tag, count, 0)) + // specified count will be added. + void ExpectActiveUseUpdate(int count) { + EXPECT_CALL(*daily_use_mock_, Add(count)) .Times(1) .RetiresOnSaturation(); - EXPECT_CALL(*kernel_crash_interval_, Update(0, count, 0)) + EXPECT_CALL(*kernel_crash_interval_mock_, Add(count)) .Times(1) .RetiresOnSaturation(); - EXPECT_CALL(*user_crash_interval_, Update(0, count, 0)) + EXPECT_CALL(*user_crash_interval_mock_, Add(count)) .Times(1) .RetiresOnSaturation(); - ExpectFrequencyFlushCalls(); } - // Adds active use aggregation counters update expectations that - // ignore the update arguments. + // As above, but ignore values of counter updates. void IgnoreActiveUseUpdate() { - EXPECT_CALL(*daily_use_, Update(_, _, _)) + EXPECT_CALL(*daily_use_mock_, Add(_)) .Times(1) .RetiresOnSaturation(); - EXPECT_CALL(*kernel_crash_interval_, Update(_, _, _)) + EXPECT_CALL(*kernel_crash_interval_mock_, Add(_)) .Times(1) .RetiresOnSaturation(); - EXPECT_CALL(*user_crash_interval_, Update(_, _, _)) + EXPECT_CALL(*user_crash_interval_mock_, Add(_)) .Times(1) .RetiresOnSaturation(); - ExpectFrequencyFlushCalls(); } // Adds a metrics library mock expectation that the specified metric // will be generated. - void ExpectMetric(const string& name, int sample, - int min, int max, int buckets) { - EXPECT_CALL(metrics_lib_, SendToUMA(name, sample, min, max, buckets)) + void ExpectSample(int sample) { + EXPECT_CALL(metrics_lib_, SendToUMA(_, sample, _, _, _)) .Times(1) .WillOnce(Return(true)) .RetiresOnSaturation(); @@ -205,11 +142,8 @@ class MetricsDaemonTest : public testing::Test { // Adds a metrics library mock expectation that the specified daily // use time metric will be generated. - void ExpectDailyUseTimeMetric(int sample) { - ExpectMetric(MetricsDaemon::kMetricDailyUseTimeName, sample, - MetricsDaemon::kMetricDailyUseTimeMin, - MetricsDaemon::kMetricDailyUseTimeMax, - MetricsDaemon::kMetricDailyUseTimeBuckets); + void ExpectDailyUseTimeSample(int sample) { + ExpectSample(sample); } // Converts from seconds to a Time object. @@ -247,12 +181,6 @@ class MetricsDaemonTest : public testing::Test { dbus_message_unref(msg); } - // Gets the frequency counter for the given name. - FrequencyCounterMock& GetFrequencyMock(const char* histogram_name) { - return *static_cast( - daemon_.frequency_counters_[histogram_name]); - } - // Creates or overwrites an input file containing fake disk stats. void CreateFakeDiskStatsFile(const char* fake_stats) { if (unlink(kFakeDiskStatsPath) < 0) { @@ -277,17 +205,13 @@ class MetricsDaemonTest : public testing::Test { // The MetricsDaemon under test. MetricsDaemon daemon_; - // Metrics library mock. It's a strict mock so that all unexpected - // metric generation calls are marked as failures. + // Mocks. They are strict mock so that all unexpected + // calls are marked as failures. StrictMock metrics_lib_; - - // 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* daily_use_; - StrictMock* kernel_crash_interval_; - StrictMock* user_crash_interval_; - StrictMock* unclean_shutdown_interval_; + StrictMock* daily_use_mock_; + StrictMock* kernel_crash_interval_mock_; + StrictMock* user_crash_interval_mock_; + StrictMock* unclean_shutdown_interval_mock_; }; TEST_F(MetricsDaemonTest, CheckSystemCrash) { @@ -305,10 +229,10 @@ TEST_F(MetricsDaemonTest, CheckSystemCrash) { } TEST_F(MetricsDaemonTest, ReportDailyUse) { - ExpectDailyUseTimeMetric(/* sample */ 2); + ExpectDailyUseTimeSample(/* sample */ 2); MetricsDaemon::ReportDailyUse(&daemon_, /* count */ 90); - ExpectDailyUseTimeMetric(/* sample */ 1); + ExpectDailyUseTimeSample(/* sample */ 1); MetricsDaemon::ReportDailyUse(&daemon_, /* count */ 89); // There should be no metrics generated for the calls below. @@ -335,6 +259,9 @@ TEST_F(MetricsDaemonTest, LookupSessionState) { } TEST_F(MetricsDaemonTest, MessageFilter) { + // Ignore calls to SendToUMA. + EXPECT_CALL(metrics_lib_, SendToUMA(_, _, _, _, _)).Times(AtLeast(0)); + DBusMessage* msg = dbus_message_new(DBUS_MESSAGE_TYPE_METHOD_CALL); DBusHandlerResult res = MetricsDaemon::MessageFilter(/* connection */ NULL, msg, &daemon_); @@ -342,25 +269,6 @@ TEST_F(MetricsDaemonTest, MessageFilter) { DeleteDBusMessage(msg); IgnoreActiveUseUpdate(); - EXPECT_CALL(GetFrequencyMock(MetricsDaemon::kMetricAnyCrashesDailyName), - Update(1)) - .Times(1) - .RetiresOnSaturation(); - EXPECT_CALL(GetFrequencyMock(MetricsDaemon::kMetricAnyCrashesWeeklyName), - Update(1)) - .Times(1) - .RetiresOnSaturation(); - EXPECT_CALL(GetFrequencyMock(MetricsDaemon::kMetricUserCrashesDailyName), - Update(1)) - .Times(1) - .RetiresOnSaturation(); - EXPECT_CALL(GetFrequencyMock(MetricsDaemon::kMetricUserCrashesWeeklyName), - Update(1)) - .Times(1) - .RetiresOnSaturation(); - EXPECT_CALL(*user_crash_interval_, Flush()) - .Times(1) - .RetiresOnSaturation(); vector signal_args; msg = NewDBusSignalString("/", "org.chromium.CrashReporter", @@ -421,13 +329,16 @@ TEST_F(MetricsDaemonTest, MessageFilter) { } TEST_F(MetricsDaemonTest, PowerStateChanged) { - ExpectActiveUseUpdate(7, 0); + // Ignore calls to SendToUMA. + EXPECT_CALL(metrics_lib_, SendToUMA(_, _, _, _, _)).Times(AtLeast(0)); + + ExpectActiveUseUpdate(0); daemon_.SetUserActiveState(/* active */ true, TestTime(7 * kSecondsPerDay + 15)); EXPECT_TRUE(daemon_.user_active_); EXPECT_EQ(TestTime(7 * kSecondsPerDay + 15), daemon_.user_active_last_); - ExpectActiveUseUpdate(7, 30); + ExpectActiveUseUpdate(30); daemon_.PowerStateChanged("mem", TestTime(7 * kSecondsPerDay + 45)); EXPECT_EQ(MetricsDaemon::kPowerStateMem, daemon_.power_state_); EXPECT_FALSE(daemon_.user_active_); @@ -438,82 +349,36 @@ TEST_F(MetricsDaemonTest, PowerStateChanged) { EXPECT_FALSE(daemon_.user_active_); EXPECT_EQ(TestTime(7 * kSecondsPerDay + 45), daemon_.user_active_last_); - ExpectActiveUseUpdate(7, 0); + ExpectActiveUseUpdate(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, ProcessKernelCrash) { - IgnoreActiveUseUpdate(); - EXPECT_CALL(*kernel_crash_interval_, Flush()) - .Times(1) - .RetiresOnSaturation(); - EXPECT_CALL(GetFrequencyMock(MetricsDaemon::kMetricAnyCrashesDailyName), - Update(1)); - EXPECT_CALL(GetFrequencyMock(MetricsDaemon::kMetricAnyCrashesWeeklyName), - Update(1)); - EXPECT_CALL(GetFrequencyMock(MetricsDaemon::kMetricKernelCrashesDailyName), - Update(1)); - EXPECT_CALL(GetFrequencyMock(MetricsDaemon::kMetricKernelCrashesWeeklyName), - Update(1)); - daemon_.ProcessKernelCrash(); -} - -TEST_F(MetricsDaemonTest, ProcessUncleanShutdown) { - IgnoreActiveUseUpdate(); - EXPECT_CALL(*unclean_shutdown_interval_, Flush()) - .Times(1) - .RetiresOnSaturation(); - EXPECT_CALL(GetFrequencyMock(MetricsDaemon::kMetricAnyCrashesDailyName), - Update(1)); - EXPECT_CALL(GetFrequencyMock(MetricsDaemon::kMetricAnyCrashesWeeklyName), - Update(1)); - EXPECT_CALL(GetFrequencyMock(MetricsDaemon::kMetricUncleanShutdownsDailyName), - Update(1)); - EXPECT_CALL( - GetFrequencyMock(MetricsDaemon::kMetricUncleanShutdownsWeeklyName), - Update(1)); - daemon_.ProcessUncleanShutdown(); -} - -TEST_F(MetricsDaemonTest, ProcessUserCrash) { - IgnoreActiveUseUpdate(); - EXPECT_CALL(*user_crash_interval_, Flush()) - .Times(1) - .RetiresOnSaturation(); - EXPECT_CALL(GetFrequencyMock(MetricsDaemon::kMetricAnyCrashesDailyName), - Update(1)); - EXPECT_CALL(GetFrequencyMock(MetricsDaemon::kMetricAnyCrashesWeeklyName), - Update(1)); - EXPECT_CALL(GetFrequencyMock(MetricsDaemon::kMetricUserCrashesDailyName), - Update(1)); - EXPECT_CALL(GetFrequencyMock(MetricsDaemon::kMetricUserCrashesWeeklyName), - Update(1)); - daemon_.ProcessUserCrash(); -} - -TEST_F(MetricsDaemonTest, SendMetric) { - ExpectMetric("Dummy.Metric", 3, 1, 100, 50); - daemon_.SendMetric("Dummy.Metric", /* sample */ 3, +TEST_F(MetricsDaemonTest, SendSample) { + ExpectSample(3); + daemon_.SendSample("Dummy.Metric", /* sample */ 3, /* min */ 1, /* max */ 100, /* buckets */ 50); } TEST_F(MetricsDaemonTest, SessionStateChanged) { - ExpectActiveUseUpdate(15, 0); + // Ignore calls to SendToUMA. + EXPECT_CALL(metrics_lib_, SendToUMA(_, _, _, _, _)).Times(AtLeast(0)); + + ExpectActiveUseUpdate(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_); - ExpectActiveUseUpdate(15, 130); + ExpectActiveUseUpdate(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_); - ExpectActiveUseUpdate(15, 0); + ExpectActiveUseUpdate(0); daemon_.SessionStateChanged("otherstate", TestTime(15 * kSecondsPerDay + 300)); EXPECT_EQ(MetricsDaemon::kUnknownSessionState, daemon_.session_state_); @@ -522,31 +387,34 @@ TEST_F(MetricsDaemonTest, SessionStateChanged) { } TEST_F(MetricsDaemonTest, SetUserActiveState) { - ExpectActiveUseUpdate(5, 0); + // Ignore calls to SendToUMA. + EXPECT_CALL(metrics_lib_, SendToUMA(_, _, _, _, _)).Times(AtLeast(0)); + + ExpectActiveUseUpdate(0); daemon_.SetUserActiveState(/* active */ false, TestTime(5 * kSecondsPerDay + 10)); EXPECT_FALSE(daemon_.user_active_); EXPECT_EQ(TestTime(5 * kSecondsPerDay + 10), daemon_.user_active_last_); - ExpectActiveUseUpdate(6, 0); + ExpectActiveUseUpdate(0); daemon_.SetUserActiveState(/* active */ true, TestTime(6 * kSecondsPerDay + 20)); EXPECT_TRUE(daemon_.user_active_); EXPECT_EQ(TestTime(6 * kSecondsPerDay + 20), daemon_.user_active_last_); - ExpectActiveUseUpdate(6, 100); + ExpectActiveUseUpdate(100); daemon_.SetUserActiveState(/* active */ true, TestTime(6 * kSecondsPerDay + 120)); EXPECT_TRUE(daemon_.user_active_); EXPECT_EQ(TestTime(6 * kSecondsPerDay + 120), daemon_.user_active_last_); - ExpectActiveUseUpdate(6, 110); + ExpectActiveUseUpdate(110); daemon_.SetUserActiveState(/* active */ false, TestTime(6 * kSecondsPerDay + 230)); EXPECT_FALSE(daemon_.user_active_); EXPECT_EQ(TestTime(6 * kSecondsPerDay + 230), daemon_.user_active_last_); - ExpectActiveUseUpdate(6, 0); + ExpectActiveUseUpdate(0); daemon_.SetUserActiveState(/* active */ false, TestTime(6 * kSecondsPerDay + 260)); EXPECT_FALSE(daemon_.user_active_); @@ -554,31 +422,28 @@ TEST_F(MetricsDaemonTest, SetUserActiveState) { } TEST_F(MetricsDaemonTest, SetUserActiveStateTimeJump) { - ExpectActiveUseUpdate(10, 0); + // Ignore calls to SendToUMA. + EXPECT_CALL(metrics_lib_, SendToUMA(_, _, _, _, _)).Times(AtLeast(0)); + + ExpectActiveUseUpdate(0); daemon_.SetUserActiveState(/* active */ true, TestTime(10 * kSecondsPerDay + 500)); EXPECT_TRUE(daemon_.user_active_); EXPECT_EQ(TestTime(10 * kSecondsPerDay + 500), daemon_.user_active_last_); - ExpectActiveUseUpdate(10, 0); + ExpectActiveUseUpdate(0); daemon_.SetUserActiveState(/* active */ true, TestTime(10 * kSecondsPerDay + 300)); EXPECT_TRUE(daemon_.user_active_); EXPECT_EQ(TestTime(10 * kSecondsPerDay + 300), daemon_.user_active_last_); - ExpectActiveUseUpdate(10, 0); + ExpectActiveUseUpdate(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, GetHistogramPath) { - EXPECT_EQ("/var/log/metrics/Logging.AnyCrashesDaily", - daemon_.GetHistogramPath( - MetricsDaemon::kMetricAnyCrashesDailyName).value()); -} - TEST_F(MetricsDaemonTest, ReportDiskStats) { long int read_sectors_now, write_sectors_now; diff --git a/metrics/persistent_integer.cc b/metrics/persistent_integer.cc new file mode 100644 index 000000000..c6ccf0ec7 --- /dev/null +++ b/metrics/persistent_integer.cc @@ -0,0 +1,97 @@ +// Copyright (c) 2014 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 "persistent_integer.h" + +#include + +#include +#include + +#include "metrics_library.h" + +namespace chromeos_metrics { + +// The directory for the persistent storage. +const char* const kBackingFilesDirectory = "/var/log/metrics/"; + +// Static class member instantiation. +bool PersistentInteger::testing_ = false; + +PersistentInteger::PersistentInteger(const std::string& name) : + value_(0), + version_(kVersion), + name_(name), + synced_(false) { + if (testing_) { + backing_file_name_ = name_; + } else { + backing_file_name_ = kBackingFilesDirectory + name_; + } +} + +PersistentInteger::~PersistentInteger() {} + +void PersistentInteger::Set(int64 value) { + value_ = value; + Write(value); +} + +int64 PersistentInteger::Get() { + // If not synced, then read. If the read fails, it's a good idea to write. + if (!synced_ && !Read()) + Write(value_); + return value_; +} + +int64 PersistentInteger::GetAndClear() { + int64 v = Get(); + Set(0); + return v; +} + +void PersistentInteger::Add(int64 x) { + Set(Get() + x); +} + +void PersistentInteger::Write(int64 value) { + int fd = HANDLE_EINTR(open(backing_file_name_.c_str(), + O_WRONLY | O_CREAT | O_TRUNC, + S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH)); + PCHECK(fd >= 0) << "cannot open " << backing_file_name_ << " for writing"; + PCHECK((HANDLE_EINTR(write(fd, &version_, sizeof(version_))) == + sizeof(version_)) && + (HANDLE_EINTR(write(fd, &value_, sizeof(value_))) == + sizeof(value_))) + << "cannot write to " << backing_file_name_; + close(fd); + synced_ = true; +} + +bool PersistentInteger::Read() { + int fd = HANDLE_EINTR(open(backing_file_name_.c_str(), O_RDONLY)); + if (fd < 0) { + PLOG(WARNING) << "cannot open " << backing_file_name_ << " for reading"; + return false; + } + int32 version; + int64 value; + bool read_succeeded = false; + if (HANDLE_EINTR(read(fd, &version, sizeof(version))) == sizeof(version) && + version == version_ && + HANDLE_EINTR(read(fd, &value, sizeof(value))) == sizeof(value)) { + value_ = value; + read_succeeded = true; + synced_ = true; + } + close(fd); + return read_succeeded; +} + +void PersistentInteger::SetTestingMode(bool testing) { + testing_ = testing; +} + + +} // namespace chromeos_metrics diff --git a/metrics/persistent_integer.h b/metrics/persistent_integer.h new file mode 100644 index 000000000..7d920b5c7 --- /dev/null +++ b/metrics/persistent_integer.h @@ -0,0 +1,65 @@ +// Copyright (c) 2014 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_PERSISTENT_INTEGER_H_ +#define METRICS_PERSISTENT_INTEGER_H_ + +#include +#include + +namespace chromeos_metrics { + +// PersistentIntegers is a named 64-bit integer value backed by a file. +// The in-memory value acts as a write-through cache of the file value. +// If the backing file doesn't exist or has bad content, the value is 0. + +class PersistentInteger { + public: + PersistentInteger(const std::string& name); + + // Virtual only because of mock. + virtual ~PersistentInteger(); + + // Sets the value. This writes through to the backing file. + void Set(int64 v); + + // Gets the value. May sync from backing file first. + int64 Get(); + + // Returns the name of the object. + std::string Name() { return name_; } + + // Convenience function for Get() followed by Set(0). + int64 GetAndClear(); + + // Convenience function for v = Get, Set(v + x). + // Virtual only because of mock. + virtual void Add(int64 x); + + // After calling with |testing| = true, changes some behavior for the purpose + // of testing. For instance: instances created while testing use the current + // directory for the backing files. + static void SetTestingMode(bool testing); + + private: + static const int kVersion = 1001; + + // Writes |value| to the backing file, creating it if necessary. + void Write(int64 value); + + // Reads the value from the backing file, stores it in |value_|, and returns + // true if the backing file is valid. Returns false otherwise. + bool Read(); + + int64 value_; + int32 version_; + std::string name_; + std::string backing_file_name_; + bool synced_; + static bool testing_; +}; + +} + +#endif // METRICS_PERSISTENT_INTEGER_H_ diff --git a/metrics/persistent_integer_mock.h b/metrics/persistent_integer_mock.h new file mode 100644 index 000000000..46570f966 --- /dev/null +++ b/metrics/persistent_integer_mock.h @@ -0,0 +1,24 @@ +// 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_PERSISTENT_INTEGER_MOCK_H_ +#define METRICS_PERSISTENT_INTEGER_MOCK_H_ + +#include + +#include + +#include "persistent_integer.h" + +namespace chromeos_metrics { + +class PersistentIntegerMock : public PersistentInteger { + public: + PersistentIntegerMock(const std::string& name) : PersistentInteger(name) {} + MOCK_METHOD1(Add, void(int64 count)); +}; + +} // namespace chromeos_metrics + +#endif // METRICS_PERSISTENT_INTEGER_MOCK_H_