blob: be75971162416494a757d709e93c19d504f525bb [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_manager_impl.h"
#include "base/json/json_writer.h"
#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "base/time/time.h"
#include "content/public/browser/background_tracing_preemptive_config.h"
#include "content/public/browser/background_tracing_reactive_config.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/tracing_delegate.h"
#include "content/public/common/content_client.h"
namespace content {
namespace {
base::LazyInstance<BackgroundTracingManagerImpl>::Leaky g_controller =
LAZY_INSTANCE_INITIALIZER;
const char kMetaDataConfigKey[] = "config";
const char kMetaDataVersionKey[] = "product_version";
// These values are used for a histogram. Do not reorder.
enum BackgroundTracingMetrics {
SCENARIO_ACTIVATION_REQUESTED = 0,
SCENARIO_ACTIVATED_SUCCESSFULLY = 1,
RECORDING_ENABLED = 2,
PREEMPTIVE_TRIGGERED = 3,
REACTIVE_TRIGGERED = 4,
FINALIZATION_ALLOWED = 5,
FINALIZATION_DISALLOWED = 6,
FINALIZATION_STARTED = 7,
FINALIZATION_COMPLETE = 8,
NUMBER_OF_BACKGROUND_TRACING_METRICS,
};
void RecordBackgroundTracingMetric(BackgroundTracingMetrics metric) {
UMA_HISTOGRAM_ENUMERATION("Tracing.Background.ScenarioState", metric,
NUMBER_OF_BACKGROUND_TRACING_METRICS);
}
} // namespace
BackgroundTracingManagerImpl::TracingTimer::TracingTimer(
StartedFinalizingCallback callback) : callback_(callback) {
}
BackgroundTracingManagerImpl::TracingTimer::~TracingTimer() {
}
void BackgroundTracingManagerImpl::TracingTimer::StartTimer() {
const int kTimeoutSecs = 10;
tracing_timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(kTimeoutSecs),
this, &BackgroundTracingManagerImpl::TracingTimer::TracingTimerFired);
}
void BackgroundTracingManagerImpl::TracingTimer::CancelTimer() {
tracing_timer_.Stop();
}
void BackgroundTracingManagerImpl::TracingTimer::TracingTimerFired() {
BackgroundTracingManagerImpl::GetInstance()->BeginFinalizing(callback_);
}
void BackgroundTracingManagerImpl::TracingTimer::FireTimerForTesting() {
CancelTimer();
TracingTimerFired();
}
BackgroundTracingManager* BackgroundTracingManager::GetInstance() {
return BackgroundTracingManagerImpl::GetInstance();
}
BackgroundTracingManagerImpl* BackgroundTracingManagerImpl::GetInstance() {
return g_controller.Pointer();
}
BackgroundTracingManagerImpl::BackgroundTracingManagerImpl()
: delegate_(GetContentClient()->browser()->GetTracingDelegate()),
is_gathering_(false),
is_tracing_(false),
requires_anonymized_data_(true),
trigger_handle_ids_(0) {
}
BackgroundTracingManagerImpl::~BackgroundTracingManagerImpl() {
NOTREACHED();
}
void BackgroundTracingManagerImpl::WhenIdle(
base::Callback<void()> idle_callback) {
CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
idle_callback_ = idle_callback;
}
bool BackgroundTracingManagerImpl::IsSupportedConfig(
BackgroundTracingConfig* config) {
// No config is just fine, we just don't do anything.
if (!config)
return true;
if (config->mode == BackgroundTracingConfig::PREEMPTIVE_TRACING_MODE) {
BackgroundTracingPreemptiveConfig* preemptive_config =
static_cast<BackgroundTracingPreemptiveConfig*>(config);
const std::vector<BackgroundTracingPreemptiveConfig::MonitoringRule>&
configs = preemptive_config->configs;
for (size_t i = 0; i < configs.size(); ++i) {
if (configs[i].type !=
BackgroundTracingPreemptiveConfig::
MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED)
return false;
}
}
if (config->mode == BackgroundTracingConfig::REACTIVE_TRACING_MODE) {
BackgroundTracingReactiveConfig* reactive_config =
static_cast<BackgroundTracingReactiveConfig*>(config);
const std::vector<BackgroundTracingReactiveConfig::TracingRule>&
configs = reactive_config->configs;
for (size_t i = 0; i < configs.size(); ++i) {
if (configs[i].type !=
BackgroundTracingReactiveConfig::TRACE_FOR_10S_OR_TRIGGER_OR_FULL)
return false;
}
}
return true;
}
bool BackgroundTracingManagerImpl::SetActiveScenario(
scoped_ptr<BackgroundTracingConfig> config,
const BackgroundTracingManager::ReceiveCallback& receive_callback,
DataFiltering data_filtering) {
CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
RecordBackgroundTracingMetric(SCENARIO_ACTIVATION_REQUESTED);
if (is_tracing_)
return false;
bool requires_anonymized_data = (data_filtering == ANONYMIZE_DATA);
// If the I/O thread isn't running, this is a startup scenario and
// we have to wait until initialization is finished to validate that the
// scenario can run.
if (BrowserThread::IsThreadInitialized(BrowserThread::IO)) {
// TODO(oysteine): Retry when time_until_allowed has elapsed.
if (config && delegate_ &&
!delegate_->IsAllowedToBeginBackgroundScenario(
*config.get(), requires_anonymized_data)) {
return false;
}
} else {
base::MessageLoop::current()->PostTask(
FROM_HERE,
base::Bind(&BackgroundTracingManagerImpl::ValidateStartupScenario,
base::Unretained(this)));
}
if (!IsSupportedConfig(config.get()))
return false;
// No point in tracing if there's nowhere to send it.
if (config && receive_callback.is_null())
return false;
config_ = config.Pass();
receive_callback_ = receive_callback;
requires_anonymized_data_ = requires_anonymized_data;
EnableRecordingIfConfigNeedsIt();
RecordBackgroundTracingMetric(SCENARIO_ACTIVATED_SUCCESSFULLY);
return true;
}
bool BackgroundTracingManagerImpl::HasActiveScenarioForTesting() {
return config_;
}
void BackgroundTracingManagerImpl::ValidateStartupScenario() {
if (!config_ || !delegate_)
return;
if (!delegate_->IsAllowedToBeginBackgroundScenario(
*config_.get(), requires_anonymized_data_)) {
AbortScenario();
}
}
void BackgroundTracingManagerImpl::EnableRecordingIfConfigNeedsIt() {
if (!config_)
return;
if (config_->mode == BackgroundTracingConfig::PREEMPTIVE_TRACING_MODE) {
EnableRecording(GetCategoryFilterStringForCategoryPreset(
static_cast<BackgroundTracingPreemptiveConfig*>(config_.get())
->category_preset),
base::trace_event::RECORD_CONTINUOUSLY);
}
// There is nothing to do in case of reactive tracing.
}
bool BackgroundTracingManagerImpl::IsAbleToTriggerTracing(
TriggerHandle handle) const {
if (!config_)
return false;
// If the last trace is still uploading, we don't allow a new one to trigger.
if (is_gathering_)
return false;
if (!IsTriggerHandleValid(handle)) {
return false;
}
std::string trigger_name = GetTriggerNameFromHandle(handle);
if (config_->mode == BackgroundTracingConfig::PREEMPTIVE_TRACING_MODE) {
BackgroundTracingPreemptiveConfig* preemptive_config =
static_cast<BackgroundTracingPreemptiveConfig*>(config_.get());
const std::vector<BackgroundTracingPreemptiveConfig::MonitoringRule>&
configs = preemptive_config->configs;
for (size_t i = 0; i < configs.size(); ++i) {
if (configs[i].type != BackgroundTracingPreemptiveConfig::
MONITOR_AND_DUMP_WHEN_TRIGGER_NAMED)
continue;
if (trigger_name == configs[i].named_trigger_info.trigger_name) {
return true;
}
}
} else {
BackgroundTracingReactiveConfig* reactive_config =
static_cast<BackgroundTracingReactiveConfig*>(config_.get());
const std::vector<BackgroundTracingReactiveConfig::TracingRule>&
configs = reactive_config->configs;
for (size_t i = 0; i < configs.size(); ++i) {
if (configs[i].type !=
BackgroundTracingReactiveConfig::
TRACE_FOR_10S_OR_TRIGGER_OR_FULL)
continue;
if (trigger_name == configs[i].trigger_name) {
return true;
}
}
}
return false;
}
void BackgroundTracingManagerImpl::TriggerNamedEvent(
BackgroundTracingManagerImpl::TriggerHandle handle,
StartedFinalizingCallback callback) {
if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(&BackgroundTracingManagerImpl::TriggerNamedEvent,
base::Unretained(this), handle, callback));
return;
}
if (!IsAbleToTriggerTracing(handle)) {
if (!callback.is_null())
callback.Run(false);
return;
}
if (config_->mode == BackgroundTracingConfig::PREEMPTIVE_TRACING_MODE) {
RecordBackgroundTracingMetric(PREEMPTIVE_TRIGGERED);
BeginFinalizing(callback);
} else {
RecordBackgroundTracingMetric(REACTIVE_TRIGGERED);
if (is_tracing_) {
tracing_timer_->CancelTimer();
BeginFinalizing(callback);
return;
}
// It was not already tracing, start a new trace.
BackgroundTracingReactiveConfig* reactive_config =
static_cast<BackgroundTracingReactiveConfig*>(config_.get());
const std::vector<BackgroundTracingReactiveConfig::TracingRule>&
configs = reactive_config->configs;
std::string trigger_name = GetTriggerNameFromHandle(handle);
for (size_t i = 0; i < configs.size(); ++i) {
if (configs[i].trigger_name == trigger_name) {
EnableRecording(
GetCategoryFilterStringForCategoryPreset(
configs[i].category_preset),
base::trace_event::RECORD_UNTIL_FULL);
tracing_timer_.reset(new TracingTimer(callback));
tracing_timer_->StartTimer();
break;
}
}
}
}
BackgroundTracingManagerImpl::TriggerHandle
BackgroundTracingManagerImpl::RegisterTriggerType(const char* trigger_name) {
CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
trigger_handle_ids_ += 1;
trigger_handles_.insert(
std::pair<TriggerHandle, std::string>(trigger_handle_ids_, trigger_name));
return static_cast<TriggerHandle>(trigger_handle_ids_);
}
bool BackgroundTracingManagerImpl::IsTriggerHandleValid(
BackgroundTracingManager::TriggerHandle handle) const {
return trigger_handles_.find(handle) != trigger_handles_.end();
}
std::string BackgroundTracingManagerImpl::GetTriggerNameFromHandle(
BackgroundTracingManager::TriggerHandle handle) const {
CHECK(IsTriggerHandleValid(handle));
return trigger_handles_.find(handle)->second;
}
void BackgroundTracingManagerImpl::GetTriggerNameList(
std::vector<std::string>* trigger_names) {
for (std::map<TriggerHandle, std::string>::iterator it =
trigger_handles_.begin();
it != trigger_handles_.end(); ++it)
trigger_names->push_back(it->second);
}
void BackgroundTracingManagerImpl::InvalidateTriggerHandlesForTesting() {
trigger_handles_.clear();
}
void BackgroundTracingManagerImpl::SetTracingEnabledCallbackForTesting(
const base::Closure& callback) {
tracing_enabled_callback_for_testing_ = callback;
};
void BackgroundTracingManagerImpl::FireTimerForTesting() {
tracing_timer_->FireTimerForTesting();
}
void BackgroundTracingManagerImpl::EnableRecording(
std::string category_filter_str,
base::trace_event::TraceRecordMode record_mode) {
base::trace_event::TraceConfig trace_config(category_filter_str, record_mode);
if (requires_anonymized_data_)
trace_config.EnableArgumentFilter();
is_tracing_ = TracingController::GetInstance()->EnableRecording(
trace_config, tracing_enabled_callback_for_testing_);
RecordBackgroundTracingMetric(RECORDING_ENABLED);
}
void BackgroundTracingManagerImpl::OnFinalizeStarted(
base::RefCountedString* file_contents) {
CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
RecordBackgroundTracingMetric(FINALIZATION_STARTED);
UMA_HISTOGRAM_MEMORY_KB("Tracing.Background.FinalizingTraceSizeInKB",
file_contents->size() / 1024);
if (!receive_callback_.is_null()) {
receive_callback_.Run(
file_contents, GenerateMetadataDict(),
base::Bind(&BackgroundTracingManagerImpl::OnFinalizeComplete,
base::Unretained(this)));
}
}
void BackgroundTracingManagerImpl::OnFinalizeComplete() {
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&BackgroundTracingManagerImpl::OnFinalizeComplete,
base::Unretained(this)));
return;
}
CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
is_gathering_ = false;
if (!idle_callback_.is_null())
idle_callback_.Run();
// Now that a trace has completed, we may need to enable recording again.
// TODO(oysteine): Retry later if IsAllowedToBeginBackgroundScenario fails.
if (!delegate_ ||
delegate_->IsAllowedToBeginBackgroundScenario(
*config_.get(), requires_anonymized_data_)) {
EnableRecordingIfConfigNeedsIt();
}
RecordBackgroundTracingMetric(FINALIZATION_COMPLETE);
}
scoped_ptr<base::DictionaryValue>
BackgroundTracingManagerImpl::GenerateMetadataDict() const {
// Grab the product version.
std::string product_version = GetContentClient()->GetProduct();
// Serialize the config into json.
scoped_ptr<base::DictionaryValue> config_dict(new base::DictionaryValue());
BackgroundTracingConfig::IntoDict(config_.get(), config_dict.get());
scoped_ptr<base::DictionaryValue> metadata_dict(new base::DictionaryValue());
metadata_dict->Set(kMetaDataConfigKey, config_dict.Pass());
metadata_dict->SetString(kMetaDataVersionKey, product_version);
return metadata_dict.Pass();
}
void BackgroundTracingManagerImpl::BeginFinalizing(
StartedFinalizingCallback callback) {
is_gathering_ = true;
is_tracing_ = false;
bool is_allowed_finalization =
!delegate_ || (config_ &&
delegate_->IsAllowedToEndBackgroundScenario(
*config_.get(), requires_anonymized_data_));
scoped_refptr<TracingControllerImpl::TraceDataSink> trace_data_sink;
if (is_allowed_finalization) {
trace_data_sink = content::TracingController::CreateCompressedStringSink(
content::TracingController::CreateCallbackEndpoint(
base::Bind(&BackgroundTracingManagerImpl::OnFinalizeStarted,
base::Unretained(this))));
RecordBackgroundTracingMetric(FINALIZATION_ALLOWED);
if (auto metadata_dict = GenerateMetadataDict()) {
std::string results;
if (base::JSONWriter::Write(*metadata_dict.get(), &results))
trace_data_sink->SetMetadata(results);
}
} else {
RecordBackgroundTracingMetric(FINALIZATION_DISALLOWED);
}
content::TracingController::GetInstance()->DisableRecording(trace_data_sink);
if (!callback.is_null())
callback.Run(is_allowed_finalization);
}
void BackgroundTracingManagerImpl::AbortScenario() {
is_tracing_ = false;
config_.reset();
content::TracingController::GetInstance()->DisableRecording(nullptr);
}
std::string
BackgroundTracingManagerImpl::GetCategoryFilterStringForCategoryPreset(
BackgroundTracingConfig::CategoryPreset preset) const {
switch (preset) {
case BackgroundTracingConfig::CategoryPreset::BENCHMARK:
return "benchmark,toplevel";
case BackgroundTracingConfig::CategoryPreset::BENCHMARK_DEEP:
return "*,disabled-by-default-benchmark.detailed";
}
NOTREACHED();
return "";
}
} // namspace content