// Copyright 2014 The Chromium Authors
// 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 <string_view>
#include "base/check_op.h"
#include "base/metrics/histogram.h"
#include "base/metrics/statistics_recorder.h"
#include "base/no_destructor.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;
size_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:
// TODO(crbug.com/40824087): min/max parameters are redundant with "ranges"
// and can probably be removed.
GroupedHistogram(const char* metric_to_override,
Sample minimum,
Sample maximum,
const base::BucketRanges* ranges)
: Histogram(metric_to_override, ranges),
minimum_(minimum),
maximum_(maximum),
bucket_count_(ranges->bucket_count()) {}
GroupedHistogram(const GroupedHistogram&) = delete;
GroupedHistogram& operator=(const GroupedHistogram&) = delete;
~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_;
};
// 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,
size_t bucket_count,
int32_t flags) {
std::string_view 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 < std::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