1 // Copyright (c) 2006-2008 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/histogram.h" 11 12 #include <math.h> 13 #include <string> 14 15 #include "base/logging.h" 16 #include "base/pickle.h" 17 #include "base/string_util.h" 18 19 using base::TimeDelta; 20 21 typedef Histogram::Count Count; 22 23 scoped_refptr<Histogram> Histogram::FactoryGet(const std::string& name, 24 Sample minimum, Sample maximum, size_t bucket_count, Flags flags) { 25 scoped_refptr<Histogram> histogram(NULL); 26 27 // Defensive code. 28 if (minimum <= 0) 29 minimum = 1; 30 if (maximum >= kSampleType_MAX) 31 maximum = kSampleType_MAX - 1; 32 33 if (StatisticsRecorder::FindHistogram(name, &histogram)) { 34 DCHECK(histogram.get() != NULL); 35 } else { 36 histogram = new Histogram(name, minimum, maximum, bucket_count); 37 scoped_refptr<Histogram> registered_histogram(NULL); 38 StatisticsRecorder::FindHistogram(name, ®istered_histogram); 39 // Allow a NULL return to mean that the StatisticsRecorder was not started. 40 if (registered_histogram.get() != NULL && 41 registered_histogram.get() != histogram.get()) 42 histogram = registered_histogram; 43 } 44 45 DCHECK(HISTOGRAM == histogram->histogram_type()); 46 DCHECK(histogram->HasConstructorArguments(minimum, maximum, bucket_count)); 47 histogram->SetFlags(flags); 48 return histogram; 49 } 50 51 scoped_refptr<Histogram> Histogram::FactoryGet(const std::string& name, 52 base::TimeDelta minimum, base::TimeDelta maximum, size_t bucket_count, 53 Flags flags) { 54 return FactoryGet(name, minimum.InMilliseconds(), maximum.InMilliseconds(), 55 bucket_count, flags); 56 } 57 58 Histogram::Histogram(const std::string& name, Sample minimum, 59 Sample maximum, size_t bucket_count) 60 : histogram_name_(name), 61 declared_min_(minimum), 62 declared_max_(maximum), 63 bucket_count_(bucket_count), 64 flags_(kNoFlags), 65 ranges_(bucket_count + 1, 0), 66 sample_() { 67 Initialize(); 68 } 69 70 Histogram::Histogram(const std::string& name, TimeDelta minimum, 71 TimeDelta maximum, size_t bucket_count) 72 : histogram_name_(name), 73 declared_min_(static_cast<int> (minimum.InMilliseconds())), 74 declared_max_(static_cast<int> (maximum.InMilliseconds())), 75 bucket_count_(bucket_count), 76 flags_(kNoFlags), 77 ranges_(bucket_count + 1, 0), 78 sample_() { 79 Initialize(); 80 } 81 82 Histogram::~Histogram() { 83 if (StatisticsRecorder::dump_on_exit()) { 84 std::string output; 85 WriteAscii(true, "\n", &output); 86 LOG(INFO) << output; 87 } 88 89 // Just to make sure most derived class did this properly... 90 DCHECK(ValidateBucketRanges()); 91 } 92 93 void Histogram::Add(int value) { 94 if (value >= kSampleType_MAX) 95 value = kSampleType_MAX - 1; 96 if (value < 0) 97 value = 0; 98 size_t index = BucketIndex(value); 99 DCHECK(value >= ranges(index)); 100 DCHECK(value < ranges(index + 1)); 101 Accumulate(value, 1, index); 102 } 103 104 void Histogram::AddSampleSet(const SampleSet& sample) { 105 sample_.Add(sample); 106 } 107 108 // The following methods provide a graphical histogram display. 109 void Histogram::WriteHTMLGraph(std::string* output) const { 110 // TBD(jar) Write a nice HTML bar chart, with divs an mouse-overs etc. 111 output->append("<PRE>"); 112 WriteAscii(true, "<br>", output); 113 output->append("</PRE>"); 114 } 115 116 void Histogram::WriteAscii(bool graph_it, const std::string& newline, 117 std::string* output) const { 118 // Get local (stack) copies of all effectively volatile class data so that we 119 // are consistent across our output activities. 120 SampleSet snapshot; 121 SnapshotSample(&snapshot); 122 Count sample_count = snapshot.TotalCount(); 123 124 WriteAsciiHeader(snapshot, sample_count, output); 125 output->append(newline); 126 127 // Prepare to normalize graphical rendering of bucket contents. 128 double max_size = 0; 129 if (graph_it) 130 max_size = GetPeakBucketSize(snapshot); 131 132 // Calculate space needed to print bucket range numbers. Leave room to print 133 // nearly the largest bucket range without sliding over the histogram. 134 size_t largest_non_empty_bucket = bucket_count() - 1; 135 while (0 == snapshot.counts(largest_non_empty_bucket)) { 136 if (0 == largest_non_empty_bucket) 137 break; // All buckets are empty. 138 --largest_non_empty_bucket; 139 } 140 141 // Calculate largest print width needed for any of our bucket range displays. 142 size_t print_width = 1; 143 for (size_t i = 0; i < bucket_count(); ++i) { 144 if (snapshot.counts(i)) { 145 size_t width = GetAsciiBucketRange(i).size() + 1; 146 if (width > print_width) 147 print_width = width; 148 } 149 } 150 151 int64 remaining = sample_count; 152 int64 past = 0; 153 // Output the actual histogram graph. 154 for (size_t i = 0; i < bucket_count(); ++i) { 155 Count current = snapshot.counts(i); 156 if (!current && !PrintEmptyBucket(i)) 157 continue; 158 remaining -= current; 159 std::string range = GetAsciiBucketRange(i); 160 output->append(range); 161 for (size_t j = 0; range.size() + j < print_width + 1; ++j) 162 output->push_back(' '); 163 if (0 == current && i < bucket_count() - 1 && 0 == snapshot.counts(i + 1)) { 164 while (i < bucket_count() - 1 && 0 == snapshot.counts(i + 1)) 165 ++i; 166 output->append("... "); 167 output->append(newline); 168 continue; // No reason to plot emptiness. 169 } 170 double current_size = GetBucketSize(current, i); 171 if (graph_it) 172 WriteAsciiBucketGraph(current_size, max_size, output); 173 WriteAsciiBucketContext(past, current, remaining, i, output); 174 output->append(newline); 175 past += current; 176 } 177 DCHECK(past == sample_count); 178 } 179 180 bool Histogram::ValidateBucketRanges() const { 181 // Standard assertions that all bucket ranges should satisfy. 182 DCHECK(ranges_.size() == bucket_count_ + 1); 183 DCHECK_EQ(0, ranges_[0]); 184 DCHECK(declared_min() == ranges_[1]); 185 DCHECK(declared_max() == ranges_[bucket_count_ - 1]); 186 DCHECK(kSampleType_MAX == ranges_[bucket_count_]); 187 return true; 188 } 189 190 void Histogram::Initialize() { 191 sample_.Resize(*this); 192 if (declared_min_ <= 0) 193 declared_min_ = 1; 194 if (declared_max_ >= kSampleType_MAX) 195 declared_max_ = kSampleType_MAX - 1; 196 DCHECK(declared_min_ <= declared_max_); 197 DCHECK_LT(1u, bucket_count_); 198 size_t maximal_bucket_count = declared_max_ - declared_min_ + 2; 199 DCHECK(bucket_count_ <= maximal_bucket_count); 200 DCHECK_EQ(0, ranges_[0]); 201 ranges_[bucket_count_] = kSampleType_MAX; 202 InitializeBucketRange(); 203 DCHECK(ValidateBucketRanges()); 204 StatisticsRecorder::Register(this); 205 } 206 207 // Calculate what range of values are held in each bucket. 208 // We have to be careful that we don't pick a ratio between starting points in 209 // consecutive buckets that is sooo small, that the integer bounds are the same 210 // (effectively making one bucket get no values). We need to avoid: 211 // (ranges_[i] == ranges_[i + 1] 212 // To avoid that, we just do a fine-grained bucket width as far as we need to 213 // until we get a ratio that moves us along at least 2 units at a time. From 214 // that bucket onward we do use the exponential growth of buckets. 215 void Histogram::InitializeBucketRange() { 216 double log_max = log(static_cast<double>(declared_max())); 217 double log_ratio; 218 double log_next; 219 size_t bucket_index = 1; 220 Sample current = declared_min(); 221 SetBucketRange(bucket_index, current); 222 while (bucket_count() > ++bucket_index) { 223 double log_current; 224 log_current = log(static_cast<double>(current)); 225 // Calculate the count'th root of the range. 226 log_ratio = (log_max - log_current) / (bucket_count() - bucket_index); 227 // See where the next bucket would start. 228 log_next = log_current + log_ratio; 229 int next; 230 next = static_cast<int>(floor(exp(log_next) + 0.5)); 231 if (next > current) 232 current = next; 233 else 234 ++current; // Just do a narrow bucket, and keep trying. 235 SetBucketRange(bucket_index, current); 236 } 237 238 DCHECK(bucket_count() == bucket_index); 239 } 240 241 size_t Histogram::BucketIndex(Sample value) const { 242 // Use simple binary search. This is very general, but there are better 243 // approaches if we knew that the buckets were linearly distributed. 244 DCHECK(ranges(0) <= value); 245 DCHECK(ranges(bucket_count()) > value); 246 size_t under = 0; 247 size_t over = bucket_count(); 248 size_t mid; 249 250 do { 251 DCHECK(over >= under); 252 mid = (over + under)/2; 253 if (mid == under) 254 break; 255 if (ranges(mid) <= value) 256 under = mid; 257 else 258 over = mid; 259 } while (true); 260 261 DCHECK(ranges(mid) <= value && ranges(mid+1) > value); 262 return mid; 263 } 264 265 // Use the actual bucket widths (like a linear histogram) until the widths get 266 // over some transition value, and then use that transition width. Exponentials 267 // get so big so fast (and we don't expect to see a lot of entries in the large 268 // buckets), so we need this to make it possible to see what is going on and 269 // not have 0-graphical-height buckets. 270 double Histogram::GetBucketSize(Count current, size_t i) const { 271 DCHECK(ranges(i + 1) > ranges(i)); 272 static const double kTransitionWidth = 5; 273 double denominator = ranges(i + 1) - ranges(i); 274 if (denominator > kTransitionWidth) 275 denominator = kTransitionWidth; // Stop trying to normalize. 276 return current/denominator; 277 } 278 279 //------------------------------------------------------------------------------ 280 // The following two methods can be overridden to provide a thread safe 281 // version of this class. The cost of locking is low... but an error in each 282 // of these methods has minimal impact. For now, I'll leave this unlocked, 283 // and I don't believe I can loose more than a count or two. 284 // The vectors are NOT reallocated, so there is no risk of them moving around. 285 286 // Update histogram data with new sample. 287 void Histogram::Accumulate(Sample value, Count count, size_t index) { 288 // Note locking not done in this version!!! 289 sample_.Accumulate(value, count, index); 290 } 291 292 // Do a safe atomic snapshot of sample data. 293 // This implementation assumes we are on a safe single thread. 294 void Histogram::SnapshotSample(SampleSet* sample) const { 295 // Note locking not done in this version!!! 296 *sample = sample_; 297 } 298 299 //------------------------------------------------------------------------------ 300 // Accessor methods 301 302 void Histogram::SetBucketRange(size_t i, Sample value) { 303 DCHECK(bucket_count_ > i); 304 ranges_[i] = value; 305 } 306 307 //------------------------------------------------------------------------------ 308 // Private methods 309 310 double Histogram::GetPeakBucketSize(const SampleSet& snapshot) const { 311 double max = 0; 312 for (size_t i = 0; i < bucket_count() ; ++i) { 313 double current_size = GetBucketSize(snapshot.counts(i), i); 314 if (current_size > max) 315 max = current_size; 316 } 317 return max; 318 } 319 320 void Histogram::WriteAsciiHeader(const SampleSet& snapshot, 321 Count sample_count, 322 std::string* output) const { 323 StringAppendF(output, 324 "Histogram: %s recorded %d samples", 325 histogram_name().c_str(), 326 sample_count); 327 if (0 == sample_count) { 328 DCHECK_EQ(0, snapshot.sum()); 329 } else { 330 double average = static_cast<float>(snapshot.sum()) / sample_count; 331 double variance = static_cast<float>(snapshot.square_sum())/sample_count 332 - average * average; 333 double standard_deviation = sqrt(variance); 334 335 StringAppendF(output, 336 ", average = %.1f, standard deviation = %.1f", 337 average, standard_deviation); 338 } 339 if (flags_ & ~kHexRangePrintingFlag ) 340 StringAppendF(output, " (flags = 0x%x)", flags_ & ~kHexRangePrintingFlag); 341 } 342 343 void Histogram::WriteAsciiBucketContext(const int64 past, 344 const Count current, 345 const int64 remaining, 346 const size_t i, 347 std::string* output) const { 348 double scaled_sum = (past + current + remaining) / 100.0; 349 WriteAsciiBucketValue(current, scaled_sum, output); 350 if (0 < i) { 351 double percentage = past / scaled_sum; 352 StringAppendF(output, " {%3.1f%%}", percentage); 353 } 354 } 355 356 const std::string Histogram::GetAsciiBucketRange(size_t i) const { 357 std::string result; 358 if (kHexRangePrintingFlag & flags_) 359 StringAppendF(&result, "%#x", ranges(i)); 360 else 361 StringAppendF(&result, "%d", ranges(i)); 362 return result; 363 } 364 365 void Histogram::WriteAsciiBucketValue(Count current, double scaled_sum, 366 std::string* output) const { 367 StringAppendF(output, " (%d = %3.1f%%)", current, current/scaled_sum); 368 } 369 370 void Histogram::WriteAsciiBucketGraph(double current_size, double max_size, 371 std::string* output) const { 372 const int k_line_length = 72; // Maximal horizontal width of graph. 373 int x_count = static_cast<int>(k_line_length * (current_size / max_size) 374 + 0.5); 375 int x_remainder = k_line_length - x_count; 376 377 while (0 < x_count--) 378 output->append("-"); 379 output->append("O"); 380 while (0 < x_remainder--) 381 output->append(" "); 382 } 383 384 // static 385 std::string Histogram::SerializeHistogramInfo(const Histogram& histogram, 386 const SampleSet& snapshot) { 387 DCHECK(histogram.histogram_type() != NOT_VALID_IN_RENDERER); 388 389 Pickle pickle; 390 pickle.WriteString(histogram.histogram_name()); 391 pickle.WriteInt(histogram.declared_min()); 392 pickle.WriteInt(histogram.declared_max()); 393 pickle.WriteSize(histogram.bucket_count()); 394 pickle.WriteInt(histogram.histogram_type()); 395 pickle.WriteInt(histogram.flags()); 396 397 snapshot.Serialize(&pickle); 398 return std::string(static_cast<const char*>(pickle.data()), pickle.size()); 399 } 400 401 // static 402 bool Histogram::DeserializeHistogramInfo(const std::string& histogram_info) { 403 if (histogram_info.empty()) { 404 return false; 405 } 406 407 Pickle pickle(histogram_info.data(), 408 static_cast<int>(histogram_info.size())); 409 void* iter = NULL; 410 size_t bucket_count; 411 int declared_min; 412 int declared_max; 413 int histogram_type; 414 int pickle_flags; 415 std::string histogram_name; 416 SampleSet sample; 417 418 if (!pickle.ReadString(&iter, &histogram_name) || 419 !pickle.ReadInt(&iter, &declared_min) || 420 !pickle.ReadInt(&iter, &declared_max) || 421 !pickle.ReadSize(&iter, &bucket_count) || 422 !pickle.ReadInt(&iter, &histogram_type) || 423 !pickle.ReadInt(&iter, &pickle_flags) || 424 !sample.Histogram::SampleSet::Deserialize(&iter, pickle)) { 425 LOG(ERROR) << "Pickle error decoding Histogram: " << histogram_name; 426 return false; 427 } 428 DCHECK(pickle_flags & kIPCSerializationSourceFlag); 429 // Since these fields may have come from an untrusted renderer, do additional 430 // checks above and beyond those in Histogram::Initialize() 431 if (declared_max <= 0 || declared_min <= 0 || declared_max < declared_min || 432 INT_MAX / sizeof(Count) <= bucket_count || bucket_count < 2) { 433 LOG(ERROR) << "Values error decoding Histogram: " << histogram_name; 434 return false; 435 } 436 437 Flags flags = static_cast<Flags>(pickle_flags & ~kIPCSerializationSourceFlag); 438 439 DCHECK(histogram_type != NOT_VALID_IN_RENDERER); 440 441 scoped_refptr<Histogram> render_histogram(NULL); 442 443 if (histogram_type == HISTOGRAM) { 444 render_histogram = Histogram::FactoryGet( 445 histogram_name, declared_min, declared_max, bucket_count, flags); 446 } else if (histogram_type == LINEAR_HISTOGRAM) { 447 render_histogram = LinearHistogram::FactoryGet( 448 histogram_name, declared_min, declared_max, bucket_count, flags); 449 } else if (histogram_type == BOOLEAN_HISTOGRAM) { 450 render_histogram = BooleanHistogram::FactoryGet(histogram_name, flags); 451 } else { 452 LOG(ERROR) << "Error Deserializing Histogram Unknown histogram_type: " << 453 histogram_type; 454 return false; 455 } 456 457 DCHECK(declared_min == render_histogram->declared_min()); 458 DCHECK(declared_max == render_histogram->declared_max()); 459 DCHECK(bucket_count == render_histogram->bucket_count()); 460 DCHECK(histogram_type == render_histogram->histogram_type()); 461 462 if (render_histogram->flags() & kIPCSerializationSourceFlag) { 463 DLOG(INFO) << "Single process mode, histogram observed and not copied: " << 464 histogram_name; 465 } else { 466 DCHECK(flags == (flags & render_histogram->flags())); 467 render_histogram->AddSampleSet(sample); 468 } 469 470 return true; 471 } 472 473 //------------------------------------------------------------------------------ 474 // Methods for the Histogram::SampleSet class 475 //------------------------------------------------------------------------------ 476 477 Histogram::SampleSet::SampleSet() 478 : counts_(), 479 sum_(0), 480 square_sum_(0) { 481 } 482 483 void Histogram::SampleSet::Resize(const Histogram& histogram) { 484 counts_.resize(histogram.bucket_count(), 0); 485 } 486 487 void Histogram::SampleSet::CheckSize(const Histogram& histogram) const { 488 DCHECK(counts_.size() == histogram.bucket_count()); 489 } 490 491 492 void Histogram::SampleSet::Accumulate(Sample value, Count count, 493 size_t index) { 494 DCHECK(count == 1 || count == -1); 495 counts_[index] += count; 496 sum_ += count * value; 497 square_sum_ += (count * value) * static_cast<int64>(value); 498 DCHECK_GE(counts_[index], 0); 499 DCHECK_GE(sum_, 0); 500 DCHECK_GE(square_sum_, 0); 501 } 502 503 Count Histogram::SampleSet::TotalCount() const { 504 Count total = 0; 505 for (Counts::const_iterator it = counts_.begin(); 506 it != counts_.end(); 507 ++it) { 508 total += *it; 509 } 510 return total; 511 } 512 513 void Histogram::SampleSet::Add(const SampleSet& other) { 514 DCHECK(counts_.size() == other.counts_.size()); 515 sum_ += other.sum_; 516 square_sum_ += other.square_sum_; 517 for (size_t index = 0; index < counts_.size(); ++index) 518 counts_[index] += other.counts_[index]; 519 } 520 521 void Histogram::SampleSet::Subtract(const SampleSet& other) { 522 DCHECK(counts_.size() == other.counts_.size()); 523 // Note: Race conditions in snapshotting a sum or square_sum may lead to 524 // (temporary) negative values when snapshots are later combined (and deltas 525 // calculated). As a result, we don't currently CHCEK() for positive values. 526 sum_ -= other.sum_; 527 square_sum_ -= other.square_sum_; 528 for (size_t index = 0; index < counts_.size(); ++index) { 529 counts_[index] -= other.counts_[index]; 530 DCHECK_GE(counts_[index], 0); 531 } 532 } 533 534 bool Histogram::SampleSet::Serialize(Pickle* pickle) const { 535 pickle->WriteInt64(sum_); 536 pickle->WriteInt64(square_sum_); 537 pickle->WriteSize(counts_.size()); 538 539 for (size_t index = 0; index < counts_.size(); ++index) { 540 pickle->WriteInt(counts_[index]); 541 } 542 543 return true; 544 } 545 546 bool Histogram::SampleSet::Deserialize(void** iter, const Pickle& pickle) { 547 DCHECK_EQ(counts_.size(), 0u); 548 DCHECK_EQ(sum_, 0); 549 DCHECK_EQ(square_sum_, 0); 550 551 size_t counts_size; 552 553 if (!pickle.ReadInt64(iter, &sum_) || 554 !pickle.ReadInt64(iter, &square_sum_) || 555 !pickle.ReadSize(iter, &counts_size)) { 556 return false; 557 } 558 559 if (counts_size == 0) 560 return false; 561 562 for (size_t index = 0; index < counts_size; ++index) { 563 int i; 564 if (!pickle.ReadInt(iter, &i)) 565 return false; 566 counts_.push_back(i); 567 } 568 569 return true; 570 } 571 572 //------------------------------------------------------------------------------ 573 // LinearHistogram: This histogram uses a traditional set of evenly spaced 574 // buckets. 575 //------------------------------------------------------------------------------ 576 577 scoped_refptr<Histogram> LinearHistogram::FactoryGet( 578 const std::string& name, Sample minimum, Sample maximum, 579 size_t bucket_count, Flags flags) { 580 scoped_refptr<Histogram> histogram(NULL); 581 582 if (minimum <= 0) 583 minimum = 1; 584 if (maximum >= kSampleType_MAX) 585 maximum = kSampleType_MAX - 1; 586 587 if (StatisticsRecorder::FindHistogram(name, &histogram)) { 588 DCHECK(histogram.get() != NULL); 589 } else { 590 histogram = new LinearHistogram(name, minimum, maximum, bucket_count); 591 scoped_refptr<Histogram> registered_histogram(NULL); 592 StatisticsRecorder::FindHistogram(name, ®istered_histogram); 593 if (registered_histogram.get() != NULL && 594 registered_histogram.get() != histogram.get()) 595 histogram = registered_histogram; 596 } 597 598 DCHECK(LINEAR_HISTOGRAM == histogram->histogram_type()); 599 DCHECK(histogram->HasConstructorArguments(minimum, maximum, bucket_count)); 600 histogram->SetFlags(flags); 601 return histogram; 602 } 603 604 scoped_refptr<Histogram> LinearHistogram::FactoryGet(const std::string& name, 605 base::TimeDelta minimum, base::TimeDelta maximum, size_t bucket_count, 606 Flags flags) { 607 return FactoryGet(name, minimum.InMilliseconds(), maximum.InMilliseconds(), 608 bucket_count, flags); 609 } 610 611 LinearHistogram::LinearHistogram(const std::string& name, Sample minimum, 612 Sample maximum, size_t bucket_count) 613 : Histogram(name, minimum >= 1 ? minimum : 1, maximum, bucket_count) { 614 InitializeBucketRange(); 615 DCHECK(ValidateBucketRanges()); 616 } 617 618 LinearHistogram::LinearHistogram(const std::string& name, 619 TimeDelta minimum, TimeDelta maximum, size_t bucket_count) 620 : Histogram(name, minimum >= TimeDelta::FromMilliseconds(1) ? 621 minimum : TimeDelta::FromMilliseconds(1), 622 maximum, bucket_count) { 623 // Do a "better" (different) job at init than a base classes did... 624 InitializeBucketRange(); 625 DCHECK(ValidateBucketRanges()); 626 } 627 628 void LinearHistogram::SetRangeDescriptions( 629 const DescriptionPair descriptions[]) { 630 for (int i =0; descriptions[i].description; ++i) { 631 bucket_description_[descriptions[i].sample] = descriptions[i].description; 632 } 633 } 634 635 const std::string LinearHistogram::GetAsciiBucketRange(size_t i) const { 636 int range = ranges(i); 637 BucketDescriptionMap::const_iterator it = bucket_description_.find(range); 638 if (it == bucket_description_.end()) 639 return Histogram::GetAsciiBucketRange(i); 640 return it->second; 641 } 642 643 bool LinearHistogram::PrintEmptyBucket(size_t index) const { 644 return bucket_description_.find(ranges(index)) == bucket_description_.end(); 645 } 646 647 648 void LinearHistogram::InitializeBucketRange() { 649 DCHECK_LT(0, declared_min()); // 0 is the underflow bucket here. 650 double min = declared_min(); 651 double max = declared_max(); 652 size_t i; 653 for (i = 1; i < bucket_count(); ++i) { 654 double linear_range = (min * (bucket_count() -1 - i) + max * (i - 1)) / 655 (bucket_count() - 2); 656 SetBucketRange(i, static_cast<int> (linear_range + 0.5)); 657 } 658 } 659 660 double LinearHistogram::GetBucketSize(Count current, size_t i) const { 661 DCHECK(ranges(i + 1) > ranges(i)); 662 // Adjacent buckets with different widths would have "surprisingly" many (few) 663 // samples in a histogram if we didn't normalize this way. 664 double denominator = ranges(i + 1) - ranges(i); 665 return current/denominator; 666 } 667 668 //------------------------------------------------------------------------------ 669 // This section provides implementation for BooleanHistogram. 670 //------------------------------------------------------------------------------ 671 672 scoped_refptr<Histogram> BooleanHistogram::FactoryGet(const std::string& name, 673 Flags flags) { 674 scoped_refptr<Histogram> histogram(NULL); 675 676 if (StatisticsRecorder::FindHistogram(name, &histogram)) { 677 DCHECK(histogram.get() != NULL); 678 } else { 679 histogram = new BooleanHistogram(name); 680 scoped_refptr<Histogram> registered_histogram(NULL); 681 StatisticsRecorder::FindHistogram(name, ®istered_histogram); 682 if (registered_histogram.get() != NULL && 683 registered_histogram.get() != histogram.get()) 684 histogram = registered_histogram; 685 } 686 687 DCHECK(BOOLEAN_HISTOGRAM == histogram->histogram_type()); 688 histogram->SetFlags(flags); 689 return histogram; 690 } 691 692 693 //------------------------------------------------------------------------------ 694 // The next section handles global (central) support for all histograms, as well 695 // as startup/teardown of this service. 696 //------------------------------------------------------------------------------ 697 698 // This singleton instance should be started during the single threaded portion 699 // of main(), and hence it is not thread safe. It initializes globals to 700 // provide support for all future calls. 701 StatisticsRecorder::StatisticsRecorder() { 702 DCHECK(!histograms_); 703 lock_ = new Lock; 704 histograms_ = new HistogramMap; 705 } 706 707 StatisticsRecorder::~StatisticsRecorder() { 708 DCHECK(histograms_); 709 710 if (dump_on_exit_) { 711 std::string output; 712 WriteGraph("", &output); 713 LOG(INFO) << output; 714 } 715 716 // Clean up. 717 delete histograms_; 718 histograms_ = NULL; 719 delete lock_; 720 lock_ = NULL; 721 } 722 723 // static 724 bool StatisticsRecorder::WasStarted() { 725 return NULL != histograms_; 726 } 727 728 // Note: We can't accept a ref_ptr to |histogram| because we *might* not keep a 729 // reference, and we are called while in the Histogram constructor. In that 730 // scenario, a ref_ptr would have incremented the ref count when the histogram 731 // was passed to us, decremented it when we returned, and the instance would be 732 // destroyed before assignment (when value was returned by new). 733 // static 734 void StatisticsRecorder::Register(Histogram* histogram) { 735 if (!histograms_) 736 return; 737 const std::string name = histogram->histogram_name(); 738 AutoLock auto_lock(*lock_); 739 740 DCHECK(histograms_->end() == histograms_->find(name)); 741 742 (*histograms_)[name] = histogram; 743 return; 744 } 745 746 // static 747 void StatisticsRecorder::WriteHTMLGraph(const std::string& query, 748 std::string* output) { 749 if (!histograms_) 750 return; 751 output->append("<html><head><title>About Histograms"); 752 if (!query.empty()) 753 output->append(" - " + query); 754 output->append("</title>" 755 // We'd like the following no-cache... but it doesn't work. 756 // "<META HTTP-EQUIV=\"Pragma\" CONTENT=\"no-cache\">" 757 "</head><body>"); 758 759 Histograms snapshot; 760 GetSnapshot(query, &snapshot); 761 for (Histograms::iterator it = snapshot.begin(); 762 it != snapshot.end(); 763 ++it) { 764 (*it)->WriteHTMLGraph(output); 765 output->append("<br><hr><br>"); 766 } 767 output->append("</body></html>"); 768 } 769 770 // static 771 void StatisticsRecorder::WriteGraph(const std::string& query, 772 std::string* output) { 773 if (!histograms_) 774 return; 775 if (query.length()) 776 StringAppendF(output, "Collections of histograms for %s\n", query.c_str()); 777 else 778 output->append("Collections of all histograms\n"); 779 780 Histograms snapshot; 781 GetSnapshot(query, &snapshot); 782 for (Histograms::iterator it = snapshot.begin(); 783 it != snapshot.end(); 784 ++it) { 785 (*it)->WriteAscii(true, "\n", output); 786 output->append("\n"); 787 } 788 } 789 790 // static 791 void StatisticsRecorder::GetHistograms(Histograms* output) { 792 if (!histograms_) 793 return; 794 AutoLock auto_lock(*lock_); 795 for (HistogramMap::iterator it = histograms_->begin(); 796 histograms_->end() != it; 797 ++it) { 798 output->push_back(it->second); 799 } 800 } 801 802 bool StatisticsRecorder::FindHistogram(const std::string& name, 803 scoped_refptr<Histogram>* histogram) { 804 if (!histograms_) 805 return false; 806 AutoLock auto_lock(*lock_); 807 HistogramMap::iterator it = histograms_->find(name); 808 if (histograms_->end() == it) 809 return false; 810 *histogram = it->second; 811 return true; 812 } 813 814 // private static 815 void StatisticsRecorder::GetSnapshot(const std::string& query, 816 Histograms* snapshot) { 817 AutoLock auto_lock(*lock_); 818 for (HistogramMap::iterator it = histograms_->begin(); 819 histograms_->end() != it; 820 ++it) { 821 if (it->first.find(query) != std::string::npos) 822 snapshot->push_back(it->second); 823 } 824 } 825 826 // static 827 StatisticsRecorder::HistogramMap* StatisticsRecorder::histograms_ = NULL; 828 // static 829 Lock* StatisticsRecorder::lock_ = NULL; 830 // static 831 bool StatisticsRecorder::dump_on_exit_ = false; 832