Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(134)

Side by Side Diff: chrome/common/metrics/metrics_log_base.cc

Issue 9232071: Upload UMA data using protocol buffers. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/common/metrics/metrics_log_base.h" 5 #include "chrome/common/metrics/metrics_log_base.h"
6 6
7 #include "base/base64.h" 7 #include "base/base64.h"
8 #include "base/basictypes.h" 8 #include "base/basictypes.h"
9 #include "base/md5.h" 9 #include "base/md5.h"
10 #include "base/perftimer.h" 10 #include "base/perftimer.h"
11 #include "base/string_number_conversions.h" 11 #include "base/string_number_conversions.h"
12 #include "base/sys_byteorder.h"
12 #include "base/sys_info.h" 13 #include "base/sys_info.h"
13 #include "base/third_party/nspr/prtime.h" 14 #include "base/third_party/nspr/prtime.h"
14 #include "base/utf_string_conversions.h" 15 #include "base/utf_string_conversions.h"
15 #include "chrome/common/logging_chrome.h" 16 #include "chrome/common/logging_chrome.h"
17 #include "chrome/common/metrics/proto/histogram_event.pb.h"
18 #include "chrome/common/metrics/proto/user_action_event.pb.h"
16 #include "libxml/xmlwriter.h" 19 #include "libxml/xmlwriter.h"
17 20
18 #define OPEN_ELEMENT_FOR_SCOPE(name) ScopedElement scoped_element(this, name) 21 #define OPEN_ELEMENT_FOR_SCOPE(name) ScopedElement scoped_element(this, name)
19 22
20 using base::Histogram; 23 using base::Histogram;
21 using base::Time; 24 using base::Time;
22 using base::TimeDelta; 25 using base::TimeDelta;
23 26 using metrics::HistogramEventProto;
24 // http://blogs.msdn.com/oldnewthing/archive/2004/10/25/247180.aspx 27 using metrics::UserActionEventProto;
25 #if defined(OS_WIN)
26 extern "C" IMAGE_DOS_HEADER __ImageBase;
27 #endif
28 28
29 namespace { 29 namespace {
30 30
31 // libxml take xmlChar*, which is unsigned char* 31 // libxml take xmlChar*, which is unsigned char*
32 inline const unsigned char* UnsignedChar(const char* input) { 32 inline const unsigned char* UnsignedChar(const char* input) {
33 return reinterpret_cast<const unsigned char*>(input); 33 return reinterpret_cast<const unsigned char*>(input);
34 } 34 }
35 35
36 // Any id less than 16 bytes is considered to be a testing id.
37 bool IsTestingID(const std::string& id) {
38 return id.size() < 16;
39 }
40
41 // Converts the 8-byte prefix of an MD5 hash into a uint64 value.
42 inline uint64 HashToUInt64(const std::string& hash) {
43 uint64 value;
44 DCHECK_GE(hash.size(), sizeof(value));
45 memcpy(&value, hash.data(), sizeof(value));
46 return base::htonll(value);
47 }
48
49 // Creates an MD5 hash of the given value, and returns hash as a byte buffer
50 // encoded as an std::string.
51 std::string CreateHash(const std::string& value) {
52 base::MD5Context context;
53 base::MD5Init(&context);
54 base::MD5Update(&context, value);
55
56 base::MD5Digest digest;
57 base::MD5Final(&digest, &context);
58
59 std::string hash(reinterpret_cast<char*>(digest.a), arraysize(digest.a));
60
61 // The following log is VERY helpful when folks add some named histogram into
62 // the code, but forgot to update the descriptive list of histograms. When
63 // that happens, all we get to see (server side) is a hash of the histogram
64 // name. We can then use this logging to find out what histogram name was
65 // being hashed to a given MD5 value by just running the version of Chromium
66 // in question with --enable-logging.
67 DVLOG(1) << "Metrics: Hash numeric [" << value << "]=[" << HashToUInt64(hash)
68 << "]";
69
70 return hash;
71 }
72
36 } // namespace 73 } // namespace
37 74
38 class MetricsLogBase::XmlWrapper { 75 class MetricsLogBase::XmlWrapper {
39 public: 76 public:
40 XmlWrapper() 77 XmlWrapper()
41 : doc_(NULL), 78 : doc_(NULL),
42 buffer_(NULL), 79 buffer_(NULL),
43 writer_(NULL) { 80 writer_(NULL) {
44 buffer_ = xmlBufferCreate(); 81 buffer_ = xmlBufferCreate();
45 DCHECK(buffer_); 82 DCHECK(buffer_);
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
85 }; 122 };
86 123
87 MetricsLogBase::MetricsLogBase(const std::string& client_id, int session_id, 124 MetricsLogBase::MetricsLogBase(const std::string& client_id, int session_id,
88 const std::string& version_string) 125 const std::string& version_string)
89 : start_time_(Time::Now()), 126 : start_time_(Time::Now()),
90 client_id_(client_id), 127 client_id_(client_id),
91 session_id_(base::IntToString(session_id)), 128 session_id_(base::IntToString(session_id)),
92 locked_(false), 129 locked_(false),
93 xml_wrapper_(new XmlWrapper), 130 xml_wrapper_(new XmlWrapper),
94 num_events_(0) { 131 num_events_(0) {
132 int64_t build_time = GetBuildTime();
95 133
134 // Write the XML version.
96 StartElement("log"); 135 StartElement("log");
97 WriteAttribute("clientid", client_id_); 136 WriteAttribute("clientid", client_id_);
98 WriteInt64Attribute("buildtime", GetBuildTime()); 137 WriteInt64Attribute("buildtime", build_time);
99 WriteAttribute("appversion", version_string); 138 WriteAttribute("appversion", version_string);
139
140 // Write the protobuf version.
141 if (IsTestingID(client_id_)) {
142 uma_proto_.set_client_id(0);
143 } else {
144 uma_proto_.set_client_id(HashToUInt64(CreateHash(client_id)));
145 }
146 uma_proto_.set_session_id(session_id);
147 uma_proto_.mutable_system_profile()->set_build_timestamp(build_time);
148 uma_proto_.mutable_system_profile()->set_app_version(version_string);
100 } 149 }
101 150
102 MetricsLogBase::~MetricsLogBase() { 151 MetricsLogBase::~MetricsLogBase() {
103 if (!locked_) { 152 if (!locked_) {
104 locked_ = true; 153 locked_ = true;
105 int result = xmlTextWriterEndDocument(xml_wrapper_->writer()); 154 int result = xmlTextWriterEndDocument(xml_wrapper_->writer());
106 DCHECK_GE(result, 0); 155 DCHECK_GE(result, 0);
107 } 156 }
108 delete xml_wrapper_; 157 delete xml_wrapper_;
109 xml_wrapper_ = NULL; 158 xml_wrapper_ = NULL;
110 } 159 }
111 160
161 // static
162 void MetricsLogBase::CreateHashes(const std::string& string,
163 std::string* base64_encoded_hash,
164 uint64* numeric_hash) {
165 std::string hash = CreateHash(string);
166
167 std::string encoded_digest;
168 if (base::Base64Encode(hash, &encoded_digest))
169 DVLOG(1) << "Metrics: Hash [" << encoded_digest << "]=[" << string << "]";
170
171 *base64_encoded_hash = encoded_digest;
172 *numeric_hash = HashToUInt64(hash);
173 }
174
175 // static
176 int64 MetricsLogBase::GetBuildTime() {
177 static int64 integral_build_time = 0;
178 if (!integral_build_time) {
179 Time time;
180 const char* kDateTime = __DATE__ " " __TIME__ " GMT";
181 bool result = Time::FromString(kDateTime, &time);
182 DCHECK(result);
183 integral_build_time = static_cast<int64>(time.ToTimeT());
184 }
185 return integral_build_time;
186 }
187
188 // static
189 int64 MetricsLogBase::GetCurrentTime() {
190 return (base::TimeTicks::Now() - base::TimeTicks()).InSeconds();
191 }
192
112 void MetricsLogBase::CloseLog() { 193 void MetricsLogBase::CloseLog() {
113 DCHECK(!locked_); 194 DCHECK(!locked_);
114 locked_ = true; 195 locked_ = true;
115 196
116 int result = xmlTextWriterEndDocument(xml_wrapper_->writer()); 197 int result = xmlTextWriterEndDocument(xml_wrapper_->writer());
117 DCHECK_GE(result, 0); 198 DCHECK_GE(result, 0);
118 199
119 result = xmlTextWriterFlush(xml_wrapper_->writer()); 200 result = xmlTextWriterFlush(xml_wrapper_->writer());
120 DCHECK_GE(result, 0); 201 DCHECK_GE(result, 0);
121 202
122 #if defined(OS_CHROMEOS) 203 #if defined(OS_CHROMEOS)
204 // TODO(isherman): Once the XML pipeline is deprecated, there will be no need
205 // to track the hardware class in a separate member variable and only write it
206 // out when the log is closed.
123 xmlNodePtr root = xmlDocGetRootElement(xml_wrapper_->doc()); 207 xmlNodePtr root = xmlDocGetRootElement(xml_wrapper_->doc());
124 if (!hardware_class_.empty()) { 208 if (!hardware_class_.empty()) {
125 // The hardware class is determined after the first ongoing log is 209 // The hardware class is determined after the first ongoing log is
126 // constructed, so this adds the root element's "hardwareclass" 210 // constructed, so this adds the root element's "hardwareclass"
127 // attribute when the log is closed instead. 211 // attribute when the log is closed instead.
128 xmlNewProp(root, UnsignedChar("hardwareclass"), 212 xmlNewProp(root, UnsignedChar("hardwareclass"),
129 UnsignedChar(hardware_class_.c_str())); 213 UnsignedChar(hardware_class_.c_str()));
214
215 // Write to the protobuf too.
216 uma_proto_.mutable_system_profile()->mutable_hardware()->set_hardware_class(
217 hardware_class_);
130 } 218 }
131 219
132 // Flattens the XML tree into a character buffer. 220 // Flattens the XML tree into a character buffer.
133 PerfTimer dump_timer; 221 PerfTimer dump_timer;
134 result = xmlNodeDump(xml_wrapper_->buffer(), xml_wrapper_->doc(), 222 result = xmlNodeDump(xml_wrapper_->buffer(), xml_wrapper_->doc(),
135 root, /* level */ 0, /* format */ 1); 223 root, /* level */ 0, /* format */ 1);
136 DCHECK_GE(result, 0); 224 DCHECK_GE(result, 0);
137 UMA_HISTOGRAM_TIMES("UMA.XMLNodeDumpTime", dump_timer.Elapsed()); 225 UMA_HISTOGRAM_TIMES("UMA.XMLNodeDumpTime", dump_timer.Elapsed());
138 226
139 PerfTimer free_timer; 227 PerfTimer free_timer;
140 xml_wrapper_->FreeDocWriter(); 228 xml_wrapper_->FreeDocWriter();
141 UMA_HISTOGRAM_TIMES("UMA.XMLWriterDestructionTime", free_timer.Elapsed()); 229 UMA_HISTOGRAM_TIMES("UMA.XMLWriterDestructionTime", free_timer.Elapsed());
142 #endif // OS_CHROMEOS 230 #endif // OS_CHROMEOS
143 } 231 }
144 232
145 int MetricsLogBase::GetEncodedLogSize() { 233 int MetricsLogBase::GetEncodedLogSizeXml() {
146 DCHECK(locked_); 234 DCHECK(locked_);
147 CHECK(xml_wrapper_); 235 CHECK(xml_wrapper_);
148 CHECK(xml_wrapper_->buffer()); 236 CHECK(xml_wrapper_->buffer());
149 return xml_wrapper_->buffer()->use; 237 return xml_wrapper_->buffer()->use;
150 } 238 }
151 239
152 bool MetricsLogBase::GetEncodedLog(char* buffer, int buffer_size) { 240 bool MetricsLogBase::GetEncodedLogXml(char* buffer, int buffer_size) {
153 DCHECK(locked_); 241 DCHECK(locked_);
154 if (buffer_size < GetEncodedLogSize()) 242 if (buffer_size < GetEncodedLogSizeXml())
155 return false; 243 return false;
156 244
157 memcpy(buffer, xml_wrapper_->buffer()->content, GetEncodedLogSize()); 245 memcpy(buffer, xml_wrapper_->buffer()->content, GetEncodedLogSizeXml());
158 return true; 246 return true;
159 } 247 }
160 248
161 std::string MetricsLogBase::GetEncodedLogString() { 249 void MetricsLogBase::GetEncodedLogProto(std::string* encoded_log) {
162 DCHECK(locked_); 250 DCHECK(locked_);
163 return std::string(reinterpret_cast<char*>(xml_wrapper_->buffer()->content)); 251 uma_proto_.SerializeToString(encoded_log);
164 } 252 }
165 253
166 int MetricsLogBase::GetElapsedSeconds() { 254 int MetricsLogBase::GetElapsedSeconds() {
167 return static_cast<int>((Time::Now() - start_time_).InSeconds()); 255 return static_cast<int>((Time::Now() - start_time_).InSeconds());
168 } 256 }
169 257
170 std::string MetricsLogBase::CreateHash(const std::string& value) {
171 base::MD5Context ctx;
172 base::MD5Init(&ctx);
173 base::MD5Update(&ctx, value);
174
175 base::MD5Digest digest;
176 base::MD5Final(&digest, &ctx);
177
178 uint64 reverse_uint64;
179 // UMA only uses first 8 chars of hash. We use the above uint64 instead
180 // of a unsigned char[8] so that we don't run into strict aliasing issues
181 // in the LOG statement below when trying to interpret reverse as a uint64.
182 unsigned char* reverse = reinterpret_cast<unsigned char *>(&reverse_uint64);
183 DCHECK(arraysize(digest.a) >= sizeof(reverse_uint64));
184 for (size_t i = 0; i < sizeof(reverse_uint64); ++i)
185 reverse[i] = digest.a[sizeof(reverse_uint64) - i - 1];
186 // The following log is VERY helpful when folks add some named histogram into
187 // the code, but forgot to update the descriptive list of histograms. When
188 // that happens, all we get to see (server side) is a hash of the histogram
189 // name. We can then use this logging to find out what histogram name was
190 // being hashed to a given MD5 value by just running the version of Chromium
191 // in question with --enable-logging.
192 DVLOG(1) << "Metrics: Hash numeric [" << value
193 << "]=[" << reverse_uint64 << "]";
194 return std::string(reinterpret_cast<char*>(digest.a), arraysize(digest.a));
195 }
196
197 std::string MetricsLogBase::CreateBase64Hash(const std::string& string) {
198 std::string encoded_digest;
199 if (base::Base64Encode(CreateHash(string), &encoded_digest)) {
200 DVLOG(1) << "Metrics: Hash [" << encoded_digest << "]=[" << string << "]";
201 return encoded_digest;
202 }
203 return std::string();
204 }
205
206 void MetricsLogBase::RecordUserAction(const char* key) { 258 void MetricsLogBase::RecordUserAction(const char* key) {
207 DCHECK(!locked_); 259 DCHECK(!locked_);
208 260
209 std::string command_hash = CreateBase64Hash(key); 261 std::string base64_hash;
210 if (command_hash.empty()) { 262 uint64 numeric_hash;
263 CreateHashes(key, &base64_hash, &numeric_hash);
264 if (base64_hash.empty()) {
211 NOTREACHED() << "Unable generate encoded hash of command: " << key; 265 NOTREACHED() << "Unable generate encoded hash of command: " << key;
212 return; 266 return;
213 } 267 }
214 268
269 // Write the XML version.
215 OPEN_ELEMENT_FOR_SCOPE("uielement"); 270 OPEN_ELEMENT_FOR_SCOPE("uielement");
216 WriteAttribute("action", "command"); 271 WriteAttribute("action", "command");
217 WriteAttribute("targetidhash", command_hash); 272 WriteAttribute("targetidhash", base64_hash);
273
274 // Write the protobuf version.
275 UserActionEventProto* user_action = uma_proto_.add_user_action_event();
276 user_action->set_name_hash(numeric_hash);
277 user_action->set_time(MetricsLogBase::GetCurrentTime());
218 278
219 // TODO(jhughes): Properly track windows. 279 // TODO(jhughes): Properly track windows.
220 WriteIntAttribute("window", 0); 280 WriteIntAttribute("window", 0);
221 WriteCommonEventAttributes(); 281 WriteCommonEventAttributes();
222 282
223 ++num_events_; 283 ++num_events_;
224 } 284 }
225 285
226 void MetricsLogBase::RecordLoadEvent(int window_id, 286 void MetricsLogBase::RecordLoadEvent(int window_id,
227 const GURL& url, 287 const GURL& url,
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after
303 return base::Uint64ToString(Time::Now().ToTimeT()); 363 return base::Uint64ToString(Time::Now().ToTimeT());
304 } 364 }
305 365
306 // These are the attributes that are common to every event. 366 // These are the attributes that are common to every event.
307 void MetricsLogBase::WriteCommonEventAttributes() { 367 void MetricsLogBase::WriteCommonEventAttributes() {
308 WriteAttribute("session", session_id_); 368 WriteAttribute("session", session_id_);
309 WriteAttribute("time", GetCurrentTimeString()); 369 WriteAttribute("time", GetCurrentTimeString());
310 } 370 }
311 371
312 void MetricsLogBase::WriteAttribute(const std::string& name, 372 void MetricsLogBase::WriteAttribute(const std::string& name,
313 const std::string& value) { 373 const std::string& value) {
314 DCHECK(!locked_); 374 DCHECK(!locked_);
315 DCHECK(!name.empty()); 375 DCHECK(!name.empty());
316 376
317 int result = xmlTextWriterWriteAttribute(xml_wrapper_->writer(), 377 int result = xmlTextWriterWriteAttribute(xml_wrapper_->writer(),
318 UnsignedChar(name.c_str()), 378 UnsignedChar(name.c_str()),
319 UnsignedChar(value.c_str())); 379 UnsignedChar(value.c_str()));
320 DCHECK_GE(result, 0); 380 DCHECK_GE(result, 0);
321 } 381 }
322 382
323 void MetricsLogBase::WriteIntAttribute(const std::string& name, int value) { 383 void MetricsLogBase::WriteIntAttribute(const std::string& name, int value) {
(...skipping 28 matching lines...) Expand all
352 DCHECK_GE(result, 0); 412 DCHECK_GE(result, 0);
353 } 413 }
354 414
355 void MetricsLogBase::EndElement() { 415 void MetricsLogBase::EndElement() {
356 DCHECK(!locked_); 416 DCHECK(!locked_);
357 417
358 int result = xmlTextWriterEndElement(xml_wrapper_->writer()); 418 int result = xmlTextWriterEndElement(xml_wrapper_->writer());
359 DCHECK_GE(result, 0); 419 DCHECK_GE(result, 0);
360 } 420 }
361 421
362 // static
363 int64 MetricsLogBase::GetBuildTime() {
364 static int64 integral_build_time = 0;
365 if (!integral_build_time) {
366 Time time;
367 const char* kDateTime = __DATE__ " " __TIME__ " GMT";
368 bool result = Time::FromString(kDateTime, &time);
369 DCHECK(result);
370 integral_build_time = static_cast<int64>(time.ToTimeT());
371 }
372 return integral_build_time;
373 }
374
375 // TODO(JAR): A The following should really be part of the histogram class. 422 // TODO(JAR): A The following should really be part of the histogram class.
376 // Internal state is being needlessly exposed, and it would be hard to reuse 423 // Internal state is being needlessly exposed, and it would be hard to reuse
377 // this code. If we moved this into the Histogram class, then we could use 424 // this code. If we moved this into the Histogram class, then we could use
378 // the same infrastructure for logging StatsCounters, RatesCounters, etc. 425 // the same infrastructure for logging StatsCounters, RatesCounters, etc.
379 void MetricsLogBase::RecordHistogramDelta( 426 void MetricsLogBase::RecordHistogramDelta(
380 const Histogram& histogram, 427 const Histogram& histogram,
381 const Histogram::SampleSet& snapshot) { 428 const Histogram::SampleSet& snapshot) {
382 DCHECK(!locked_); 429 DCHECK(!locked_);
383 DCHECK_NE(0, snapshot.TotalCount()); 430 DCHECK_NE(0, snapshot.TotalCount());
384 snapshot.CheckSize(histogram); 431 snapshot.CheckSize(histogram);
385 432
386 // We will ignore the MAX_INT/infinite value in the last element of range[]. 433 // We will ignore the MAX_INT/infinite value in the last element of range[].
387 434
388 OPEN_ELEMENT_FOR_SCOPE("histogram"); 435 OPEN_ELEMENT_FOR_SCOPE("histogram");
389 436
390 WriteAttribute("name", CreateBase64Hash(histogram.histogram_name())); 437 std::string base64_name_hash;
438 uint64 numeric_name_hash;
439 CreateHashes(histogram.histogram_name(),
440 &base64_name_hash,
441 &numeric_name_hash);
442
443 // Write the XML version.
444 WriteAttribute("name", base64_name_hash);
391 445
392 WriteInt64Attribute("sum", snapshot.sum()); 446 WriteInt64Attribute("sum", snapshot.sum());
393 // TODO(jar): Remove sumsquares when protobuffer accepts this as optional. 447 // TODO(jar): Remove sumsquares when protobuffer accepts this as optional.
394 WriteInt64Attribute("sumsquares", 0); 448 WriteInt64Attribute("sumsquares", 0);
395 449
396 for (size_t i = 0; i < histogram.bucket_count(); i++) { 450 for (size_t i = 0; i < histogram.bucket_count(); i++) {
397 if (snapshot.counts(i)) { 451 if (snapshot.counts(i)) {
398 OPEN_ELEMENT_FOR_SCOPE("histogrambucket"); 452 OPEN_ELEMENT_FOR_SCOPE("histogrambucket");
399 WriteIntAttribute("min", histogram.ranges(i)); 453 WriteIntAttribute("min", histogram.ranges(i));
400 WriteIntAttribute("max", histogram.ranges(i + 1)); 454 WriteIntAttribute("max", histogram.ranges(i + 1));
401 WriteIntAttribute("count", snapshot.counts(i)); 455 WriteIntAttribute("count", snapshot.counts(i));
402 } 456 }
403 } 457 }
458
459 // Write the protobuf version.
460 HistogramEventProto* histogram_proto = uma_proto_.add_histogram_event();
461 histogram_proto->set_name_hash(numeric_name_hash);
462 histogram_proto->set_sum(snapshot.sum());
463
464 for (size_t i = 0; i < histogram.bucket_count(); ++i) {
465 if (snapshot.counts(i)) {
466 HistogramEventProto::Bucket* bucket = histogram_proto->add_bucket();
467 bucket->set_min(histogram.ranges(i));
468 bucket->set_max(histogram.ranges(i + 1));
469 bucket->set_bucket_index(i);
470 bucket->set_count(snapshot.counts(i));
471 }
472 }
404 } 473 }
OLDNEW
« no previous file with comments | « chrome/common/metrics/metrics_log_base.h ('k') | chrome/common/metrics/metrics_log_base_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698