blob: 25dcbdc4c55d8f89f78bbfb8dc50802e897a89fd [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 "base/json/values_util.h"
#include "base/task/single_thread_task_runner.h"
#include "base/values.h"
#include "components/tracing/common/pref_names.h"
#include "content/public/browser/background_tracing_config.h"
#include "content/public/browser/browser_thread.h"
namespace {
constexpr char kTracingStateKey[] = "state";
constexpr char kUploadTimesKey[] = "upload_times";
constexpr char kScenarioKey[] = "scenario";
constexpr char kUploadTimestampKey[] = "time";
const int kMinDaysUntilNextUpload = 7;
// Removes any version numbers from the scenario name.
std::string StripScenarioName(const std::string& scenario_name) {
std::string stripped_scenario_name;
base::RemoveChars(scenario_name, "1234567890", &stripped_scenario_name);
return stripped_scenario_name;
}
} // namespace
namespace tracing {
BackgroundTracingStateManager::BackgroundTracingStateManager() = default;
BackgroundTracingStateManager::~BackgroundTracingStateManager() = default;
BackgroundTracingStateManager& BackgroundTracingStateManager::GetInstance() {
static base::NoDestructor<BackgroundTracingStateManager> instance;
return *instance;
}
void BackgroundTracingStateManager::SetPrefServiceForTesting(
PrefService* local_state) {
if (!local_state_ && local_state)
local_state_ = local_state;
}
void BackgroundTracingStateManager::Initialize(PrefService* local_state) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (initialized_)
return;
initialized_ = true;
if (!local_state_ && local_state)
local_state_ = local_state;
DCHECK(local_state_);
const base::Value::Dict& dict =
local_state_->GetDict(kBackgroundTracingSessionState);
absl::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;
}
}
const base::Value::List* upload_times = dict.FindList(kUploadTimesKey);
if (upload_times) {
for (const base::Value& scenario_val : *upload_times) {
DCHECK(scenario_val.is_dict());
const base::Value::Dict& scenario_dict = scenario_val.GetDict();
const std::string* scenario = scenario_dict.FindString(kScenarioKey);
const base::Value* timestamp_val =
scenario_dict.Find(kUploadTimestampKey);
if (!scenario || !timestamp_val) {
continue;
}
absl::optional<base::Time> upload_time = base::ValueToTime(timestamp_val);
if (!upload_time) {
continue;
}
if ((base::Time::Now() - *upload_time) >
base::Days(kMinDaysUntilNextUpload)) {
continue;
}
scenario_last_upload_timestamp_[*scenario] = *upload_time;
}
}
// Save state to update the current session state, replacing the previous
// session state.
SaveState();
}
void BackgroundTracingStateManager::SaveState() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(initialized_);
SaveState(scenario_last_upload_timestamp_, state_);
}
void BackgroundTracingStateManager::SaveState(
const ScenarioUploadTimestampMap& scenario_upload_times,
BackgroundTracingState state) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::Value::Dict dict;
dict.Set(kTracingStateKey, static_cast<int>(state));
base::Value::List upload_times;
for (const auto& it : scenario_upload_times) {
base::Value::Dict scenario;
scenario.Set(kScenarioKey, StripScenarioName(it.first));
scenario.Set(kUploadTimestampKey, base::TimeToValue(it.second));
upload_times.Append(std::move(scenario));
}
dict.Set(kUploadTimesKey, std::move(upload_times));
local_state_->SetDict(kBackgroundTracingSessionState, std::move(dict));
local_state_->CommitPendingWrite();
}
bool BackgroundTracingStateManager::DidLastSessionEndUnexpectedly() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(initialized_);
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;
}
}
bool BackgroundTracingStateManager::DidRecentlyUploadForScenario(
const content::BackgroundTracingConfig& config) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(initialized_);
std::string stripped_scenario_name =
StripScenarioName(config.scenario_name());
auto it = scenario_last_upload_timestamp_.find(stripped_scenario_name);
if (it != scenario_last_upload_timestamp_.end()) {
return (base::Time::Now() - it->second) <=
base::Days(kMinDaysUntilNextUpload);
}
return false;
}
void BackgroundTracingStateManager::NotifyTracingStarted() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
SetState(BackgroundTracingState::STARTED);
base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, base::BindOnce([]() {
BackgroundTracingStateManager::GetInstance().SetState(
BackgroundTracingState::RAN_30_SECONDS);
}),
base::Seconds(30));
}
void BackgroundTracingStateManager::NotifyFinalizationStarted() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
SetState(BackgroundTracingState::FINALIZATION_STARTED);
}
void BackgroundTracingStateManager::SetState(BackgroundTracingState new_state) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(initialized_);
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::OnScenarioUploaded(
const std::string& scenario_name) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(initialized_);
scenario_last_upload_timestamp_[StripScenarioName(scenario_name)] =
base::Time::Now();
SaveState();
}
void BackgroundTracingStateManager::Reset() {
initialized_ = false;
local_state_ = nullptr;
state_ = BackgroundTracingState::NOT_ACTIVATED;
last_session_end_state_ = BackgroundTracingState::NOT_ACTIVATED;
scenario_last_upload_timestamp_.clear();
}
} // namespace tracing