diff options
author | bsimonnet@chromium.org <bsimonnet@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-02 00:59:06 +0000 |
---|---|---|
committer | bsimonnet@chromium.org <bsimonnet@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-05-02 00:59:06 +0000 |
commit | 064107ef1030b102ee805d17d1de9b9b24a0f1e9 (patch) | |
tree | cef90be3817689a11946c330f824cb6e8d9acf01 /components/metrics | |
parent | 8e05f0f285002c8468046dabe4956fc90b66c328 (diff) | |
download | chromium_src-064107ef1030b102ee805d17d1de9b9b24a0f1e9.zip chromium_src-064107ef1030b102ee805d17d1de9b9b24a0f1e9.tar.gz chromium_src-064107ef1030b102ee805d17d1de9b9b24a0f1e9.tar.bz2 |
Move part of metrics from chrome/common to components
Chrome OS needs to have access to the metrics aggretion and serialization logic
to build a metric sender service replacing chrome. Protobuf definitions are
moved to components too.
The code remaining will be extrated in components/metrics and
components/variation during a refactoring later this year.
BUG=chromium:360183
TEST=run unittests
TBR=sky@chromium.org, benwells@chromium.org
Review URL: https://codereview.chromium.org/239093004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@267709 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'components/metrics')
-rw-r--r-- | components/metrics/DEPS | 4 | ||||
-rw-r--r-- | components/metrics/README | 22 | ||||
-rw-r--r-- | components/metrics/metrics_log_base.cc | 142 | ||||
-rw-r--r-- | components/metrics/metrics_log_base.h | 110 | ||||
-rw-r--r-- | components/metrics/metrics_log_base_unittest.cc | 125 | ||||
-rw-r--r-- | components/metrics/metrics_log_manager.cc | 208 | ||||
-rw-r--r-- | components/metrics/metrics_log_manager.h | 213 | ||||
-rw-r--r-- | components/metrics/metrics_log_manager_unittest.cc | 427 | ||||
-rw-r--r-- | components/metrics/proto/chrome_user_metrics_extension.proto | 58 | ||||
-rw-r--r-- | components/metrics/proto/histogram_event.proto | 45 | ||||
-rw-r--r-- | components/metrics/proto/omnibox_event.proto | 257 | ||||
-rw-r--r-- | components/metrics/proto/perf_data.proto | 388 | ||||
-rw-r--r-- | components/metrics/proto/profiler_event.proto | 92 | ||||
-rw-r--r-- | components/metrics/proto/system_profile.proto | 509 | ||||
-rw-r--r-- | components/metrics/proto/user_action_event.proto | 21 |
15 files changed, 2621 insertions, 0 deletions
diff --git a/components/metrics/DEPS b/components/metrics/DEPS new file mode 100644 index 0000000..ccaaef4 --- /dev/null +++ b/components/metrics/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "-content", + "-net", +] diff --git a/components/metrics/README b/components/metrics/README new file mode 100644 index 0000000..3461dea --- /dev/null +++ b/components/metrics/README @@ -0,0 +1,22 @@ +This component contains the base classes for the metrics service and only +depends on //base. It is used by ChromeOS as the base for a standalone service +that will upload the metrics when ChromeOS is not installed (headless install). + +This is the first step towards the componentization of metrics that will happen +later this spring. + +A proposed structure for the metrics component is: +//components/metrics/base, + Depends on base only. Contains the protobuf definitions. +//components/metrics/core + Depends on everything iOS depends on +//components/metrics/content + Depends on content + +Ideally, the component would abstract the network stack and have a clean +separation between the metrics upload logic (protbuf generation, retry, etc...), +the chrome part (gathering histogram from all the threads, populating the +log with hardware characteristics, plugin state, etc.). + +It is a plus if the component stays in a single directory as it would be easier +for ChromeOS to pull it :). diff --git a/components/metrics/metrics_log_base.cc b/components/metrics/metrics_log_base.cc new file mode 100644 index 0000000..99e4edb --- /dev/null +++ b/components/metrics/metrics_log_base.cc @@ -0,0 +1,142 @@ +// Copyright 2014 The Chromium 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 "components/metrics/metrics_log_base.h" + +#include "base/metrics/histogram_base.h" +#include "base/metrics/histogram_samples.h" +#include "components/metrics/metrics_hashes.h" +#include "components/metrics/proto/histogram_event.pb.h" +#include "components/metrics/proto/system_profile.pb.h" +#include "components/metrics/proto/user_action_event.pb.h" + +using base::Histogram; +using base::HistogramBase; +using base::HistogramSamples; +using base::SampleCountIterator; +using base::Time; +using base::TimeDelta; +using metrics::HistogramEventProto; +using metrics::SystemProfileProto; +using metrics::UserActionEventProto; + +namespace metrics { +namespace { + +// Any id less than 16 bytes is considered to be a testing id. +bool IsTestingID(const std::string& id) { + return id.size() < 16; +} + +} // namespace + +MetricsLogBase::MetricsLogBase(const std::string& client_id, + int session_id, + LogType log_type, + const std::string& version_string) + : num_events_(0), + locked_(false), + log_type_(log_type) { + DCHECK_NE(NO_LOG, log_type); + if (IsTestingID(client_id)) + uma_proto_.set_client_id(0); + else + uma_proto_.set_client_id(Hash(client_id)); + + uma_proto_.set_session_id(session_id); + uma_proto_.mutable_system_profile()->set_build_timestamp(GetBuildTime()); + uma_proto_.mutable_system_profile()->set_app_version(version_string); +} + +MetricsLogBase::~MetricsLogBase() {} + +// static +uint64 MetricsLogBase::Hash(const std::string& value) { + uint64 hash = metrics::HashMetricName(value); + + // The following log is VERY helpful when folks add some named histogram into + // the code, but forgot to update the descriptive list of histograms. When + // that happens, all we get to see (server side) is a hash of the histogram + // name. We can then use this logging to find out what histogram name was + // being hashed to a given MD5 value by just running the version of Chromium + // in question with --enable-logging. + DVLOG(1) << "Metrics: Hash numeric [" << value << "]=[" << hash << "]"; + + return hash; +} + +// static +int64 MetricsLogBase::GetBuildTime() { + static int64 integral_build_time = 0; + if (!integral_build_time) { + Time time; + const char* kDateTime = __DATE__ " " __TIME__ " GMT"; + bool result = Time::FromString(kDateTime, &time); + DCHECK(result); + integral_build_time = static_cast<int64>(time.ToTimeT()); + } + return integral_build_time; +} + +// static +int64 MetricsLogBase::GetCurrentTime() { + return (base::TimeTicks::Now() - base::TimeTicks()).InSeconds(); +} + +void MetricsLogBase::CloseLog() { + DCHECK(!locked_); + locked_ = true; +} + +void MetricsLogBase::GetEncodedLog(std::string* encoded_log) { + DCHECK(locked_); + uma_proto_.SerializeToString(encoded_log); +} + +void MetricsLogBase::RecordUserAction(const std::string& key) { + DCHECK(!locked_); + + UserActionEventProto* user_action = uma_proto_.add_user_action_event(); + user_action->set_name_hash(Hash(key)); + user_action->set_time(GetCurrentTime()); + + ++num_events_; +} + +void MetricsLogBase::RecordHistogramDelta(const std::string& histogram_name, + const HistogramSamples& snapshot) { + DCHECK(!locked_); + DCHECK_NE(0, snapshot.TotalCount()); + + // We will ignore the MAX_INT/infinite value in the last element of range[]. + + HistogramEventProto* histogram_proto = uma_proto_.add_histogram_event(); + histogram_proto->set_name_hash(Hash(histogram_name)); + histogram_proto->set_sum(snapshot.sum()); + + for (scoped_ptr<SampleCountIterator> it = snapshot.Iterator(); !it->Done(); + it->Next()) { + HistogramBase::Sample min; + HistogramBase::Sample max; + HistogramBase::Count count; + it->Get(&min, &max, &count); + HistogramEventProto::Bucket* bucket = histogram_proto->add_bucket(); + bucket->set_min(min); + bucket->set_max(max); + bucket->set_count(count); + } + + // Omit fields to save space (see rules in histogram_event.proto comments). + for (int i = 0; i < histogram_proto->bucket_size(); ++i) { + HistogramEventProto::Bucket* bucket = histogram_proto->mutable_bucket(i); + if (i + 1 < histogram_proto->bucket_size() && + bucket->max() == histogram_proto->bucket(i + 1).min()) { + bucket->clear_max(); + } else if (bucket->max() == bucket->min() + 1) { + bucket->clear_min(); + } + } +} + +} // namespace metrics diff --git a/components/metrics/metrics_log_base.h b/components/metrics/metrics_log_base.h new file mode 100644 index 0000000..25a6bd0 --- /dev/null +++ b/components/metrics/metrics_log_base.h @@ -0,0 +1,110 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file defines a set of user experience metrics data recorded by +// the MetricsService. This is the unit of data that is sent to the server. + +#ifndef COMPONENTS_METRICS_METRICS_LOG_BASE_H_ +#define COMPONENTS_METRICS_METRICS_LOG_BASE_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/metrics/histogram.h" +#include "base/time/time.h" +#include "components/metrics/proto/chrome_user_metrics_extension.pb.h" + +namespace base { +class HistogramSamples; +} // namespace base + +namespace metrics { + +// This class provides base functionality for logging metrics data. +class MetricsLogBase { + public: + // TODO(asvitkine): Remove the NO_LOG value. + enum LogType { + INITIAL_STABILITY_LOG, // The initial log containing stability stats. + ONGOING_LOG, // Subsequent logs in a session. + NO_LOG, // Placeholder value for when there is no log. + }; + + // Creates a new metrics log of the specified type. + // client_id is the identifier for this profile on this installation + // session_id is an integer that's incremented on each application launch + MetricsLogBase(const std::string& client_id, + int session_id, + LogType log_type, + const std::string& version_string); + virtual ~MetricsLogBase(); + + // Computes the MD5 hash of the given string, and returns the first 8 bytes of + // the hash. + static uint64 Hash(const std::string& value); + + // Get the GMT buildtime for the current binary, expressed in seconds since + // January 1, 1970 GMT. + // The value is used to identify when a new build is run, so that previous + // reliability stats, from other builds, can be abandoned. + static int64 GetBuildTime(); + + // Convenience function to return the current time at a resolution in seconds. + // This wraps base::TimeTicks, and hence provides an abstract time that is + // always incrementing for use in measuring time durations. + static int64 GetCurrentTime(); + + // Records a user-initiated action. + void RecordUserAction(const std::string& key); + + // Record any changes in a given histogram for transmission. + void RecordHistogramDelta(const std::string& histogram_name, + const base::HistogramSamples& snapshot); + + // Stop writing to this record and generate the encoded representation. + // None of the Record* methods can be called after this is called. + void CloseLog(); + + // Fills |encoded_log| with the serialized protobuf representation of the + // record. Must only be called after CloseLog() has been called. + void GetEncodedLog(std::string* encoded_log); + + int num_events() { return num_events_; } + + void set_hardware_class(const std::string& hardware_class) { + uma_proto_.mutable_system_profile()->mutable_hardware()->set_hardware_class( + hardware_class); + } + + LogType log_type() const { return log_type_; } + + protected: + bool locked() const { return locked_; } + + metrics::ChromeUserMetricsExtension* uma_proto() { return &uma_proto_; } + const metrics::ChromeUserMetricsExtension* uma_proto() const { + return &uma_proto_; + } + + // TODO(isherman): Remove this once the XML pipeline is outta here. + int num_events_; // the number of events recorded in this log + + private: + // locked_ is true when record has been packed up for sending, and should + // no longer be written to. It is only used for sanity checking and is + // not a real lock. + bool locked_; + + // The type of the log, i.e. initial or ongoing. + const LogType log_type_; + + // Stores the protocol buffer representation for this log. + metrics::ChromeUserMetricsExtension uma_proto_; + + DISALLOW_COPY_AND_ASSIGN(MetricsLogBase); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_LOG_BASE_H_ diff --git a/components/metrics/metrics_log_base_unittest.cc b/components/metrics/metrics_log_base_unittest.cc new file mode 100644 index 0000000..cc7a173 --- /dev/null +++ b/components/metrics/metrics_log_base_unittest.cc @@ -0,0 +1,125 @@ +// Copyright 2014 The Chromium 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 "components/metrics/metrics_log_base.h" + +#include <string> + +#include "base/base64.h" +#include "base/metrics/bucket_ranges.h" +#include "base/metrics/sample_vector.h" +#include "components/metrics/proto/chrome_user_metrics_extension.pb.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +namespace { + +class TestMetricsLogBase : public MetricsLogBase { + public: + TestMetricsLogBase() + : MetricsLogBase("client_id", 1, MetricsLogBase::ONGOING_LOG, "1.2.3.4") { + } + virtual ~TestMetricsLogBase() {} + + using MetricsLogBase::uma_proto; + + private: + DISALLOW_COPY_AND_ASSIGN(TestMetricsLogBase); +}; + +} // namespace + +TEST(MetricsLogBaseTest, LogType) { + MetricsLogBase log1("id", 0, MetricsLogBase::ONGOING_LOG, "1.2.3"); + EXPECT_EQ(MetricsLogBase::ONGOING_LOG, log1.log_type()); + + MetricsLogBase log2("id", 0, MetricsLogBase::INITIAL_STABILITY_LOG, "1.2.3"); + EXPECT_EQ(MetricsLogBase::INITIAL_STABILITY_LOG, log2.log_type()); +} + +TEST(MetricsLogBaseTest, EmptyRecord) { + MetricsLogBase log("totally bogus client ID", 137, + MetricsLogBase::ONGOING_LOG, "bogus version"); + log.set_hardware_class("sample-class"); + log.CloseLog(); + + std::string encoded; + log.GetEncodedLog(&encoded); + + // A couple of fields are hard to mock, so these will be copied over directly + // for the expected output. + metrics::ChromeUserMetricsExtension parsed; + ASSERT_TRUE(parsed.ParseFromString(encoded)); + + metrics::ChromeUserMetricsExtension expected; + expected.set_client_id(5217101509553811875); // Hashed bogus client ID + expected.set_session_id(137); + expected.mutable_system_profile()->set_build_timestamp( + parsed.system_profile().build_timestamp()); + expected.mutable_system_profile()->set_app_version("bogus version"); + expected.mutable_system_profile()->mutable_hardware()->set_hardware_class( + "sample-class"); + + EXPECT_EQ(expected.SerializeAsString(), encoded); +} + +TEST(MetricsLogBaseTest, HistogramBucketFields) { + // Create buckets: 1-5, 5-7, 7-8, 8-9, 9-10, 10-11, 11-12. + base::BucketRanges ranges(8); + ranges.set_range(0, 1); + ranges.set_range(1, 5); + ranges.set_range(2, 7); + ranges.set_range(3, 8); + ranges.set_range(4, 9); + ranges.set_range(5, 10); + ranges.set_range(6, 11); + ranges.set_range(7, 12); + + base::SampleVector samples(&ranges); + samples.Accumulate(3, 1); // Bucket 1-5. + samples.Accumulate(6, 1); // Bucket 5-7. + samples.Accumulate(8, 1); // Bucket 8-9. (7-8 skipped) + samples.Accumulate(10, 1); // Bucket 10-11. (9-10 skipped) + samples.Accumulate(11, 1); // Bucket 11-12. + + TestMetricsLogBase log; + log.RecordHistogramDelta("Test", samples); + + const metrics::ChromeUserMetricsExtension* uma_proto = log.uma_proto(); + const metrics::HistogramEventProto& histogram_proto = + uma_proto->histogram_event(uma_proto->histogram_event_size() - 1); + + // Buckets with samples: 1-5, 5-7, 8-9, 10-11, 11-12. + // Should become: 1-/, 5-7, /-9, 10-/, /-12. + ASSERT_EQ(5, histogram_proto.bucket_size()); + + // 1-5 becomes 1-/ (max is same as next min). + EXPECT_TRUE(histogram_proto.bucket(0).has_min()); + EXPECT_FALSE(histogram_proto.bucket(0).has_max()); + EXPECT_EQ(1, histogram_proto.bucket(0).min()); + + // 5-7 stays 5-7 (no optimization possible). + EXPECT_TRUE(histogram_proto.bucket(1).has_min()); + EXPECT_TRUE(histogram_proto.bucket(1).has_max()); + EXPECT_EQ(5, histogram_proto.bucket(1).min()); + EXPECT_EQ(7, histogram_proto.bucket(1).max()); + + // 8-9 becomes /-9 (min is same as max - 1). + EXPECT_FALSE(histogram_proto.bucket(2).has_min()); + EXPECT_TRUE(histogram_proto.bucket(2).has_max()); + EXPECT_EQ(9, histogram_proto.bucket(2).max()); + + // 10-11 becomes 10-/ (both optimizations apply, omit max is prioritized). + EXPECT_TRUE(histogram_proto.bucket(3).has_min()); + EXPECT_FALSE(histogram_proto.bucket(3).has_max()); + EXPECT_EQ(10, histogram_proto.bucket(3).min()); + + // 11-12 becomes /-12 (last record must keep max, min is same as max - 1). + EXPECT_FALSE(histogram_proto.bucket(4).has_min()); + EXPECT_TRUE(histogram_proto.bucket(4).has_max()); + EXPECT_EQ(12, histogram_proto.bucket(4).max()); +} + +} // namespace metrics diff --git a/components/metrics/metrics_log_manager.cc b/components/metrics/metrics_log_manager.cc new file mode 100644 index 0000000..e537eef --- /dev/null +++ b/components/metrics/metrics_log_manager.cc @@ -0,0 +1,208 @@ +// Copyright 2014 The Chromium 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 "components/metrics/metrics_log_manager.h" + +#include <algorithm> + +#include "base/metrics/histogram.h" +#include "base/sha1.h" +#include "base/strings/string_util.h" +#include "base/timer/elapsed_timer.h" +#include "components/metrics/metrics_log_base.h" + +namespace metrics { + +MetricsLogManager::SerializedLog::SerializedLog() {} +MetricsLogManager::SerializedLog::~SerializedLog() {} + +bool MetricsLogManager::SerializedLog::IsEmpty() const { + return log_text_.empty(); +} + +void MetricsLogManager::SerializedLog::SwapLogText(std::string* log_text) { + log_text_.swap(*log_text); + if (log_text_.empty()) + log_hash_.clear(); + else + log_hash_ = base::SHA1HashString(log_text_); +} + +void MetricsLogManager::SerializedLog::Clear() { + log_text_.clear(); + log_hash_.clear(); +} + +void MetricsLogManager::SerializedLog::Swap( + MetricsLogManager::SerializedLog* other) { + log_text_.swap(other->log_text_); + log_hash_.swap(other->log_hash_); +} + +MetricsLogManager::MetricsLogManager() + : unsent_logs_loaded_(false), + staged_log_type_(MetricsLogBase::NO_LOG), + max_ongoing_log_store_size_(0), + last_provisional_store_index_(-1), + last_provisional_store_type_(MetricsLogBase::INITIAL_STABILITY_LOG) {} + +MetricsLogManager::~MetricsLogManager() {} + +void MetricsLogManager::BeginLoggingWithLog(MetricsLogBase* log) { + DCHECK(!current_log_.get()); + current_log_.reset(log); +} + +void MetricsLogManager::FinishCurrentLog() { + DCHECK(current_log_.get()); + current_log_->CloseLog(); + SerializedLog compressed_log; + CompressCurrentLog(&compressed_log); + if (!compressed_log.IsEmpty()) + StoreLog(&compressed_log, current_log_->log_type(), NORMAL_STORE); + current_log_.reset(); +} + +void MetricsLogManager::StageNextLogForUpload() { + // Prioritize initial logs for uploading. + std::vector<SerializedLog>* source_list = + unsent_initial_logs_.empty() ? &unsent_ongoing_logs_ + : &unsent_initial_logs_; + LogType source_type = (source_list == &unsent_ongoing_logs_) ? + MetricsLogBase::ONGOING_LOG : MetricsLogBase::INITIAL_STABILITY_LOG; + // CHECK, rather than DCHECK, because swap()ing with an empty list causes + // hard-to-identify crashes much later. + CHECK(!source_list->empty()); + DCHECK(staged_log_.IsEmpty()); + DCHECK_EQ(MetricsLogBase::NO_LOG, staged_log_type_); + staged_log_.Swap(&source_list->back()); + staged_log_type_ = source_type; + source_list->pop_back(); + + // If the staged log was the last provisional store, clear that. + if (last_provisional_store_index_ != -1) { + if (source_type == last_provisional_store_type_ && + static_cast<unsigned int>(last_provisional_store_index_) == + source_list->size()) { + last_provisional_store_index_ = -1; + } + } +} + +bool MetricsLogManager::has_staged_log() const { + return !staged_log_.IsEmpty(); +} + +void MetricsLogManager::DiscardStagedLog() { + staged_log_.Clear(); + staged_log_type_ = MetricsLogBase::NO_LOG; +} + +void MetricsLogManager::DiscardCurrentLog() { + current_log_->CloseLog(); + current_log_.reset(); +} + +void MetricsLogManager::PauseCurrentLog() { + DCHECK(!paused_log_.get()); + paused_log_.reset(current_log_.release()); +} + +void MetricsLogManager::ResumePausedLog() { + DCHECK(!current_log_.get()); + current_log_.reset(paused_log_.release()); +} + +void MetricsLogManager::StoreStagedLogAsUnsent(StoreType store_type) { + DCHECK(has_staged_log()); + + // If compressing the log failed, there's nothing to store. + if (staged_log_.IsEmpty()) + return; + + StoreLog(&staged_log_, staged_log_type_, store_type); + DiscardStagedLog(); +} + +void MetricsLogManager::StoreLog(SerializedLog* log, + LogType log_type, + StoreType store_type) { + DCHECK_NE(MetricsLogBase::NO_LOG, log_type); + std::vector<SerializedLog>* destination_list = + (log_type == MetricsLogBase::INITIAL_STABILITY_LOG) ? + &unsent_initial_logs_ : &unsent_ongoing_logs_; + destination_list->push_back(SerializedLog()); + destination_list->back().Swap(log); + + if (store_type == PROVISIONAL_STORE) { + last_provisional_store_index_ = destination_list->size() - 1; + last_provisional_store_type_ = log_type; + } +} + +void MetricsLogManager::DiscardLastProvisionalStore() { + if (last_provisional_store_index_ == -1) + return; + std::vector<SerializedLog>* source_list = + (last_provisional_store_type_ == MetricsLogBase::ONGOING_LOG) + ? &unsent_ongoing_logs_ + : &unsent_initial_logs_; + DCHECK_LT(static_cast<unsigned int>(last_provisional_store_index_), + source_list->size()); + source_list->erase(source_list->begin() + last_provisional_store_index_); + last_provisional_store_index_ = -1; +} + +void MetricsLogManager::PersistUnsentLogs() { + DCHECK(log_serializer_.get()); + if (!log_serializer_.get()) + return; + DCHECK(unsent_logs_loaded_); + if (!unsent_logs_loaded_) + return; + + base::ElapsedTimer timer; + // Remove any ongoing logs that are over the serialization size limit. + if (max_ongoing_log_store_size_) { + for (std::vector<SerializedLog>::iterator it = unsent_ongoing_logs_.begin(); + it != unsent_ongoing_logs_.end();) { + size_t log_size = it->log_text().length(); + if (log_size > max_ongoing_log_store_size_) { + UMA_HISTOGRAM_COUNTS("UMA.Large Accumulated Log Not Persisted", + static_cast<int>(log_size)); + it = unsent_ongoing_logs_.erase(it); + } else { + ++it; + } + } + } + log_serializer_->SerializeLogs(unsent_initial_logs_, + MetricsLogBase::INITIAL_STABILITY_LOG); + log_serializer_->SerializeLogs(unsent_ongoing_logs_, + MetricsLogBase::ONGOING_LOG); + UMA_HISTOGRAM_TIMES("UMA.StoreLogsTime", timer.Elapsed()); +} + +void MetricsLogManager::LoadPersistedUnsentLogs() { + DCHECK(log_serializer_.get()); + if (!log_serializer_.get()) + return; + + base::ElapsedTimer timer; + log_serializer_->DeserializeLogs(MetricsLogBase::INITIAL_STABILITY_LOG, + &unsent_initial_logs_); + log_serializer_->DeserializeLogs(MetricsLogBase::ONGOING_LOG, + &unsent_ongoing_logs_); + UMA_HISTOGRAM_TIMES("UMA.LoadLogsTime", timer.Elapsed()); + + unsent_logs_loaded_ = true; +} + +void MetricsLogManager::CompressCurrentLog(SerializedLog* compressed_log) { + std::string log_text; + current_log_->GetEncodedLog(&log_text); + compressed_log->SwapLogText(&log_text); +} + +} // namespace metrics diff --git a/components/metrics/metrics_log_manager.h b/components/metrics/metrics_log_manager.h new file mode 100644 index 0000000..006d4ed --- /dev/null +++ b/components/metrics/metrics_log_manager.h @@ -0,0 +1,213 @@ +// Copyright 2014 The Chromium 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 COMPONENTS_METRICS_METRICS_LOG_MANAGER_H_ +#define COMPONENTS_METRICS_METRICS_LOG_MANAGER_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "components/metrics/metrics_log_base.h" + +namespace metrics { + +// Manages all the log objects used by a MetricsService implementation. Keeps +// track of both an in progress log and a log that is staged for uploading as +// text, as well as saving logs to, and loading logs from, persistent storage. +class MetricsLogManager { + public: + typedef MetricsLogBase::LogType LogType; + + MetricsLogManager(); + ~MetricsLogManager(); + + class SerializedLog { + public: + SerializedLog(); + ~SerializedLog(); + + const std::string& log_text() const { return log_text_; } + const std::string& log_hash() const { return log_hash_; } + + // Returns true if the log is empty. + bool IsEmpty() const; + + // Swaps log text with |log_text| and updates the hash. This is more + // performant than a regular setter as it avoids doing a large string copy. + void SwapLogText(std::string* log_text); + + // Clears the log. + void Clear(); + + // Swaps log contents with |other|. + void Swap(SerializedLog* other); + + private: + // Non-human readable log text (serialized proto). + std::string log_text_; + + // Non-human readable SHA1 of |log_text| or empty if |log_text| is empty. + std::string log_hash_; + + // Intentionally omits DISALLOW_COPY_AND_ASSIGN() so that it can be used + // in std::vector<SerializedLog>. + }; + + enum StoreType { + NORMAL_STORE, // A standard store operation. + PROVISIONAL_STORE, // A store operation that can be easily reverted later. + }; + + // Takes ownership of |log| and makes it the current_log. This should only be + // called if there is not a current log. + void BeginLoggingWithLog(MetricsLogBase* log); + + // Returns the in-progress log. + MetricsLogBase* current_log() { return current_log_.get(); } + + // Closes current_log(), compresses it, and stores the compressed log for + // later, leaving current_log() NULL. + void FinishCurrentLog(); + + // Returns true if there are any logs waiting to be uploaded. + bool has_unsent_logs() const { + return !unsent_initial_logs_.empty() || !unsent_ongoing_logs_.empty(); + } + + // Populates staged_log_text() with the next stored log to send. + // Should only be called if has_unsent_logs() is true. + void StageNextLogForUpload(); + + // Returns true if there is a log that needs to be, or is being, uploaded. + bool has_staged_log() const; + + // The text of the staged log, as a serialized protobuf. Empty if there is no + // staged log, or if compression of the staged log failed. + const std::string& staged_log_text() const { return staged_log_.log_text(); } + + // The SHA1 hash (non-human readable) of the staged log or empty if there is + // no staged log. + const std::string& staged_log_hash() const { return staged_log_.log_hash(); } + + // Discards the staged log. + void DiscardStagedLog(); + + // Closes and discards |current_log|. + void DiscardCurrentLog(); + + // Sets current_log to NULL, but saves the current log for future use with + // ResumePausedLog(). Only one log may be paused at a time. + // TODO(stuartmorgan): Pause/resume support is really a workaround for a + // design issue in initial log writing; that should be fixed, and pause/resume + // removed. + void PauseCurrentLog(); + + // Restores the previously paused log (if any) to current_log(). + // This should only be called if there is not a current log. + void ResumePausedLog(); + + // Saves the staged log, then clears staged_log(). + // If |store_type| is PROVISIONAL_STORE, it can be dropped from storage with + // a later call to DiscardLastProvisionalStore (if it hasn't already been + // staged again). + // This is intended to be used when logs are being saved while an upload is in + // progress, in case the upload later succeeds. + // This can only be called if has_staged_log() is true. + void StoreStagedLogAsUnsent(StoreType store_type); + + // Discards the last log stored with StoreStagedLogAsUnsent with |store_type| + // set to PROVISIONAL_STORE, as long as it hasn't already been re-staged. If + // the log is no longer present, this is a no-op. + void DiscardLastProvisionalStore(); + + // Sets the threshold for how large an onging log can be and still be written + // to persistant storage. Ongoing logs larger than this will be discarded + // before persisting. 0 is interpreted as no limit. + void set_max_ongoing_log_store_size(size_t max_size) { + max_ongoing_log_store_size_ = max_size; + } + + // Interface for a utility class to serialize and deserialize logs for + // persistent storage. + class LogSerializer { + public: + virtual ~LogSerializer() {} + + // Serializes |logs| to persistent storage, replacing any previously + // serialized logs of the same type. + virtual void SerializeLogs(const std::vector<SerializedLog>& logs, + LogType log_type) = 0; + + // Populates |logs| with logs of type |log_type| deserialized from + // persistent storage. + virtual void DeserializeLogs(LogType log_type, + std::vector<SerializedLog>* logs) = 0; + }; + + // Sets the serializer to use for persisting and loading logs; takes ownership + // of |serializer|. + void set_log_serializer(LogSerializer* serializer) { + log_serializer_.reset(serializer); + } + + // Saves any unsent logs to persistent storage using the current log + // serializer. Can only be called after set_log_serializer. + void PersistUnsentLogs(); + + // Loads any unsent logs from persistent storage using the current log + // serializer. Can only be called after set_log_serializer. + void LoadPersistedUnsentLogs(); + + private: + // Saves |log| as the given type (or discards it in accordance with + // |max_ongoing_log_store_size_|). + // NOTE: This clears the contents of |log| (to avoid an expensive copy), + // so the log should be discarded after this call. + void StoreLog(SerializedLog* log, LogType log_type, StoreType store_type); + + // Compresses |current_log_| into |compressed_log|. + void CompressCurrentLog(SerializedLog* compressed_log); + + // Tracks whether unsent logs (if any) have been loaded from the serializer. + bool unsent_logs_loaded_; + + // The log that we are still appending to. + scoped_ptr<MetricsLogBase> current_log_; + + // A paused, previously-current log. + scoped_ptr<MetricsLogBase> paused_log_; + + // Helper class to handle serialization/deserialization of logs for persistent + // storage. May be NULL. + scoped_ptr<LogSerializer> log_serializer_; + + // The current staged log, ready for upload to the server. + SerializedLog staged_log_; + LogType staged_log_type_; + + // Logs from a previous session that have not yet been sent. + // Note that the vector has the oldest logs listed first (early in the + // vector), and we'll discard old logs if we have gathered too many logs. + std::vector<SerializedLog> unsent_initial_logs_; + std::vector<SerializedLog> unsent_ongoing_logs_; + + size_t max_ongoing_log_store_size_; + + // The index and type of the last provisional store. If nothing has been + // provisionally stored, or the last provisional store has already been + // re-staged, the index will be -1; + // This is necessary because during an upload there are two logs (staged + // and current) and a client might store them in either order, so it's + // not necessarily the case that the provisional store is the last store. + int last_provisional_store_index_; + LogType last_provisional_store_type_; + + DISALLOW_COPY_AND_ASSIGN(MetricsLogManager); +}; + +} // namespace metrics + +#endif // COMPONENTS_METRICS_METRICS_LOG_MANAGER_H_ diff --git a/components/metrics/metrics_log_manager_unittest.cc b/components/metrics/metrics_log_manager_unittest.cc new file mode 100644 index 0000000..ec0ca38f --- /dev/null +++ b/components/metrics/metrics_log_manager_unittest.cc @@ -0,0 +1,427 @@ +// Copyright 2014 The Chromium 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 "components/metrics/metrics_log_manager.h" + +#include <string> +#include <utility> +#include <vector> + +#include "base/sha1.h" +#include "components/metrics/metrics_log_base.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace metrics { + +namespace { + +// Dummy serializer that just stores logs in memory. +class DummyLogSerializer : public MetricsLogManager::LogSerializer { + public: + virtual void SerializeLogs( + const std::vector<MetricsLogManager::SerializedLog>& logs, + MetricsLogManager::LogType log_type) OVERRIDE { + persisted_logs_[log_type] = logs; + } + + virtual void DeserializeLogs( + MetricsLogManager::LogType log_type, + std::vector<MetricsLogManager::SerializedLog>* logs) OVERRIDE { + ASSERT_NE(static_cast<void*>(NULL), logs); + *logs = persisted_logs_[log_type]; + } + + // Returns the number of logs of the given type. + size_t TypeCount(MetricsLogManager::LogType log_type) { + return persisted_logs_[log_type].size(); + } + + // In-memory "persitent storage". + std::vector<MetricsLogManager::SerializedLog> persisted_logs_[2]; +}; + +} // namespace + +TEST(MetricsLogManagerTest, StandardFlow) { + MetricsLogManager log_manager; + + // Make sure a new manager has a clean slate. + EXPECT_EQ(NULL, log_manager.current_log()); + EXPECT_FALSE(log_manager.has_staged_log()); + EXPECT_FALSE(log_manager.has_unsent_logs()); + + // Check that the normal flow works. + MetricsLogBase* initial_log = + new MetricsLogBase("id", 0, MetricsLogBase::INITIAL_STABILITY_LOG, "v"); + log_manager.BeginLoggingWithLog(initial_log); + EXPECT_EQ(initial_log, log_manager.current_log()); + EXPECT_FALSE(log_manager.has_staged_log()); + + log_manager.FinishCurrentLog(); + EXPECT_EQ(NULL, log_manager.current_log()); + EXPECT_TRUE(log_manager.has_unsent_logs()); + EXPECT_FALSE(log_manager.has_staged_log()); + + MetricsLogBase* second_log = + new MetricsLogBase("id", 0, MetricsLogBase::ONGOING_LOG, "version"); + log_manager.BeginLoggingWithLog(second_log); + EXPECT_EQ(second_log, log_manager.current_log()); + + log_manager.StageNextLogForUpload(); + EXPECT_TRUE(log_manager.has_staged_log()); + EXPECT_FALSE(log_manager.staged_log_text().empty()); + + log_manager.DiscardStagedLog(); + EXPECT_EQ(second_log, log_manager.current_log()); + EXPECT_FALSE(log_manager.has_staged_log()); + EXPECT_FALSE(log_manager.has_unsent_logs()); + EXPECT_TRUE(log_manager.staged_log_text().empty()); + + EXPECT_FALSE(log_manager.has_unsent_logs()); +} + +TEST(MetricsLogManagerTest, AbandonedLog) { + MetricsLogManager log_manager; + + MetricsLogBase* dummy_log = + new MetricsLogBase("id", 0, MetricsLogBase::INITIAL_STABILITY_LOG, "v"); + log_manager.BeginLoggingWithLog(dummy_log); + EXPECT_EQ(dummy_log, log_manager.current_log()); + + log_manager.DiscardCurrentLog(); + EXPECT_EQ(NULL, log_manager.current_log()); + EXPECT_FALSE(log_manager.has_staged_log()); +} + +TEST(MetricsLogManagerTest, InterjectedLog) { + MetricsLogManager log_manager; + + MetricsLogBase* ongoing_log = + new MetricsLogBase("id", 0, MetricsLogBase::ONGOING_LOG, "v"); + MetricsLogBase* temp_log = + new MetricsLogBase("id", 0, MetricsLogBase::INITIAL_STABILITY_LOG, "v"); + + log_manager.BeginLoggingWithLog(ongoing_log); + EXPECT_EQ(ongoing_log, log_manager.current_log()); + + log_manager.PauseCurrentLog(); + EXPECT_EQ(NULL, log_manager.current_log()); + + log_manager.BeginLoggingWithLog(temp_log); + EXPECT_EQ(temp_log, log_manager.current_log()); + log_manager.FinishCurrentLog(); + EXPECT_EQ(NULL, log_manager.current_log()); + + log_manager.ResumePausedLog(); + EXPECT_EQ(ongoing_log, log_manager.current_log()); + + EXPECT_FALSE(log_manager.has_staged_log()); + log_manager.StageNextLogForUpload(); + log_manager.DiscardStagedLog(); + EXPECT_FALSE(log_manager.has_unsent_logs()); +} + +TEST(MetricsLogManagerTest, InterjectedLogPreservesType) { + MetricsLogManager log_manager; + DummyLogSerializer* serializer = new DummyLogSerializer; + log_manager.set_log_serializer(serializer); + log_manager.LoadPersistedUnsentLogs(); + + MetricsLogBase* ongoing_log = + new MetricsLogBase("id", 0, MetricsLogBase::ONGOING_LOG, "v"); + MetricsLogBase* temp_log = + new MetricsLogBase("id", 0, MetricsLogBase::INITIAL_STABILITY_LOG, "v"); + + log_manager.BeginLoggingWithLog(ongoing_log); + log_manager.PauseCurrentLog(); + log_manager.BeginLoggingWithLog(temp_log); + log_manager.FinishCurrentLog(); + log_manager.ResumePausedLog(); + log_manager.StageNextLogForUpload(); + log_manager.DiscardStagedLog(); + + // Verify that the remaining log (which is the original ongoing log) still + // has the right type. + log_manager.FinishCurrentLog(); + log_manager.PersistUnsentLogs(); + EXPECT_EQ(0U, serializer->TypeCount(MetricsLogBase::INITIAL_STABILITY_LOG)); + EXPECT_EQ(1U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); +} + +TEST(MetricsLogManagerTest, StoreAndLoad) { + std::vector<MetricsLogManager::SerializedLog> initial_logs; + std::vector<MetricsLogManager::SerializedLog> ongoing_logs; + + // Set up some in-progress logging in a scoped log manager simulating the + // leadup to quitting, then persist as would be done on quit. + { + MetricsLogManager log_manager; + DummyLogSerializer* serializer = new DummyLogSerializer; + log_manager.set_log_serializer(serializer); + log_manager.LoadPersistedUnsentLogs(); + + // Simulate a log having already been unsent from a previous session. + MetricsLogManager::SerializedLog log; + std::string text = "proto"; + log.SwapLogText(&text); + serializer->persisted_logs_[MetricsLogBase::ONGOING_LOG].push_back(log); + EXPECT_FALSE(log_manager.has_unsent_logs()); + log_manager.LoadPersistedUnsentLogs(); + EXPECT_TRUE(log_manager.has_unsent_logs()); + + MetricsLogBase* log1 = + new MetricsLogBase("id", 0, MetricsLogBase::INITIAL_STABILITY_LOG, "v"); + MetricsLogBase* log2 = + new MetricsLogBase("id", 0, MetricsLogBase::ONGOING_LOG, "v"); + log_manager.BeginLoggingWithLog(log1); + log_manager.FinishCurrentLog(); + log_manager.BeginLoggingWithLog(log2); + log_manager.StageNextLogForUpload(); + log_manager.StoreStagedLogAsUnsent(MetricsLogManager::NORMAL_STORE); + log_manager.FinishCurrentLog(); + + // Nothing should be written out until PersistUnsentLogs is called. + EXPECT_EQ(0U, serializer->TypeCount(MetricsLogBase::INITIAL_STABILITY_LOG)); + EXPECT_EQ(1U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); + log_manager.PersistUnsentLogs(); + EXPECT_EQ(1U, serializer->TypeCount(MetricsLogBase::INITIAL_STABILITY_LOG)); + EXPECT_EQ(2U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); + + // Save the logs to transfer over to a new serializer (since log_manager + // owns |serializer|, so it's about to go away. + initial_logs = + serializer->persisted_logs_[MetricsLogBase::INITIAL_STABILITY_LOG]; + ongoing_logs = serializer->persisted_logs_[MetricsLogBase::ONGOING_LOG]; + } + + // Now simulate the relaunch, ensure that the log manager restores + // everything correctly, and verify that once the are handled they are not + // re-persisted. + { + MetricsLogManager log_manager; + + DummyLogSerializer* serializer = new DummyLogSerializer; + serializer->persisted_logs_[MetricsLogBase::INITIAL_STABILITY_LOG] = + initial_logs; + serializer->persisted_logs_[MetricsLogBase::ONGOING_LOG] = ongoing_logs; + + log_manager.set_log_serializer(serializer); + log_manager.LoadPersistedUnsentLogs(); + EXPECT_TRUE(log_manager.has_unsent_logs()); + + log_manager.StageNextLogForUpload(); + log_manager.DiscardStagedLog(); + // The initial log should be sent first; update the persisted storage to + // verify. + log_manager.PersistUnsentLogs(); + EXPECT_EQ(0U, serializer->TypeCount(MetricsLogBase::INITIAL_STABILITY_LOG)); + EXPECT_EQ(2U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); + + // Handle the first ongoing log. + log_manager.StageNextLogForUpload(); + log_manager.DiscardStagedLog(); + EXPECT_TRUE(log_manager.has_unsent_logs()); + + // Handle the last log. + log_manager.StageNextLogForUpload(); + log_manager.DiscardStagedLog(); + EXPECT_FALSE(log_manager.has_unsent_logs()); + + // Nothing should have changed "on disk" since PersistUnsentLogs hasn't been + // called again. + EXPECT_EQ(2U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); + // Persist, and make sure nothing is left. + log_manager.PersistUnsentLogs(); + EXPECT_EQ(0U, serializer->TypeCount(MetricsLogBase::INITIAL_STABILITY_LOG)); + EXPECT_EQ(0U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); + } +} + +TEST(MetricsLogManagerTest, StoreStagedLogTypes) { + // Ensure that types are preserved when storing staged logs. + { + MetricsLogManager log_manager; + DummyLogSerializer* serializer = new DummyLogSerializer; + log_manager.set_log_serializer(serializer); + log_manager.LoadPersistedUnsentLogs(); + + MetricsLogBase* log = + new MetricsLogBase("id", 0, MetricsLogBase::ONGOING_LOG, "version"); + log_manager.BeginLoggingWithLog(log); + log_manager.FinishCurrentLog(); + log_manager.StageNextLogForUpload(); + log_manager.StoreStagedLogAsUnsent(MetricsLogManager::NORMAL_STORE); + log_manager.PersistUnsentLogs(); + + EXPECT_EQ(0U, serializer->TypeCount(MetricsLogBase::INITIAL_STABILITY_LOG)); + EXPECT_EQ(1U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); + } + + { + MetricsLogManager log_manager; + DummyLogSerializer* serializer = new DummyLogSerializer; + log_manager.set_log_serializer(serializer); + log_manager.LoadPersistedUnsentLogs(); + + MetricsLogBase* log = + new MetricsLogBase("id", 0, MetricsLogBase::INITIAL_STABILITY_LOG, "v"); + log_manager.BeginLoggingWithLog(log); + log_manager.FinishCurrentLog(); + log_manager.StageNextLogForUpload(); + log_manager.StoreStagedLogAsUnsent(MetricsLogManager::NORMAL_STORE); + log_manager.PersistUnsentLogs(); + + EXPECT_EQ(1U, serializer->TypeCount(MetricsLogBase::INITIAL_STABILITY_LOG)); + EXPECT_EQ(0U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); + } +} + +TEST(MetricsLogManagerTest, LargeLogDiscarding) { + MetricsLogManager log_manager; + DummyLogSerializer* serializer = new DummyLogSerializer; + log_manager.set_log_serializer(serializer); + log_manager.LoadPersistedUnsentLogs(); + + // Set the size threshold very low, to verify that it's honored. + log_manager.set_max_ongoing_log_store_size(1); + + MetricsLogBase* log1 = + new MetricsLogBase("id", 0, MetricsLogBase::INITIAL_STABILITY_LOG, "v"); + MetricsLogBase* log2 = + new MetricsLogBase("id", 0, MetricsLogBase::ONGOING_LOG, "v"); + log_manager.BeginLoggingWithLog(log1); + log_manager.FinishCurrentLog(); + log_manager.BeginLoggingWithLog(log2); + log_manager.FinishCurrentLog(); + + // Only the ongoing log should be written out, due to the threshold. + log_manager.PersistUnsentLogs(); + EXPECT_EQ(1U, serializer->TypeCount(MetricsLogBase::INITIAL_STABILITY_LOG)); + EXPECT_EQ(0U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); +} + +TEST(MetricsLogManagerTest, ProvisionalStoreStandardFlow) { + // Ensure that provisional store works, and discards the correct log. + { + MetricsLogManager log_manager; + DummyLogSerializer* serializer = new DummyLogSerializer; + log_manager.set_log_serializer(serializer); + log_manager.LoadPersistedUnsentLogs(); + + MetricsLogBase* log1 = + new MetricsLogBase("id", 0, MetricsLogBase::INITIAL_STABILITY_LOG, "v"); + MetricsLogBase* log2 = + new MetricsLogBase("id", 0, MetricsLogBase::ONGOING_LOG, "v"); + log_manager.BeginLoggingWithLog(log1); + log_manager.FinishCurrentLog(); + log_manager.BeginLoggingWithLog(log2); + log_manager.StageNextLogForUpload(); + log_manager.StoreStagedLogAsUnsent(MetricsLogManager::PROVISIONAL_STORE); + log_manager.FinishCurrentLog(); + log_manager.DiscardLastProvisionalStore(); + + log_manager.PersistUnsentLogs(); + EXPECT_EQ(0U, serializer->TypeCount(MetricsLogBase::INITIAL_STABILITY_LOG)); + EXPECT_EQ(1U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); + } +} + +TEST(MetricsLogManagerTest, ProvisionalStoreNoop) { + // Ensure that trying to drop a sent log is a no-op, even if another log has + // since been staged. + { + MetricsLogManager log_manager; + DummyLogSerializer* serializer = new DummyLogSerializer; + log_manager.set_log_serializer(serializer); + log_manager.LoadPersistedUnsentLogs(); + + MetricsLogBase* log1 = + new MetricsLogBase("id", 0, MetricsLogBase::ONGOING_LOG, "version"); + MetricsLogBase* log2 = + new MetricsLogBase("id", 0, MetricsLogBase::ONGOING_LOG, "version"); + log_manager.BeginLoggingWithLog(log1); + log_manager.FinishCurrentLog(); + log_manager.StageNextLogForUpload(); + log_manager.StoreStagedLogAsUnsent(MetricsLogManager::PROVISIONAL_STORE); + log_manager.StageNextLogForUpload(); + log_manager.DiscardStagedLog(); + log_manager.BeginLoggingWithLog(log2); + log_manager.FinishCurrentLog(); + log_manager.StageNextLogForUpload(); + log_manager.StoreStagedLogAsUnsent(MetricsLogManager::NORMAL_STORE); + log_manager.DiscardLastProvisionalStore(); + + log_manager.PersistUnsentLogs(); + EXPECT_EQ(1U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); + } + + // Ensure that trying to drop more than once is a no-op + { + MetricsLogManager log_manager; + DummyLogSerializer* serializer = new DummyLogSerializer; + log_manager.set_log_serializer(serializer); + log_manager.LoadPersistedUnsentLogs(); + + MetricsLogBase* log1 = + new MetricsLogBase("id", 0, MetricsLogBase::ONGOING_LOG, "version"); + MetricsLogBase* log2 = + new MetricsLogBase("id", 0, MetricsLogBase::ONGOING_LOG, "version"); + log_manager.BeginLoggingWithLog(log1); + log_manager.FinishCurrentLog(); + log_manager.StageNextLogForUpload(); + log_manager.StoreStagedLogAsUnsent(MetricsLogManager::NORMAL_STORE); + log_manager.BeginLoggingWithLog(log2); + log_manager.FinishCurrentLog(); + log_manager.StageNextLogForUpload(); + log_manager.StoreStagedLogAsUnsent(MetricsLogManager::PROVISIONAL_STORE); + log_manager.DiscardLastProvisionalStore(); + log_manager.DiscardLastProvisionalStore(); + + log_manager.PersistUnsentLogs(); + EXPECT_EQ(1U, serializer->TypeCount(MetricsLogBase::ONGOING_LOG)); + } +} + +TEST(MetricsLogManagerTest, SerializedLog) { + const char kFooText[] = "foo"; + const std::string foo_hash = base::SHA1HashString(kFooText); + const char kBarText[] = "bar"; + const std::string bar_hash = base::SHA1HashString(kBarText); + + MetricsLogManager::SerializedLog log; + EXPECT_TRUE(log.log_text().empty()); + EXPECT_TRUE(log.log_hash().empty()); + + std::string foo = kFooText; + log.SwapLogText(&foo); + EXPECT_TRUE(foo.empty()); + EXPECT_FALSE(log.IsEmpty()); + EXPECT_EQ(kFooText, log.log_text()); + EXPECT_EQ(foo_hash, log.log_hash()); + + std::string bar = kBarText; + log.SwapLogText(&bar); + EXPECT_EQ(kFooText, bar); + EXPECT_FALSE(log.IsEmpty()); + EXPECT_EQ(kBarText, log.log_text()); + EXPECT_EQ(bar_hash, log.log_hash()); + + log.Clear(); + EXPECT_TRUE(log.IsEmpty()); + EXPECT_TRUE(log.log_text().empty()); + EXPECT_TRUE(log.log_hash().empty()); + + MetricsLogManager::SerializedLog log2; + foo = kFooText; + log2.SwapLogText(&foo); + log.Swap(&log2); + EXPECT_FALSE(log.IsEmpty()); + EXPECT_EQ(kFooText, log.log_text()); + EXPECT_EQ(foo_hash, log.log_hash()); + EXPECT_TRUE(log2.IsEmpty()); + EXPECT_TRUE(log2.log_text().empty()); + EXPECT_TRUE(log2.log_hash().empty()); +} + +} // namespace metrics diff --git a/components/metrics/proto/chrome_user_metrics_extension.proto b/components/metrics/proto/chrome_user_metrics_extension.proto new file mode 100644 index 0000000..80f3499 --- /dev/null +++ b/components/metrics/proto/chrome_user_metrics_extension.proto @@ -0,0 +1,58 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Protocol buffer for Chrome UMA (User Metrics Analysis). + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package metrics; + +import "histogram_event.proto"; +import "omnibox_event.proto"; +import "profiler_event.proto"; +import "system_profile.proto"; +import "user_action_event.proto"; +import "perf_data.proto"; + +// Next tag: 11 +message ChromeUserMetricsExtension { + // The product (i.e. end user application) for a given UMA log. + enum Product { + // Google Chrome product family. + CHROME = 0; + } + // The product corresponding to this log. Note: The default value is Chrome, + // so Chrome products will not transmit this field. + optional Product product = 10 [default = CHROME]; + + // The id of the client install that generated these events. + // + // For Chrome clients, this id is unique to a top-level (one level above the + // "Default" directory) Chrome user data directory [1], and so is shared among + // all Chrome user profiles contained in this user data directory. + // An id of 0 is reserved for test data (monitoring and internal testing) and + // should normally be ignored in analysis of the data. + // [1] http://www.chromium.org/user-experience/user-data-directory + optional fixed64 client_id = 1; + + // The session id for this user. + // Values such as tab ids are only meaningful within a particular session. + // The client keeps track of the session id and sends it with each event. + // The session id is simply an integer that is incremented each time the user + // relaunches Chrome. + optional int32 session_id = 2; + + // Information about the user's browser and system configuration. + optional SystemProfileProto system_profile = 3; + + // This message will log one or more of the following event types: + repeated UserActionEventProto user_action_event = 4; + repeated OmniboxEventProto omnibox_event = 5; + repeated HistogramEventProto histogram_event = 6; + repeated ProfilerEventProto profiler_event = 7; + + repeated PerfDataProto perf_data = 8; +} diff --git a/components/metrics/proto/histogram_event.proto b/components/metrics/proto/histogram_event.proto new file mode 100644 index 0000000..7cb0e8c --- /dev/null +++ b/components/metrics/proto/histogram_event.proto @@ -0,0 +1,45 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Histogram-collected metrics. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package metrics; + +// Next tag: 4 +message HistogramEventProto { + // The name of the histogram, hashed. + optional fixed64 name_hash = 1; + + // The sum of all the sample values. + // Together with the total count of the sample values, this allows us to + // compute the average value. The count of all sample values is just the sum + // of the counts of all the buckets. + optional int64 sum = 2; + + // The per-bucket data. + message Bucket { + // Each bucket's range is bounded by min <= x < max. + // It is valid to omit one of these two fields in a bucket, but not both. + // If the min field is omitted, its value is assumed to be equal to max - 1. + // If the max field is omitted, its value is assumed to be equal to the next + // bucket's min value (possibly computed per above). The last bucket in a + // histogram should always include the max field. + optional int64 min = 1; + optional int64 max = 2; + + // The bucket's index in the list of buckets, sorted in ascending order. + // This field was intended to provide extra redundancy to detect corrupted + // records, but was never used. As of M31, it is no longer sent by Chrome + // clients to reduce the UMA upload size. + optional int32 bucket_index = 3 [deprecated = true]; + + // The number of entries in this bucket. + optional int64 count = 4; + } + repeated Bucket bucket = 3; +} diff --git a/components/metrics/proto/omnibox_event.proto b/components/metrics/proto/omnibox_event.proto new file mode 100644 index 0000000..6e3f91c --- /dev/null +++ b/components/metrics/proto/omnibox_event.proto @@ -0,0 +1,257 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Stores information about an omnibox interaction. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package metrics; + +// Next tag: 17 +message OmniboxEventProto { + // The timestamp for the event, in seconds since the epoch. + optional int64 time = 1; + + // The id of the originating tab for this omnibox interaction. + // This is the current tab *unless* the user opened the target in a new tab. + // In those cases, this is unset. Tab ids are unique for a given session_id + // (in the containing protocol buffer ChromeUserMetricsExtensionProto). + optional int32 tab_id = 2; + + // The number of characters the user had typed before autocompleting. + optional int32 typed_length = 3; + + // Whether the user deleted text immediately before selecting an omnibox + // suggestion. This is usually the result of pressing backspace or delete. + optional bool just_deleted_text = 11; + + // The number of terms that the user typed in the omnibox. + optional int32 num_typed_terms = 4; + + // The index of the item that the user selected in the omnibox popup list. + // This corresponds the index of the |suggestion| below. + optional int32 selected_index = 5; + + // Whether or not the top match was hidden in the omnibox suggestions + // dropdown. + optional bool is_top_result_hidden_in_dropdown = 14; + + // Whether the omnibox popup is open. It can be closed if, for instance, + // the user clicks in the omnibox and hits return to reload the same page. + // If the popup is closed, the suggestion list will contain only one item + // and selected_index will be 0 (pointing to that single item). Because + // paste-and-search/paste-and-go actions ignore the current content of the + // omnibox dropdown (if it is open) when they happen, we pretend the + // dropdown is closed when logging these. + optional bool is_popup_open = 15; + + // True if this is a paste-and-search or paste-and-go action. (The codebase + // refers to both these types as paste-and-go.) + optional bool is_paste_and_go = 16; + + // The length of the inline autocomplete text in the omnibox. + // The sum |typed_length| + |completed_length| gives the full length of the + // user-visible text in the omnibox. + // This field is only set for inlineable suggestions selected at position 0 + // (|selected_index| = 0) and will be omitted otherwise. + optional int32 completed_length = 6; + + // The amount of time, in milliseconds, since the user first began modifying + // the text in the omnibox. If at some point after modifying the text, the + // user reverts the modifications (thus seeing the current web page's URL + // again), then writes in the omnibox again, this elapsed time should start + // from the time of the second series of modification. + optional int64 typing_duration_ms = 7; + + // The amount of time, in milliseconds, since the last time the default + // (inline) match changed. This may be longer than the time since the + // last keystroke. (The last keystroke may not have changed the default + // match.) It may also be shorter than the time since the last keystroke + // because the default match might have come from an asynchronous + // provider. Regardless, it should always be less than or equal to + // the field |typing_duration_ms|. + optional int64 duration_since_last_default_match_update_ms = 13; + + // The type of page currently displayed when the user used the omnibox. + enum PageClassification { + // An invalid URL; shouldn't happen. + INVALID_SPEC = 0; + + // chrome://newtab/. This can be either the built-in version or a + // replacement new tab page from an extension. Note that when Instant + // Extended is enabled, the new tab page will be reported as either + // INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS or + // INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS below, + // unless an extension is replacing the new tab page, in which case + // it will still be reported as NTP. + NTP = 1; + + // about:blank. + BLANK = 2; + + // The user's home page. Note that if the home page is set to any + // of the new tab page versions or to about:blank, then we'll + // classify the page into those categories, not HOME_PAGE. + HOME_PAGE = 3; + + // The catch-all entry of everything not included somewhere else + // on this list. + OTHER = 4; + + // The instant new tab page enum value was deprecated on August 2, 2013. + OBSOLETE_INSTANT_NTP = 5; + + // The user is on a search result page that's doing search term + // replacement, meaning the search terms should've appeared in the omnibox + // before the user started editing it, not the URL of the page. + SEARCH_RESULT_PAGE_DOING_SEARCH_TERM_REPLACEMENT = 6; + + // The new tab page in which this omnibox interaction first started + // with the user having focus in the omnibox. + INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS = 7; + + // The new tab page in which this omnibox interaction first started + // with the user having focus in the fakebox. + INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS = 8; + + // The user is on a search result page that's not doing search term + // replacement, meaning the URL of the page should've appeared in the + // omnibox before the user started editing it, not the search terms. + SEARCH_RESULT_PAGE_NO_SEARCH_TERM_REPLACEMENT = 9; + + // When adding new classifications, please consider adding them in + // chrome/browser/resources/omnibox/omnibox.html + // so that these new options are displayed on about:omnibox. + } + optional PageClassification current_page_classification = 10; + + // What kind of input the user provided. + enum InputType { + INVALID = 0; // Empty input (should not reach here) + UNKNOWN = 1; // Valid input whose type cannot be determined + REQUESTED_URL = 2; // DEPRECATED. Input autodetected as UNKNOWN, which the + // user wants to treat as an URL by specifying a + // desired_tld + URL = 3; // Input autodetected as a URL + QUERY = 4; // Input autodetected as a query + FORCED_QUERY = 5; // Input forced to be a query by an initial '?' + } + optional InputType input_type = 8; + + // An enum used in multiple places below. + enum ProviderType { + UNKNOWN_PROVIDER = 0; // Unknown provider (should not reach here) + HISTORY_URL = 1; // URLs in history, or user-typed URLs + HISTORY_CONTENTS = 2; // Matches for page contents of pages in history + HISTORY_QUICK = 3; // Matches for recently or frequently visited pages + // in history + SEARCH = 4; // Search suggestions for the default search engine + KEYWORD = 5; // Keyword-triggered searches + BUILTIN = 6; // Built-in URLs, such as chrome://version + SHORTCUTS = 7; // Recently selected omnibox suggestions + EXTENSION_APPS = 8; // Custom suggestions from extensions and/or apps + CONTACT = 9; // DEPRECATED. The user's contacts + BOOKMARK = 10; // The user's bookmarks + ZERO_SUGGEST = 11; // Suggestions based on the current page + // This enum value is currently only used by Android GSA. It represents + // a suggestion from the phone. + ON_DEVICE = 12; + } + + // The result set displayed on the completion popup + // Next tag: 6 + message Suggestion { + // Where does this result come from? + optional ProviderType provider = 1; + + // What kind of result this is. + // This corresponds to the AutocompleteMatch::Type enumeration in + // chrome/browser/autocomplete/autocomplete_match.h (except for Android + // GSA result types). + enum ResultType { + UNKNOWN_RESULT_TYPE = 0; // Unknown type (should not reach here) + URL_WHAT_YOU_TYPED = 1; // The input as a URL + HISTORY_URL = 2; // A past page whose URL contains the input + HISTORY_TITLE = 3; // A past page whose title contains the input + HISTORY_BODY = 4; // A past page whose body contains the input + HISTORY_KEYWORD = 5; // A past page whose keyword contains the + // input + NAVSUGGEST = 6; // A suggested URL + SEARCH_WHAT_YOU_TYPED = 7; // The input as a search query (with the + // default engine) + SEARCH_HISTORY = 8; // A past search (with the default engine) + // containing the input + SEARCH_SUGGEST = 9; // A suggested search (with the default + // engine) query that doesn't fall into one of + // the more specific suggestion categories + // below. + SEARCH_OTHER_ENGINE = 10; // A search with a non-default engine + EXTENSION_APP = 11; // An Extension App with a title/url that + // contains the input + CONTACT = 12; // DEPRECATED. One of the user's contacts + BOOKMARK_TITLE = 13; // A bookmark whose title contains the input. + SEARCH_SUGGEST_ENTITY = 14; // A suggested search for an entity. + SEARCH_SUGGEST_INFINITE = 15; // A suggested search to complete the tail + // of the query. + SEARCH_SUGGEST_PERSONALIZED = 16; // A personalized suggested search. + SEARCH_SUGGEST_PROFILE = 17; // A personalized suggested search for a + // Google+ profile. + APP_RESULT = 18; // Result from an installed app + // (eg: a gmail email). + // Used by Android GSA for on-device + // suggestion logging. + APP = 19; // An app result (eg: the gmail app). + // Used by Android GSA for on-device + // suggestion logging. + } + optional ResultType result_type = 2; + + // The relevance score for this suggestion. + optional int32 relevance = 3; + + // How many times this result was typed in / selected from the omnibox. + // Only set for some providers and result_types. At the time of + // writing this comment, it is only set for HistoryURL and + // HistoryQuickProvider matches. + optional int32 typed_count = 5; + + // Whether this item is starred (bookmarked) or not. + optional bool is_starred = 4; + } + repeated Suggestion suggestion = 9; + + // A data structure that holds per-provider information, general information + // not associated with a particular result. + // Next tag: 5 + message ProviderInfo { + // Which provider generated this ProviderInfo entry. + optional ProviderType provider = 1; + + // The provider's done() value, i.e., whether it's completed processing + // the query. Providers which don't do any asynchronous processing + // will always be done. + optional bool provider_done = 2; + + // The set of field trials that have triggered in the most recent query, + // possibly affecting the shown suggestions. Each element is a hash + // of the corresponding field trial name. + // See chrome/browser/autocomplete/search_provider.cc for a specific usage + // example. + repeated fixed32 field_trial_triggered = 3; + + // Same as above except that the set of field trials is a union of all field + // trials that have triggered within the current omnibox session including + // the most recent query. + // See AutocompleteController::ResetSession() for more details on the + // definition of a session. + // See chrome/browser/autocomplete/search_provider.cc for a specific usage + // example. + repeated fixed32 field_trial_triggered_in_session = 4; + } + // A list of diagnostic information about each provider. Providers + // will appear at most once in this list. + repeated ProviderInfo provider_info = 12; +} diff --git a/components/metrics/proto/perf_data.proto b/components/metrics/proto/perf_data.proto new file mode 100644 index 0000000..6278649 --- /dev/null +++ b/components/metrics/proto/perf_data.proto @@ -0,0 +1,388 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package metrics; + +// Stores information from a perf session generated via running: +// "perf record" +// +// See $kernel/tools/perf/design.txt for more details. + +// Please do not modify this protobuf directly, except to mirror the upstream +// version found here: +// https://chromium.googlesource.com/chromiumos/platform/chromiumos-wide-profiling/+/master/perf_data.proto +// with some fields omitted for privacy reasons. Because it is a read-only copy +// of the upstream protobuf, "Next tag:" comments are also absent. + +message PerfDataProto { + + // Perf event attribute. Stores the event description. + // This data structure is defined in the linux kernel: + // $kernel/tools/perf/util/event.h. + message PerfEventAttr { + // Type of the event. Type is an enumeration and can be: + // IP: an instruction-pointer was stored in the event. + // MMAP: a DLL was loaded. + // FORK: a process was forked. + // etc. + optional uint32 type = 1; + + // Size of the event data in bytes. + optional uint32 size = 2; + + // The config stores the CPU-specific counter information. + optional uint64 config = 3; + + // Sample period of the event. Indicates how often the event is + // triggered in terms of # of events. After |sample_period| events, an event + // will be recorded and stored. + optional uint64 sample_period = 4; + + // Sample frequency of the event. Indicates how often the event is + // triggered in terms of # per second. The kernel will try to record + // |sample_freq| events per second. + optional uint64 sample_freq = 5; + + // Sample type is a bitfield that records attributes of the sample. Example, + // whether an entire callchain was recorded, etc. + optional uint64 sample_type = 6; + + // Bitfield that indicates whether reads on the counter will return the + // total time enabled and total time running. + optional uint64 read_format = 7; + + // Indicates whether the counter starts off disabled. + optional bool disabled = 8; + + // Indicates whether child processes inherit the counter. + optional bool inherit = 9; + + // Indicates whether the counter is pinned to a particular CPU. + optional bool pinned = 10; + + // Indicates whether this counter's group has exclusive access to the CPU's + // counters. + optional bool exclusive = 11; + + // The following bits restrict events to be counted when the CPU is in user, + // kernel, hypervisor or idle modes. + optional bool exclude_user = 12; + optional bool exclude_kernel = 13; + optional bool exclude_hv = 14; + optional bool exclude_idle = 15; + + // Indicates whether mmap events should be recorded. + optional bool mmap = 16; + + // Indicates whether process comm information should be recorded upon + // process creation. + optional bool comm = 17; + + // Indicates that we are in frequency mode, not period mode. + optional bool freq = 18; + + // Indicates whether we have per-task counts. + optional bool inherit_stat = 19; + + // Indicates whether we enable perf events after an exec() function call. + optional bool enable_on_exec = 20; + + // Indicates whether we trace fork/exit. + optional bool task = 21; + + // Indicates whether we are using a watermark to wake up. + optional bool watermark = 22; + + // CPUs often "skid" when recording events. That means the instruction + // pointer may not be the same as the one that caused the counter overflow. + // Indicates the capabilities of the CPU in terms of recording precise + // instruction pointer. + optional uint32 precise_ip = 23; + + // Indicates whether we have non-exec mmap data. + optional bool mmap_data = 24; + + // If set, all the event types will have the same sample_type. + optional bool sample_id_all = 25; + + // Indicates whether we are counting events from the host (when running a + // VM). + optional bool exclude_host = 26; + + // Exclude events that happen on a guest OS. + optional bool exclude_guest = 27; + + // Contains the number of events after which we wake up. + optional uint32 wakeup_events = 28; + + // Contains the number of bytes after which we wake up. + optional uint32 wakeup_watermark = 29; + + // Information about the type of the breakpoint. + optional uint32 bp_type = 30; + + // Contains the breakpoint address. + optional uint64 bp_addr = 31; + + // This is an extension of config (see above). + optional uint64 config1 = 32; + + // The length of the breakpoint data in bytes. + optional uint64 bp_len = 33; + + // This is an extension of config (see above). + optional uint64 config2 = 34; + + // Contains the type of branch, example: user, kernel, call, return, etc. + optional uint64 branch_sample_type = 35; + } + + // Describes a perf.data file attribute. + message PerfFileAttr { + optional PerfEventAttr attr = 1; + + // List of perf file attribute ids. Each id describes an event. + repeated uint64 ids = 2; + } + + // This message contains information about a perf sample itself, as opposed to + // a perf event captured by a sample. + message SampleInfo { + // Process ID / thread ID from which this sample was taken. + optional uint32 pid = 1; + optional uint32 tid = 2; + + // Time this sample was taken (NOT the same as an event time). + // It is the number of nanoseconds since bootup. + optional uint64 sample_time_ns = 3; + + // The ID of the sample's event type (cycles, instructions, etc). + // The event type IDs are defined in PerfFileAttr. + optional uint64 id = 4; + + // The CPU on which this sample was taken. + optional uint32 cpu = 5; + } + + message CommEvent { + // Process id. + optional uint32 pid = 1; + + // Thread id. + optional uint32 tid = 2; + + // Comm string's md5 prefix. + // The comm string was field 3 and has been intentionally left out. + optional uint64 comm_md5_prefix = 4; + + // Time the sample was taken. + // Deprecated, use |sample_info| instead. + optional uint64 sample_time = 5 [deprecated=true]; + + // Info about the perf sample containing this event. + optional SampleInfo sample_info = 6; + } + + message MMapEvent { + // Process id. + optional uint32 pid = 1; + + // Thread id. + optional uint32 tid = 2; + + // Start address. + optional uint64 start = 3; + + // Length. + optional uint64 len = 4; + + // PG Offset. + optional uint64 pgoff = 5; + + // Filename's md5 prefix. + // The filename was field 6 and has been intentionally left out. + optional uint64 filename_md5_prefix = 7; + + // Info about the perf sample containing this event. + optional SampleInfo sample_info = 8; + } + + message BranchStackEntry { + // Branch source address. + optional uint64 from_ip = 1; + + // Branch destination address. + optional uint64 to_ip = 2; + + // Indicates a mispredicted branch. + optional bool mispredicted = 3; + } + + message SampleEvent { + // Instruction pointer. + optional uint64 ip = 1; + + // Process id. + optional uint32 pid = 2; + + // Thread id. + optional uint32 tid = 3; + + // The time after boot when the sample was recorded, in nanoseconds. + optional uint64 sample_time_ns = 4; + + // The address of the sample. + optional uint64 addr = 5; + + // The id of the sample. + optional uint64 id = 6; + + // The stream id of the sample. + optional uint64 stream_id = 7; + + // The period of the sample. + optional uint64 period = 8; + + // The CPU where the event was recorded. + optional uint32 cpu = 9; + + // The raw size of the event in bytes. + optional uint32 raw_size = 10; + + // Sample callchain info. + repeated uint64 callchain = 11; + + // Branch stack info. + repeated BranchStackEntry branch_stack = 12; + } + + // ForkEvent is used for both FORK and EXIT events, which have the same data + // format. We don't want to call this "ForkOrExitEvent", in case a separate + // exit event is introduced in the future. + message ForkEvent { + // Forked process ID. + optional uint32 pid = 1; + + // Parent process ID. + optional uint32 ppid = 2; + + // Forked process thread ID. + optional uint32 tid = 3; + + // Parent process thread ID. + optional uint32 ptid = 4; + + // Time of fork event in nanoseconds since bootup. + optional uint64 fork_time_ns = 5; + + // Info about the perf sample containing this event. + optional SampleInfo sample_info = 11; + } + + message EventHeader { + // Type of event. + optional uint32 type = 1; + optional uint32 misc = 2; + // Size of event. + optional uint32 size = 3; + } + + message PerfEvent { + optional EventHeader header = 1; + + optional MMapEvent mmap_event = 2; + optional SampleEvent sample_event = 3; + optional CommEvent comm_event = 4; + optional ForkEvent fork_event = 5; + } + + message PerfEventStats { + // Total number of events read from perf data. + optional uint32 num_events_read = 1; + + // Total number of various types of events. + optional uint32 num_sample_events = 2; + optional uint32 num_mmap_events = 3; + optional uint32 num_fork_events = 4; + optional uint32 num_exit_events = 5; + + // Number of sample events that were successfully mapped by the address + // mapper, a quipper module that is used to obscure addresses and convert + // them to DSO name + offset. Sometimes it fails to process sample events. + // This field allows us to track the success rate of the address mapper. + optional uint32 num_sample_events_mapped = 6; + + // Whether address remapping was enabled. + optional bool did_remap = 7; + } + + message PerfBuildID { + // Misc field in perf_event_header. + // Indicates whether the file is mapped in kernel mode or user mode. + optional uint32 misc = 1; + + // Process ID. + optional uint32 pid = 2; + + // Build id. Should always contain kBuildIDArraySize bytes of data. + // perf_reader.h in Chrome OS defines kBuildIDArraySize = 20. + optional bytes build_hash = 3; + + // Filename Md5sum prefix. + // The filename was field 4 and has been intentionally left out. + optional uint64 filename_md5_prefix = 5; + } + + repeated PerfFileAttr file_attrs = 1; + repeated PerfEvent events = 2; + + // Time when quipper generated this perf data / protobuf, given as seconds + // since the epoch. + optional uint64 timestamp_sec = 3; + + // Records some stats about the serialized perf events. + optional PerfEventStats stats = 4; + + // Not added from original: repeated uint64 metadata_mask = 5; + + // Build ID metadata. + repeated PerfBuildID build_ids = 7; + + // Not added from original: repeated PerfUint32Metadata uint32_metadata = 8; + // Not added from original: repeated PerfUint64Metadata uint64_metadata = 9; + // Not added from original: + // optional PerfCPUTopologyMetadata cpu_topology = 11; + // Not added from original: + // repeated PerfNodeTopologyMetadata numa_topology = 12; + + message StringMetadata { + message StringAndMd5sumPrefix { + // The string value was field 1 and has been intentionally left out. + + // The string value's md5sum prefix. + optional uint64 value_md5_prefix = 2; + } + + // Not added from original: optional StringAndMd5sumPrefix hostname = 1; + // Not added from original: + // optional StringAndMd5sumPrefix kernel_version =2; + // Not added from original: optional StringAndMd5sumPrefix perf_version = 3; + // Not added from original: optional StringAndMd5sumPrefix architecture = 4; + // Not added from original: + // optional StringAndMd5sumPrefix cpu_description = 5; + // Not added from original: optional StringAndMd5sumPrefix cpu_id = 6; + // Not added from original: + // repeated StringAndMd5sumPrefix perf_command_line_token = 7; + + // The command line stored as a single string. + optional StringAndMd5sumPrefix perf_command_line_whole = 8; + } + + // All the string metadata from the perf data file. + optional StringMetadata string_metadata = 13; +} diff --git a/components/metrics/proto/profiler_event.proto b/components/metrics/proto/profiler_event.proto new file mode 100644 index 0000000..1f18b2a --- /dev/null +++ b/components/metrics/proto/profiler_event.proto @@ -0,0 +1,92 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Performance metrics collected via Chrome's built-in profiler. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package metrics; + + +// Next tag: 4 +message ProfilerEventProto { + // The type of this profile. + enum ProfileType { + UNKNOWN_PROFILE = 0; // Unknown type (should not reach here). + STARTUP_PROFILE = 1; // Startup profile, logged approximately 60 seconds + // after launch. + } + optional ProfileType profile_type = 1; + + // The source based upon which "time" measurements are made. + // We currently only measure wall clock time; but we are exploring other + // measurement sources as well, such as CPU time or TCMalloc statistics. + enum TimeSource { + UNKNOWN_TIME_SOURCE = 0; // Unknown source (should not reach here). + WALL_CLOCK_TIME = 1; // Total time elapsed between the start and end of + // the task's execution. + } + optional TimeSource time_source = 2; + + // Data for a single tracked object (typically, a Task). + message TrackedObject { + // The name of the thread from which this task was posted, hashed. + optional fixed64 birth_thread_name_hash = 1; + + // The name of the thread on which this task was executed, hashed. + optional fixed64 exec_thread_name_hash = 2; + + // The source file name from which this task was posted, hashed. + optional fixed64 source_file_name_hash = 3; + + // Function name from which this task was posted, hashed. + optional fixed64 source_function_name_hash = 4; + + // The line number within the source file from which this task was posted. + optional int32 source_line_number = 5; + + // The number of times this task was executed. + optional int32 exec_count = 6; + + // The total execution time for instances this task. + optional int32 exec_time_total = 7; + + // The execution time for a uniformly randomly sampled instance of this + // task. + optional int32 exec_time_sampled = 8; + + // The total time instances this task spent waiting (e.g. in a message loop) + // before they were run. + optional int32 queue_time_total = 9; + + // The time that a uniformly randomly sampled instance of this task spent + // waiting (e.g. in a message loop) before it was run. + optional int32 queue_time_sampled = 10; + + // The type of process within which this task was executed. + enum ProcessType { + UNKNOWN = 0; // Should not reach here + BROWSER = 1; + RENDERER = 2; + PLUGIN = 3; + WORKER = 4; + NACL_LOADER = 5; + UTILITY = 6; + PROFILE_IMPORT = 7; + ZYGOTE = 8; + SANDBOX_HELPER = 9; + NACL_BROKER = 10; + GPU = 11; + PPAPI_PLUGIN = 12; + PPAPI_BROKER = 13; + } + optional ProcessType process_type = 11; + + // The local PID for the process within which this task was executed. + optional uint32 process_id = 12; + } + repeated TrackedObject tracked_object = 3; +} diff --git a/components/metrics/proto/system_profile.proto b/components/metrics/proto/system_profile.proto new file mode 100644 index 0000000..98a0f5d --- /dev/null +++ b/components/metrics/proto/system_profile.proto @@ -0,0 +1,509 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Stores information about the user's brower and system configuration. +// The system configuration fields are recorded once per client session. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package metrics; + +// Next tag: 19 +message SystemProfileProto { + // The time when the client was compiled/linked, in seconds since the epoch. + optional int64 build_timestamp = 1; + + // A version number string for the application. + // Most commonly this is the browser version number found in a user agent + // string, and is typically a 4-tuple of numbers separated by periods. In + // cases where the user agent version might be ambiguous (example: Linux 64- + // bit build, rather than 32-bit build, or a Windows version used in some + // special context, such as ChromeFrame running in IE), then this may include + // some additional postfix to provide clarification not available in the UA + // string. + // + // An example of a browser version 4-tuple is "5.0.322.0". Currently used + // postfixes are: + // + // "-64": a 64-bit build + // "-F": Chrome is running under control of ChromeFrame + // "-devel": this is not an official build of Chrome + // + // A full version number string could look similar to: + // "5.0.322.0-F-devel". + // + // This value, when available, is more trustworthy than the UA string + // associated with the request; and including the postfix, may be more + // specific. + optional string app_version = 2; + + // The brand code or distribution tag assigned to a partner, if available. + // Brand codes are only available on Windows. Not every Windows install + // though will have a brand code. + optional string brand_code = 12; + + // The possible channels for an installation, from least to most stable. + enum Channel { + CHANNEL_UNKNOWN = 0; // Unknown channel -- perhaps an unofficial build? + CHANNEL_CANARY = 1; + CHANNEL_DEV = 2; + CHANNEL_BETA = 3; + CHANNEL_STABLE = 4; + } + optional Channel channel = 10; + + // The date the user enabled UMA, in seconds since the epoch. + // If the user has toggled the UMA enabled state multiple times, this will + // be the most recent date on which UMA was enabled. + // For privacy, this is rounded to the nearest hour. + optional int64 uma_enabled_date = 3; + + // The time when the client was installed, in seconds since the epoch. + // For privacy, this is rounded to the nearest hour. + optional int64 install_date = 16; + + // The user's selected application locale, i.e. the user interface language. + // The locale includes a language code and, possibly, also a country code, + // e.g. "en-US". + optional string application_locale = 4; + + // Information on the user's operating system. + message OS { + // The user's operating system. + optional string name = 1; + + // The version of the OS. The meaning of this field is OS-dependent. + optional string version = 2; + + // The fingerprint of the build. This field is used only on Android. + optional string fingerprint = 3; + } + optional OS os = 5; + + // Next tag for Hardware: 16 + // Information on the user's hardware. + message Hardware { + // The CPU architecture (x86, PowerPC, x86_64, ...) + optional string cpu_architecture = 1; + + // The amount of RAM present on the system, in megabytes. + optional int64 system_ram_mb = 2; + + // The base memory address that chrome.dll was loaded at. + // (Logged only on Windows.) + optional int64 dll_base = 3; + + // The Chrome OS device hardware class ID is a unique string associated with + // each Chrome OS device product revision generally assigned at hardware + // qualification time. The hardware class effectively identifies the + // configured system components such as CPU, WiFi adapter, etc. + // + // An example of such a hardware class is "IEC MARIO PONY 6101". An + // internal database associates this hardware class with the qualified + // device specifications including OEM information, schematics, hardware + // qualification reports, test device tags, etc. + optional string hardware_class = 4; + + // The number of physical screens. + optional int32 screen_count = 5; + + // The screen dimensions of the primary screen, in pixels. + optional int32 primary_screen_width = 6; + optional int32 primary_screen_height = 7; + + // The device scale factor of the primary screen. + optional float primary_screen_scale_factor = 12; + + // Max DPI for any attached screen. (Windows only) + optional float max_dpi_x = 9; + optional float max_dpi_y = 10; + + // Information on the CPU obtained by CPUID. + message CPU { + // A 12 character string naming the vendor, e.g. "GeniuneIntel". + optional string vendor_name = 1; + + // The signature reported by CPUID (from EAX). + optional uint32 signature = 2; + } + optional CPU cpu = 13; + + // Information on the GPU + message Graphics { + // The GPU manufacturer's vendor id. + optional uint32 vendor_id = 1; + + // The GPU manufacturer's device id for the chip set. + optional uint32 device_id = 2; + + // The driver version on the GPU. + optional string driver_version = 3; + + // The driver date on the GPU. + optional string driver_date = 4; + + // The GPU performance statistics. + // See http://src.chromium.org/viewvc/chrome/trunk/src/content/public/common/gpu_performance_stats.h?view=markup + // for details. Currently logged only on Windows. + message PerformanceStatistics { + optional float graphics_score = 1; + optional float gaming_score = 2; + optional float overall_score = 3; + } + optional PerformanceStatistics performance_statistics = 5; + + // The GL_VENDOR string. An example of a gl_vendor string is + // "Imagination Technologies". "" if we are not using OpenGL. + optional string gl_vendor = 6; + + // The GL_RENDERER string. An example of a gl_renderer string is + // "PowerVR SGX 540". "" if we are not using OpenGL. + optional string gl_renderer = 7; + } + optional Graphics gpu = 8; + + // Information about Bluetooth devices paired with the system. + message Bluetooth { + // Whether Bluetooth is present on this system. + optional bool is_present = 1; + + // Whether Bluetooth is enabled on this system. + optional bool is_enabled = 2; + + // Describes a paired device. + message PairedDevice { + // Assigned class of the device. This is a bitfield according to the + // Bluetooth specification available at the following URL: + // https://www.bluetooth.org/en-us/specification/assigned-numbers-overview/baseband + optional uint32 bluetooth_class = 1; + + // Decoded device type. + enum Type { + DEVICE_UNKNOWN = 0; + DEVICE_COMPUTER = 1; + DEVICE_PHONE = 2; + DEVICE_MODEM = 3; + DEVICE_AUDIO = 4; + DEVICE_CAR_AUDIO = 5; + DEVICE_VIDEO = 6; + DEVICE_PERIPHERAL = 7; + DEVICE_JOYSTICK = 8; + DEVICE_GAMEPAD = 9; + DEVICE_KEYBOARD = 10; + DEVICE_MOUSE = 11; + DEVICE_TABLET = 12; + DEVICE_KEYBOARD_MOUSE_COMBO = 13; + } + optional Type type = 2; + + // Vendor prefix of the Bluetooth address, these are OUI registered by + // the IEEE and are encoded with the first byte in bits 16-23, the + // second byte in bits 8-15 and the third byte in bits 0-7. + // + // ie. Google's OUI (00:1A:11) is encoded as 0x00001A11 + optional uint32 vendor_prefix = 4; + + // The Vendor ID of a device, returned in vendor_id below, can be + // either allocated by the Bluetooth SIG or USB IF, providing two + // completely overlapping namespaces for identifiers. + // + // This field should be read along with vendor_id to correctly + // identify the vendor. For example Google is identified by either + // vendor_id_source = VENDOR_ID_BLUETOOTH, vendor_id = 0x00E0 or + // vendor_id_source = VENDOR_ID_USB, vendor_id = 0x18D1. + // + // If the device does not support the Device ID specification the + // unknown value will be set. + enum VendorIDSource { + VENDOR_ID_UNKNOWN = 0; + VENDOR_ID_BLUETOOTH = 1; + VENDOR_ID_USB = 2; + } + optional VendorIDSource vendor_id_source = 8; + + // Vendor ID of the device, where available. + optional uint32 vendor_id = 5; + + // Product ID of the device, where available. + optional uint32 product_id = 6; + + // Device ID of the device, generally the release or version number in + // BCD format, where available. + optional uint32 device_id = 7; + } + repeated PairedDevice paired_device = 3; + } + optional Bluetooth bluetooth = 11; + + // Whether the internal display produces touch events. Omitted if unknown. + // Logged on ChromeOS only. + optional bool internal_display_supports_touch = 14; + + // Vendor ids and product ids of external touchscreens. + message TouchScreen { + // Touch screen vendor id. + optional uint32 vendor_id = 1; + // Touch screen product id. + optional uint32 product_id = 2; + } + // Lists vendor and product ids of external touchscreens. + // Logged on ChromeOS only. + repeated TouchScreen external_touchscreen = 15; + } + optional Hardware hardware = 6; + + // Information about the network connection. + message Network { + // Set to true if connection_type changed during the lifetime of the log. + optional bool connection_type_is_ambiguous = 1; + + // See net::NetworkChangeNotifier::ConnectionType. + enum ConnectionType { + CONNECTION_UNKNOWN = 0; + CONNECTION_ETHERNET = 1; + CONNECTION_WIFI = 2; + CONNECTION_2G = 3; + CONNECTION_3G = 4; + CONNECTION_4G = 5; + } + // The connection type according to NetworkChangeNotifier. + optional ConnectionType connection_type = 2; + + // Set to true if wifi_phy_layer_protocol changed during the lifetime of the log. + optional bool wifi_phy_layer_protocol_is_ambiguous = 3; + + // See net::WifiPHYLayerProtocol. + enum WifiPHYLayerProtocol { + WIFI_PHY_LAYER_PROTOCOL_NONE = 0; + WIFI_PHY_LAYER_PROTOCOL_ANCIENT = 1; + WIFI_PHY_LAYER_PROTOCOL_A = 2; + WIFI_PHY_LAYER_PROTOCOL_B = 3; + WIFI_PHY_LAYER_PROTOCOL_G = 4; + WIFI_PHY_LAYER_PROTOCOL_N = 5; + WIFI_PHY_LAYER_PROTOCOL_UNKNOWN = 6; + } + // The physical layer mode of the associated wifi access point, if any. + optional WifiPHYLayerProtocol wifi_phy_layer_protocol = 4; + } + optional Network network = 13; + + // Information on the Google Update install that is managing this client. + message GoogleUpdate { + // Whether the Google Update install is system-level or user-level. + optional bool is_system_install = 1; + + // The date at which Google Update last started performing an automatic + // update check, in seconds since the Unix epoch. + optional int64 last_automatic_start_timestamp = 2; + + // The date at which Google Update last successfully sent an update check + // and recieved an intact response from the server, in seconds since the + // Unix epoch. (The updates don't need to be successfully installed.) + optional int64 last_update_check_timestamp = 3; + + // Describes a product being managed by Google Update. (This can also + // describe Google Update itself.) + message ProductInfo { + // The current version of the product that is installed. + optional string version = 1; + + // The date at which Google Update successfully updated this product, + // stored in seconds since the Unix epoch. This is updated when an update + // is successfully applied, or if the server reports that no update + // is available. + optional int64 last_update_success_timestamp = 2; + + // The result reported by the product updater on its last run. + enum InstallResult { + INSTALL_RESULT_SUCCESS = 0; + INSTALL_RESULT_FAILED_CUSTOM_ERROR = 1; + INSTALL_RESULT_FAILED_MSI_ERROR = 2; + INSTALL_RESULT_FAILED_SYSTEM_ERROR = 3; + INSTALL_RESULT_EXIT_CODE = 4; + } + optional InstallResult last_result = 3; + + // The error code reported by the product updater on its last run. This + // will typically be a error code specific to the product installer. + optional int32 last_error = 4; + + // The extra error code reported by the product updater on its last run. + // This will typically be a Win32 error code. + optional int32 last_extra_error = 5; + } + optional ProductInfo google_update_status = 4; + optional ProductInfo client_status = 5; + } + optional GoogleUpdate google_update = 11; + + // Information on all installed plugins. + message Plugin { + // The plugin's self-reported name and filename (without path). + optional string name = 1; + optional string filename = 2; + + // The plugin's version. + optional string version = 3; + + // True if the plugin is disabled. + // If a client has multiple local Chrome user accounts, this is logged based + // on the first user account launched during the current session. + optional bool is_disabled = 4; + + // True if the plugin is PPAPI. + optional bool is_pepper = 5; + } + repeated Plugin plugin = 7; + + // Figures that can be used to generate application stability metrics. + // All values are counts of events since the last time that these + // values were reported. + // Next tag: 24 + message Stability { + // Total amount of time that the program was running, in seconds, + // since the last time a log was recorded, as measured using a client-side + // clock implemented via TimeTicks, which guarantees that it is monotonic + // and does not jump if the user changes his/her clock. The TimeTicks + // implementation also makes the clock not count time the computer is + // suspended. + optional int64 incremental_uptime_sec = 1; + + // Total amount of time that the program was running, in seconds, + // since startup, as measured using a client-side clock implemented + // via TimeTicks, which guarantees that it is monotonic and does not + // jump if the user changes his/her clock. The TimeTicks implementation + // also makes the clock not count time the computer is suspended. + // This field was added for M-35. + optional int64 uptime_sec = 23; + + // Page loads along with renderer crashes and hangs, since page load count + // roughly corresponds to usage. + optional int32 page_load_count = 2; + optional int32 renderer_crash_count = 3; + optional int32 renderer_hang_count = 4; + + // Number of renderer crashes that were for extensions. + // TODO(isherman): Figure out whether this is also counted in + // |renderer_crash_count|. + optional int32 extension_renderer_crash_count = 5; + + // Number of non-renderer child process crashes. + optional int32 child_process_crash_count = 6; + + // Number of times the browser has crashed while logged in as the "other + // user" (guest) account. + // Logged on ChromeOS only. + optional int32 other_user_crash_count = 7; + + // Number of times the kernel has crashed. + // Logged on ChromeOS only. + optional int32 kernel_crash_count = 8; + + // Number of times the system has shut down uncleanly. + // Logged on ChromeOS only. + optional int32 unclean_system_shutdown_count = 9; + + // + // All the remaining fields in the Stability are recorded at most once per + // client session. + // + + // The number of times the program was launched. + // This will typically be equal to 1. However, it is possible that Chrome + // was unable to upload stability metrics for previous launches (e.g. due to + // crashing early during startup), and hence this value might be greater + // than 1. + optional int32 launch_count = 15; + // The number of times that it didn't exit cleanly (which we assume to be + // mostly crashes). + optional int32 crash_count = 16; + + // The number of times the program began, but did not complete, the shutdown + // process. (For example, this may occur when Windows is shutting down, and + // it only gives the process a few seconds to clean up.) + optional int32 incomplete_shutdown_count = 17; + + // The number of times the program was able register with breakpad crash + // services. + optional int32 breakpad_registration_success_count = 18; + + // The number of times the program failed to register with breakpad crash + // services. If crash registration fails then when the program crashes no + // crash report will be generated. + optional int32 breakpad_registration_failure_count = 19; + + // The number of times the program has run under a debugger. This should + // be an exceptional condition. Running under a debugger prevents crash + // dumps from being generated. + optional int32 debugger_present_count = 20; + + // The number of times the program has run without a debugger attached. + // This should be most common scenario and should be very close to + // |launch_count|. + optional int32 debugger_not_present_count = 21; + + // Stability information for all installed plugins. + message PluginStability { + // The relevant plugin's information (name, etc.) + optional Plugin plugin = 1; + + // The number of times this plugin's process was launched. + optional int32 launch_count = 2; + + // The number of times this plugin was instantiated on a web page. + // This will be >= |launch_count|. + // (A page load with multiple sections drawn by this plugin will + // increase this count multiple times.) + optional int32 instance_count = 3; + + // The number of times this plugin process crashed. + // This value will be <= |launch_count|. + optional int32 crash_count = 4; + + // The number of times this plugin could not be loaded. + optional int32 loading_error_count = 5; + } + repeated PluginStability plugin_stability = 22; + } + optional Stability stability = 8; + + // Description of a field trial or experiment that the user is currently + // enrolled in. + // All metrics reported in this upload can potentially be influenced by the + // field trial. + message FieldTrial { + // The name of the field trial, as a 32-bit identifier. + // Currently, the identifier is a hash of the field trial's name. + optional fixed32 name_id = 1; + + // The user's group within the field trial, as a 32-bit identifier. + // Currently, the identifier is a hash of the group's name. + optional fixed32 group_id = 2; + } + repeated FieldTrial field_trial = 9; + + // Number of users currently signed into a multiprofile session. + // A zero value indicates that the user count changed while the log is open. + // Logged only on ChromeOS. + optional uint32 multi_profile_user_count = 17; + + // Information about extensions that are installed, masked to provide better + // privacy. Only extensions from a single profile are reported; this will + // generally be the profile used when the browser is started. The profile + // reported on will remain consistent at least until the browser is + // relaunched (or the profile is deleted by the user). + // + // Each client first picks a value for client_key derived from its UMA + // client_id: + // client_key = client_id % 4096 + // Then, each installed extension is mapped into a hash bucket according to + // bucket = CityHash64(StringPrintf("%d:%s", + // client_key, extension_id)) % 1024 + // The client reports the set of hash buckets occupied by all installed + // extensions. If multiple extensions map to the same bucket, that bucket is + // still only reported once. + repeated int32 occupied_extension_bucket = 18; +} diff --git a/components/metrics/proto/user_action_event.proto b/components/metrics/proto/user_action_event.proto new file mode 100644 index 0000000..6817dcd --- /dev/null +++ b/components/metrics/proto/user_action_event.proto @@ -0,0 +1,21 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Stores information about an event that occurs in response to a user action, +// e.g. an interaction with a browser UI element. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package metrics; + +// Next tag: 3 +message UserActionEventProto { + // The name of the action, hashed. + optional fixed64 name_hash = 1; + + // The timestamp for the event, in seconds since the epoch. + optional int64 time = 2; +} |