Chromium Code Reviews| Index: chrome/common/metrics/metrics_log_base.cc |
| diff --git a/chrome/common/metrics/metrics_log_base.cc b/chrome/common/metrics/metrics_log_base.cc |
| index b2c42f6db3f4a8aa44ae238152db9b39cfc0a870..a08f2801688b8032a7674274848d9148babf5cf2 100644 |
| --- a/chrome/common/metrics/metrics_log_base.cc |
| +++ b/chrome/common/metrics/metrics_log_base.cc |
| @@ -9,10 +9,13 @@ |
| #include "base/md5.h" |
| #include "base/perftimer.h" |
| #include "base/string_number_conversions.h" |
| +#include "base/sys_byteorder.h" |
| #include "base/sys_info.h" |
| #include "base/third_party/nspr/prtime.h" |
| #include "base/utf_string_conversions.h" |
| #include "chrome/common/logging_chrome.h" |
| +#include "chrome/common/metrics/proto/histogram_event.pb.h" |
| +#include "chrome/common/metrics/proto/user_action_event.pb.h" |
| #include "libxml/xmlwriter.h" |
| #define OPEN_ELEMENT_FOR_SCOPE(name) ScopedElement scoped_element(this, name) |
| @@ -20,11 +23,8 @@ |
| using base::Histogram; |
| using base::Time; |
| using base::TimeDelta; |
| - |
| -// http://blogs.msdn.com/oldnewthing/archive/2004/10/25/247180.aspx |
| -#if defined(OS_WIN) |
| -extern "C" IMAGE_DOS_HEADER __ImageBase; |
| -#endif |
| +using metrics::HistogramEventProto; |
| +using metrics::UserActionEventProto; |
| namespace { |
| @@ -33,6 +33,43 @@ inline const unsigned char* UnsignedChar(const char* input) { |
| return reinterpret_cast<const unsigned char*>(input); |
| } |
| +// Any id less than 16 bytes is considered to be a testing id. |
|
jar (doing other things)
2012/02/23 01:59:18
Why not just use some fixed ID? Probability of co
Ilya Sherman
2012/02/24 02:10:06
No idea -- I was just trying to match the existing
|
| +bool IsTestingID(const std::string& id) { |
| + return id.size() < 16; |
| +} |
| + |
| +// Converts the 8-byte prefix of an MD5 hash into a uint64 value. |
| +inline uint64 HashToUInt64(const std::string& hash) { |
| + uint64 value; |
| + DCHECK_GE(hash.size(), sizeof(value)); |
| + memcpy(&value, hash.data(), sizeof(value)); |
| + return base::htonll(value); |
| +} |
| + |
| +// Creates an MD5 hash of the given value, and returns hash as a byte buffer |
| +// encoded as an std::string. |
| +std::string CreateHash(const std::string& value) { |
| + base::MD5Context context; |
| + base::MD5Init(&context); |
| + base::MD5Update(&context, value); |
| + |
| + base::MD5Digest digest; |
| + base::MD5Final(&digest, &context); |
| + |
| + std::string hash(reinterpret_cast<char*>(digest.a), arraysize(digest.a)); |
| + |
| + // 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 << "]=[" << HashToUInt64(hash) |
|
jar (doing other things)
2012/02/23 01:59:18
It is good this is being preserved! ;-) Thanks!
|
| + << "]"; |
| + |
| + return hash; |
| +} |
| + |
| } // namespace |
| class MetricsLogBase::XmlWrapper { |
| @@ -92,11 +129,23 @@ MetricsLogBase::MetricsLogBase(const std::string& client_id, int session_id, |
| locked_(false), |
| xml_wrapper_(new XmlWrapper), |
| num_events_(0) { |
| + int64_t build_time = GetBuildTime(); |
| + // Write the XML version. |
| StartElement("log"); |
| WriteAttribute("clientid", client_id_); |
| - WriteInt64Attribute("buildtime", GetBuildTime()); |
| + WriteInt64Attribute("buildtime", build_time); |
| WriteAttribute("appversion", version_string); |
| + |
| + // Write the protobuf version. |
| + if (IsTestingID(client_id_)) { |
|
jar (doing other things)
2012/02/23 01:59:18
This seems a little strange. It is nicer to not h
|
| + uma_proto_.set_client_id(0); |
| + } else { |
| + uma_proto_.set_client_id(HashToUInt64(CreateHash(client_id))); |
| + } |
| + uma_proto_.set_session_id(session_id); |
| + uma_proto_.mutable_system_profile()->set_build_timestamp(build_time); |
| + uma_proto_.mutable_system_profile()->set_app_version(version_string); |
| } |
| MetricsLogBase::~MetricsLogBase() { |
| @@ -120,6 +169,9 @@ void MetricsLogBase::CloseLog() { |
| DCHECK_GE(result, 0); |
| #if defined(OS_CHROMEOS) |
| + // TODO(isherman): Once the XML pipeline is deprecated, there will be no need |
| + // to track the hardware class in a separate member variable and only write it |
| + // out when the log is closed. |
| xmlNodePtr root = xmlDocGetRootElement(xml_wrapper_->doc()); |
| if (!hardware_class_.empty()) { |
| // The hardware class is determined after the first ongoing log is |
| @@ -127,8 +179,12 @@ void MetricsLogBase::CloseLog() { |
| // attribute when the log is closed instead. |
| xmlNewProp(root, UnsignedChar("hardwareclass"), |
| UnsignedChar(hardware_class_.c_str())); |
| + |
| + // Write to the protobuf too. |
| + uma_proto_.mutable_system_profile()->set_hardware_class(hardware_class_); |
| } |
| + // TODO(isherman): Why is this done only on ChromeOS? |
|
jar (doing other things)
2012/02/23 01:59:18
I have no idea why :-/.
I'd have to read about ho
Ilya Sherman
2012/02/24 02:10:06
Ok. Well, the good news is that we're (hopefully)
|
| // Flattens the XML tree into a character buffer. |
| PerfTimer dump_timer; |
| result = xmlNodeDump(xml_wrapper_->buffer(), xml_wrapper_->doc(), |
| @@ -142,79 +198,65 @@ void MetricsLogBase::CloseLog() { |
| #endif // OS_CHROMEOS |
| } |
| -int MetricsLogBase::GetEncodedLogSize() { |
| +int MetricsLogBase::GetEncodedLogSizeXml() { |
| DCHECK(locked_); |
| CHECK(xml_wrapper_); |
| CHECK(xml_wrapper_->buffer()); |
| return xml_wrapper_->buffer()->use; |
| } |
| -bool MetricsLogBase::GetEncodedLog(char* buffer, int buffer_size) { |
| +bool MetricsLogBase::GetEncodedLogXml(char* buffer, int buffer_size) { |
| DCHECK(locked_); |
| - if (buffer_size < GetEncodedLogSize()) |
| + if (buffer_size < GetEncodedLogSizeXml()) |
| return false; |
| - memcpy(buffer, xml_wrapper_->buffer()->content, GetEncodedLogSize()); |
| + memcpy(buffer, xml_wrapper_->buffer()->content, GetEncodedLogSizeXml()); |
| return true; |
| } |
| -std::string MetricsLogBase::GetEncodedLogString() { |
| +std::string MetricsLogBase::GetEncodedLogProto() { |
| DCHECK(locked_); |
| - return std::string(reinterpret_cast<char*>(xml_wrapper_->buffer()->content)); |
| + return uma_proto_.SerializeAsString(); |
|
jar (doing other things)
2012/02/23 01:59:18
It might be nice to pass in a "std::string*" and h
Ilya Sherman
2012/02/24 02:10:06
I wrote it this way based on the recommendation in
|
| } |
| int MetricsLogBase::GetElapsedSeconds() { |
| return static_cast<int>((Time::Now() - start_time_).InSeconds()); |
| } |
| -std::string MetricsLogBase::CreateHash(const std::string& value) { |
| - base::MD5Context ctx; |
| - base::MD5Init(&ctx); |
| - base::MD5Update(&ctx, value); |
| - |
| - base::MD5Digest digest; |
| - base::MD5Final(&digest, &ctx); |
| - |
| - uint64 reverse_uint64; |
| - // UMA only uses first 8 chars of hash. We use the above uint64 instead |
| - // of a unsigned char[8] so that we don't run into strict aliasing issues |
| - // in the LOG statement below when trying to interpret reverse as a uint64. |
| - unsigned char* reverse = reinterpret_cast<unsigned char *>(&reverse_uint64); |
| - DCHECK(arraysize(digest.a) >= sizeof(reverse_uint64)); |
| - for (size_t i = 0; i < sizeof(reverse_uint64); ++i) |
| - reverse[i] = digest.a[sizeof(reverse_uint64) - i - 1]; |
| - // 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 |
| - << "]=[" << reverse_uint64 << "]"; |
| - return std::string(reinterpret_cast<char*>(digest.a), arraysize(digest.a)); |
| -} |
| +// static |
| +void MetricsLogBase::CreateHashes(const std::string& string, |
| + std::string* base64_encoded_hash, |
| + uint64* numeric_hash) { |
| + std::string hash = CreateHash(string); |
| -std::string MetricsLogBase::CreateBase64Hash(const std::string& string) { |
| std::string encoded_digest; |
| - if (base::Base64Encode(CreateHash(string), &encoded_digest)) { |
| + if (base::Base64Encode(hash, &encoded_digest)) |
| DVLOG(1) << "Metrics: Hash [" << encoded_digest << "]=[" << string << "]"; |
| - return encoded_digest; |
| - } |
| - return std::string(); |
| + |
| + *base64_encoded_hash = encoded_digest; |
| + *numeric_hash = HashToUInt64(hash); |
| } |
| void MetricsLogBase::RecordUserAction(const char* key) { |
| DCHECK(!locked_); |
| - std::string command_hash = CreateBase64Hash(key); |
| - if (command_hash.empty()) { |
| + std::string base64_hash; |
| + uint64 numeric_hash; |
| + CreateHashes(key, &base64_hash, &numeric_hash); |
| + if (base64_hash.empty()) { |
| NOTREACHED() << "Unable generate encoded hash of command: " << key; |
| return; |
| } |
| + // Write the XML version. |
| OPEN_ELEMENT_FOR_SCOPE("uielement"); |
| WriteAttribute("action", "command"); |
| - WriteAttribute("targetidhash", command_hash); |
| + WriteAttribute("targetidhash", base64_hash); |
| + |
| + // Write the protobuf version. |
| + UserActionEventProto* user_action = uma_proto_.add_user_action_event(); |
| + user_action->set_name_hash(numeric_hash); |
|
jar (doing other things)
2012/02/23 01:59:18
Have you consistently decided to use a full 64bit
Ilya Sherman
2012/02/24 02:10:06
Yes, that was my understanding.
|
| + user_action->set_time(Time::Now().ToTimeT()); |
|
jar (doing other things)
2012/02/23 01:59:18
Note that Time::Now() is not monotonic, and will v
Ilya Sherman
2012/02/24 02:10:06
Hmm, that's a really good point. I think TimeTick
|
| // TODO(jhughes): Properly track windows. |
| WriteIntAttribute("window", 0); |
| @@ -310,7 +352,7 @@ void MetricsLogBase::WriteCommonEventAttributes() { |
| } |
| void MetricsLogBase::WriteAttribute(const std::string& name, |
| - const std::string& value) { |
| + const std::string& value) { |
| DCHECK(!locked_); |
| DCHECK(!name.empty()); |
| @@ -387,7 +429,14 @@ void MetricsLogBase::RecordHistogramDelta( |
| OPEN_ELEMENT_FOR_SCOPE("histogram"); |
| - WriteAttribute("name", CreateBase64Hash(histogram.histogram_name())); |
| + std::string base64_name_hash; |
| + uint64 numeric_name_hash; |
| + CreateHashes(histogram.histogram_name(), |
| + &base64_name_hash, |
| + &numeric_name_hash); |
| + |
| + // Write the XML version. |
| + WriteAttribute("name", base64_name_hash); |
| WriteInt64Attribute("sum", snapshot.sum()); |
| // TODO(jar): Remove sumsquares when protobuffer accepts this as optional. |
| @@ -401,4 +450,19 @@ void MetricsLogBase::RecordHistogramDelta( |
| WriteIntAttribute("count", snapshot.counts(i)); |
| } |
| } |
| + |
| + // Write the protobuf version. |
| + HistogramEventProto* histogram_proto = uma_proto_.add_histogram_event(); |
| + histogram_proto->set_name_hash(numeric_name_hash); |
| + histogram_proto->set_sum(snapshot.sum()); |
| + |
| + for (size_t i = 0; i < histogram.bucket_count(); ++i) { |
| + if (snapshot.counts(i)) { |
| + HistogramEventProto::Bucket* bucket = histogram_proto->add_bucket(); |
| + bucket->set_min(histogram.ranges(i)); |
| + bucket->set_max(histogram.ranges(i + 1)); |
| + bucket->set_bucket_index(i); |
| + bucket->set_count(snapshot.counts(i)); |
| + } |
| + } |
| } |