[go: nahoru, domu]

blob: 48907fdc576a501f553affb79666eba3fae08590 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/tracing/background_tracing_rule.h"
#include <limits>
#include <string>
#include <type_traits>
#include "base/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/trace_event.h"
#include "base/trace_event/trace_id_helper.h"
#include "base/values.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/abseil-cpp/absl/types/optional.h"
#include "third_party/perfetto/protos/perfetto/trace/track_event/chrome_histogram_sample.pbzero.h"
namespace {
const char kConfigRuleKey[] = "rule";
const char kConfigCategoryKey[] = "category";
const char kConfigRuleTriggerNameKey[] = "trigger_name";
const char kConfigRuleTriggerDelay[] = "trigger_delay";
const char kConfigRuleTriggerChance[] = "trigger_chance";
const char kConfigRuleStopTracingOnRepeatedReactive[] =
"stop_tracing_on_repeated_reactive";
const char kConfigRuleArgsKey[] = "args";
const char kConfigRuleIdKey[] = "rule_id";
const char kConfigIsCrashKey[] = "is_crash";
const char kConfigRuleHistogramNameKey[] = "histogram_name";
const char kConfigRuleHistogramValueOldKey[] = "histogram_value";
const char kConfigRuleHistogramValue1Key[] = "histogram_lower_value";
const char kConfigRuleHistogramValue2Key[] = "histogram_upper_value";
const char kConfigRuleHistogramRepeatKey[] = "histogram_repeat";
const char kConfigRuleHistogramUnitsKey[] = "histogram_units";
const char kConfigRuleRandomIntervalTimeoutMin[] = "timeout_min";
const char kConfigRuleRandomIntervalTimeoutMax[] = "timeout_max";
const char kConfigRuleTypeMonitorNamed[] =
"MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED";
const char kConfigRuleTypeMonitorHistogram[] =
"MONITOR_AND_DUMP_WHEN_SPECIFIC_HISTOGRAM_AND_VALUE";
const char kConfigRuleTypeTraceOnNavigationUntilTriggerOrFull[] =
"TRACE_ON_NAVIGATION_UNTIL_TRIGGER_OR_FULL";
const char kConfigRuleTypeTraceAtRandomIntervals[] =
"TRACE_AT_RANDOM_INTERVALS";
const char kTraceAtRandomIntervalsEventName[] =
"ReactiveTraceAtRandomIntervals";
const int kConfigTypeNavigationTimeout = 30;
const int kReactiveTraceRandomStartTimeMin = 60;
const int kReactiveTraceRandomStartTimeMax = 120;
} // namespace
namespace content {
BackgroundTracingRule::BackgroundTracingRule() = default;
BackgroundTracingRule::BackgroundTracingRule(int trigger_delay)
: trigger_delay_(trigger_delay) {}
BackgroundTracingRule::~BackgroundTracingRule() = default;
bool BackgroundTracingRule::ShouldTriggerNamedEvent(
const std::string& named_event) const {
return false;
}
int BackgroundTracingRule::GetTraceDelay() const {
return trigger_delay_;
}
std::string BackgroundTracingRule::GetDefaultRuleId() const {
return "org.chromium.background_tracing.trigger";
}
base::Value BackgroundTracingRule::ToDict() const {
base::Value dict(base::Value::Type::DICTIONARY);
if (trigger_chance_ < 1.0)
dict.SetDoubleKey(kConfigRuleTriggerChance, trigger_chance_);
if (trigger_delay_ != -1)
dict.SetIntKey(kConfigRuleTriggerDelay, trigger_delay_);
if (stop_tracing_on_repeated_reactive_) {
dict.SetBoolKey(kConfigRuleStopTracingOnRepeatedReactive,
stop_tracing_on_repeated_reactive_);
}
if (rule_id_ != GetDefaultRuleId()) {
dict.SetStringKey(kConfigRuleIdKey, rule_id_);
}
if (category_preset_ != BackgroundTracingConfigImpl::CATEGORY_PRESET_UNSET) {
dict.SetStringKey(
kConfigCategoryKey,
BackgroundTracingConfigImpl::CategoryPresetToString(category_preset_));
}
if (is_crash_) {
dict.SetBoolKey(kConfigIsCrashKey, is_crash_);
}
return dict;
}
void BackgroundTracingRule::GenerateMetadataProto(
BackgroundTracingRule::MetadataProto* out) const {}
void BackgroundTracingRule::Setup(const base::Value& dict) {
if (auto trigger_chance = dict.FindDoubleKey(kConfigRuleTriggerChance)) {
trigger_chance_ = *trigger_chance;
}
if (auto trigger_delay = dict.FindIntKey(kConfigRuleTriggerDelay)) {
trigger_delay_ = *trigger_delay;
}
if (auto stop_tracing_on_repeated_reactive =
dict.FindBoolKey(kConfigRuleStopTracingOnRepeatedReactive)) {
stop_tracing_on_repeated_reactive_ = *stop_tracing_on_repeated_reactive;
}
if (const std::string* rule_id = dict.FindStringKey(kConfigRuleIdKey)) {
rule_id_ = *rule_id;
} else {
rule_id_ = GetDefaultRuleId();
}
if (auto is_crash = dict.FindBoolKey(kConfigIsCrashKey)) {
is_crash_ = *is_crash;
}
}
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 base::Value& dict) {
if (const std::string* trigger_name =
dict.FindStringKey(kConfigRuleTriggerNameKey)) {
return base::WrapUnique<BackgroundTracingRule>(
new NamedTriggerRule(*trigger_name));
}
return nullptr;
}
base::Value ToDict() const override {
base::Value dict = BackgroundTracingRule::ToDict();
DCHECK(dict.is_dict());
dict.SetStringKey(kConfigRuleKey, kConfigRuleTypeMonitorNamed);
dict.SetStringKey(kConfigRuleTriggerNameKey, named_event_.c_str());
return dict;
}
void GenerateMetadataProto(
BackgroundTracingRule::MetadataProto* out) const override {
DCHECK(out);
BackgroundTracingRule::GenerateMetadataProto(out);
out->set_trigger_type(MetadataProto::MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED);
auto* named_rule = out->set_named_rule();
if (named_event_ == "startup-config") {
named_rule->set_event_type(MetadataProto::NamedRule::STARTUP);
} else if (named_event_ == "navigation-config") {
named_rule->set_event_type(MetadataProto::NamedRule::NAVIGATION);
} else if (named_event_ == "session-restore-config") {
named_rule->set_event_type(MetadataProto::NamedRule::SESSION_RESTORE);
} else if (named_event_ == "reached-code-config") {
named_rule->set_event_type(MetadataProto::NamedRule::REACHED_CODE);
} else if (named_event_ ==
BackgroundTracingManager::kContentTriggerConfig) {
named_rule->set_event_type(MetadataProto::NamedRule::CONTENT_TRIGGER);
// TODO(chrisha): Set the |content_trigger_name_hash|.
} else if (named_event_ == "preemptive_test") {
named_rule->set_event_type(MetadataProto::NamedRule::TEST_RULE);
}
}
bool ShouldTriggerNamedEvent(const std::string& named_event) const override {
return named_event == named_event_;
}
protected:
std::string GetDefaultRuleId() const override {
return base::StrCat({"org.chromium.background_tracing.", named_event_});
}
private:
std::string named_event_;
};
class HistogramRule : public BackgroundTracingRule,
public BackgroundTracingManagerImpl::AgentObserver {
private:
// Units that can be displayed specially in OnHistogramChangedCallback.
enum class Units : int {
kUnspecified = 0,
kMilliseconds,
kMicroseconds,
};
static Units IntToUnits(int units_value) {
static_assert(std::is_same<std::underlying_type_t<Units>,
decltype(units_value)>::value,
"not safe to cast units_value to Units");
Units units = static_cast<Units>(units_value);
switch (units) {
case Units::kUnspecified:
case Units::kMilliseconds:
case Units::kMicroseconds:
// Recognized enum value.
return units;
}
// Unrecognized enum value.
return Units::kUnspecified;
}
HistogramRule(const std::string& histogram_name,
int histogram_lower_value,
int histogram_upper_value,
Units units,
bool repeat)
: histogram_name_(histogram_name),
histogram_lower_value_(histogram_lower_value),
histogram_upper_value_(histogram_upper_value),
units_(units),
repeat_(repeat),
installed_(false) {}
public:
static std::unique_ptr<BackgroundTracingRule> Create(
const base::Value& dict) {
const std::string* histogram_name =
dict.FindStringKey(kConfigRuleHistogramNameKey);
if (!histogram_name)
return nullptr;
// Optional parameter, so we don't need to check if the key exists.
bool repeat =
dict.FindBoolKey(kConfigRuleHistogramRepeatKey).value_or(true);
absl::optional<int> histogram_lower_value =
dict.FindIntKey(kConfigRuleHistogramValue1Key);
if (!histogram_lower_value) {
// Check for the old naming.
histogram_lower_value = dict.FindIntKey(kConfigRuleHistogramValueOldKey);
if (!histogram_lower_value)
return nullptr;
}
int histogram_upper_value = dict.FindIntKey(kConfigRuleHistogramValue2Key)
.value_or(std::numeric_limits<int>::max());
if (*histogram_lower_value > histogram_upper_value)
return nullptr;
Units units = Units::kUnspecified;
if (auto units_value = dict.FindIntKey(kConfigRuleHistogramUnitsKey)) {
units = IntToUnits(*units_value);
}
std::unique_ptr<BackgroundTracingRule> rule(
new HistogramRule(*histogram_name, *histogram_lower_value,
histogram_upper_value, units, repeat));
const base::Value* args_dict = dict.FindDictKey(kConfigRuleArgsKey);
if (args_dict)
rule->SetArgs(*args_dict);
return rule;
}
~HistogramRule() override {
if (installed_) {
BackgroundTracingManagerImpl::GetInstance()->RemoveAgentObserver(this);
}
}
// BackgroundTracingRule implementation
void Install() override {
histogram_sample_callback_ = std::make_unique<
base::StatisticsRecorder::ScopedHistogramSampleObserver>(
histogram_name_,
base::BindRepeating(&HistogramRule::OnHistogramChangedCallback,
base::Unretained(this), histogram_lower_value_,
histogram_upper_value_, units_, repeat_));
BackgroundTracingManagerImpl::GetInstance()->AddAgentObserver(this);
installed_ = true;
}
base::Value ToDict() const override {
base::Value dict = BackgroundTracingRule::ToDict();
DCHECK(dict.is_dict());
dict.SetStringKey(kConfigRuleKey, kConfigRuleTypeMonitorHistogram);
dict.SetStringKey(kConfigRuleHistogramNameKey, histogram_name_.c_str());
dict.SetIntKey(kConfigRuleHistogramValue1Key, histogram_lower_value_);
dict.SetIntKey(kConfigRuleHistogramValue2Key, histogram_upper_value_);
if (units_ != Units::kUnspecified)
dict.SetIntKey(kConfigRuleHistogramUnitsKey, static_cast<int>(units_));
dict.SetBoolKey(kConfigRuleHistogramRepeatKey, repeat_);
return dict;
}
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_);
}
void OnHistogramTrigger(const std::string& histogram_name) const override {
if (histogram_name != histogram_name_)
return;
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
&BackgroundTracingManagerImpl::OnRuleTriggered,
base::Unretained(BackgroundTracingManagerImpl::GetInstance()), this,
BackgroundTracingManager::StartedFinalizingCallback()));
}
void AbortTracing() {
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
&BackgroundTracingManagerImpl::AbortScenario,
base::Unretained(BackgroundTracingManagerImpl::GetInstance())));
}
// BackgroundTracingManagerImpl::AgentObserver implementation
void OnAgentAdded(tracing::mojom::BackgroundTracingAgent* agent) override {
agent->SetUMACallback(histogram_name_, histogram_lower_value_,
histogram_upper_value_, repeat_);
}
void OnAgentRemoved(tracing::mojom::BackgroundTracingAgent* agent) override {
agent->ClearUMACallback(histogram_name_);
}
void OnHistogramChangedCallback(base::Histogram::Sample reference_lower_value,
base::Histogram::Sample reference_upper_value,
Units units,
bool repeat,
const char* histogram_name,
uint64_t name_hash,
base::Histogram::Sample actual_value) {
if (reference_lower_value > actual_value ||
reference_upper_value < actual_value) {
if (!repeat)
AbortTracing();
return;
}
// Add the histogram name and its corresponding value to the trace.
TRACE_EVENT_INSTANT2("toplevel",
"BackgroundTracingRule::OnHistogramTrigger",
TRACE_EVENT_SCOPE_THREAD, "histogram_name",
histogram_name, "value", actual_value);
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);
};
const auto track =
perfetto::Track::FromPointer(this, perfetto::ProcessTrack::Current());
const auto now = base::TimeTicks::Now();
if (units == Units::kUnspecified) {
TRACE_EVENT_INSTANT("toplevel", "HistogramSampleTrigger", track, now,
trace_details);
} else {
base::TimeDelta delta;
switch (units) {
case Units::kUnspecified:
NOTREACHED(); // Handled above.
break;
case Units::kMilliseconds:
delta = base::Milliseconds(actual_value);
break;
case Units::kMicroseconds:
delta = base::Microseconds(actual_value);
break;
}
TRACE_EVENT_BEGIN("toplevel", "HistogramSampleTrigger", track,
now - delta, trace_details);
TRACE_EVENT_END("toplevel", track, now);
}
OnHistogramTrigger(histogram_name);
}
bool ShouldTriggerNamedEvent(const std::string& named_event) const override {
return named_event == histogram_name_;
}
protected:
std::string GetDefaultRuleId() const override {
return base::StrCat({"org.chromium.background_tracing.", histogram_name_});
}
private:
std::string histogram_name_;
int histogram_lower_value_;
int histogram_upper_value_;
Units units_;
bool repeat_;
bool installed_;
std::unique_ptr<base::StatisticsRecorder::ScopedHistogramSampleObserver>
histogram_sample_callback_;
};
class TraceForNSOrTriggerOrFullRule : public BackgroundTracingRule {
private:
explicit TraceForNSOrTriggerOrFullRule(const std::string& named_event)
: BackgroundTracingRule(kConfigTypeNavigationTimeout),
named_event_(named_event) {}
public:
static std::unique_ptr<BackgroundTracingRule> Create(
const base::Value& dict) {
if (const std::string* trigger_name =
dict.FindStringKey(kConfigRuleTriggerNameKey)) {
return base::WrapUnique<BackgroundTracingRule>(
new TraceForNSOrTriggerOrFullRule(*trigger_name));
}
return nullptr;
}
// BackgroundTracingRule implementation
base::Value ToDict() const override {
base::Value dict = BackgroundTracingRule::ToDict();
DCHECK(dict.is_dict());
dict.SetStringKey(kConfigRuleKey,
kConfigRuleTypeTraceOnNavigationUntilTriggerOrFull);
dict.SetStringKey(kConfigRuleTriggerNameKey, named_event_.c_str());
return dict;
}
void GenerateMetadataProto(
BackgroundTracingRule::MetadataProto* out) const override {
DCHECK(out);
BackgroundTracingRule::GenerateMetadataProto(out);
out->set_trigger_type(MetadataProto::MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED);
out->set_named_rule()->set_event_type(MetadataProto::NamedRule::NAVIGATION);
}
bool ShouldTriggerNamedEvent(const std::string& named_event) const override {
return named_event == named_event_;
}
protected:
std::string GetDefaultRuleId() const override {
return base::StrCat({"org.chromium.background_tracing.", named_event_});
}
private:
std::string named_event_;
};
class TraceAtRandomIntervalsRule : public BackgroundTracingRule {
private:
TraceAtRandomIntervalsRule(int timeout_min, int timeout_max)
: timeout_min_(timeout_min), timeout_max_(timeout_max) {
named_event_ = GenerateUniqueName();
}
public:
static std::unique_ptr<BackgroundTracingRule> Create(
const base::Value& dict) {
absl::optional<int> timeout_min =
dict.FindIntKey(kConfigRuleRandomIntervalTimeoutMin);
if (!timeout_min)
return nullptr;
absl::optional<int> timeout_max =
dict.FindIntKey(kConfigRuleRandomIntervalTimeoutMax);
if (!timeout_max)
return nullptr;
if (*timeout_min > *timeout_max)
return nullptr;
return std::unique_ptr<BackgroundTracingRule>(
new TraceAtRandomIntervalsRule(*timeout_min, *timeout_max));
}
~TraceAtRandomIntervalsRule() override {}
base::Value ToDict() const override {
base::Value dict = BackgroundTracingRule::ToDict();
DCHECK(dict.is_dict());
dict.SetStringKey(kConfigRuleKey, kConfigRuleTypeTraceAtRandomIntervals);
dict.SetIntKey(kConfigRuleRandomIntervalTimeoutMin, timeout_min_);
dict.SetIntKey(kConfigRuleRandomIntervalTimeoutMax, timeout_max_);
return dict;
}
void GenerateMetadataProto(
BackgroundTracingRule::MetadataProto* out) const override {
// TODO(ssid): Add config if we enabled this type of trigger.
NOTREACHED();
}
void Install() override {
handle_ = BackgroundTracingManagerImpl::GetInstance()->RegisterTriggerType(
named_event_.c_str());
StartTimer();
}
void OnStartedFinalizing(bool success) {
if (!success)
return;
StartTimer();
}
void OnTriggerTimer() {
BackgroundTracingManagerImpl::GetInstance()->TriggerNamedEvent(
handle_,
base::BindOnce(&TraceAtRandomIntervalsRule::OnStartedFinalizing,
base::Unretained(this)));
}
void StartTimer() {
int time_to_wait = base::RandInt(kReactiveTraceRandomStartTimeMin,
kReactiveTraceRandomStartTimeMax);
trigger_timer_.Start(
FROM_HERE, base::Seconds(time_to_wait),
base::BindOnce(&TraceAtRandomIntervalsRule::OnTriggerTimer,
base::Unretained(this)));
}
int GetTraceDelay() const override {
return base::RandInt(timeout_min_, timeout_max_);
}
bool ShouldTriggerNamedEvent(const std::string& named_event) const override {
return named_event == named_event_;
}
std::string GenerateUniqueName() const {
static int ids = 0;
char work_buffer[256];
base::strings::SafeSNPrintf(work_buffer, sizeof(work_buffer), "%s_%d",
kTraceAtRandomIntervalsEventName, ids++);
return work_buffer;
}
private:
std::string named_event_;
base::OneShotTimer trigger_timer_;
BackgroundTracingManagerImpl::TriggerHandle handle_;
int timeout_min_;
int timeout_max_;
};
} // namespace
std::unique_ptr<BackgroundTracingRule>
BackgroundTracingRule::CreateRuleFromDict(const base::Value& dict) {
DCHECK(dict.is_dict());
const std::string* type = dict.FindStringKey(kConfigRuleKey);
if (!type)
return nullptr;
std::unique_ptr<BackgroundTracingRule> tracing_rule;
if (*type == kConfigRuleTypeMonitorNamed) {
tracing_rule = NamedTriggerRule::Create(dict);
} else if (*type == kConfigRuleTypeMonitorHistogram) {
tracing_rule = HistogramRule::Create(dict);
} else if (*type == kConfigRuleTypeTraceOnNavigationUntilTriggerOrFull) {
tracing_rule = TraceForNSOrTriggerOrFullRule::Create(dict);
} else if (*type == kConfigRuleTypeTraceAtRandomIntervals) {
tracing_rule = TraceAtRandomIntervalsRule::Create(dict);
}
if (tracing_rule)
tracing_rule->Setup(dict);
return tracing_rule;
}
} // namespace content