blob: f9d9e46a59af818c673bcf783f27de4ef80ef57e [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/tracing/common/background_tracing_state_manager.h"
#include <cstddef>
#include <cstdint>
#include <memory>
#include "base/json/values_util.h"
#include "base/memory/ptr_util.h"
#include "base/task/single_thread_task_runner.h"
#include "base/values.h"
#include "components/tracing/common/pref_names.h"
namespace tracing {
namespace {
constexpr char kTracingStateKey[] = "state";
constexpr char kTracingEnabledScenariosKey[] = "enabled_scenarios";
constexpr char kTracingPrivacyFilterKey[] = "privacy_filter";
BackgroundTracingStateManager* g_background_tracing_state_manager = nullptr;
} // namespace
BackgroundTracingStateManager::BackgroundTracingStateManager(
PrefService* local_state)
: local_state_(local_state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(g_background_tracing_state_manager, nullptr);
g_background_tracing_state_manager = this;
Initialize();
}
BackgroundTracingStateManager::~BackgroundTracingStateManager() {
DCHECK_EQ(g_background_tracing_state_manager, this);
g_background_tracing_state_manager = nullptr;
}
std::unique_ptr<BackgroundTracingStateManager>
BackgroundTracingStateManager::CreateInstance(PrefService* local_state) {
if (local_state == nullptr) {
return nullptr;
}
return base::WrapUnique(new BackgroundTracingStateManager(local_state));
}
BackgroundTracingStateManager& BackgroundTracingStateManager::GetInstance() {
CHECK_NE(nullptr, g_background_tracing_state_manager);
return *g_background_tracing_state_manager;
}
void BackgroundTracingStateManager::Initialize() {
DCHECK(local_state_);
const base::Value::Dict& dict =
local_state_->GetDict(kBackgroundTracingSessionState);
std::optional<int> state = dict.FindInt(kTracingStateKey);
if (state) {
if (*state >= 0 &&
*state <= static_cast<int>(BackgroundTracingState::LAST)) {
last_session_end_state_ = static_cast<BackgroundTracingState>(*state);
} else {
last_session_end_state_ = BackgroundTracingState::NOT_ACTIVATED;
}
}
auto* scenarios = dict.FindList(kTracingEnabledScenariosKey);
if (scenarios) {
for (const auto& item : *scenarios) {
auto* scenario_hash = item.GetIfString();
if (scenario_hash) {
enabled_scenarios_.push_back(*scenario_hash);
}
}
}
std::optional<bool> privacy_filter_enabled =
dict.FindBool(kTracingPrivacyFilterKey);
if (privacy_filter_enabled) {
privacy_filter_enabled_ = *privacy_filter_enabled;
}
// Save state to update the current session state, replacing the previous
// session state.
SaveState();
}
void BackgroundTracingStateManager::SaveState() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(local_state_);
base::Value::Dict dict;
dict.Set(kTracingStateKey, static_cast<int>(state_));
if (!enabled_scenarios_.empty()) {
base::Value::List scenarios;
for (const auto& scenario_name : enabled_scenarios_) {
scenarios.Append(scenario_name);
}
dict.Set(kTracingEnabledScenariosKey, std::move(scenarios));
}
dict.Set(kTracingPrivacyFilterKey, privacy_filter_enabled_);
local_state_->SetDict(kBackgroundTracingSessionState, std::move(dict));
local_state_->CommitPendingWrite();
}
bool BackgroundTracingStateManager::DidLastSessionEndUnexpectedly() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
switch (last_session_end_state_) {
case BackgroundTracingState::NOT_ACTIVATED:
case BackgroundTracingState::RAN_30_SECONDS:
case BackgroundTracingState::FINALIZATION_STARTED:
return false;
case BackgroundTracingState::STARTED:
// If the browser did not run for 30 seconds after tracing started in
// previous session then do not start tracing in current session as a
// safeguard. This would be impacted by short sessions (eg: on Android),
// but worth the tradeoff of crashing loop on startup. Checking for
// previous session crash status is platform dependent and the crash
// status is initialized at later point than when tracing begins. So, this
// check is safer than waiting for crash metrics to be available. Note
// that this setting only checks for last session and not sessions before
// that. So, the next session might still crash due to tracing if the user
// has another tracing experiment. But, meanwhile we would be able to turn
// off tracing experiments based on uploaded crash metrics.
return true;
}
}
void BackgroundTracingStateManager::OnTracingStarted() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
SetState(BackgroundTracingState::STARTED);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, base::BindOnce([]() {
BackgroundTracingStateManager::GetInstance().SetState(
BackgroundTracingState::RAN_30_SECONDS);
}),
base::Seconds(30));
}
void BackgroundTracingStateManager::OnTracingStopped() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
SetState(BackgroundTracingState::FINALIZATION_STARTED);
}
void BackgroundTracingStateManager::UpdateEnabledScenarios(
std::vector<std::string> enabled_scenarios) {
enabled_scenarios_ = std::move(enabled_scenarios);
SaveState();
}
void BackgroundTracingStateManager::UpdatePrivacyFilter(bool enabled) {
privacy_filter_enabled_ = enabled;
SaveState();
}
void BackgroundTracingStateManager::SetState(BackgroundTracingState new_state) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (state_ == new_state) {
return;
}
// If finalization started before 30 seconds, skip recording the new state.
if (new_state == BackgroundTracingState::RAN_30_SECONDS &&
state_ == BackgroundTracingState::FINALIZATION_STARTED) {
return;
}
state_ = new_state;
SaveState();
}
void BackgroundTracingStateManager::ResetForTesting() {
state_ = BackgroundTracingState::NOT_ACTIVATED;
last_session_end_state_ = BackgroundTracingState::NOT_ACTIVATED;
enabled_scenarios_ = {};
privacy_filter_enabled_ = true;
Initialize();
}
} // namespace tracing