0

[tracing] Implement HistogramDataSource

To record all or specific histogram on threshold.
This has a few advantages over CustomEventRecorder
- Supports perfetto config coming from field tracing
- Supports thresholds
- Supports multiple sessions
- Doesn't rely on chrome_config string

Change-Id: I05287bd98901056c6a2b4cd46b97772dd8493d45
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6254221
Commit-Queue: Etienne Pierre-Doray <etiennep@chromium.org>
Reviewed-by: Mikhail Khokhlov <khokhlov@google.com>
Reviewed-by: Giovanni Ortuno Urquidi <ortuno@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1422191}
This commit is contained in:
Etienne Pierre-doray
2025-02-19 13:57:50 -08:00
committed by Chromium LUCI CQ
parent b5872e20b1
commit 60dfd767e5
13 changed files with 502 additions and 137 deletions

@ -286,10 +286,9 @@ class HistogramRule : public BackgroundTracingRule,
new_sample->set_sample(actual_value);
perfetto::Flow::Global(flow_id)(ctx);
};
TRACE_EVENT_INSTANT(
"toplevel,latency", "HistogramSampleTrigger",
perfetto::Track::FromPointer(this, perfetto::ProcessTrack::Current()),
base::TimeTicks::Now(), trace_details);
auto track = perfetto::NamedTrack("HistogramSamples");
TRACE_EVENT_INSTANT("toplevel,latency", "HistogramSampleTrigger", track,
trace_details);
OnRuleTriggered(actual_value, flow_id);
}

