blob: 01de403f3a9fc77332504a16fdf46dbd3dc0b3fc [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/tracing/background_tracing_rule.h"
#include <limits>
#include <optional>
#include <string>
#include <type_traits>
#include "base/functional/bind.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/metrics_hashes.h"
#include "base/metrics/statistics_recorder.h"
#include "base/rand_util.h"
#include "base/strings/safe_sprintf.h"
#include "base/strings/strcat.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/trace_event/histogram_scope.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_id_helper.h"
#include "base/unguessable_token.h"
#include "components/variations/hashing.h"
#include "content/browser/tracing/background_tracing_manager_impl.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "services/tracing/public/cpp/perfetto/macros.h"
#include "services/tracing/public/mojom/background_tracing_agent.mojom.h"
#include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_histogram_sample.pbzero.h"
namespace content {
BackgroundTracingRule::BackgroundTracingRule() = default;
BackgroundTracingRule::~BackgroundTracingRule() {
DCHECK(!installed());
}
void BackgroundTracingRule::Install(RuleTriggeredCallback trigger_callback) {
DCHECK(!installed());
installed_ = true;
trigger_callback_ = std::move(trigger_callback);
if (activation_delay_) {
activation_timer_.Start(FROM_HERE, *activation_delay_,
base::BindOnce(&BackgroundTracingRule::DoInstall,
base::Unretained(this)));
} else {
DoInstall();
}
}
void BackgroundTracingRule::Uninstall() {
if (!installed()) {
return;
}
installed_ = false;
trigger_timer_.Stop();
activation_timer_.Stop();
trigger_callback_.Reset();
DoUninstall();
}
bool BackgroundTracingRule::OnRuleTriggered(std::optional<int32_t> value,
uint64_t flow_id) {
if (!installed()) {
return false;
}
DCHECK(trigger_callback_);
if (trigger_chance_ < 1.0 && base::RandDouble() > trigger_chance_) {
return false;
}
triggered_value_ = value;
flow_id_ = flow_id;
if (delay_) {
trigger_timer_.Start(FROM_HERE, *delay_,
base::BindOnce(base::IgnoreResult(trigger_callback_),
base::Unretained(this)));
return true;
} else {
return trigger_callback_.Run(this);
}
}
std::string BackgroundTracingRule::GetDefaultRuleName() const {
return "trigger";
}
perfetto::protos::gen::TriggerRule BackgroundTracingRule::ToProtoForTesting()
const {
perfetto::protos::gen::TriggerRule config;
if (trigger_chance_ < 1.0) {
config.set_trigger_chance(trigger_chance_);
}
if (delay_) {
config.set_delay_ms(delay_->InMilliseconds());
}
config.set_name(rule_name_);
return config;
}
void BackgroundTracingRule::GenerateMetadataProto(
BackgroundTracingRule::MetadataProto* out) const {
uint32_t name_hash = variations::HashName(rule_name());
out->set_name_hash(name_hash);
}
void BackgroundTracingRule::Setup(
const perfetto::protos::gen::TriggerRule& config) {
if (config.has_trigger_chance()) {
trigger_chance_ = config.trigger_chance();
}
if (config.has_delay_ms()) {
delay_ = base::Milliseconds(config.delay_ms());
}
if (config.has_activation_delay_ms()) {
activation_delay_ = base::Milliseconds(config.activation_delay_ms());
}
if (config.has_name()) {
rule_name_ = config.name();
} else {
rule_name_ = base::StrCat(
{"org.chromium.background_tracing.", GetDefaultRuleName()});
}
}
namespace {
class NamedTriggerRule : public BackgroundTracingRule {
private:
explicit NamedTriggerRule(const std::string& named_event)
: named_event_(named_event) {}
public:
static std::unique_ptr<BackgroundTracingRule> Create(
const perfetto::protos::gen::TriggerRule& config) {
if (config.has_manual_trigger_name()) {
return base::WrapUnique<BackgroundTracingRule>(
new NamedTriggerRule(config.manual_trigger_name()));
}
return nullptr;
}
void DoInstall() override {
BackgroundTracingManagerImpl::GetInstance().AddNamedTriggerObserver(
named_event_, this);
}
void DoUninstall() override {
BackgroundTracingManagerImpl::GetInstance().RemoveNamedTriggerObserver(
named_event_, this);
}
perfetto::protos::gen::TriggerRule ToProtoForTesting() const override {
perfetto::protos::gen::TriggerRule config =
BackgroundTracingRule::ToProtoForTesting();
config.set_manual_trigger_name(named_event_);
return config;
}
void GenerateMetadataProto(
BackgroundTracingRule::MetadataProto* out) const override {
DCHECK(out);
BackgroundTracingRule::GenerateMetadataProto(out);
out->set_trigger_type(MetadataProto::MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED);
}
protected:
std::string GetDefaultRuleName() const override { return named_event_; }
private:
std::string named_event_;
};
class HistogramRule : public BackgroundTracingRule,
public BackgroundTracingManagerImpl::AgentObserver {
private:
HistogramRule(const std::string& histogram_name,
int histogram_lower_value,
int histogram_upper_value)
: histogram_name_(histogram_name),
rule_id_(base::UnguessableToken::Create().ToString()),
histogram_lower_value_(histogram_lower_value),
histogram_upper_value_(histogram_upper_value) {}
public:
static std::unique_ptr<BackgroundTracingRule> Create(
const perfetto::protos::gen::TriggerRule& config) {
DCHECK(config.has_histogram());
if (!config.histogram().has_histogram_name()) {
return nullptr;
}
int histogram_lower_value = 0;
if (config.histogram().has_min_value()) {
histogram_lower_value = config.histogram().min_value();
}
int histogram_upper_value = std::numeric_limits<int>::max();
if (config.histogram().has_max_value()) {
histogram_upper_value = config.histogram().max_value();
}
if (histogram_lower_value > histogram_upper_value) {
return nullptr;
}
return base::WrapUnique(
new HistogramRule(config.histogram().histogram_name(),
histogram_lower_value, histogram_upper_value));
}
~HistogramRule() override = default;
// BackgroundTracingRule implementation
void DoInstall() override {
histogram_sample_callback_.emplace(
histogram_name_,
base::BindRepeating(&HistogramRule::OnHistogramChangedCallback,
base::Unretained(this), histogram_lower_value_,
histogram_upper_value_));
BackgroundTracingManagerImpl::GetInstance().AddNamedTriggerObserver(
rule_id_, this);
BackgroundTracingManagerImpl::GetInstance().AddAgentObserver(this);
}
void DoUninstall() override {
histogram_sample_callback_.reset();
BackgroundTracingManagerImpl::GetInstance().RemoveAgentObserver(this);
BackgroundTracingManagerImpl::GetInstance().RemoveNamedTriggerObserver(
rule_id_, this);
}
perfetto::protos::gen::TriggerRule ToProtoForTesting() const override {
perfetto::protos::gen::TriggerRule config =
BackgroundTracingRule::ToProtoForTesting();
auto* histogram = config.mutable_histogram();
histogram->set_histogram_name(histogram_name_);
histogram->set_min_value(histogram_lower_value_);
histogram->set_max_value(histogram_upper_value_);
return config;
}
void GenerateMetadataProto(
BackgroundTracingRule::MetadataProto* out) const override {
DCHECK(out);
BackgroundTracingRule::GenerateMetadataProto(out);
out->set_trigger_type(
MetadataProto::MONITOR_AND_DUMP_WHEN_SPECIFIC_HISTOGRAM_AND_VALUE);
auto* rule = out->set_histogram_rule();
rule->set_histogram_name_hash(base::HashMetricName(histogram_name_));
rule->set_histogram_min_trigger(histogram_lower_value_);
rule->set_histogram_max_trigger(histogram_upper_value_);
}
// BackgroundTracingManagerImpl::AgentObserver implementation
void OnAgentAdded(tracing::mojom::BackgroundTracingAgent* agent) override {
agent->SetUMACallback(tracing::mojom::BackgroundTracingRule::New(rule_id_),
histogram_name_, histogram_lower_value_,
histogram_upper_value_);
}
void OnAgentRemoved(tracing::mojom::BackgroundTracingAgent* agent) override {
agent->ClearUMACallback(
tracing::mojom::BackgroundTracingRule::New(rule_id_));
}
void OnHistogramChangedCallback(
base::Histogram::Sample32 reference_lower_value,
base::Histogram::Sample32 reference_upper_value,
std::optional<uint64_t> event_id,
std::string_view histogram_name,
uint64_t name_hash,
base::Histogram::Sample32 actual_value) {
DCHECK_EQ(histogram_name, histogram_name_);
if (reference_lower_value > actual_value ||
reference_upper_value < actual_value) {
return;
}
uint64_t flow_id =
event_id.value_or(base::trace_event::GetNextGlobalTraceId());
// Add the histogram name and its corresponding value to the trace.
const auto trace_details = [&](perfetto::EventContext& ctx) {
perfetto::protos::pbzero::ChromeHistogramSample* new_sample =
ctx.event()->set_chrome_histogram_sample();
new_sample->set_name_hash(base::HashMetricName(histogram_name));
new_sample->set_sample(actual_value);
perfetto::Flow::Global(flow_id)(ctx);
};
auto track = perfetto::NamedTrack("HistogramSamples");
TRACE_EVENT_INSTANT("toplevel,latency", "HistogramSampleTrigger", track,
trace_details);
OnRuleTriggered(actual_value, flow_id);
}
protected:
std::string GetDefaultRuleName() const override { return histogram_name_; }
private:
std::string histogram_name_;
std::string rule_id_;
int histogram_lower_value_;
int histogram_upper_value_;
std::optional<base::StatisticsRecorder::ScopedHistogramSampleObserver>
histogram_sample_callback_;
};
class TimerRule : public BackgroundTracingRule {
private:
explicit TimerRule() = default;
public:
static std::unique_ptr<BackgroundTracingRule> Create(
const perfetto::protos::gen::TriggerRule& config) {
return base::WrapUnique<TimerRule>(new TimerRule());
}
void DoInstall() override {
OnRuleTriggered(std::nullopt, base::trace_event::GetNextGlobalTraceId());
}
void DoUninstall() override {}
void GenerateMetadataProto(
BackgroundTracingRule::MetadataProto* out) const override {
DCHECK(out);
BackgroundTracingRule::GenerateMetadataProto(out);
out->set_trigger_type(MetadataProto::TRIGGER_UNSPECIFIED);
}
protected:
std::string GetDefaultRuleName() const override { return "timer"; }
};
class RepeatingIntervalRule : public BackgroundTracingRule {
private:
RepeatingIntervalRule(base::TimeDelta period, bool randomized)
: period_(period),
randomized_(randomized),
interval_phase_(base::TimeTicks::Now()) {}
public:
static std::unique_ptr<BackgroundTracingRule> Create(
const perfetto::protos::gen::TriggerRule& config) {
DCHECK(config.has_repeating_interval());
const auto& interval = config.repeating_interval();
if (!interval.has_period_ms()) {
return nullptr;
}
return base::WrapUnique<RepeatingIntervalRule>(new RepeatingIntervalRule(
base::Milliseconds(interval.period_ms()), interval.randomized()));
}
void DoInstall() override { ScheduleNextTick(); }
void DoUninstall() override { timer_.Stop(); }
perfetto::protos::gen::TriggerRule ToProtoForTesting() const override {
perfetto::protos::gen::TriggerRule config =
BackgroundTracingRule::ToProtoForTesting();
auto* histogram = config.mutable_repeating_interval();
histogram->set_period_ms(period_.InMilliseconds());
histogram->set_randomized(randomized_);
return config;
}
void GenerateMetadataProto(
BackgroundTracingRule::MetadataProto* out) const override {
DCHECK(out);
BackgroundTracingRule::GenerateMetadataProto(out);
out->set_trigger_type(MetadataProto::TRIGGER_UNSPECIFIED);
}
protected:
std::string GetDefaultRuleName() const override {
return "repeating_interval";
}
private:
base::TimeTicks GetFireTimeForInterval(base::TimeTicks interval_start) const {
if (randomized_) {
return interval_start +
base::Microseconds(base::RandGenerator(period_.InMicroseconds()));
}
return interval_start;
}
base::TimeTicks GetNextIntervalStart(base::TimeTicks now) const {
base::TimeTicks next_interval_start =
now.SnappedToNextTick(interval_phase_, period_);
if (next_interval_start == now) {
return next_interval_start + period_;
}
return next_interval_start;
}
void UpdateNextFireTime(base::TimeTicks now) {
base::TimeTicks next_interval_start = GetNextIntervalStart(now);
base::TimeTicks prev_interval_start = next_interval_start - period_;
// If the previously scheduled fire time is in a past interval, the current
// interval can still fire. Compute a new fire time for the current
// interval and keep it if it hasn't passed, otherwise move on to the next
// interval.
if (randomized_ && (scheduled_fire_time_ < prev_interval_start)) {
scheduled_fire_time_ = GetFireTimeForInterval(prev_interval_start);
if (scheduled_fire_time_ >= now) {
return;
}
}
scheduled_fire_time_ = GetFireTimeForInterval(next_interval_start);
}
void ScheduleNextTick() {
base::TimeTicks now = base::TimeTicks::Now();
// Update the next fire time only if it's in the past.
if (scheduled_fire_time_ <= now) {
UpdateNextFireTime(now);
}
timer_.Start(
FROM_HERE, scheduled_fire_time_,
base::BindOnce(&RepeatingIntervalRule::OnTick, base::Unretained(this)),
base::subtle::DelayPolicy::kFlexibleNoSooner);
}
void OnTick() {
OnRuleTriggered(std::nullopt, base::trace_event::GetNextGlobalTraceId());
ScheduleNextTick();
}
const base::TimeDelta period_;
const bool randomized_;
const base::TimeTicks interval_phase_;
base::TimeTicks scheduled_fire_time_;
base::DeadlineTimer timer_;
};
} // namespace
std::unique_ptr<BackgroundTracingRule> BackgroundTracingRule::Create(
const perfetto::protos::gen::TriggerRule& config) {
std::unique_ptr<BackgroundTracingRule> tracing_rule;
if (config.has_manual_trigger_name()) {
tracing_rule = NamedTriggerRule::Create(config);
} else if (config.has_histogram()) {
tracing_rule = HistogramRule::Create(config);
} else if (config.has_repeating_interval()) {
tracing_rule = RepeatingIntervalRule::Create(config);
} else if (config.has_delay_ms()) {
tracing_rule = TimerRule::Create(config);
} else {
return nullptr;
}
if (tracing_rule) {
tracing_rule->Setup(config);
}
return tracing_rule;
}
bool BackgroundTracingRule::Append(
const std::vector<perfetto::protos::gen::TriggerRule>& configs,
std::vector<std::unique_ptr<BackgroundTracingRule>>& rules) {
for (const auto& rule_config : configs) {
auto rule = BackgroundTracingRule::Create(rule_config);
if (!rule) {
return false;
}
rules.push_back(std::move(rule));
}
return true;
}
} // namespace content