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 |
| 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 Loading... |
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 Loading... |
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 } |
OLD | NEW |