1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 // Histogram is an object that aggregates statistics, and can summarize them in 6 // various forms, including ASCII graphical, HTML, and numerically (as a 7 // vector of numbers corresponding to each of the aggregating buckets). 8 // See header file for details and examples. 9 10 #include "base/metrics/histogram.h" 11 12 #include <math.h> 13 14 #include <algorithm> 15 #include <string> 16 17 #include "base/logging.h" 18 #include "base/pickle.h" 19 #include "base/stringprintf.h" 20 #include "base/synchronization/lock.h" 21 22 namespace base { 23 24 // Static table of checksums for all possible 8 bit bytes. 25 const uint32 Histogram::kCrcTable[256] = {0x0, 0x77073096L, 0xee0e612cL, 26 0x990951baL, 0x76dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0xedb8832L, 27 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, 0x9b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 28 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, 0x1adad47dL, 29 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, 30 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 31 0x4c69105eL, 0xd56041e4L, 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 32 0xa50ab56bL, 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, 33 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, 0xc8d75180L, 34 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL, 0x2802b89eL, 35 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 36 0xb6662d3dL, 0x76dc4190L, 0x1db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 37 0x6b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0xf00f934L, 0x9609a88eL, 38 0xe10e9818L, 0x7f6a0dbbL, 0x86d3d2dL, 0x91646c97L, 0xe6635c01L, 0x6b6b51f4L, 39 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 40 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 41 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 42 0xd4bb30e2L, 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, 43 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 44 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, 0x5768b525L, 45 0x206f85b3L, 0xb966d409L, 0xce61e49fL, 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 46 0xc7d7a8b4L, 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 47 0x9abfb3b6L, 0x3b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x4db2615L, 48 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0xd6d6a3eL, 0x7a6a5aa8L, 0xe40ecf0bL, 49 0x9309ff9dL, 0xa00ae27L, 0x7d079eb1L, 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 50 0x6906c2feL, 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 51 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 52 0x60b08ed5L, 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, 53 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, 54 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L, 0xcb61b38cL, 55 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 56 0x5505262fL, 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 57 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 58 0x26d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x5005713L, 0x95bf4a82L, 59 0xe2b87a14L, 0x7bb12baeL, 0xcb61b38L, 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, 60 0xbdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 61 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 62 0x11010b5cL, 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, 63 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, 0x4969474dL, 64 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, 0xa9bcae53L, 65 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 66 0x24b4a3a6L, 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 67 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 68 0x2d02ef8dL, 69 }; 70 71 typedef Histogram::Count Count; 72 73 // static 74 const size_t Histogram::kBucketCount_MAX = 16384u; 75 76 Histogram* Histogram::FactoryGet(const std::string& name, 77 Sample minimum, 78 Sample maximum, 79 size_t bucket_count, 80 Flags flags) { 81 Histogram* histogram(NULL); 82 83 // Defensive code. 84 if (minimum < 1) 85 minimum = 1; 86 if (maximum > kSampleType_MAX - 1) 87 maximum = kSampleType_MAX - 1; 88 89 if (!StatisticsRecorder::FindHistogram(name, &histogram)) { 90 // Extra variable is not needed... but this keeps this section basically 91 // identical to other derived classes in this file (and compiler will 92 // optimize away the extra variable. 93 // To avoid racy destruction at shutdown, the following will be leaked. 94 Histogram* tentative_histogram = 95 new Histogram(name, minimum, maximum, bucket_count); 96 tentative_histogram->InitializeBucketRange(); 97 tentative_histogram->SetFlags(flags); 98 histogram = 99 StatisticsRecorder::RegisterOrDeleteDuplicate(tentative_histogram); 100 } 101 102 DCHECK_EQ(HISTOGRAM, histogram->histogram_type()); 103 DCHECK(histogram->HasConstructorArguments(minimum, maximum, bucket_count)); 104 return histogram; 105 } 106 107 Histogram* Histogram::FactoryTimeGet(const std::string& name, 108 TimeDelta minimum, 109 TimeDelta maximum, 110 size_t bucket_count, 111 Flags flags) { 112 return FactoryGet(name, minimum.InMilliseconds(), maximum.InMilliseconds(), 113 bucket_count, flags); 114 } 115 116 void Histogram::Add(int value) { 117 if (value > kSampleType_MAX - 1) 118 value = kSampleType_MAX - 1; 119 if (value < 0) 120 value = 0; 121 size_t index = BucketIndex(value); 122 DCHECK_GE(value, ranges(index)); 123 DCHECK_LT(value, ranges(index + 1)); 124 Accumulate(value, 1, index); 125 } 126 127 void Histogram::AddBoolean(bool value) { 128 DCHECK(false); 129 } 130 131 void Histogram::AddSampleSet(const SampleSet& sample) { 132 sample_.Add(sample); 133 } 134 135 void Histogram::SetRangeDescriptions(const DescriptionPair descriptions[]) { 136 DCHECK(false); 137 } 138 139 // The following methods provide a graphical histogram display. 140 void Histogram::WriteHTMLGraph(std::string* output) const { 141 // TBD(jar) Write a nice HTML bar chart, with divs an mouse-overs etc. 142 output->append("<PRE>"); 143 WriteAscii(true, "<br>", output); 144 output->append("</PRE>"); 145 } 146 147 void Histogram::WriteAscii(bool graph_it, const std::string& newline, 148 std::string* output) const { 149 // Get local (stack) copies of all effectively volatile class data so that we 150 // are consistent across our output activities. 151 SampleSet snapshot; 152 SnapshotSample(&snapshot); 153 Count sample_count = snapshot.TotalCount(); 154 155 WriteAsciiHeader(snapshot, sample_count, output); 156 output->append(newline); 157 158 // Prepare to normalize graphical rendering of bucket contents. 159 double max_size = 0; 160 if (graph_it) 161 max_size = GetPeakBucketSize(snapshot); 162 163 // Calculate space needed to print bucket range numbers. Leave room to print 164 // nearly the largest bucket range without sliding over the histogram. 165 size_t largest_non_empty_bucket = bucket_count() - 1; 166 while (0 == snapshot.counts(largest_non_empty_bucket)) { 167 if (0 == largest_non_empty_bucket) 168 break; // All buckets are empty. 169 --largest_non_empty_bucket; 170 } 171 172 // Calculate largest print width needed for any of our bucket range displays. 173 size_t print_width = 1; 174 for (size_t i = 0; i < bucket_count(); ++i) { 175 if (snapshot.counts(i)) { 176 size_t width = GetAsciiBucketRange(i).size() + 1; 177 if (width > print_width) 178 print_width = width; 179 } 180 } 181 182 int64 remaining = sample_count; 183 int64 past = 0; 184 // Output the actual histogram graph. 185 for (size_t i = 0; i < bucket_count(); ++i) { 186 Count current = snapshot.counts(i); 187 if (!current && !PrintEmptyBucket(i)) 188 continue; 189 remaining -= current; 190 std::string range = GetAsciiBucketRange(i); 191 output->append(range); 192 for (size_t j = 0; range.size() + j < print_width + 1; ++j) 193 output->push_back(' '); 194 if (0 == current && i < bucket_count() - 1 && 0 == snapshot.counts(i + 1)) { 195 while (i < bucket_count() - 1 && 0 == snapshot.counts(i + 1)) 196 ++i; 197 output->append("... "); 198 output->append(newline); 199 continue; // No reason to plot emptiness. 200 } 201 double current_size = GetBucketSize(current, i); 202 if (graph_it) 203 WriteAsciiBucketGraph(current_size, max_size, output); 204 WriteAsciiBucketContext(past, current, remaining, i, output); 205 output->append(newline); 206 past += current; 207 } 208 DCHECK_EQ(sample_count, past); 209 } 210 211 // static 212 std::string Histogram::SerializeHistogramInfo(const Histogram& histogram, 213 const SampleSet& snapshot) { 214 DCHECK_NE(NOT_VALID_IN_RENDERER, histogram.histogram_type()); 215 216 Pickle pickle; 217 pickle.WriteString(histogram.histogram_name()); 218 pickle.WriteInt(histogram.declared_min()); 219 pickle.WriteInt(histogram.declared_max()); 220 pickle.WriteSize(histogram.bucket_count()); 221 pickle.WriteUInt32(histogram.range_checksum()); 222 pickle.WriteInt(histogram.histogram_type()); 223 pickle.WriteInt(histogram.flags()); 224 225 snapshot.Serialize(&pickle); 226 return std::string(static_cast<const char*>(pickle.data()), pickle.size()); 227 } 228 229 // static 230 bool Histogram::DeserializeHistogramInfo(const std::string& histogram_info) { 231 if (histogram_info.empty()) { 232 return false; 233 } 234 235 Pickle pickle(histogram_info.data(), 236 static_cast<int>(histogram_info.size())); 237 std::string histogram_name; 238 int declared_min; 239 int declared_max; 240 size_t bucket_count; 241 uint32 range_checksum; 242 int histogram_type; 243 int pickle_flags; 244 SampleSet sample; 245 246 void* iter = NULL; 247 if (!pickle.ReadString(&iter, &histogram_name) || 248 !pickle.ReadInt(&iter, &declared_min) || 249 !pickle.ReadInt(&iter, &declared_max) || 250 !pickle.ReadSize(&iter, &bucket_count) || 251 !pickle.ReadUInt32(&iter, &range_checksum) || 252 !pickle.ReadInt(&iter, &histogram_type) || 253 !pickle.ReadInt(&iter, &pickle_flags) || 254 !sample.Histogram::SampleSet::Deserialize(&iter, pickle)) { 255 LOG(ERROR) << "Pickle error decoding Histogram: " << histogram_name; 256 return false; 257 } 258 DCHECK(pickle_flags & kIPCSerializationSourceFlag); 259 // Since these fields may have come from an untrusted renderer, do additional 260 // checks above and beyond those in Histogram::Initialize() 261 if (declared_max <= 0 || declared_min <= 0 || declared_max < declared_min || 262 INT_MAX / sizeof(Count) <= bucket_count || bucket_count < 2) { 263 LOG(ERROR) << "Values error decoding Histogram: " << histogram_name; 264 return false; 265 } 266 267 Flags flags = static_cast<Flags>(pickle_flags & ~kIPCSerializationSourceFlag); 268 269 DCHECK_NE(NOT_VALID_IN_RENDERER, histogram_type); 270 271 Histogram* render_histogram(NULL); 272 273 if (histogram_type == HISTOGRAM) { 274 render_histogram = Histogram::FactoryGet( 275 histogram_name, declared_min, declared_max, bucket_count, flags); 276 } else if (histogram_type == LINEAR_HISTOGRAM) { 277 render_histogram = LinearHistogram::FactoryGet( 278 histogram_name, declared_min, declared_max, bucket_count, flags); 279 } else if (histogram_type == BOOLEAN_HISTOGRAM) { 280 render_histogram = BooleanHistogram::FactoryGet(histogram_name, flags); 281 } else { 282 LOG(ERROR) << "Error Deserializing Histogram Unknown histogram_type: " 283 << histogram_type; 284 return false; 285 } 286 287 DCHECK_EQ(render_histogram->declared_min(), declared_min); 288 DCHECK_EQ(render_histogram->declared_max(), declared_max); 289 DCHECK_EQ(render_histogram->bucket_count(), bucket_count); 290 DCHECK_EQ(render_histogram->range_checksum(), range_checksum); 291 DCHECK_EQ(render_histogram->histogram_type(), histogram_type); 292 293 if (render_histogram->flags() & kIPCSerializationSourceFlag) { 294 DVLOG(1) << "Single process mode, histogram observed and not copied: " 295 << histogram_name; 296 } else { 297 DCHECK_EQ(flags & render_histogram->flags(), flags); 298 render_histogram->AddSampleSet(sample); 299 } 300 301 return true; 302 } 303 304 //------------------------------------------------------------------------------ 305 // Methods for the validating a sample and a related histogram. 306 //------------------------------------------------------------------------------ 307 308 Histogram::Inconsistencies Histogram::FindCorruption( 309 const SampleSet& snapshot) const { 310 int inconsistencies = NO_INCONSISTENCIES; 311 Sample previous_range = -1; // Bottom range is always 0. 312 int64 count = 0; 313 for (size_t index = 0; index < bucket_count(); ++index) { 314 count += snapshot.counts(index); 315 int new_range = ranges(index); 316 if (previous_range >= new_range) 317 inconsistencies |= BUCKET_ORDER_ERROR; 318 previous_range = new_range; 319 } 320 321 if (!HasValidRangeChecksum()) 322 inconsistencies |= RANGE_CHECKSUM_ERROR; 323 324 int64 delta64 = snapshot.redundant_count() - count; 325 if (delta64 != 0) { 326 int delta = static_cast<int>(delta64); 327 if (delta != delta64) 328 delta = INT_MAX; // Flag all giant errors as INT_MAX. 329 // Since snapshots of histograms are taken asynchronously relative to 330 // sampling (and snapped from different threads), it is pretty likely that 331 // we'll catch a redundant count that doesn't match the sample count. We 332 // allow for a certain amount of slop before flagging this as an 333 // inconsistency. Even with an inconsistency, we'll snapshot it again (for 334 // UMA in about a half hour, so we'll eventually get the data, if it was 335 // not the result of a corruption. If histograms show that 1 is "too tight" 336 // then we may try to use 2 or 3 for this slop value. 337 const int kCommonRaceBasedCountMismatch = 1; 338 if (delta > 0) { 339 UMA_HISTOGRAM_COUNTS("Histogram.InconsistentCountHigh", delta); 340 if (delta > kCommonRaceBasedCountMismatch) 341 inconsistencies |= COUNT_HIGH_ERROR; 342 } else { 343 DCHECK_GT(0, delta); 344 UMA_HISTOGRAM_COUNTS("Histogram.InconsistentCountLow", -delta); 345 if (-delta > kCommonRaceBasedCountMismatch) 346 inconsistencies |= COUNT_LOW_ERROR; 347 } 348 } 349 return static_cast<Inconsistencies>(inconsistencies); 350 } 351 352 Histogram::ClassType Histogram::histogram_type() const { 353 return HISTOGRAM; 354 } 355 356 Histogram::Sample Histogram::ranges(size_t i) const { 357 return ranges_[i]; 358 } 359 360 size_t Histogram::bucket_count() const { 361 return bucket_count_; 362 } 363 364 // Do a safe atomic snapshot of sample data. 365 // This implementation assumes we are on a safe single thread. 366 void Histogram::SnapshotSample(SampleSet* sample) const { 367 // Note locking not done in this version!!! 368 *sample = sample_; 369 } 370 371 bool Histogram::HasConstructorArguments(Sample minimum, 372 Sample maximum, 373 size_t bucket_count) { 374 return ((minimum == declared_min_) && (maximum == declared_max_) && 375 (bucket_count == bucket_count_)); 376 } 377 378 bool Histogram::HasConstructorTimeDeltaArguments(TimeDelta minimum, 379 TimeDelta maximum, 380 size_t bucket_count) { 381 return ((minimum.InMilliseconds() == declared_min_) && 382 (maximum.InMilliseconds() == declared_max_) && 383 (bucket_count == bucket_count_)); 384 } 385 386 bool Histogram::HasValidRangeChecksum() const { 387 return CalculateRangeChecksum() == range_checksum_; 388 } 389 390 Histogram::Histogram(const std::string& name, Sample minimum, 391 Sample maximum, size_t bucket_count) 392 : histogram_name_(name), 393 declared_min_(minimum), 394 declared_max_(maximum), 395 bucket_count_(bucket_count), 396 flags_(kNoFlags), 397 ranges_(bucket_count + 1, 0), 398 range_checksum_(0), 399 sample_() { 400 Initialize(); 401 } 402 403 Histogram::Histogram(const std::string& name, TimeDelta minimum, 404 TimeDelta maximum, size_t bucket_count) 405 : histogram_name_(name), 406 declared_min_(static_cast<int> (minimum.InMilliseconds())), 407 declared_max_(static_cast<int> (maximum.InMilliseconds())), 408 bucket_count_(bucket_count), 409 flags_(kNoFlags), 410 ranges_(bucket_count + 1, 0), 411 range_checksum_(0), 412 sample_() { 413 Initialize(); 414 } 415 416 Histogram::~Histogram() { 417 if (StatisticsRecorder::dump_on_exit()) { 418 std::string output; 419 WriteAscii(true, "\n", &output); 420 LOG(INFO) << output; 421 } 422 423 // Just to make sure most derived class did this properly... 424 DCHECK(ValidateBucketRanges()); 425 } 426 427 // Calculate what range of values are held in each bucket. 428 // We have to be careful that we don't pick a ratio between starting points in 429 // consecutive buckets that is sooo small, that the integer bounds are the same 430 // (effectively making one bucket get no values). We need to avoid: 431 // ranges_[i] == ranges_[i + 1] 432 // To avoid that, we just do a fine-grained bucket width as far as we need to 433 // until we get a ratio that moves us along at least 2 units at a time. From 434 // that bucket onward we do use the exponential growth of buckets. 435 void Histogram::InitializeBucketRange() { 436 double log_max = log(static_cast<double>(declared_max())); 437 double log_ratio; 438 double log_next; 439 size_t bucket_index = 1; 440 Sample current = declared_min(); 441 SetBucketRange(bucket_index, current); 442 while (bucket_count() > ++bucket_index) { 443 double log_current; 444 log_current = log(static_cast<double>(current)); 445 // Calculate the count'th root of the range. 446 log_ratio = (log_max - log_current) / (bucket_count() - bucket_index); 447 // See where the next bucket would start. 448 log_next = log_current + log_ratio; 449 int next; 450 next = static_cast<int>(floor(exp(log_next) + 0.5)); 451 if (next > current) 452 current = next; 453 else 454 ++current; // Just do a narrow bucket, and keep trying. 455 SetBucketRange(bucket_index, current); 456 } 457 ResetRangeChecksum(); 458 459 DCHECK_EQ(bucket_count(), bucket_index); 460 } 461 462 bool Histogram::PrintEmptyBucket(size_t index) const { 463 return true; 464 } 465 466 size_t Histogram::BucketIndex(Sample value) const { 467 // Use simple binary search. This is very general, but there are better 468 // approaches if we knew that the buckets were linearly distributed. 469 DCHECK_LE(ranges(0), value); 470 DCHECK_GT(ranges(bucket_count()), value); 471 size_t under = 0; 472 size_t over = bucket_count(); 473 size_t mid; 474 475 do { 476 DCHECK_GE(over, under); 477 mid = under + (over - under)/2; 478 if (mid == under) 479 break; 480 if (ranges(mid) <= value) 481 under = mid; 482 else 483 over = mid; 484 } while (true); 485 486 DCHECK_LE(ranges(mid), value); 487 CHECK_GT(ranges(mid+1), value); 488 return mid; 489 } 490 491 // Use the actual bucket widths (like a linear histogram) until the widths get 492 // over some transition value, and then use that transition width. Exponentials 493 // get so big so fast (and we don't expect to see a lot of entries in the large 494 // buckets), so we need this to make it possible to see what is going on and 495 // not have 0-graphical-height buckets. 496 double Histogram::GetBucketSize(Count current, size_t i) const { 497 DCHECK_GT(ranges(i + 1), ranges(i)); 498 static const double kTransitionWidth = 5; 499 double denominator = ranges(i + 1) - ranges(i); 500 if (denominator > kTransitionWidth) 501 denominator = kTransitionWidth; // Stop trying to normalize. 502 return current/denominator; 503 } 504 505 void Histogram::ResetRangeChecksum() { 506 range_checksum_ = CalculateRangeChecksum(); 507 } 508 509 const std::string Histogram::GetAsciiBucketRange(size_t i) const { 510 std::string result; 511 if (kHexRangePrintingFlag & flags_) 512 StringAppendF(&result, "%#x", ranges(i)); 513 else 514 StringAppendF(&result, "%d", ranges(i)); 515 return result; 516 } 517 518 // Update histogram data with new sample. 519 void Histogram::Accumulate(Sample value, Count count, size_t index) { 520 // Note locking not done in this version!!! 521 sample_.Accumulate(value, count, index); 522 } 523 524 void Histogram::SetBucketRange(size_t i, Sample value) { 525 DCHECK_GT(bucket_count_, i); 526 ranges_[i] = value; 527 } 528 529 bool Histogram::ValidateBucketRanges() const { 530 // Standard assertions that all bucket ranges should satisfy. 531 DCHECK_EQ(bucket_count_ + 1, ranges_.size()); 532 DCHECK_EQ(0, ranges_[0]); 533 DCHECK_EQ(declared_min(), ranges_[1]); 534 DCHECK_EQ(declared_max(), ranges_[bucket_count_ - 1]); 535 DCHECK_EQ(kSampleType_MAX, ranges_[bucket_count_]); 536 return true; 537 } 538 539 uint32 Histogram::CalculateRangeChecksum() const { 540 DCHECK_EQ(ranges_.size(), bucket_count() + 1); 541 uint32 checksum = static_cast<uint32>(ranges_.size()); // Seed checksum. 542 for (size_t index = 0; index < bucket_count(); ++index) 543 checksum = Crc32(checksum, ranges(index)); 544 return checksum; 545 } 546 547 void Histogram::Initialize() { 548 sample_.Resize(*this); 549 if (declared_min_ < 1) 550 declared_min_ = 1; 551 if (declared_max_ > kSampleType_MAX - 1) 552 declared_max_ = kSampleType_MAX - 1; 553 DCHECK_LE(declared_min_, declared_max_); 554 DCHECK_GT(bucket_count_, 1u); 555 CHECK_LT(bucket_count_, kBucketCount_MAX); 556 size_t maximal_bucket_count = declared_max_ - declared_min_ + 2; 557 DCHECK_LE(bucket_count_, maximal_bucket_count); 558 DCHECK_EQ(0, ranges_[0]); 559 ranges_[bucket_count_] = kSampleType_MAX; 560 } 561 562 // We generate the CRC-32 using the low order bits to select whether to XOR in 563 // the reversed polynomial 0xedb88320L. This is nice and simple, and allows us 564 // to keep the quotient in a uint32. Since we're not concerned about the nature 565 // of corruptions (i.e., we don't care about bit sequencing, since we are 566 // handling memory changes, which are more grotesque) so we don't bother to 567 // get the CRC correct for big-endian vs little-ending calculations. All we 568 // need is a nice hash, that tends to depend on all the bits of the sample, with 569 // very little chance of changes in one place impacting changes in another 570 // place. 571 uint32 Histogram::Crc32(uint32 sum, Histogram::Sample range) { 572 const bool kUseRealCrc = true; // TODO(jar): Switch to false and watch stats. 573 if (kUseRealCrc) { 574 union { 575 Histogram::Sample range; 576 unsigned char bytes[sizeof(Histogram::Sample)]; 577 } converter; 578 converter.range = range; 579 for (size_t i = 0; i < sizeof(converter); ++i) 580 sum = kCrcTable[(sum & 0xff) ^ converter.bytes[i]] ^ (sum >> 8); 581 } else { 582 // Use hash techniques provided in ReallyFastHash, except we don't care 583 // about "avalanching" (which would worsten the hash, and add collisions), 584 // and we don't care about edge cases since we have an even number of bytes. 585 union { 586 Histogram::Sample range; 587 uint16 ints[sizeof(Histogram::Sample) / 2]; 588 } converter; 589 DCHECK_EQ(sizeof(Histogram::Sample), sizeof(converter)); 590 converter.range = range; 591 sum += converter.ints[0]; 592 sum = (sum << 16) ^ sum ^ (static_cast<uint32>(converter.ints[1]) << 11); 593 sum += sum >> 11; 594 } 595 return sum; 596 } 597 598 //------------------------------------------------------------------------------ 599 // Private methods 600 601 double Histogram::GetPeakBucketSize(const SampleSet& snapshot) const { 602 double max = 0; 603 for (size_t i = 0; i < bucket_count() ; ++i) { 604 double current_size = GetBucketSize(snapshot.counts(i), i); 605 if (current_size > max) 606 max = current_size; 607 } 608 return max; 609 } 610 611 void Histogram::WriteAsciiHeader(const SampleSet& snapshot, 612 Count sample_count, 613 std::string* output) const { 614 StringAppendF(output, 615 "Histogram: %s recorded %d samples", 616 histogram_name().c_str(), 617 sample_count); 618 if (0 == sample_count) { 619 DCHECK_EQ(snapshot.sum(), 0); 620 } else { 621 double average = static_cast<float>(snapshot.sum()) / sample_count; 622 623 StringAppendF(output, ", average = %.1f", average); 624 } 625 if (flags_ & ~kHexRangePrintingFlag) 626 StringAppendF(output, " (flags = 0x%x)", flags_ & ~kHexRangePrintingFlag); 627 } 628 629 void Histogram::WriteAsciiBucketContext(const int64 past, 630 const Count current, 631 const int64 remaining, 632 const size_t i, 633 std::string* output) const { 634 double scaled_sum = (past + current + remaining) / 100.0; 635 WriteAsciiBucketValue(current, scaled_sum, output); 636 if (0 < i) { 637 double percentage = past / scaled_sum; 638 StringAppendF(output, " {%3.1f%%}", percentage); 639 } 640 } 641 642 void Histogram::WriteAsciiBucketValue(Count current, double scaled_sum, 643 std::string* output) const { 644 StringAppendF(output, " (%d = %3.1f%%)", current, current/scaled_sum); 645 } 646 647 void Histogram::WriteAsciiBucketGraph(double current_size, double max_size, 648 std::string* output) const { 649 const int k_line_length = 72; // Maximal horizontal width of graph. 650 int x_count = static_cast<int>(k_line_length * (current_size / max_size) 651 + 0.5); 652 int x_remainder = k_line_length - x_count; 653 654 while (0 < x_count--) 655 output->append("-"); 656 output->append("O"); 657 while (0 < x_remainder--) 658 output->append(" "); 659 } 660 661 //------------------------------------------------------------------------------ 662 // Methods for the Histogram::SampleSet class 663 //------------------------------------------------------------------------------ 664 665 Histogram::SampleSet::SampleSet() 666 : counts_(), 667 sum_(0), 668 redundant_count_(0) { 669 } 670 671 Histogram::SampleSet::~SampleSet() { 672 } 673 674 void Histogram::SampleSet::Resize(const Histogram& histogram) { 675 counts_.resize(histogram.bucket_count(), 0); 676 } 677 678 void Histogram::SampleSet::CheckSize(const Histogram& histogram) const { 679 DCHECK_EQ(histogram.bucket_count(), counts_.size()); 680 } 681 682 683 void Histogram::SampleSet::Accumulate(Sample value, Count count, 684 size_t index) { 685 DCHECK(count == 1 || count == -1); 686 counts_[index] += count; 687 sum_ += count * value; 688 redundant_count_ += count; 689 DCHECK_GE(counts_[index], 0); 690 DCHECK_GE(sum_, 0); 691 DCHECK_GE(redundant_count_, 0); 692 } 693 694 Count Histogram::SampleSet::TotalCount() const { 695 Count total = 0; 696 for (Counts::const_iterator it = counts_.begin(); 697 it != counts_.end(); 698 ++it) { 699 total += *it; 700 } 701 return total; 702 } 703 704 void Histogram::SampleSet::Add(const SampleSet& other) { 705 DCHECK_EQ(counts_.size(), other.counts_.size()); 706 sum_ += other.sum_; 707 redundant_count_ += other.redundant_count_; 708 for (size_t index = 0; index < counts_.size(); ++index) 709 counts_[index] += other.counts_[index]; 710 } 711 712 void Histogram::SampleSet::Subtract(const SampleSet& other) { 713 DCHECK_EQ(counts_.size(), other.counts_.size()); 714 // Note: Race conditions in snapshotting a sum may lead to (temporary) 715 // negative values when snapshots are later combined (and deltas calculated). 716 // As a result, we don't currently CHCEK() for positive values. 717 sum_ -= other.sum_; 718 redundant_count_ -= other.redundant_count_; 719 for (size_t index = 0; index < counts_.size(); ++index) { 720 counts_[index] -= other.counts_[index]; 721 DCHECK_GE(counts_[index], 0); 722 } 723 } 724 725 bool Histogram::SampleSet::Serialize(Pickle* pickle) const { 726 pickle->WriteInt64(sum_); 727 pickle->WriteInt64(redundant_count_); 728 pickle->WriteSize(counts_.size()); 729 730 for (size_t index = 0; index < counts_.size(); ++index) { 731 pickle->WriteInt(counts_[index]); 732 } 733 734 return true; 735 } 736 737 bool Histogram::SampleSet::Deserialize(void** iter, const Pickle& pickle) { 738 DCHECK_EQ(counts_.size(), 0u); 739 DCHECK_EQ(sum_, 0); 740 DCHECK_EQ(redundant_count_, 0); 741 742 size_t counts_size; 743 744 if (!pickle.ReadInt64(iter, &sum_) || 745 !pickle.ReadInt64(iter, &redundant_count_) || 746 !pickle.ReadSize(iter, &counts_size)) { 747 return false; 748 } 749 750 if (counts_size == 0) 751 return false; 752 753 int count = 0; 754 for (size_t index = 0; index < counts_size; ++index) { 755 int i; 756 if (!pickle.ReadInt(iter, &i)) 757 return false; 758 counts_.push_back(i); 759 count += i; 760 } 761 DCHECK_EQ(count, redundant_count_); 762 return count == redundant_count_; 763 } 764 765 //------------------------------------------------------------------------------ 766 // LinearHistogram: This histogram uses a traditional set of evenly spaced 767 // buckets. 768 //------------------------------------------------------------------------------ 769 770 LinearHistogram::~LinearHistogram() { 771 } 772 773 Histogram* LinearHistogram::FactoryGet(const std::string& name, 774 Sample minimum, 775 Sample maximum, 776 size_t bucket_count, 777 Flags flags) { 778 Histogram* histogram(NULL); 779 780 if (minimum < 1) 781 minimum = 1; 782 if (maximum > kSampleType_MAX - 1) 783 maximum = kSampleType_MAX - 1; 784 785 if (!StatisticsRecorder::FindHistogram(name, &histogram)) { 786 // To avoid racy destruction at shutdown, the following will be leaked. 787 LinearHistogram* tentative_histogram = 788 new LinearHistogram(name, minimum, maximum, bucket_count); 789 tentative_histogram->InitializeBucketRange(); 790 tentative_histogram->SetFlags(flags); 791 histogram = 792 StatisticsRecorder::RegisterOrDeleteDuplicate(tentative_histogram); 793 } 794 795 DCHECK_EQ(LINEAR_HISTOGRAM, histogram->histogram_type()); 796 DCHECK(histogram->HasConstructorArguments(minimum, maximum, bucket_count)); 797 return histogram; 798 } 799 800 Histogram* LinearHistogram::FactoryTimeGet(const std::string& name, 801 TimeDelta minimum, 802 TimeDelta maximum, 803 size_t bucket_count, 804 Flags flags) { 805 return FactoryGet(name, minimum.InMilliseconds(), maximum.InMilliseconds(), 806 bucket_count, flags); 807 } 808 809 Histogram::ClassType LinearHistogram::histogram_type() const { 810 return LINEAR_HISTOGRAM; 811 } 812 813 void LinearHistogram::SetRangeDescriptions( 814 const DescriptionPair descriptions[]) { 815 for (int i =0; descriptions[i].description; ++i) { 816 bucket_description_[descriptions[i].sample] = descriptions[i].description; 817 } 818 } 819 820 LinearHistogram::LinearHistogram(const std::string& name, 821 Sample minimum, 822 Sample maximum, 823 size_t bucket_count) 824 : Histogram(name, minimum >= 1 ? minimum : 1, maximum, bucket_count) { 825 } 826 827 LinearHistogram::LinearHistogram(const std::string& name, 828 TimeDelta minimum, 829 TimeDelta maximum, 830 size_t bucket_count) 831 : Histogram(name, minimum >= TimeDelta::FromMilliseconds(1) ? 832 minimum : TimeDelta::FromMilliseconds(1), 833 maximum, bucket_count) { 834 } 835 836 void LinearHistogram::InitializeBucketRange() { 837 DCHECK_GT(declared_min(), 0); // 0 is the underflow bucket here. 838 double min = declared_min(); 839 double max = declared_max(); 840 size_t i; 841 for (i = 1; i < bucket_count(); ++i) { 842 double linear_range = (min * (bucket_count() -1 - i) + max * (i - 1)) / 843 (bucket_count() - 2); 844 SetBucketRange(i, static_cast<int> (linear_range + 0.5)); 845 } 846 ResetRangeChecksum(); 847 } 848 849 double LinearHistogram::GetBucketSize(Count current, size_t i) const { 850 DCHECK_GT(ranges(i + 1), ranges(i)); 851 // Adjacent buckets with different widths would have "surprisingly" many (few) 852 // samples in a histogram if we didn't normalize this way. 853 double denominator = ranges(i + 1) - ranges(i); 854 return current/denominator; 855 } 856 857 const std::string LinearHistogram::GetAsciiBucketRange(size_t i) const { 858 int range = ranges(i); 859 BucketDescriptionMap::const_iterator it = bucket_description_.find(range); 860 if (it == bucket_description_.end()) 861 return Histogram::GetAsciiBucketRange(i); 862 return it->second; 863 } 864 865 bool LinearHistogram::PrintEmptyBucket(size_t index) const { 866 return bucket_description_.find(ranges(index)) == bucket_description_.end(); 867 } 868 869 870 //------------------------------------------------------------------------------ 871 // This section provides implementation for BooleanHistogram. 872 //------------------------------------------------------------------------------ 873 874 Histogram* BooleanHistogram::FactoryGet(const std::string& name, Flags flags) { 875 Histogram* histogram(NULL); 876 877 if (!StatisticsRecorder::FindHistogram(name, &histogram)) { 878 // To avoid racy destruction at shutdown, the following will be leaked. 879 BooleanHistogram* tentative_histogram = new BooleanHistogram(name); 880 tentative_histogram->InitializeBucketRange(); 881 tentative_histogram->SetFlags(flags); 882 histogram = 883 StatisticsRecorder::RegisterOrDeleteDuplicate(tentative_histogram); 884 } 885 886 DCHECK_EQ(BOOLEAN_HISTOGRAM, histogram->histogram_type()); 887 return histogram; 888 } 889 890 Histogram::ClassType BooleanHistogram::histogram_type() const { 891 return BOOLEAN_HISTOGRAM; 892 } 893 894 void BooleanHistogram::AddBoolean(bool value) { 895 Add(value ? 1 : 0); 896 } 897 898 BooleanHistogram::BooleanHistogram(const std::string& name) 899 : LinearHistogram(name, 1, 2, 3) { 900 } 901 902 //------------------------------------------------------------------------------ 903 // CustomHistogram: 904 //------------------------------------------------------------------------------ 905 906 Histogram* CustomHistogram::FactoryGet(const std::string& name, 907 const std::vector<Sample>& custom_ranges, 908 Flags flags) { 909 Histogram* histogram(NULL); 910 911 // Remove the duplicates in the custom ranges array. 912 std::vector<int> ranges = custom_ranges; 913 ranges.push_back(0); // Ensure we have a zero value. 914 std::sort(ranges.begin(), ranges.end()); 915 ranges.erase(std::unique(ranges.begin(), ranges.end()), ranges.end()); 916 if (ranges.size() <= 1) { 917 DCHECK(false); 918 // Note that we pushed a 0 in above, so for defensive code.... 919 ranges.push_back(1); // Put in some data so we can index to [1]. 920 } 921 922 DCHECK_LT(ranges.back(), kSampleType_MAX); 923 924 if (!StatisticsRecorder::FindHistogram(name, &histogram)) { 925 // To avoid racy destruction at shutdown, the following will be leaked. 926 CustomHistogram* tentative_histogram = new CustomHistogram(name, ranges); 927 tentative_histogram->InitializedCustomBucketRange(ranges); 928 tentative_histogram->SetFlags(flags); 929 histogram = 930 StatisticsRecorder::RegisterOrDeleteDuplicate(tentative_histogram); 931 } 932 933 DCHECK_EQ(histogram->histogram_type(), CUSTOM_HISTOGRAM); 934 DCHECK(histogram->HasConstructorArguments(ranges[1], ranges.back(), 935 ranges.size())); 936 return histogram; 937 } 938 939 Histogram::ClassType CustomHistogram::histogram_type() const { 940 return CUSTOM_HISTOGRAM; 941 } 942 943 CustomHistogram::CustomHistogram(const std::string& name, 944 const std::vector<Sample>& custom_ranges) 945 : Histogram(name, custom_ranges[1], custom_ranges.back(), 946 custom_ranges.size()) { 947 DCHECK_GT(custom_ranges.size(), 1u); 948 DCHECK_EQ(custom_ranges[0], 0); 949 } 950 951 void CustomHistogram::InitializedCustomBucketRange( 952 const std::vector<Sample>& custom_ranges) { 953 DCHECK_GT(custom_ranges.size(), 1u); 954 DCHECK_EQ(custom_ranges[0], 0); 955 DCHECK_LE(custom_ranges.size(), bucket_count()); 956 for (size_t index = 0; index < custom_ranges.size(); ++index) 957 SetBucketRange(index, custom_ranges[index]); 958 ResetRangeChecksum(); 959 } 960 961 double CustomHistogram::GetBucketSize(Count current, size_t i) const { 962 return 1; 963 } 964 965 //------------------------------------------------------------------------------ 966 // The next section handles global (central) support for all histograms, as well 967 // as startup/teardown of this service. 968 //------------------------------------------------------------------------------ 969 970 // This singleton instance should be started during the single threaded portion 971 // of main(), and hence it is not thread safe. It initializes globals to 972 // provide support for all future calls. 973 StatisticsRecorder::StatisticsRecorder() { 974 DCHECK(!histograms_); 975 if (lock_ == NULL) { 976 // This will leak on purpose. It's the only way to make sure we won't race 977 // against the static uninitialization of the module while one of our 978 // static methods relying on the lock get called at an inappropriate time 979 // during the termination phase. Since it's a static data member, we will 980 // leak one per process, which would be similar to the instance allocated 981 // during static initialization and released only on process termination. 982 lock_ = new base::Lock; 983 } 984 base::AutoLock auto_lock(*lock_); 985 histograms_ = new HistogramMap; 986 } 987 988 StatisticsRecorder::~StatisticsRecorder() { 989 DCHECK(histograms_ && lock_); 990 991 if (dump_on_exit_) { 992 std::string output; 993 WriteGraph("", &output); 994 LOG(INFO) << output; 995 } 996 // Clean up. 997 HistogramMap* histograms = NULL; 998 { 999 base::AutoLock auto_lock(*lock_); 1000 histograms = histograms_; 1001 histograms_ = NULL; 1002 } 1003 delete histograms; 1004 // We don't delete lock_ on purpose to avoid having to properly protect 1005 // against it going away after we checked for NULL in the static methods. 1006 } 1007 1008 // static 1009 bool StatisticsRecorder::IsActive() { 1010 if (lock_ == NULL) 1011 return false; 1012 base::AutoLock auto_lock(*lock_); 1013 return NULL != histograms_; 1014 } 1015 1016 Histogram* StatisticsRecorder::RegisterOrDeleteDuplicate(Histogram* histogram) { 1017 DCHECK(histogram->HasValidRangeChecksum()); 1018 if (lock_ == NULL) 1019 return histogram; 1020 base::AutoLock auto_lock(*lock_); 1021 if (!histograms_) 1022 return histogram; 1023 const std::string name = histogram->histogram_name(); 1024 HistogramMap::iterator it = histograms_->find(name); 1025 // Avoid overwriting a previous registration. 1026 if (histograms_->end() == it) { 1027 (*histograms_)[name] = histogram; 1028 } else { 1029 delete histogram; // We already have one by this name. 1030 histogram = it->second; 1031 } 1032 return histogram; 1033 } 1034 1035 // static 1036 void StatisticsRecorder::WriteHTMLGraph(const std::string& query, 1037 std::string* output) { 1038 if (!IsActive()) 1039 return; 1040 output->append("<html><head><title>About Histograms"); 1041 if (!query.empty()) 1042 output->append(" - " + query); 1043 output->append("</title>" 1044 // We'd like the following no-cache... but it doesn't work. 1045 // "<META HTTP-EQUIV=\"Pragma\" CONTENT=\"no-cache\">" 1046 "</head><body>"); 1047 1048 Histograms snapshot; 1049 GetSnapshot(query, &snapshot); 1050 for (Histograms::iterator it = snapshot.begin(); 1051 it != snapshot.end(); 1052 ++it) { 1053 (*it)->WriteHTMLGraph(output); 1054 output->append("<br><hr><br>"); 1055 } 1056 output->append("</body></html>"); 1057 } 1058 1059 // static 1060 void StatisticsRecorder::WriteGraph(const std::string& query, 1061 std::string* output) { 1062 if (!IsActive()) 1063 return; 1064 if (query.length()) 1065 StringAppendF(output, "Collections of histograms for %s\n", query.c_str()); 1066 else 1067 output->append("Collections of all histograms\n"); 1068 1069 Histograms snapshot; 1070 GetSnapshot(query, &snapshot); 1071 for (Histograms::iterator it = snapshot.begin(); 1072 it != snapshot.end(); 1073 ++it) { 1074 (*it)->WriteAscii(true, "\n", output); 1075 output->append("\n"); 1076 } 1077 } 1078 1079 // static 1080 void StatisticsRecorder::GetHistograms(Histograms* output) { 1081 if (lock_ == NULL) 1082 return; 1083 base::AutoLock auto_lock(*lock_); 1084 if (!histograms_) 1085 return; 1086 for (HistogramMap::iterator it = histograms_->begin(); 1087 histograms_->end() != it; 1088 ++it) { 1089 DCHECK_EQ(it->first, it->second->histogram_name()); 1090 output->push_back(it->second); 1091 } 1092 } 1093 1094 bool StatisticsRecorder::FindHistogram(const std::string& name, 1095 Histogram** histogram) { 1096 if (lock_ == NULL) 1097 return false; 1098 base::AutoLock auto_lock(*lock_); 1099 if (!histograms_) 1100 return false; 1101 HistogramMap::iterator it = histograms_->find(name); 1102 if (histograms_->end() == it) 1103 return false; 1104 *histogram = it->second; 1105 return true; 1106 } 1107 1108 // private static 1109 void StatisticsRecorder::GetSnapshot(const std::string& query, 1110 Histograms* snapshot) { 1111 if (lock_ == NULL) 1112 return; 1113 base::AutoLock auto_lock(*lock_); 1114 if (!histograms_) 1115 return; 1116 for (HistogramMap::iterator it = histograms_->begin(); 1117 histograms_->end() != it; 1118 ++it) { 1119 if (it->first.find(query) != std::string::npos) 1120 snapshot->push_back(it->second); 1121 } 1122 } 1123 1124 // static 1125 StatisticsRecorder::HistogramMap* StatisticsRecorder::histograms_ = NULL; 1126 // static 1127 base::Lock* StatisticsRecorder::lock_ = NULL; 1128 // static 1129 bool StatisticsRecorder::dump_on_exit_ = false; 1130 1131 } // namespace base 1132