blob: e2012ec32c47089bca7f9c82b584a781eb456ce7 [file] [log] [blame]
// Copyright 2023 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/metrics/structured/structured_metrics_service.h"
#include <memory>
#include "base/functional/callback_forward.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "components/metrics/metrics_log.h"
#include "components/metrics/metrics_service_client.h"
#include "components/metrics/structured/reporting/structured_metrics_reporting_service.h"
#include "components/metrics/structured/structured_metrics_features.h"
#include "components/metrics/structured/structured_metrics_scheduler.h"
#include "third_party/metrics_proto/system_profile.pb.h"
namespace metrics::structured {
#if BUILDFLAG(IS_CHROMEOS_ASH)
StructuredMetricsService::ServiceIOHelper::ServiceIOHelper(
scoped_refptr<StructuredMetricsRecorder> recorder)
: recorder_(std::move(recorder)) {}
StructuredMetricsService::ServiceIOHelper::~ServiceIOHelper() = default;
ChromeUserMetricsExtension
StructuredMetricsService::ServiceIOHelper::ProvideEvents() {
ChromeUserMetricsExtension uma_proto;
recorder_->ProvideEventMetrics(uma_proto);
return uma_proto;
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
StructuredMetricsService::StructuredMetricsService(
MetricsServiceClient* client,
PrefService* local_state,
scoped_refptr<StructuredMetricsRecorder> recorder)
: recorder_(std::move(recorder)),
// This service is only enabled if both structured metrics and the service
// flags are enabled.
structured_metrics_enabled_(
base::FeatureList::IsEnabled(metrics::features::kStructuredMetrics) &&
base::FeatureList::IsEnabled(kEnabledStructuredMetricsService)),
client_(client) {
CHECK(client_);
CHECK(local_state);
CHECK(recorder_);
#if BUILDFLAG(IS_CHROMEOS_ASH)
task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
{base::TaskPriority::BEST_EFFORT, base::MayBlock(),
// Blocking because the works being done isn't to expensive.
base::TaskShutdownBehavior::BLOCK_SHUTDOWN});
io_helper_.emplace(task_runner_, recorder_);
#endif
// If the StructuredMetricsService is not enabled then return early. The
// recorder needs to be initialized, but not the reporting service or
// scheduler.
if (!structured_metrics_enabled_) {
return;
}
// Setup the reporting service.
const UnsentLogStore::UnsentLogStoreLimits storage_limits =
GetLogStoreLimits();
reporting_service_ =
std::make_unique<reporting::StructuredMetricsReportingService>(
client_, local_state, storage_limits);
reporting_service_->Initialize();
// Setup the log rotation scheduler.
base::RepeatingClosure rotate_callback = base::BindRepeating(
&StructuredMetricsService::RotateLogsAndSend, weak_factory_.GetWeakPtr());
base::RepeatingCallback<base::TimeDelta(void)> get_upload_interval_callback =
base::BindRepeating(&StructuredMetricsService::GetUploadTimeInterval,
base::Unretained(this));
const bool fast_startup_for_test = client->ShouldStartUpFastForTesting();
scheduler_ = std::make_unique<StructuredMetricsScheduler>(
rotate_callback, get_upload_interval_callback, fast_startup_for_test);
}
StructuredMetricsService::~StructuredMetricsService() {
// Will create a new log for all in-memory events.
// With this, we may be able to add a fast path initialization because flushed
// events do not need to be loaded.
if (recorder_ && recorder_->CanProvideMetrics() &&
recorder_->event_storage()->HasEvents()) {
Flush(metrics::MetricsLogsEventManager::CreateReason::kServiceShutdown);
}
}
void StructuredMetricsService::EnableRecording() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!structured_metrics_enabled_) {
return;
}
if (!initialize_complete_) {
Initialize();
}
recorder_->EnableRecording();
// Attempt an upload if reporting is also active.
if (initialize_complete_ && reporting_active()) {
MaybeStartUpload();
}
}
void StructuredMetricsService::DisableRecording() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!structured_metrics_enabled_) {
return;
}
recorder_->DisableRecording();
}
void StructuredMetricsService::EnableReporting() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!structured_metrics_enabled_) {
return;
}
if (!reporting_active()) {
scheduler_->Start();
}
reporting_service_->EnableReporting();
// Attempt an upload if recording is also enabled.
if (initialize_complete_ && recording_enabled()) {
MaybeStartUpload();
}
}
void StructuredMetricsService::DisableReporting() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!structured_metrics_enabled_) {
return;
}
reporting_service_->DisableReporting();
scheduler_->Stop();
}
void StructuredMetricsService::Flush(
metrics::MetricsLogsEventManager::CreateReason reason) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// The log should not be built if there aren't any events to log.
if (!recorder_->event_storage()->HasEvents()) {
return;
}
ChromeUserMetricsExtension uma_proto;
InitializeUmaProto(uma_proto);
recorder_->ProvideEventMetrics(uma_proto);
const std::string serialized_log = SerializeLog(uma_proto);
reporting_service_->StoreLog(serialized_log, reason);
reporting_service_->log_store()->TrimAndPersistUnsentLogs(true);
}
void StructuredMetricsService::Purge() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!structured_metrics_enabled_) {
return;
}
recorder_->Purge();
reporting_service_->Purge();
}
base::TimeDelta StructuredMetricsService::GetUploadTimeInterval() {
return base::Seconds(GetUploadInterval());
}
void StructuredMetricsService::RotateLogsAndSend() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Verify that the recorder has been initialized and can be providing metrics.
// And if it is, then see if there are any events ready to be uploaded.
if (!recorder_->CanProvideMetrics() ||
!recorder_->event_storage()->HasEvents()) {
return;
}
// If we do not have any logs then nothing to do.
if (!reporting_service_->log_store()->has_unsent_logs()) {
CreateLogs(metrics::MetricsLogsEventManager::CreateReason::kPeriodic,
/*notify_scheduler=*/true);
return;
}
// If we already have a completed log then we can upload here.
reporting_service_->Start();
scheduler_->RotationFinished();
}
void StructuredMetricsService::CreateLogs(
metrics::MetricsLogsEventManager::CreateReason reason,
bool notify_scheduler) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// An async version is used on Ash because events could potentially be stored on
// disk and must be accessed from an IO sequence.
// Other platforms (Windows, Mac, and Linux), the events are stored only
// in-memory and thus a blocking function isn't needed.
#if BUILDFLAG(IS_CHROMEOS_ASH)
BuildAndStoreLog(reason, notify_scheduler);
#else
BuildAndStoreLogSync(reason, notify_scheduler);
#endif
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
void StructuredMetricsService::BuildAndStoreLog(
metrics::MetricsLogsEventManager::CreateReason reason,
bool notify_scheduler) {
ChromeUserMetricsExtension uma_proto;
InitializeUmaProto(uma_proto);
io_helper_.AsyncCall(&ServiceIOHelper::ProvideEvents)
.Then(base::BindOnce(&StructuredMetricsService::StoreLogAndStartUpload,
weak_factory_.GetWeakPtr(), reason,
notify_scheduler));
}
#endif
void StructuredMetricsService::BuildAndStoreLogSync(
metrics::MetricsLogsEventManager::CreateReason reason,
bool notify_scheduler) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
ChromeUserMetricsExtension uma_proto;
InitializeUmaProto(uma_proto);
recorder_->ProvideEventMetrics(uma_proto);
StoreLogAndStartUpload(reason, notify_scheduler, std::move(uma_proto));
}
void StructuredMetricsService::StoreLogAndStartUpload(
metrics::MetricsLogsEventManager::CreateReason reason,
bool notify_scheduler,
ChromeUserMetricsExtension uma_proto) {
// The |uma_proto| is created by |io_helper_|, this adds all additional
// metadata to the output proto.
InitializeUmaProto(uma_proto);
const std::string serialized_log = SerializeLog(uma_proto);
reporting_service_->StoreLog(serialized_log, reason);
// If this callback is set, then run it and return.
// It will only be set from tests where we do not want to upload.
if (create_log_callback_for_tests_) {
std::move(create_log_callback_for_tests_).Run();
return;
}
reporting_service_->Start();
if (notify_scheduler) {
scheduler_->RotationFinished();
}
}
void StructuredMetricsService::Initialize() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!initialize_complete_);
initialize_complete_ = true;
// Notifies the scheduler that it is ready to start creating logs.
scheduler_->InitTaskComplete();
}
void StructuredMetricsService::InitializeUmaProto(
ChromeUserMetricsExtension& uma_proto) {
const int32_t product = client_->GetProduct();
if (product != uma_proto.product()) {
uma_proto.set_product(product);
}
recorder_->ProvideLogMetadata(uma_proto);
SystemProfileProto* system_profile = uma_proto.mutable_system_profile();
metrics::MetricsLog::RecordCoreSystemProfile(client_, system_profile);
}
void StructuredMetricsService::RegisterPrefs(PrefRegistrySimple* registry) {
reporting::StructuredMetricsReportingService::RegisterPrefs(registry);
}
void StructuredMetricsService::SetRecorderForTest(
scoped_refptr<StructuredMetricsRecorder> recorder) {
recorder_ = std::move(recorder);
#if BUILDFLAG(IS_CHROMEOS_ASH)
// Reset the |io_helper_| with the new recorder.
io_helper_.emplace(task_runner_, recorder_);
#endif
}
MetricsServiceClient* StructuredMetricsService::GetMetricsServiceClient()
const {
return client_;
}
void StructuredMetricsService::ManualUpload() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!recorder_->CanProvideMetrics() ||
!recorder_->event_storage()->HasEvents()) {
return;
}
if (!reporting_service_->log_store()->has_unsent_logs()) {
CreateLogs(metrics::MetricsLogsEventManager::CreateReason::kUnknown,
/*notify_scheduler=*/false);
return;
}
reporting_service_->Start();
}
void StructuredMetricsService::MaybeStartUpload() {
// We do not have any logs to upload. Nothing to do.
if (!reporting_service_->log_store()->has_unsent_logs()) {
return;
}
if (initial_upload_started_) {
return;
}
initial_upload_started_ = true;
// Starts an upload. If a log is not staged the next log will be staged for
// upload.
reporting_service_->Start();
}
void StructuredMetricsService::SetCreateLogsCallbackInTests(
base::OnceClosure callback) {
create_log_callback_for_tests_ = std::move(callback);
}
// static:
std::string StructuredMetricsService::SerializeLog(
const ChromeUserMetricsExtension& uma_proto) {
std::string log_data;
const bool status = uma_proto.SerializeToString(&log_data);
DCHECK(status);
return log_data;
}
// static:
UnsentLogStore::UnsentLogStoreLimits
StructuredMetricsService::GetLogStoreLimits() {
return UnsentLogStore::UnsentLogStoreLimits{
.min_log_count = static_cast<size_t>(kMinLogQueueCount.Get()),
.min_queue_size_bytes = static_cast<size_t>(kMinLogQueueSizeBytes.Get()),
.max_log_size_bytes = static_cast<size_t>(kMaxLogSizeBytes.Get()),
};
}
} // namespace metrics::structured