text: // Copyright 2014 The Chromium Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "chromecast/base/metrics/grouped_histogram.h"
  
  #include <stddef.h>
  #include <stdint.h>
  
  #include "base/check_op.h"
  #include "base/metrics/histogram.h"
  #include "base/metrics/statistics_recorder.h"
  #include "base/no_destructor.h"
  #include "base/stl_util.h"
  #include "base/strings/string_piece.h"
  #include "base/strings/stringprintf.h"
  #include "base/synchronization/lock.h"
  #include "base/time/time.h"
  
  namespace chromecast {
  namespace metrics {
  
  namespace {
  
  const char kAppNameErrorNoApp[] = "ERROR_NO_APP_REGISTERED";
  
  // Current app name guarded by lock.
  struct CurrentAppNameWithLock {
    base::Lock lock;
    std::string app_name;
  };
  
  CurrentAppNameWithLock& GetCurrentApp() {
    static base::NoDestructor<CurrentAppNameWithLock> current_app;
    return *current_app;
  }
  
  std::string GetAppName() {
    base::AutoLock lock(GetCurrentApp().lock);
    const std::string& app_name = GetCurrentApp().app_name;
    return app_name.empty() ? kAppNameErrorNoApp : app_name;
  }
  
  struct HistogramArgs {
    const char* name;
    int minimum;
    int maximum;
    uint32_t bucket_count;
  };
  
  // List of metrics to collect using a GroupedHistogram.
  //
  // When adding more Histograms to this list, find the source of the
  // Histogram and look for the construction arguments it uses to add it in.
  const HistogramArgs kHistogramsToGroup[] = {
    {
      "DNS.TotalTime",
      1,
      1000 * 60 * 60,
      100,
    },
    {
      "Net.DNS_Resolution_And_TCP_Connection_Latency2",
      1,
      1000 * 60 * 10,
      100,
    },
    {
      "Net.SSL_Connection_Latency2",
      1,
      1000 * 60,
      100,
    },
    {
      "Net.TCP_Connection_Latency",
      1,
      1000 * 60 * 10,
      100,
    },
    {
      "Net.HttpJob.TotalTime",
      1,
      1000 * 10,
      50,
    },
  };
  
  // This class is used to override a Histogram to generate per-app metrics.
  // It intercepts calls to Add() for a given metric and generates new metrics
  // of the form "<metric-name>.<app-name>".
  class GroupedHistogram : public base::Histogram {
   public:
    GroupedHistogram(const char* metric_to_override,
                     Sample minimum,
                     Sample maximum,
                     const base::BucketRanges* ranges)
        : Histogram(metric_to_override, minimum, maximum, ranges),
          minimum_(minimum),
          maximum_(maximum),
          bucket_count_(ranges->bucket_count()) {}
  
    ~GroupedHistogram() override {
    }
  
    // base::Histogram implementation:
    void Add(Sample value) override {
      Histogram::Add(value);
  
      // Note: This is very inefficient. Fetching the app name (which has a lock)
      // plus doing a search by name with FactoryGet (which also has a lock) makes
      // incrementing a metric relatively slow.
      std::string name(
          base::StringPrintf("%s.%s", histogram_name(), GetAppName().c_str()));
      HistogramBase* grouped_histogram =
          base::Histogram::FactoryGet(name,
                                      minimum_,
                                      maximum_,
                                      bucket_count_,
                                      flags());
      DCHECK(grouped_histogram);
      grouped_histogram->Add(value);
    }
  
   private:
    // Saved construction arguments for reconstructing the Histogram later (with
    // a suffixed app name).
    Sample minimum_;
    Sample maximum_;
    uint32_t bucket_count_;
  
    DISALLOW_COPY_AND_ASSIGN(GroupedHistogram);
  };
  
  // Registers a GroupedHistogram with StatisticsRecorder.  Must be called
  // before any Histogram of the same name has been used.
  // It acts similarly to Histogram::FactoryGet but checks that
  // the histogram is being newly created and does not already exist.
  void PreregisterHistogram(const char* name,
                            GroupedHistogram::Sample minimum,
                            GroupedHistogram::Sample maximum,
                            uint32_t bucket_count,
                            int32_t flags) {
    base::StringPiece name_piece(name);
  
    DCHECK(base::Histogram::InspectConstructionArguments(
        name_piece, &minimum, &maximum, &bucket_count));
    DCHECK(!base::StatisticsRecorder::FindHistogram(name_piece))
        << "Failed to preregister " << name << ", Histogram already exists.";
  
    // To avoid racy destruction at shutdown, the following will be leaked.
    base::BucketRanges* ranges = new base::BucketRanges(bucket_count + 1);
    base::Histogram::InitializeBucketRanges(minimum, maximum, ranges);
    const base::BucketRanges* registered_ranges =
        base::StatisticsRecorder::RegisterOrDeleteDuplicateRanges(ranges);
  
    GroupedHistogram* tentative_histogram =
        new GroupedHistogram(name, minimum, maximum, registered_ranges);
  
    tentative_histogram->SetFlags(flags);
    base::HistogramBase* histogram =
        base::StatisticsRecorder::RegisterOrDeleteDuplicate(tentative_histogram);
  
    DCHECK_EQ(histogram, tentative_histogram);
    DCHECK_EQ(base::HISTOGRAM, histogram->GetHistogramType());
    DCHECK(histogram->HasConstructionArguments(minimum, maximum, bucket_count));
  }
  
  } // namespace
  
  void PreregisterAllGroupedHistograms() {
    for (size_t i = 0; i < base::size(kHistogramsToGroup); ++i) {
      PreregisterHistogram(
          kHistogramsToGroup[i].name,
          kHistogramsToGroup[i].minimum,
          kHistogramsToGroup[i].maximum,
          kHistogramsToGroup[i].bucket_count,
          base::HistogramBase::kUmaTargetedHistogramFlag);
    }
  }
  
  void TagAppStartForGroupedHistograms(const std::string& app_name) {
    base::AutoLock lock(GetCurrentApp().lock);
    GetCurrentApp().app_name = app_name;
  }
  
  } // namespace metrics
  } // namespace chromecast
  