OLD | NEW |
---|---|
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 Loading... | |
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 |
112 void MetricsLogBase::CloseLog() { | 161 void MetricsLogBase::CloseLog() { |
113 DCHECK(!locked_); | 162 DCHECK(!locked_); |
114 locked_ = true; | 163 locked_ = true; |
115 | 164 |
116 int result = xmlTextWriterEndDocument(xml_wrapper_->writer()); | 165 int result = xmlTextWriterEndDocument(xml_wrapper_->writer()); |
117 DCHECK_GE(result, 0); | 166 DCHECK_GE(result, 0); |
118 | 167 |
119 result = xmlTextWriterFlush(xml_wrapper_->writer()); | 168 result = xmlTextWriterFlush(xml_wrapper_->writer()); |
120 DCHECK_GE(result, 0); | 169 DCHECK_GE(result, 0); |
121 | 170 |
122 #if defined(OS_CHROMEOS) | 171 #if defined(OS_CHROMEOS) |
172 // TODO(isherman): Once the XML pipeline is deprecated, there will be no need | |
173 // to track the hardware class in a separate member variable and only write it | |
174 // out when the log is closed. | |
123 xmlNodePtr root = xmlDocGetRootElement(xml_wrapper_->doc()); | 175 xmlNodePtr root = xmlDocGetRootElement(xml_wrapper_->doc()); |
124 if (!hardware_class_.empty()) { | 176 if (!hardware_class_.empty()) { |
125 // The hardware class is determined after the first ongoing log is | 177 // The hardware class is determined after the first ongoing log is |
126 // constructed, so this adds the root element's "hardwareclass" | 178 // constructed, so this adds the root element's "hardwareclass" |
127 // attribute when the log is closed instead. | 179 // attribute when the log is closed instead. |
128 xmlNewProp(root, UnsignedChar("hardwareclass"), | 180 xmlNewProp(root, UnsignedChar("hardwareclass"), |
129 UnsignedChar(hardware_class_.c_str())); | 181 UnsignedChar(hardware_class_.c_str())); |
182 | |
183 // Write to the protobuf too. | |
184 uma_proto_.mutable_system_profile()->set_hardware_class(hardware_class_); | |
130 } | 185 } |
131 | 186 |
132 // Flattens the XML tree into a character buffer. | 187 // Flattens the XML tree into a character buffer. |
133 PerfTimer dump_timer; | 188 PerfTimer dump_timer; |
134 result = xmlNodeDump(xml_wrapper_->buffer(), xml_wrapper_->doc(), | 189 result = xmlNodeDump(xml_wrapper_->buffer(), xml_wrapper_->doc(), |
135 root, /* level */ 0, /* format */ 1); | 190 root, /* level */ 0, /* format */ 1); |
136 DCHECK_GE(result, 0); | 191 DCHECK_GE(result, 0); |
137 UMA_HISTOGRAM_TIMES("UMA.XMLNodeDumpTime", dump_timer.Elapsed()); | 192 UMA_HISTOGRAM_TIMES("UMA.XMLNodeDumpTime", dump_timer.Elapsed()); |
138 | 193 |
139 PerfTimer free_timer; | 194 PerfTimer free_timer; |
140 xml_wrapper_->FreeDocWriter(); | 195 xml_wrapper_->FreeDocWriter(); |
141 UMA_HISTOGRAM_TIMES("UMA.XMLWriterDestructionTime", free_timer.Elapsed()); | 196 UMA_HISTOGRAM_TIMES("UMA.XMLWriterDestructionTime", free_timer.Elapsed()); |
142 #endif // OS_CHROMEOS | 197 #endif // OS_CHROMEOS |
143 } | 198 } |
144 | 199 |
145 int MetricsLogBase::GetEncodedLogSize() { | 200 int MetricsLogBase::GetEncodedLogSizeXml() { |
146 DCHECK(locked_); | 201 DCHECK(locked_); |
147 CHECK(xml_wrapper_); | 202 CHECK(xml_wrapper_); |
148 CHECK(xml_wrapper_->buffer()); | 203 CHECK(xml_wrapper_->buffer()); |
149 return xml_wrapper_->buffer()->use; | 204 return xml_wrapper_->buffer()->use; |
150 } | 205 } |
151 | 206 |
152 bool MetricsLogBase::GetEncodedLog(char* buffer, int buffer_size) { | 207 bool MetricsLogBase::GetEncodedLogXml(char* buffer, int buffer_size) { |
153 DCHECK(locked_); | 208 DCHECK(locked_); |
154 if (buffer_size < GetEncodedLogSize()) | 209 if (buffer_size < GetEncodedLogSizeXml()) |
155 return false; | 210 return false; |
156 | 211 |
157 memcpy(buffer, xml_wrapper_->buffer()->content, GetEncodedLogSize()); | 212 memcpy(buffer, xml_wrapper_->buffer()->content, GetEncodedLogSizeXml()); |
158 return true; | 213 return true; |
159 } | 214 } |
160 | 215 |
161 std::string MetricsLogBase::GetEncodedLogString() { | 216 void MetricsLogBase::GetEncodedLogProto(std::string* encoded_log) { |
162 DCHECK(locked_); | 217 DCHECK(locked_); |
163 return std::string(reinterpret_cast<char*>(xml_wrapper_->buffer()->content)); | 218 *encoded_log = uma_proto_.SerializeAsString(); |
jar (doing other things)
2012/02/27 20:35:34
Use std::swap to avoid a string copy. There is a
Ilya Sherman
2012/02/28 00:23:10
Done.
| |
164 } | 219 } |
165 | 220 |
166 int MetricsLogBase::GetElapsedSeconds() { | 221 int MetricsLogBase::GetElapsedSeconds() { |
167 return static_cast<int>((Time::Now() - start_time_).InSeconds()); | 222 return static_cast<int>((Time::Now() - start_time_).InSeconds()); |
168 } | 223 } |
169 | 224 |
170 std::string MetricsLogBase::CreateHash(const std::string& value) { | 225 // static |
171 base::MD5Context ctx; | 226 void MetricsLogBase::CreateHashes(const std::string& string, |
172 base::MD5Init(&ctx); | 227 std::string* base64_encoded_hash, |
173 base::MD5Update(&ctx, value); | 228 uint64* numeric_hash) { |
229 std::string hash = CreateHash(string); | |
174 | 230 |
175 base::MD5Digest digest; | 231 std::string encoded_digest; |
176 base::MD5Final(&digest, &ctx); | 232 if (base::Base64Encode(hash, &encoded_digest)) |
233 DVLOG(1) << "Metrics: Hash [" << encoded_digest << "]=[" << string << "]"; | |
177 | 234 |
178 uint64 reverse_uint64; | 235 *base64_encoded_hash = encoded_digest; |
179 // UMA only uses first 8 chars of hash. We use the above uint64 instead | 236 *numeric_hash = HashToUInt64(hash); |
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 } | 237 } |
205 | 238 |
206 void MetricsLogBase::RecordUserAction(const char* key) { | 239 void MetricsLogBase::RecordUserAction(const char* key) { |
207 DCHECK(!locked_); | 240 DCHECK(!locked_); |
208 | 241 |
209 std::string command_hash = CreateBase64Hash(key); | 242 std::string base64_hash; |
210 if (command_hash.empty()) { | 243 uint64 numeric_hash; |
244 CreateHashes(key, &base64_hash, &numeric_hash); | |
245 if (base64_hash.empty()) { | |
211 NOTREACHED() << "Unable generate encoded hash of command: " << key; | 246 NOTREACHED() << "Unable generate encoded hash of command: " << key; |
212 return; | 247 return; |
213 } | 248 } |
214 | 249 |
250 // Write the XML version. | |
215 OPEN_ELEMENT_FOR_SCOPE("uielement"); | 251 OPEN_ELEMENT_FOR_SCOPE("uielement"); |
216 WriteAttribute("action", "command"); | 252 WriteAttribute("action", "command"); |
217 WriteAttribute("targetidhash", command_hash); | 253 WriteAttribute("targetidhash", base64_hash); |
254 | |
255 // Write the protobuf version. | |
256 UserActionEventProto* user_action = uma_proto_.add_user_action_event(); | |
257 user_action->set_name_hash(numeric_hash); | |
258 user_action->set_time(base::TimeTicks::Now().ToInternalValue()); | |
jar (doing other things)
2012/02/27 20:35:34
ToInternalValue returns microseconds... better wou
Ilya Sherman
2012/02/28 00:23:10
Done.
| |
218 | 259 |
219 // TODO(jhughes): Properly track windows. | 260 // TODO(jhughes): Properly track windows. |
220 WriteIntAttribute("window", 0); | 261 WriteIntAttribute("window", 0); |
221 WriteCommonEventAttributes(); | 262 WriteCommonEventAttributes(); |
222 | 263 |
223 ++num_events_; | 264 ++num_events_; |
224 } | 265 } |
225 | 266 |
226 void MetricsLogBase::RecordLoadEvent(int window_id, | 267 void MetricsLogBase::RecordLoadEvent(int window_id, |
227 const GURL& url, | 268 const GURL& url, |
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
303 return base::Uint64ToString(Time::Now().ToTimeT()); | 344 return base::Uint64ToString(Time::Now().ToTimeT()); |
304 } | 345 } |
305 | 346 |
306 // These are the attributes that are common to every event. | 347 // These are the attributes that are common to every event. |
307 void MetricsLogBase::WriteCommonEventAttributes() { | 348 void MetricsLogBase::WriteCommonEventAttributes() { |
308 WriteAttribute("session", session_id_); | 349 WriteAttribute("session", session_id_); |
309 WriteAttribute("time", GetCurrentTimeString()); | 350 WriteAttribute("time", GetCurrentTimeString()); |
310 } | 351 } |
311 | 352 |
312 void MetricsLogBase::WriteAttribute(const std::string& name, | 353 void MetricsLogBase::WriteAttribute(const std::string& name, |
313 const std::string& value) { | 354 const std::string& value) { |
314 DCHECK(!locked_); | 355 DCHECK(!locked_); |
315 DCHECK(!name.empty()); | 356 DCHECK(!name.empty()); |
316 | 357 |
317 int result = xmlTextWriterWriteAttribute(xml_wrapper_->writer(), | 358 int result = xmlTextWriterWriteAttribute(xml_wrapper_->writer(), |
318 UnsignedChar(name.c_str()), | 359 UnsignedChar(name.c_str()), |
319 UnsignedChar(value.c_str())); | 360 UnsignedChar(value.c_str())); |
320 DCHECK_GE(result, 0); | 361 DCHECK_GE(result, 0); |
321 } | 362 } |
322 | 363 |
323 void MetricsLogBase::WriteIntAttribute(const std::string& name, int value) { | 364 void MetricsLogBase::WriteIntAttribute(const std::string& name, int value) { |
(...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
380 const Histogram& histogram, | 421 const Histogram& histogram, |
381 const Histogram::SampleSet& snapshot) { | 422 const Histogram::SampleSet& snapshot) { |
382 DCHECK(!locked_); | 423 DCHECK(!locked_); |
383 DCHECK_NE(0, snapshot.TotalCount()); | 424 DCHECK_NE(0, snapshot.TotalCount()); |
384 snapshot.CheckSize(histogram); | 425 snapshot.CheckSize(histogram); |
385 | 426 |
386 // We will ignore the MAX_INT/infinite value in the last element of range[]. | 427 // We will ignore the MAX_INT/infinite value in the last element of range[]. |
387 | 428 |
388 OPEN_ELEMENT_FOR_SCOPE("histogram"); | 429 OPEN_ELEMENT_FOR_SCOPE("histogram"); |
389 | 430 |
390 WriteAttribute("name", CreateBase64Hash(histogram.histogram_name())); | 431 std::string base64_name_hash; |
432 uint64 numeric_name_hash; | |
433 CreateHashes(histogram.histogram_name(), | |
434 &base64_name_hash, | |
435 &numeric_name_hash); | |
436 | |
437 // Write the XML version. | |
438 WriteAttribute("name", base64_name_hash); | |
391 | 439 |
392 WriteInt64Attribute("sum", snapshot.sum()); | 440 WriteInt64Attribute("sum", snapshot.sum()); |
393 // TODO(jar): Remove sumsquares when protobuffer accepts this as optional. | 441 // TODO(jar): Remove sumsquares when protobuffer accepts this as optional. |
394 WriteInt64Attribute("sumsquares", 0); | 442 WriteInt64Attribute("sumsquares", 0); |
395 | 443 |
396 for (size_t i = 0; i < histogram.bucket_count(); i++) { | 444 for (size_t i = 0; i < histogram.bucket_count(); i++) { |
397 if (snapshot.counts(i)) { | 445 if (snapshot.counts(i)) { |
398 OPEN_ELEMENT_FOR_SCOPE("histogrambucket"); | 446 OPEN_ELEMENT_FOR_SCOPE("histogrambucket"); |
399 WriteIntAttribute("min", histogram.ranges(i)); | 447 WriteIntAttribute("min", histogram.ranges(i)); |
400 WriteIntAttribute("max", histogram.ranges(i + 1)); | 448 WriteIntAttribute("max", histogram.ranges(i + 1)); |
401 WriteIntAttribute("count", snapshot.counts(i)); | 449 WriteIntAttribute("count", snapshot.counts(i)); |
402 } | 450 } |
403 } | 451 } |
452 | |
453 // Write the protobuf version. | |
454 HistogramEventProto* histogram_proto = uma_proto_.add_histogram_event(); | |
455 histogram_proto->set_name_hash(numeric_name_hash); | |
456 histogram_proto->set_sum(snapshot.sum()); | |
457 | |
458 for (size_t i = 0; i < histogram.bucket_count(); ++i) { | |
459 if (snapshot.counts(i)) { | |
460 HistogramEventProto::Bucket* bucket = histogram_proto->add_bucket(); | |
461 bucket->set_min(histogram.ranges(i)); | |
462 bucket->set_max(histogram.ranges(i + 1)); | |
463 bucket->set_bucket_index(i); | |
464 bucket->set_count(snapshot.counts(i)); | |
465 } | |
466 } | |
404 } | 467 } |
OLD | NEW |