@ -2,10 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <optional>
#include <thread>
#include "base/files/scoped_temp_dir.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/statistics_recorder.h"
#include "base/strings/stringprintf.h"
#include "base/task/common/task_annotator.h"
#include "base/test/test_trace_processor.h"
@ -19,8 +22,10 @@
#include "services/resource_coordinator/public/cpp/memory_instrumentation/tracing_observer_proto.h"
#include "services/tracing/public/cpp/perfetto/metadata_data_source.h"
#include "services/tracing/public/cpp/stack_sampling/tracing_sampler_profiler.h"
#include "services/tracing/public/mojom/perfetto_service.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/perfetto/protos/perfetto/config/chrome/chrome_config.gen.h"
#include "third_party/perfetto/protos/perfetto/config/chrome/histogram_samples.gen.h"
#if BUILDFLAG(IS_POSIX)
#include "base/test/bind.h"
@ -39,28 +44,51 @@ namespace {
const char kDetailedDumpMode[] = "detailed";
const char kBackgroundDumpMode[] = "background";
perfetto::protos::gen::ChromiumHistogramSamplesConfig::HistogramSample
MakeHistogramSample(const std::string& name,
std::optional<int64_t> min_value = std::nullopt,
std::optional<int64_t> max_value = std::nullopt) {
perfetto::protos::gen::ChromiumHistogramSamplesConfig::HistogramSample sample;
sample.set_histogram_name(name);
if (min_value) {
sample.set_min_value(*min_value);
}
if (max_value) {
sample.set_max_value(*max_value);
}
return sample;
}
perfetto::protos::gen::TraceConfig TraceConfigWithHistograms(
const std::string& category_filter_string,
const std::vector<std::string>& histograms) {
// Which categories are specified in the legacy config should not affect
// anything. Only the list of enabled histograms should be read from the
// legacy config.
base::trace_event::TraceConfig trace_event_config;
for (const auto& histogram : histograms) {
trace_event_config.EnableHistogram(histogram);
const std::vector<
perfetto::protos::gen::ChromiumHistogramSamplesConfig::HistogramSample>&
histograms) {
auto perfetto_config = base::test::DefaultTraceConfig("-*", false);
auto* histogram_data_source = perfetto_config.add_data_sources();
auto* histogram_source_config = histogram_data_source->mutable_config();
histogram_source_config->set_name(tracing::mojom::kHistogramSampleSourceName);
histogram_source_config->set_target_buffer(0);
if (!histograms.empty()) {
perfetto::protos::gen::ChromiumHistogramSamplesConfig histogram_config;
for (const auto& histogram : histograms) {
*histogram_config.add_histograms() = histogram;
}
histogram_source_config->set_chromium_histogram_samples_raw(
histogram_config.SerializeAsString());
}
auto perfetto_config =
base::test::DefaultTraceConfig(category_filter_string, false);
for (auto& ds : *perfetto_config.mutable_data_sources()) {
if (ds.config().name() == "track_event") {
ds.mutable_config()->mutable_chrome_config()->set_trace_config(
trace_event_config.ToString());
}
}
return perfetto_config;
}
constexpr const char* histogram_query =
"SELECT "
"EXTRACT_ARG(arg_set_id, \"chrome_histogram_sample.name\") AS name, "
"EXTRACT_ARG(arg_set_id, \"chrome_histogram_sample.sample\") AS value "
"FROM slice "
"WHERE cat = \"disabled-by-default-histogram_samples\"";
perfetto::protos::gen::TraceConfig TraceConfigWithMemoryDumps(
const char* mode) {
const std::string trace_event_config_str = base::StringPrintf(
@ -543,64 +571,105 @@ IN_PROC_BROWSER_TEST_F(TracingEndToEndBrowserTest, TwoSessionsSimple) {
std::vector<std::string>{"cat_event"}));
}
IN_PROC_BROWSER_TEST_F(TracingEndToEndBrowserTest, TwoSessionsHistograms) {
IN_PROC_BROWSER_TEST_F(TracingEndToEndBrowserTest,
TwoSessionsGlobalHistograms) {
ASSERT_FALSE(base::StatisticsRecorder::global_sample_callback());
// Start the first session that records "Test.Foo" histograms
auto config = TraceConfigWithHistograms(
"disabled-by-default-histogram_samples", {"Test.Foo"});
auto config = TraceConfigWithHistograms({});
base::test::TestTraceProcessor ttp1;
ttp1.StartTrace(config);
UMA_HISTOGRAM_BOOLEAN("Test.Foo", true);
UMA_HISTOGRAM_BOOLEAN("Test.Bar", true);
base::UmaHistogramCounts100("Test.Foo", 1);
base::UmaHistogramCounts100("Test.Bar", 2);
// Start the second session that records "Test.Bar" histograms
config = TraceConfigWithHistograms("disabled-by-default-histogram_samples",
{"Test.Bar"});
// Start the second session that records all histograms
config = TraceConfigWithHistograms({});
base::test::TestTraceProcessor ttp2;
ttp2.StartTrace(config);
UMA_HISTOGRAM_BOOLEAN("Test.Foo", true);
UMA_HISTOGRAM_BOOLEAN("Test.Bar", true);
base::UmaHistogramCounts100("Test.Foo", 3);
base::UmaHistogramCounts100("Test.Bar", 4);
// Stop the second session. Its trace should contain only one "Test.Bar"
// sample that was recorded while the session was active.
// It will also contain a "Test.Foo" sample because right now the list of
// monitored histograms is shared between sessions.
// TODO(khokhlov): Fix the test expectations when CustomEventRecorder
// correctly tracks which samples go to which sessions.
// Stop the second session.
absl::Status status = ttp2.StopAndParseTrace();
ASSERT_TRUE(status.ok()) << status.message();
std::string query =
"SELECT "
"EXTRACT_ARG(arg_set_id, \"chrome_histogram_sample.name\") AS name "
"FROM slice "
"WHERE cat = \"disabled-by-default-histogram_samples\"";
auto result = ttp2.RunQuery(query);
auto result = ttp2.RunQuery(histogram_query);
ASSERT_TRUE(result.has_value()) << result.error();
EXPECT_THAT(result.value(),
::testing::ElementsAre(std::vector<std::string>{"name"},
std::vector<std::string>{"Test.Foo"},
std::vector<std::string>{"Test.Bar"}));
EXPECT_THAT(result.value(), ::testing::IsSupersetOf(
{std::vector<std::string>{"name", "value"},
std::vector<std::string>{"Test.Foo", "3"},
std::vector<std::string>{"Test.Bar", "4"}}));
UMA_HISTOGRAM_BOOLEAN("Test.Foo", true);
UMA_HISTOGRAM_BOOLEAN("Test.Bar", true);
base::UmaHistogramCounts100("Test.Foo", 5);
base::UmaHistogramCounts100("Test.Bar", 6);
// Stop the first session. Its trace should contain all samples that were
// recorded while the session was active.
status = ttp1.StopAndParseTrace();
ASSERT_TRUE(status.ok()) << status.message();
ASSERT_FALSE(base::StatisticsRecorder::global_sample_callback());
result = ttp1.RunQuery(histogram_query);
ASSERT_TRUE(result.has_value()) << result.error();
EXPECT_THAT(result.value(), ::testing::IsSupersetOf(
{std::vector<std::string>{"name", "value"},
std::vector<std::string>{"Test.Foo", "1"},
std::vector<std::string>{"Test.Bar", "2"},
std::vector<std::string>{"Test.Foo", "3"},
std::vector<std::string>{"Test.Bar", "4"},
std::vector<std::string>{"Test.Foo", "5"},
std::vector<std::string>{"Test.Bar", "6"}}));
}
IN_PROC_BROWSER_TEST_F(TracingEndToEndBrowserTest,
TwoSessionsTargetedHistograms) {
// Start the first session that records "Test.Foo" histograms
auto config =
TraceConfigWithHistograms({MakeHistogramSample("Test.Foo", 10, 20)});
base::test::TestTraceProcessor ttp1;
ttp1.StartTrace(config);
base::UmaHistogramCounts100("Test.Foo", 1); // Not recorded
base::UmaHistogramCounts100("Test.Foo", 10);
base::UmaHistogramCounts100("Test.Foo", 21); // Not recorded
// Start the second session that records "Test.Bar" histograms
config = TraceConfigWithHistograms({MakeHistogramSample("Test.Bar", 30, 40)});
base::test::TestTraceProcessor ttp2;
ttp2.StartTrace(config);
base::UmaHistogramCounts100("Test.Foo", 11);
base::UmaHistogramCounts100("Test.Bar", 15); // Not recorded
base::UmaHistogramCounts100("Test.Bar", 30);
// Stop the second session. Its trace should contain only one "Test.Bar"
// sample that was recorded while the session was active.
absl::Status status = ttp2.StopAndParseTrace();
ASSERT_TRUE(status.ok()) << status.message();
auto result = ttp2.RunQuery(histogram_query);
ASSERT_TRUE(result.has_value()) << result.error();
EXPECT_THAT(result.value(), ::testing::ElementsAre(
std::vector<std::string>{"name", "value"},
std::vector<std::string>{"Test.Bar", "30"}));
base::UmaHistogramCounts100("Test.Foo", 12);
base::UmaHistogramCounts100("Test.Bar", 31); // Not recorded
// Stop the first session. Its trace should contain all three "Test.Foo"
// samples that were recorded while the session was active.
// Some "Test.Bar" samples will also be there (see the previous comment).
status = ttp1.StopAndParseTrace();
ASSERT_TRUE(status.ok()) << status.message();
result = ttp1.RunQuery(query);
result = ttp1.RunQuery(histogram_query);
ASSERT_TRUE(result.has_value()) << result.error();
EXPECT_THAT(result.value(),
::testing::ElementsAre(std::vector<std::string>{"name"},
std::vector<std::string>{"Test.Foo"},
std::vector<std::string>{"Test.Foo"},
std::vector<std::string>{"Test.Bar"},
std::vector<std::string>{"Test.Foo"},
std::vector<std::string>{"Test.Bar"}));
EXPECT_THAT(result.value(), ::testing::ElementsAre(
std::vector<std::string>{"name", "value"},
std::vector<std::string>{"Test.Foo", "10"},
std::vector<std::string>{"Test.Foo", "11"},
std::vector<std::string>{"Test.Foo", "12"}));
}
IN_PROC_BROWSER_TEST_F(TracingEndToEndBrowserTest, TwoSessionsProcessNames) {

@ -60,6 +60,8 @@ target(tracing_lib_type, "cpp") {
sources += [
"perfetto/custom_event_recorder.cc",
"perfetto/custom_event_recorder.h",
"perfetto/histogram_samples_data_source.cc",
"perfetto/histogram_samples_data_source.h",
"perfetto/interning_index.h",
"perfetto/metadata_data_source.cc",
"perfetto/metadata_data_source.h",

@ -88,10 +88,10 @@ void BackgroundTracingAgentImpl::OnHistogramChanged(
return;
}
auto track = perfetto::NamedTrack("HistogramSamples");
uint64_t flow_id = base::trace_event::HistogramScope::GetFlowId().value_or(
base::trace_event::GetNextGlobalTraceId());
TRACE_EVENT("toplevel,latency", "HistogramSampleTrigger",
TRACE_EVENT("toplevel,latency", "HistogramSampleTrigger", track,
[&](perfetto::EventContext ctx) {
perfetto::protos::pbzero::ChromeHistogramSample* new_sample =
ctx.event()->set_chrome_histogram_sample();

@ -6,7 +6,6 @@
#include "base/base64.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_samples.h"
#include "base/metrics/metrics_hashes.h"
#include "base/metrics/statistics_recorder.h"
#include "base/metrics/user_metrics.h"
@ -25,7 +24,6 @@
#include "third_party/perfetto/include/perfetto/tracing/internal/track_event_internal.h"
#include "third_party/perfetto/include/perfetto/tracing/track_event_interned_data_index.h"
#include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_active_processes.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_histogram_sample.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_process_descriptor.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_user_event.pbzero.h"
@ -40,25 +38,9 @@ using perfetto::protos::pbzero::ChromeProcessDescriptor;
namespace tracing {
namespace {
constexpr char kHistogramSamplesCategory[] =
TRACE_DISABLED_BY_DEFAULT("histogram_samples");
constexpr char kUserActionSamplesCategory[] =
TRACE_DISABLED_BY_DEFAULT("user_action_samples");
struct InternedHistogramName
: public perfetto::TrackEventInternedDataIndex<
InternedHistogramName,
perfetto::protos::pbzero::InternedData::kHistogramNamesFieldNumber,
const char*> {
static void Add(perfetto::protos::pbzero::InternedData* interned_data,
size_t iid,
const char* histogram_name) {
auto* msg = interned_data->add_histogram_names();
msg->set_iid(iid);
msg->set_name(histogram_name);
}
};
} // namespace
CustomEventRecorder::CustomEventRecorder() {
@ -106,6 +88,7 @@ void CustomEventRecorder::EmitRecurringUpdates() {
void CustomEventRecorder::OnSetup(
const perfetto::DataSourceBase::SetupArgs& args) {
DCHECK_CALLED_ON_VALID_SEQUENCE(perfetto_sequence_checker_);
// The legacy chrome_config is only used to specify histogram names.
auto legacy_config = TraceConfig(args.config->chrome_config().trace_config());
ResetHistograms(legacy_config.histogram_names());
@ -116,25 +99,6 @@ void CustomEventRecorder::OnStart(const perfetto::DataSourceBase::StartArgs&) {
EmitRecurringUpdates();
bool enabled;
TRACE_EVENT_CATEGORY_GROUP_ENABLED(kHistogramSamplesCategory, &enabled);
if (enabled) {
if (histograms_.empty() &&
!base::StatisticsRecorder::global_sample_callback()) {
// Add the global callback if it wasn't already.
base::StatisticsRecorder::SetGlobalSampleCallback(
&CustomEventRecorder::OnMetricsSampleCallback);
}
for (const std::string& histogram_name : histograms_) {
if (monitored_histograms_.count(histogram_name)) {
continue;
}
monitored_histograms_[histogram_name] = std::make_unique<
base::StatisticsRecorder::ScopedHistogramSampleObserver>(
histogram_name,
base::BindRepeating(&CustomEventRecorder::OnMetricsSampleCallback));
}
}
TRACE_EVENT_CATEGORY_GROUP_ENABLED(kUserActionSamplesCategory, &enabled);
if (enabled) {
auto task_runner = base::GetRecordActionTaskRunner();
@ -159,14 +123,7 @@ void CustomEventRecorder::OnStop(const perfetto::DataSourceBase::StopArgs&) {
// Write metadata events etc.
LogHistograms();
// Clean up callbacks if no tracing sessions are recording samples.
bool enabled;
TRACE_EVENT_CATEGORY_GROUP_ENABLED(kHistogramSamplesCategory, &enabled);
if (!enabled) {
base::StatisticsRecorder::SetGlobalSampleCallback(nullptr);
monitored_histograms_.clear();
}
TRACE_EVENT_CATEGORY_GROUP_ENABLED(kUserActionSamplesCategory, &enabled);
if (!enabled) {
auto task_runner = base::GetRecordActionTaskRunner();
@ -259,23 +216,4 @@ void CustomEventRecorder::DetachFromSequence() {
DETACH_FROM_SEQUENCE(perfetto_sequence_checker_);
}
// static
void CustomEventRecorder::OnMetricsSampleCallback(
const char* histogram_name,
uint64_t name_hash,
base::HistogramBase::Sample32 sample) {
TRACE_EVENT_INSTANT(
kHistogramSamplesCategory, "HistogramSample",
[&](perfetto::EventContext ctx) {
perfetto::protos::pbzero::ChromeHistogramSample* new_sample =
ctx.event()->set_chrome_histogram_sample();
new_sample->set_name_hash(name_hash);
new_sample->set_sample(sample);
if (!ctx.ShouldFilterDebugAnnotations()) {
size_t iid = InternedHistogramName::Get(&ctx, histogram_name);
new_sample->set_name_iid(iid);
}
});
}
} // namespace tracing

@ -7,7 +7,6 @@
#include "base/component_export.h"
#include "base/metrics/histogram_samples.h"
#include "base/metrics/statistics_recorder.h"
#include "base/metrics/user_metrics.h"
#include "base/sequence_checker.h"
#include "base/trace_event/trace_config.h"
@ -45,13 +44,6 @@ class COMPONENT_EXPORT(TRACING_CPP) CustomEventRecorder
// base::RecordAction(), when tracing is enabled with a histogram category.
static void OnUserActionSampleCallback(const std::string& action,
base::TimeTicks action_time);
// Records trace event for a histogram sample. When histogram_samples category
// is enabled, it is registered with base::StatisticsRecorder to monitor the
// histograms listed in the trace config. If there are no histograms listed in
// the trace config, all the histograms will be monitored.
static void OnMetricsSampleCallback(const char* histogram_name,
uint64_t name_hash,
base::HistogramBase::Sample32 sample);
bool IsPrivacyFilteringEnabled();
// Thread can restart in Linux and ChromeOS when entering sandbox, so rebind
// sequence checker.
@ -78,12 +70,6 @@ class COMPONENT_EXPORT(TRACING_CPP) CustomEventRecorder
std::map<std::string, std::unique_ptr<base::HistogramSamples>>
startup_histogram_samples_;
std::vector<std::string> histograms_;
// Stores the registered histogram callbacks for which OnMetricsSampleCallback
// was set individually.
std::unordered_map<
std::string,
std::unique_ptr<base::StatisticsRecorder::ScopedHistogramSampleObserver>>
monitored_histograms_;
base::ActionCallback user_action_callback_ =
base::BindRepeating(&CustomEventRecorder::OnUserActionSampleCallback);
ActiveProcessesCallback active_processes_callback_;

@ -0,0 +1,246 @@
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/tracing/public/cpp/perfetto/histogram_samples_data_source.h"
#include "base/logging.h"
#include "base/metrics/metrics_hashes.h"
#include "base/numerics/safe_conversions.h"
#include "base/trace_event/trace_event.h"
#include "base/tracing/trace_time.h"
#include "services/tracing/public/mojom/perfetto_service.mojom.h"
#include "third_party/perfetto/include/perfetto/base/time.h"
#include "third_party/perfetto/include/perfetto/tracing/track_event_interned_data_index.h"
#include "third_party/perfetto/protos/perfetto/common/data_source_descriptor.gen.h"
#include "third_party/perfetto/protos/perfetto/config/chrome/chrome_config.gen.h"
#include "third_party/perfetto/protos/perfetto/config/data_source_config.gen.h"
#include "third_party/perfetto/protos/perfetto/trace/trace_packet_defaults.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_histogram_sample.pbzero.h"
namespace tracing {
namespace {
// Counts data source that record all histograms;
std::atomic_size_t g_num_global_sample_observers{0};
std::optional<base::HistogramBase::Sample32> MaybeReferenceValue(
bool has_value,
uint64_t value) {
if (has_value) {
return base::checked_cast<base::HistogramBase::Sample32>(value);
}
return std::nullopt;
}
} // namespace
struct HistogramSamplesIncrementalState {
using InternedHistogramName =
perfetto::SmallInternedDataTraits::Index<const char*>;
bool was_cleared = true;
InternedHistogramName histogram_names_;
size_t GetInternedHistogramName(
const char* value,
HistogramSamplesDataSource::TraceContext::TracePacketHandle& packet) {
size_t iid;
if (histogram_names_.LookUpOrInsert(&iid, value)) {
return iid;
}
auto* interned_data = packet->set_interned_data();
auto* msg = interned_data->add_histogram_names();
msg->set_iid(iid);
msg->set_name(value);
return iid;
}
};
struct HistogramSamplesTlsState {
explicit HistogramSamplesTlsState(
const HistogramSamplesDataSource::TraceContext& trace_context) {
auto locked_ds = trace_context.GetDataSourceLocked();
if (locked_ds.valid()) {
filter_histogram_names = locked_ds->filter_histogram_names();
if (!locked_ds->records_all_histograms()) {
instance = reinterpret_cast<uintptr_t>(&(*locked_ds));
}
}
}
bool filter_histogram_names = false;
std::optional<uintptr_t> instance;
};
void HistogramSamplesDataSource::Register() {
perfetto::DataSourceDescriptor desc;
desc.set_name(tracing::mojom::kHistogramSampleSourceName);
perfetto::DataSource<HistogramSamplesDataSource,
HistogramSamplesTraits>::Register(desc);
}
HistogramSamplesDataSource::HistogramSamplesDataSource() = default;
HistogramSamplesDataSource::~HistogramSamplesDataSource() = default;
void HistogramSamplesDataSource::OnSetup(const SetupArgs& args) {
if (args.config->chromium_histogram_samples_raw().empty()) {
return;
}
perfetto::protos::gen::ChromiumHistogramSamplesConfig config;
if (!config.ParseFromString(args.config->chromium_histogram_samples_raw())) {
DLOG(ERROR) << "Failed to parse chromium_histogram_samples";
return;
}
filter_histogram_names_ = config.filter_histogram_names();
for (const auto& histogram : config.histograms()) {
monitored_histograms_.push_back(histogram);
}
}
void HistogramSamplesDataSource::OnStart(const StartArgs&) {
if (monitored_histograms_.empty()) {
size_t num_global_sample_observers =
g_num_global_sample_observers.fetch_add(1, std::memory_order_relaxed);
if (num_global_sample_observers == 0) {
base::StatisticsRecorder::SetGlobalSampleCallback(
&HistogramSamplesDataSource::OnAnyMetricSample);
}
}
for (const auto& histogram : monitored_histograms_) {
histogram_observers_.push_back(
std::make_unique<
base::StatisticsRecorder::ScopedHistogramSampleObserver>(
histogram.histogram_name(),
base::BindRepeating(&HistogramSamplesDataSource::OnMetricSample,
base::Unretained(this),
MaybeReferenceValue(histogram.has_min_value(),
histogram.min_value()),
MaybeReferenceValue(histogram.has_max_value(),
histogram.max_value()))));
}
}
void HistogramSamplesDataSource::OnFlush(const FlushArgs&) {}
void HistogramSamplesDataSource::OnStop(const StopArgs&) {
if (monitored_histograms_.empty()) {
size_t num_global_sample_observers =
g_num_global_sample_observers.fetch_sub(1, std::memory_order_relaxed);
if (num_global_sample_observers == 1) {
base::StatisticsRecorder::SetGlobalSampleCallback(nullptr);
}
}
histogram_observers_.clear();
}
void HistogramSamplesDataSource::OnMetricSample(
std::optional<base::HistogramBase::Sample32> reference_lower_value,
std::optional<base::HistogramBase::Sample32> reference_upper_value,
const char* histogram_name,
uint64_t name_hash,
base::HistogramBase::Sample32 sample) {
if ((reference_lower_value && sample < reference_lower_value) ||
(reference_upper_value && sample > reference_upper_value)) {
return;
}
OnMetricSampleImpl(histogram_name, name_hash, sample,
reinterpret_cast<uintptr_t>(this));
}
void HistogramSamplesDataSource::OnAnyMetricSample(
const char* histogram_name,
uint64_t name_hash,
base::HistogramBase::Sample32 sample) {
OnMetricSampleImpl(histogram_name, name_hash, sample, std::nullopt);
}
void HistogramSamplesDataSource::OnMetricSampleImpl(
const char* histogram_name,
uint64_t name_hash,
base::HistogramBase::Sample32 sample,
std::optional<uintptr_t> instance) {
HistogramSamplesDataSource::Trace([&](TraceContext ctx) {
if (instance != ctx.GetCustomTlsState()->instance) {
return;
}
bool filter_histogram_names =
ctx.GetCustomTlsState()->filter_histogram_names;
auto* incr_state = ctx.GetIncrementalState();
if (incr_state->was_cleared) {
ResetIncrementalState(ctx, !instance.has_value());
incr_state->was_cleared = false;
}
auto packet = ctx.NewTracePacket();
std::optional<size_t> iid;
if (!filter_histogram_names) {
iid = incr_state->GetInternedHistogramName(histogram_name, packet);
}
packet->set_timestamp(
TRACE_TIME_TICKS_NOW().since_origin().InNanoseconds());
packet->set_sequence_flags(
::perfetto::protos::pbzero::TracePacket::SEQ_NEEDS_INCREMENTAL_STATE);
auto* event = packet->set_track_event();
event->set_name_iid(1);
event->add_category_iids(1);
event->set_type(::perfetto::protos::pbzero::TrackEvent::TYPE_INSTANT);
perfetto::protos::pbzero::ChromeHistogramSample* new_sample =
event->set_chrome_histogram_sample();
new_sample->set_name_hash(name_hash);
new_sample->set_sample(sample);
if (iid) {
new_sample->set_name_iid(*iid);
}
});
}
void HistogramSamplesDataSource::ResetIncrementalState(
TraceContext& ctx,
bool records_all_histograms) {
uint64_t track_uuid;
if (records_all_histograms) {
auto track = perfetto::ThreadTrack::Current();
perfetto::internal::TrackRegistry::Get()->SerializeTrack(
track, ctx.NewTracePacket());
track_uuid = track.uuid;
} else {
// Specific histogram samples are scoped to the process because the callback
// is not called on the thread that emitted the record.
auto track = perfetto::NamedTrack("HistogramSamples");
perfetto::internal::TrackRegistry::Get()->SerializeTrack(
track, ctx.NewTracePacket());
track_uuid = track.uuid;
}
auto packet = ctx.NewTracePacket();
packet->set_sequence_flags(
::perfetto::protos::pbzero::TracePacket::SEQ_INCREMENTAL_STATE_CLEARED);
auto* defaults = packet->set_trace_packet_defaults();
defaults->set_timestamp_clock_id(base::tracing::kTraceClockId);
auto* track_defaults = defaults->set_track_event_defaults();
track_defaults->set_track_uuid(track_uuid);
auto* interned_data = packet->set_interned_data();
auto* name = interned_data->add_event_names();
name->set_iid(1);
name->set_name("HistogramSample");
auto* category = interned_data->add_event_categories();
category->set_iid(1);
// This is a synthetic category that's created by this data source, although
// it can't be enabled on TrackEvent. The data is emitted this way mainly for
// backward compatibility with existing trace processor.
category->set_name(TRACE_DISABLED_BY_DEFAULT("histogram_samples"));
}
} // namespace tracing
PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS_WITH_ATTRS(
COMPONENT_EXPORT(TRACING_CPP),
tracing::HistogramSamplesDataSource,
tracing::HistogramSamplesTraits);

@ -0,0 +1,79 @@
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SERVICES_TRACING_PUBLIC_CPP_PERFETTO_HISTOGRAM_SAMPLES_DATA_SOURCE_H_
#define SERVICES_TRACING_PUBLIC_CPP_PERFETTO_HISTOGRAM_SAMPLES_DATA_SOURCE_H_
#include <vector>
#include "base/component_export.h"
#include "base/metrics/histogram_base.h"
#include "base/metrics/statistics_recorder.h"
#include "third_party/perfetto/include/perfetto/tracing/data_source.h"
#include "third_party/perfetto/protos/perfetto/config/chrome/histogram_samples.gen.h"
namespace tracing {
struct HistogramSamplesIncrementalState;
struct HistogramSamplesTlsState;
struct HistogramSamplesTraits : public perfetto::DefaultDataSourceTraits {
using IncrementalStateType = HistogramSamplesIncrementalState;
using TlsStateType = HistogramSamplesTlsState;
};
// A data source that record UMA histogram samples. This data source needs
// "track_event" data source to be enabled for track descriptors to be emitted.
class COMPONENT_EXPORT(TRACING_CPP) HistogramSamplesDataSource
: public perfetto::DataSource<HistogramSamplesDataSource,
HistogramSamplesTraits> {
public:
static void Register();
HistogramSamplesDataSource();
~HistogramSamplesDataSource() override;
void OnSetup(const SetupArgs&) override;
void OnStart(const StartArgs&) override;
void OnFlush(const FlushArgs&) override;
void OnStop(const StopArgs&) override;
bool filter_histogram_names() const { return filter_histogram_names_; }
bool records_all_histograms() const { return monitored_histograms_.empty(); }
private:
void OnMetricSample(
std::optional<base::HistogramBase::Sample32> reference_lower_value,
std::optional<base::HistogramBase::Sample32> reference_upper_value,
const char* histogram_name,
uint64_t name_hash,
base::HistogramBase::Sample32 actual_value);
static void OnAnyMetricSample(const char* histogram_name,
uint64_t name_hash,
base::HistogramBase::Sample32 sample);
// `instance` identifies the instance that registered a callback, or nullopt
// if this is a global callback.
static void OnMetricSampleImpl(const char* histogram_name,
uint64_t name_hash,
base::HistogramBase::Sample32 sample,
std::optional<uintptr_t> instance);
static void ResetIncrementalState(TraceContext& ctx,
bool records_all_histograms);
std::vector<
perfetto::protos::gen::ChromiumHistogramSamplesConfig::HistogramSample>
monitored_histograms_;
// Stores the registered histogram callbacks for which OnMetricSample
// was set individually.
std::vector<
std::unique_ptr<base::StatisticsRecorder::ScopedHistogramSampleObserver>>
histogram_observers_;
bool filter_histogram_names_ = false;
};
} // namespace tracing
#endif // SERVICES_TRACING_PUBLIC_CPP_PERFETTO_HISTOGRAM_SAMPLES_DATA_SOURCE_H_

@ -24,6 +24,7 @@
#include "build/chromecast_buildflags.h"
#include "components/tracing/common/tracing_switches.h"
#include "services/tracing/public/mojom/perfetto_service.mojom.h"
#include "third_party/perfetto/protos/perfetto/config/chrome/histogram_samples.gen.h"
#include "third_party/perfetto/protos/perfetto/config/track_event/track_event_config.gen.h"
namespace tracing {
@ -125,6 +126,25 @@ void AddDataSourceConfigs(
convert_to_legacy_json, client_priority,
json_agent_label_filter);
if (stripped_config.IsCategoryGroupEnabled(
TRACE_DISABLED_BY_DEFAULT("histogram_samples"))) {
auto* data_source = AddDataSourceConfig(
perfetto_config, tracing::mojom::kHistogramSampleSourceName,
chrome_config_string, privacy_filtering_enabled, convert_to_legacy_json,
client_priority, json_agent_label_filter);
perfetto::protos::gen::ChromiumHistogramSamplesConfig histogram_config;
histogram_config.set_filter_histogram_names(privacy_filtering_enabled);
for (const auto& histogram_name : stripped_config.histogram_names()) {
perfetto::protos::gen::ChromiumHistogramSamplesConfig::HistogramSample
sample;
sample.set_histogram_name(histogram_name);
*histogram_config.add_histograms() = sample;
}
data_source->mutable_config()->set_chromium_histogram_samples_raw(
histogram_config.SerializeAsString());
}
if (stripped_config.IsCategoryGroupEnabled(
TRACE_DISABLED_BY_DEFAULT("cpu_profiler"))) {
AddDataSourceConfig(
@ -285,6 +305,19 @@ void AdaptDataSourceConfig(
chrome_config->set_trace_config(chrome_config_string);
}
if (config->name() == tracing::mojom::kHistogramSampleSourceName) {
perfetto::protos::gen::ChromiumHistogramSamplesConfig histogram_config;
if (!config->chromium_histogram_samples_raw().empty() &&
!histogram_config.ParseFromString(
config->chromium_histogram_samples_raw())) {
DLOG(ERROR) << "Failed to parse chromium_histogram_samples";
return;
}
histogram_config.set_filter_histogram_names(privacy_filtering_enabled);
config->set_chromium_histogram_samples_raw(
histogram_config.SerializeAsString());
}
if (!config->track_event_config_raw().empty()) {
config->set_name("track_event");
perfetto::protos::gen::TrackEventConfig track_event_config;

@ -18,6 +18,7 @@
#include "base/tracing/perfetto_task_runner.h"
#include "build/build_config.h"
#include "services/tracing/public/cpp/perfetto/custom_event_recorder.h"
#include "services/tracing/public/cpp/perfetto/histogram_samples_data_source.h"
#include "services/tracing/public/cpp/perfetto/metadata_data_source.h"
#include "services/tracing/public/cpp/perfetto/perfetto_tracing_backend.h"
#include "services/tracing/public/cpp/perfetto/track_name_recorder.h"
@ -353,6 +354,7 @@ void PerfettoTracedProcess::SetupClientLibrary(bool enable_consumer) {
base::TrackEvent::Register();
tracing::TracingSamplerProfiler::RegisterDataSource();
tracing::HistogramSamplesDataSource::Register();
// SystemMetricsSampler will be started when enabling
// kSystemMetricsSourceName.
tracing::SystemMetricsSampler::Register(/*system_wide=*/enable_consumer);

@ -16,7 +16,7 @@ bool StructTraits<tracing::mojom::DataSourceConfigDataView,
Read(tracing::mojom::DataSourceConfigDataView data,
perfetto::DataSourceConfig* out) {
std::string name, legacy_config, track_event_config_raw, etw_config_raw,
system_metrics_config_raw;
system_metrics_config_raw, histogram_samples_config_raw;
perfetto::ChromeConfig chrome_config;
std::optional<perfetto::protos::gen::InterceptorConfig> interceptor_config;
if (!data.ReadName(&name) || !data.ReadChromeConfig(&chrome_config) ||
@ -24,6 +24,7 @@ bool StructTraits<tracing::mojom::DataSourceConfigDataView,
!data.ReadTrackEventConfigRaw(&track_event_config_raw) ||
!data.ReadEtwConfigRaw(&etw_config_raw) ||
!data.ReadSystemMetricsConfigRaw(&system_metrics_config_raw) ||
!data.ReadHistogramSamplesConfigRaw(&histogram_samples_config_raw) ||
!data.ReadInterceptorConfig(&interceptor_config)) {
return false;
}
@ -49,6 +50,9 @@ bool StructTraits<tracing::mojom::DataSourceConfigDataView,
if (!system_metrics_config_raw.empty()) {
out->set_chromium_system_metrics_raw(system_metrics_config_raw);
}
if (!histogram_samples_config_raw.empty()) {
out->set_chromium_histogram_samples_raw(histogram_samples_config_raw);
}
return true;
}
} // namespace mojo

@ -54,6 +54,10 @@ class StructTraits<tracing::mojom::DataSourceConfigDataView,
const perfetto::DataSourceConfig& src) {
return src.chromium_system_metrics_raw();
}
static const std::string& histogram_samples_config_raw(
const perfetto::DataSourceConfig& src) {
return src.chromium_histogram_samples_raw();
}
static std::optional<perfetto::protos::gen::InterceptorConfig>
interceptor_config(const perfetto::DataSourceConfig& src) {

@ -28,6 +28,8 @@ const string kNativeHeapProfilerSourceName =
"org.chromium.native_heap_profiler";
const string kSystemMetricsSourceName =
"org.chromium.system_metrics";
const string kHistogramSampleSourceName =
"org.chromium.histogram_sample";
// Brief description of the flow: There's a per-process ProducerClient which
// connects to the central PerfettoService and establishes a two-way connection
@ -132,6 +134,7 @@ struct DataSourceConfig {
mojo_base.mojom.ByteString track_event_config_raw;
mojo_base.mojom.ByteString etw_config_raw;
mojo_base.mojom.ByteString system_metrics_config_raw;
mojo_base.mojom.ByteString histogram_samples_config_raw;
};
struct DataSourceRegistration {