// 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::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 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(config); const std::vector& 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(config); const std::vector& 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 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(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(config_.get()); const std::vector& 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(config_.get()); const std::vector& 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(config_.get()); const std::vector& 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(trigger_handle_ids_, trigger_name)); return static_cast(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* trigger_names) { for (std::map::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 BackgroundTracingManagerImpl::GenerateMetadataDict() const { // Grab the product version. std::string product_version = GetContentClient()->GetProduct(); // Serialize the config into json. scoped_ptr config_dict(new base::DictionaryValue()); BackgroundTracingConfig::IntoDict(config_.get(), config_dict.get()); scoped_ptr 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 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