[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:

committed by
Chromium LUCI CQ

parent
b5872e20b1
commit
60dfd767e5
@ -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 {
|
||||
|
Reference in New Issue
Block a user