blob: c1f2f9453d31bf14c2d17fbc8063d2093d0375d2 [file] [log] [blame]
// Copyright 2017 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 "components/optimization_guide/optimization_guide_service.h"
#include <string>
#include "base/bind.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/post_task.h"
namespace optimization_guide {
namespace {
// Version "0" corresponds to no processed version. By service conventions,
// we represent it as a dotted triple.
const char kNullVersion[] = "0.0.0";
void RecordProcessHintsResult(
OptimizationGuideService::ProcessHintsResult result) {
UMA_HISTOGRAM_ENUMERATION(
"OptimizationGuide.ProcessHintsResult", static_cast<int>(result),
static_cast<int>(OptimizationGuideService::ProcessHintsResult::MAX));
}
} // namespace
ComponentInfo::ComponentInfo(const base::Version& hints_version,
const base::FilePath& hints_path)
: hints_version(hints_version), hints_path(hints_path) {}
ComponentInfo::~ComponentInfo() {}
OptimizationGuideService::OptimizationGuideService(
const scoped_refptr<base::SingleThreadTaskRunner>& io_thread_task_runner)
: background_task_runner_(base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT})),
io_thread_task_runner_(io_thread_task_runner),
latest_processed_version_(kNullVersion) {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
OptimizationGuideService::~OptimizationGuideService() {}
void OptimizationGuideService::SetLatestProcessedVersionForTesting(
const base::Version& version) {
latest_processed_version_ = version;
}
void OptimizationGuideService::AddObserver(
OptimizationGuideServiceObserver* observer) {
if (io_thread_task_runner_->BelongsToCurrentThread()) {
AddObserverOnIOThread(observer);
} else {
io_thread_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&OptimizationGuideService::AddObserverOnIOThread,
base::Unretained(this), observer));
}
}
void OptimizationGuideService::AddObserverOnIOThread(
OptimizationGuideServiceObserver* observer) {
DCHECK(io_thread_task_runner_->BelongsToCurrentThread());
observers_.AddObserver(observer);
}
void OptimizationGuideService::RemoveObserver(
OptimizationGuideServiceObserver* observer) {
if (io_thread_task_runner_->BelongsToCurrentThread()) {
RemoveObserverOnIOThread(observer);
} else {
io_thread_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&OptimizationGuideService::RemoveObserverOnIOThread,
base::Unretained(this), observer));
}
}
void OptimizationGuideService::RemoveObserverOnIOThread(
OptimizationGuideServiceObserver* observer) {
DCHECK(io_thread_task_runner_->BelongsToCurrentThread());
observers_.RemoveObserver(observer);
}
void OptimizationGuideService::ProcessHints(
const ComponentInfo& component_info) {
background_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&OptimizationGuideService::ProcessHintsInBackground,
base::Unretained(this), component_info));
}
void OptimizationGuideService::ProcessHintsInBackground(
const ComponentInfo& component_info) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(crbug.com/783246): Add crash loop detection to ensure bad component
// updates do not crash Chrome.
if (!component_info.hints_version.IsValid()) {
RecordProcessHintsResult(ProcessHintsResult::FAILED_INVALID_PARAMETERS);
return;
}
if (latest_processed_version_.CompareTo(component_info.hints_version) >= 0)
return;
if (component_info.hints_path.empty()) {
RecordProcessHintsResult(ProcessHintsResult::FAILED_INVALID_PARAMETERS);
return;
}
std::string binary_pb;
if (!base::ReadFileToString(component_info.hints_path, &binary_pb)) {
RecordProcessHintsResult(ProcessHintsResult::FAILED_READING_FILE);
return;
}
proto::Configuration new_config;
if (!new_config.ParseFromString(binary_pb)) {
RecordProcessHintsResult(ProcessHintsResult::FAILED_INVALID_CONFIGURATION);
return;
}
latest_processed_version_ = component_info.hints_version;
RecordProcessHintsResult(ProcessHintsResult::SUCCESS);
io_thread_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&OptimizationGuideService::DispatchHintsOnIOThread,
base::Unretained(this), new_config, component_info));
}
void OptimizationGuideService::DispatchHintsOnIOThread(
const proto::Configuration& config,
const ComponentInfo& component_info) {
DCHECK(io_thread_task_runner_->BelongsToCurrentThread());
for (auto& observer : observers_)
observer.OnHintsProcessed(config, component_info);
}
} // namespace optimization_guide