blob: 26dcd3f2aa0f3d35fe7502c892c75854399fcbdd [file] [log] [blame]
// Copyright 2019 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_active_scenario.h"
#include <set>
#include <utility>
#include "base/callback_helpers.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted_memory.h"
#include "base/metrics/histogram_macros.h"
#include "base/rand_util.h"
#include "base/strings/string_tokenizer.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "content/browser/tracing/background_tracing_config_impl.h"
#include "content/browser/tracing/background_tracing_manager_impl.h"
#include "content/browser/tracing/background_tracing_rule.h"
#include "content/browser/tracing/tracing_controller_impl.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "services/tracing/public/cpp/perfetto/perfetto_config.h"
#include "services/tracing/public/cpp/perfetto/perfetto_traced_process.h"
#include "services/tracing/public/cpp/perfetto/trace_event_data_source.h"
#include "services/tracing/public/cpp/perfetto/trace_packet_tokenizer.h"
#include "services/tracing/public/cpp/trace_startup.h"
#include "services/tracing/public/cpp/tracing_features.h"
#include "third_party/perfetto/include/perfetto/ext/tracing/core/trace_packet.h"
#include "third_party/perfetto/include/perfetto/tracing/tracing.h"
using base::trace_event::TraceConfig;
using Metrics = content::BackgroundTracingManagerImpl::Metrics;
namespace content {
class BackgroundTracingActiveScenario::TracingTimer {
public:
TracingTimer(BackgroundTracingActiveScenario* scenario,
BackgroundTracingManager::StartedFinalizingCallback callback)
: scenario_(scenario), callback_(std::move(callback)) {
DCHECK_NE(scenario->GetConfig()->tracing_mode(),
BackgroundTracingConfigImpl::SYSTEM);
}
~TracingTimer() = default;
void StartTimer(int seconds) {
tracing_timer_.Start(FROM_HERE, base::Seconds(seconds), this,
&TracingTimer::TracingTimerFired);
}
void CancelTimer() { tracing_timer_.Stop(); }
void FireTimerForTesting() {
CancelTimer();
TracingTimerFired();
}
private:
void TracingTimerFired() { scenario_->BeginFinalizing(std::move(callback_)); }
raw_ptr<BackgroundTracingActiveScenario> scenario_;
base::OneShotTimer tracing_timer_;
BackgroundTracingManager::StartedFinalizingCallback callback_;
};
class BackgroundTracingActiveScenario::TracingSession {
public:
TracingSession(BackgroundTracingActiveScenario* parent_scenario,
const TraceConfig& chrome_config,
const BackgroundTracingConfigImpl* config,
bool use_local_output)
: parent_scenario_(parent_scenario),
use_local_output_(use_local_output) {
#if !BUILDFLAG(IS_ANDROID)
// TODO(crbug.com/941318): Re-enable startup tracing for Android once all
// Perfetto-related deadlocks are resolved and we also handle concurrent
// system tracing for startup tracing.
if (!TracingControllerImpl::GetInstance()->IsTracing()) {
tracing::EnableStartupTracingForProcess(
chrome_config, config->requires_anonymized_data());
}
#endif
perfetto::TraceConfig perfetto_config;
perfetto_config.mutable_incremental_state_config()->set_clear_period_ms(
config->interning_reset_interval_ms());
base::StringTokenizer data_sources(config->enabled_data_sources(), ",");
std::set<std::string> data_source_filter;
while (data_sources.GetNext()) {
data_source_filter.insert(data_sources.token());
}
perfetto_config = tracing::GetPerfettoConfigWithDataSources(
chrome_config, data_source_filter, config->requires_anonymized_data(),
/*convert_to_legacy_json=*/false,
perfetto::protos::gen::ChromeConfig::BACKGROUND);
tracing_session_ =
perfetto::Tracing::NewTrace(perfetto::BackendType::kCustomBackend);
tracing_session_->Setup(perfetto_config);
auto category_preset = parent_scenario->GetConfig()->category_preset();
tracing_session_->SetOnStartCallback([category_preset] {
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
&BackgroundTracingManagerImpl::OnStartTracingDone,
base::Unretained(&BackgroundTracingManagerImpl::GetInstance()),
category_preset));
});
tracing_session_->Start();
// We check IsEnabled() before creating the LegacyTracingSession,
// so any failures to start tracing at this point would be due to invalid
// configs which we treat as a failure scenario.
}
~TracingSession() {
DCHECK(!tracing_session_);
DCHECK(!TracingControllerImpl::GetInstance()->IsTracing());
}
void BeginFinalizing(base::OnceClosure on_success,
base::OnceClosure on_failure,
bool is_crash_scenario) {
// If the finalization was already in progress, ignore this call.
if (!tracing_session_) {
return;
}
if (!BackgroundTracingManagerImpl::GetInstance().IsAllowedFinalization(
is_crash_scenario)) {
auto on_failure_cb =
base::MakeRefCounted<base::RefCountedData<base::OnceClosure>>(
std::move(on_failure));
auto tracing_session = TakeTracingSession();
tracing_session->data->SetOnStopCallback(
[tracing_session, on_failure_cb] {
GetUIThreadTaskRunner({})->PostTask(FROM_HERE,
std::move(on_failure_cb->data));
});
tracing_session->data->Stop();
return;
}
FinalizeTraceAsProtobuf(std::move(on_success));
DCHECK(!tracing_session_);
}
void AbortScenario(const base::RepeatingClosure& on_abort_callback) {
if (tracing_session_) {
auto tracing_session = TakeTracingSession();
auto on_abort_cb =
base::MakeRefCounted<base::RefCountedData<base::OnceClosure>>(
std::move(on_abort_callback));
tracing_session->data->SetOnStopCallback([on_abort_cb, tracing_session] {
GetUIThreadTaskRunner({})->PostTask(FROM_HERE,
std::move(on_abort_cb->data));
});
tracing_session->data->Stop();
} else {
on_abort_callback.Run();
}
}
private:
// Wraps the tracing session in a refcounted handle that can be passed through
// callbacks.
scoped_refptr<base::RefCountedData<std::unique_ptr<perfetto::TracingSession>>>
TakeTracingSession() {
return base::MakeRefCounted<
base::RefCountedData<std::unique_ptr<perfetto::TracingSession>>>(
std::move(tracing_session_));
}
void FinalizeTraceAsProtobuf(base::OnceClosure on_success) {
auto tracing_session = TakeTracingSession();
auto raw_data = base::MakeRefCounted<
base::RefCountedData<std::unique_ptr<std::string>>>(
std::make_unique<std::string>());
auto parent_scenario = parent_scenario_->GetWeakPtr();
const bool use_local_output = use_local_output_;
tracing_session->data->SetOnStopCallback([parent_scenario, tracing_session,
raw_data, use_local_output] {
tracing_session->data->ReadTrace(
[parent_scenario, tracing_session, raw_data, use_local_output](
perfetto::TracingSession::ReadTraceCallbackArgs args) {
if (args.size) {
raw_data->data->append(args.data, args.size);
}
if (!args.has_more) {
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(use_local_output
? &BackgroundTracingActiveScenario::
OnDataForLocalOutputComplete
: &BackgroundTracingActiveScenario::
OnProtoDataComplete,
parent_scenario, std::move(raw_data->data)));
}
});
});
tracing_session->data->Stop();
if (on_success) {
std::move(on_success).Run();
}
}
const raw_ptr<BackgroundTracingActiveScenario> parent_scenario_;
// True if the trace should be output to a local location using a
// ReceiveCallback instead of uploading through UMA.
const bool use_local_output_;
std::unique_ptr<perfetto::TracingSession> tracing_session_;
};
BackgroundTracingActiveScenario::BackgroundTracingActiveScenario(
std::unique_ptr<BackgroundTracingConfigImpl> config,
BackgroundTracingManager::ReceiveCallback receive_callback,
base::OnceClosure on_aborted_callback)
: config_(std::move(config)),
receive_callback_(std::move(receive_callback)),
on_aborted_callback_(std::move(on_aborted_callback)) {
DCHECK(config_ && !config_->rules().empty());
for (const auto& rule : config_->rules()) {
rule->Install();
}
}
BackgroundTracingActiveScenario::~BackgroundTracingActiveScenario() = default;
const BackgroundTracingConfigImpl* BackgroundTracingActiveScenario::GetConfig()
const {
return config_.get();
}
void BackgroundTracingActiveScenario::SetState(State new_state) {
auto old_state = scenario_state_;
scenario_state_ = new_state;
if ((old_state == State::kTracing) &&
base::trace_event::TraceLog::GetInstance()->IsEnabled()) {
// Leaving the kTracing state means we're supposed to have fully
// shut down tracing at this point. Since StartTracing directly enables
// tracing in TraceLog, in addition to going through Mojo, there's an
// edge-case where tracing is rapidly stopped after starting, too quickly
// for the TraceEventAgent of the browser process to register itself,
// which means that we're left in a state where the Mojo interface doesn't
// think we're tracing but TraceLog is still enabled. If that's the case,
// we abort tracing here.
DCHECK_NE(config_->tracing_mode(), BackgroundTracingConfigImpl::SYSTEM);
base::trace_event::TraceLog::GetInstance()->SetDisabled(
base::trace_event::TraceLog::GetInstance()->enabled_modes());
}
if (scenario_state_ == State::kAborted) {
DCHECK_NE(config_->tracing_mode(), BackgroundTracingConfigImpl::SYSTEM);
tracing_session_.reset();
std::move(on_aborted_callback_).Run();
}
}
void BackgroundTracingActiveScenario::FireTimerForTesting() {
DCHECK(tracing_timer_);
tracing_timer_->FireTimerForTesting();
}
void BackgroundTracingActiveScenario::SetRuleTriggeredCallbackForTesting(
const base::RepeatingClosure& callback) {
rule_triggered_callback_for_testing_ = callback;
}
base::WeakPtr<BackgroundTracingActiveScenario>
BackgroundTracingActiveScenario::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
void BackgroundTracingActiveScenario::StartTracingIfConfigNeedsIt() {
DCHECK(config_);
if (config_->tracing_mode() == BackgroundTracingConfigImpl::PREEMPTIVE) {
StartTracing();
}
// There is nothing to do in case of reactive tracing.
}
bool BackgroundTracingActiveScenario::StartTracing() {
DCHECK_NE(config_->tracing_mode(), BackgroundTracingConfigImpl::SYSTEM);
TraceConfig chrome_config = config_->GetTraceConfig();
// If the tracing controller is tracing, i.e. DevTools or about://tracing,
// we don't start background tracing to not interfere with the user activity.
if (TracingControllerImpl::GetInstance()->IsTracing()) {
AbortScenario();
return false;
}
// Activate the categories immediately. StartTracing eventually does this
// itself, but asynchronously via Mojo, and in the meantime events will be
// dropped. This ensures that we start recording events for those categories
// immediately.
DCHECK(!tracing_session_);
tracing_session_ = std::make_unique<TracingSession>(
this, chrome_config, config_.get(),
/*use_local_output=*/!receive_callback_.is_null());
SetState(State::kTracing);
BackgroundTracingManagerImpl::RecordMetric(Metrics::RECORDING_ENABLED);
return true;
}
void BackgroundTracingActiveScenario::BeginFinalizing(
BackgroundTracingManager::StartedFinalizingCallback callback) {
DCHECK_NE(config_->tracing_mode(), BackgroundTracingConfigImpl::SYSTEM);
triggered_named_event_handle_ = -1;
tracing_timer_.reset();
// |callback| is only run once, but we need 2 callbacks pointing to it.
auto split_callback = base::SplitOnceCallback(std::move(callback));
base::OnceClosure on_begin_finalization_success = base::BindOnce(
[](base::WeakPtr<BackgroundTracingActiveScenario> weak_this,
BackgroundTracingManager::StartedFinalizingCallback callback) {
if (!weak_this) {
return;
}
weak_this->SetState(State::kFinalizing);
BackgroundTracingManagerImpl::RecordMetric(
Metrics::FINALIZATION_ALLOWED);
DCHECK(!weak_this->started_finalizing_closure_);
if (!callback.is_null()) {
weak_this->started_finalizing_closure_ = base::BindOnce(
std::move(callback), /*is_allowed_finalization=*/true);
}
},
weak_ptr_factory_.GetWeakPtr(), std::move(split_callback.first));
base::OnceClosure on_begin_finalization_failure = base::BindOnce(
[](base::WeakPtr<BackgroundTracingActiveScenario> weak_this,
BackgroundTracingManager::StartedFinalizingCallback callback) {
if (!weak_this) {
return;
}
BackgroundTracingManagerImpl::RecordMetric(
Metrics::FINALIZATION_DISALLOWED);
weak_this->SetState(State::kAborted);
if (!callback.is_null()) {
std::move(callback).Run(false);
}
},
weak_ptr_factory_.GetWeakPtr(), std::move(split_callback.second));
tracing_session_->BeginFinalizing(std::move(on_begin_finalization_success),
std::move(on_begin_finalization_failure),
last_triggered_rule_->is_crash());
}
void BackgroundTracingActiveScenario::OnProtoDataComplete(
std::unique_ptr<std::string> proto_trace) {
BackgroundTracingManagerImpl::RecordMetric(Metrics::FINALIZATION_STARTED);
UMA_HISTOGRAM_MEMORY_KB("Tracing.Background.FinalizingTraceSizeInKB",
proto_trace->size() / 1024);
// Store the trace to be uploaded through UMA.
// BackgroundTracingMetricsProvider::ProvideIndependentMetrics will call
// OnFinalizeComplete once the upload is done.
DCHECK(receive_callback_.is_null());
BackgroundTracingManagerImpl::GetInstance().SetTraceToUpload(
std::move(proto_trace));
if (started_finalizing_closure_) {
std::move(started_finalizing_closure_).Run();
}
}
void BackgroundTracingActiveScenario::OnDataForLocalOutputComplete(
std::unique_ptr<std::string> file_contents) {
BackgroundTracingManagerImpl::RecordMetric(
Metrics::FINALIZATION_STARTED_WITH_LOCAL_OUTPUT);
// Send the finalized and compressed tracing data to the destination
// callback.
DCHECK(!receive_callback_.is_null());
receive_callback_.Run(
std::move(file_contents),
base::BindOnce(&BackgroundTracingActiveScenario::OnFinalizeComplete,
weak_ptr_factory_.GetWeakPtr()));
if (started_finalizing_closure_) {
std::move(started_finalizing_closure_).Run();
}
}
void BackgroundTracingActiveScenario::OnFinalizeComplete(bool success) {
if (success) {
BackgroundTracingManagerImpl::RecordMetric(Metrics::UPLOAD_SUCCEEDED);
} else {
BackgroundTracingManagerImpl::RecordMetric(Metrics::UPLOAD_FAILED);
}
tracing_session_.reset();
SetState(State::kIdle);
// Now that a trace has completed, we may need to enable recording again.
StartTracingIfConfigNeedsIt();
}
void BackgroundTracingActiveScenario::AbortScenario() {
if (tracing_session_) {
tracing_session_->AbortScenario(base::BindRepeating(
[](base::WeakPtr<BackgroundTracingActiveScenario> weak_this) {
if (weak_this) {
weak_this->SetState(State::kAborted);
}
},
weak_ptr_factory_.GetWeakPtr()));
} else if (config_->tracing_mode() == BackgroundTracingConfig::SYSTEM) {
// We can't 'abort' system tracing since we aren't the consumer. Instead we
// send a trigger into the system tracing so that we can tell the time the
// scenario stopped.
tracing::PerfettoTracedProcess::Get()->ActivateSystemTriggers(
{"org.chromium.background_tracing.scenario_aborted"});
} else {
// Setting the kAborted state will cause |this| to be destroyed.
SetState(State::kAborted);
}
}
void BackgroundTracingActiveScenario::TriggerNamedEvent(
BackgroundTracingManager::TriggerHandle handle,
BackgroundTracingManager::StartedFinalizingCallback callback) {
std::string trigger_name =
BackgroundTracingManagerImpl::GetInstance().GetTriggerNameFromHandle(
handle);
auto* triggered_rule = GetRuleAbleToTriggerTracing(trigger_name);
if (!triggered_rule) {
if (!callback.is_null()) {
std::move(callback).Run(false);
}
return;
}
// A different reactive config than the running one tried to trigger.
if ((config_->tracing_mode() == BackgroundTracingConfigImpl::REACTIVE &&
(state() == State::kTracing) &&
triggered_named_event_handle_ != handle)) {
if (!callback.is_null()) {
std::move(callback).Run(false);
}
return;
}
triggered_named_event_handle_ = handle;
OnRuleTriggered(triggered_rule, std::move(callback));
}
void BackgroundTracingActiveScenario::OnHistogramTrigger(
const std::string& histogram_name) {
for (const auto& rule : config_->rules()) {
if (rule->ShouldTriggerNamedEvent(histogram_name)) {
OnRuleTriggered(rule.get(),
BackgroundTracingManager::StartedFinalizingCallback());
}
}
}
void BackgroundTracingActiveScenario::OnRuleTriggered(
const BackgroundTracingRule* triggered_rule,
BackgroundTracingManager::StartedFinalizingCallback callback) {
DCHECK_NE(state(), State::kAborted);
double trigger_chance = triggered_rule->trigger_chance();
if (trigger_chance < 1.0 && base::RandDouble() > trigger_chance) {
if (!callback.is_null()) {
std::move(callback).Run(false);
}
return;
}
last_triggered_rule_ = triggered_rule;
int trace_delay = triggered_rule->GetTraceDelay();
switch (config_->tracing_mode()) {
case BackgroundTracingConfigImpl::REACTIVE:
// In reactive mode, a trigger starts tracing, or finalizes tracing
// immediately if it's already running.
BackgroundTracingManagerImpl::RecordMetric(Metrics::REACTIVE_TRIGGERED);
if (state() != State::kTracing) {
// It was not already tracing, start a new trace.
if (!StartTracing()) {
return;
}
} else {
// Some reactive configs that trigger again while tracing should just
// end right away (to not capture multiple navigations, for example).
// For others we just want to ignore the repeated trigger.
if (triggered_rule->stop_tracing_on_repeated_reactive()) {
trace_delay = -1;
} else {
if (!callback.is_null()) {
std::move(callback).Run(false);
}
return;
}
}
break;
case BackgroundTracingConfigImpl::SYSTEM:
BackgroundTracingManagerImpl::RecordMetric(Metrics::SYSTEM_TRIGGERED);
tracing::PerfettoTracedProcess::Get()->ActivateSystemTriggers(
{triggered_rule->rule_id()});
if (!rule_triggered_callback_for_testing_.is_null()) {
rule_triggered_callback_for_testing_.Run();
}
// We drop |callback| on the floor because we won't know when the system
// service starts finalizing the trace and the callback isn't relevant to
// this scenario.
return;
case BackgroundTracingConfigImpl::PREEMPTIVE:
// In preemptive mode, a trigger starts finalizing a trace if one is
// running and we haven't got a finalization timer running,
// otherwise we do nothing.
if ((state() != State::kTracing) || tracing_timer_) {
if (!callback.is_null()) {
std::move(callback).Run(false);
}
return;
}
BackgroundTracingManagerImpl::RecordMetric(Metrics::PREEMPTIVE_TRIGGERED);
break;
}
if (trace_delay < 0) {
BeginFinalizing(std::move(callback));
} else {
tracing_timer_ = std::make_unique<TracingTimer>(this, std::move(callback));
tracing_timer_->StartTimer(trace_delay);
}
if (!rule_triggered_callback_for_testing_.is_null()) {
rule_triggered_callback_for_testing_.Run();
}
}
BackgroundTracingRule*
BackgroundTracingActiveScenario::GetRuleAbleToTriggerTracing(
const std::string& trigger_name) {
// If the last trace is still uploading, we don't allow a new one to trigger.
if (state() == State::kFinalizing) {
return nullptr;
}
for (const auto& rule : config_->rules()) {
if (rule->ShouldTriggerNamedEvent(trigger_name)) {
return rule.get();
}
}
return nullptr;
}
base::Value::Dict BackgroundTracingActiveScenario::GenerateMetadataDict() {
base::Value::Dict metadata_dict;
metadata_dict.Set("config", config_->ToDict());
metadata_dict.Set("scenario_name", config_->scenario_name());
if (last_triggered_rule_) {
metadata_dict.Set("last_triggered_rule", last_triggered_rule_->ToDict());
}
return metadata_dict;
}
void BackgroundTracingActiveScenario::GenerateMetadataProto(
perfetto::protos::pbzero::ChromeMetadataPacket* metadata) {
if (!last_triggered_rule_) {
return;
}
auto* triggered_rule =
metadata->set_background_tracing_metadata()->set_triggered_rule();
last_triggered_rule_->GenerateMetadataProto(triggered_rule);
}
size_t BackgroundTracingActiveScenario::GetTraceUploadLimitKb() const {
return config_->GetTraceUploadLimitKb();
}
} // namespace content