chromium/chrome/browser/metrics/structured/ash_structured_metrics_recorder.cc

// 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 "chrome/browser/metrics/structured/ash_structured_metrics_recorder.h"

#include <memory>

#include "base/files/file_path.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/metrics_hashes.h"
#include "base/task/current_thread.h"
#include "chrome/browser/metrics/structured/ash_event_storage.h"
#include "chrome/browser/metrics/structured/key_data_provider_ash.h"
#include "chrome/browser/metrics/structured/storage_manager_impl.h"
#include "components/metrics/structured/enums.h"
#include "components/metrics/structured/histogram_util.h"
#include "components/metrics/structured/structured_metrics_features.h"
#include "components/metrics/structured/structured_metrics_validator.h"
#include "third_party/metrics_proto/chrome_user_metrics_extension.pb.h"
#include "third_party/metrics_proto/structured_data.pb.h"

namespace metrics::structured {

namespace {

using ::metrics::ChromeUserMetricsExtension;
using ::metrics::SystemProfileProto;

// Directory containing serialized event protos to read.
constexpr char kExternalMetricsDir[] = "/var/lib/metrics/structured/events";

// The path used to store events before the start of a user session.
constexpr char kAshPreUserStorePath[] =
    "/var/lib/metrics/structured/chromium/events";

std::unique_ptr<EventStorage<StructuredEventProto>> CreateEventStorage() {
  if (base::FeatureList::IsEnabled(kEventStorageManager)) {
    const StorageManagerConfig config =
        StorageManagerImpl::GetStorageManagerConfig();
    return std::make_unique<StorageManagerImpl>(config);
  }
  return std::make_unique<AshEventStorage>(
      AshEventStorage::kSaveDelay, base::FilePath(kAshPreUserStorePath));
}

}  // namespace

AshStructuredMetricsRecorder::AshStructuredMetricsRecorder(
    metrics::MetricsProvider* system_profile_provider)
    : AshStructuredMetricsRecorder(std::make_unique<KeyDataProviderAsh>(),
                                   CreateEventStorage(),
                                   system_profile_provider) {}

AshStructuredMetricsRecorder::AshStructuredMetricsRecorder(
    std::unique_ptr<KeyDataProvider> key_provider,
    std::unique_ptr<EventStorage<StructuredEventProto>> event_storage,
    metrics::MetricsProvider* system_profile_provider)
    : StructuredMetricsRecorder(std::move(key_provider),
                                std::move(event_storage)),
      system_profile_provider_(system_profile_provider) {}

AshStructuredMetricsRecorder::~AshStructuredMetricsRecorder() = default;

void AshStructuredMetricsRecorder::EnableRecording() {
  StructuredMetricsRecorder::EnableRecording();

  if (external_metrics_) {
    external_metrics_->EnableRecording();
  }
}

void AshStructuredMetricsRecorder::DisableRecording() {
  StructuredMetricsRecorder::DisableRecording();

  if (external_metrics_) {
    external_metrics_->DisableRecording();
  }
}

void AshStructuredMetricsRecorder::ProvideEventMetrics(
    ChromeUserMetricsExtension& uma_proto) {
  if (!CanProvideMetrics()) {
    return;
  }

  // Base class handles most of the work that is needed.
  StructuredMetricsRecorder::ProvideEventMetrics(uma_proto);

  // Handle External Metrics statistics.
  LogExternalMetricsScanInUpload(external_metrics_scans_);
  external_metrics_scans_ = 0;
}

void AshStructuredMetricsRecorder::AddSequenceMetadata(
    StructuredEventProto* proto,
    const Event& event,
    const ProjectValidator& project_validator,
    const KeyData& key_data) {
  auto* event_sequence_metadata = proto->mutable_event_sequence_metadata();

  event_sequence_metadata->set_reset_counter(
      event.event_sequence_metadata().reset_counter);
  event_sequence_metadata->set_system_uptime(
      event.recorded_time_since_boot().InMilliseconds());
  event_sequence_metadata->set_event_unique_id(
      base::HashMetricName(event.event_sequence_metadata().event_unique_id));

  const int rotation_age =
      key_data.GetKeyAgeInWeeks(project_validator.project_hash()).value_or(0);
  event_sequence_metadata->set_client_id_rotation_weeks(rotation_age);

  std::optional<uint64_t> primary_id =
      key_data_provider_->GetId(event.project_name());
  if (primary_id.has_value()) {
    proto->set_user_project_id(primary_id.value());
  }

  std::optional<uint64_t> secondary_id =
      key_data_provider_->GetSecondaryId(event.project_name());
  if (secondary_id.has_value()) {
    proto->set_device_project_id(secondary_id.value());
  }
}

void AshStructuredMetricsRecorder::ProvideLogMetadata(
    ChromeUserMetricsExtension& uma_proto) {
  StructuredMetricsRecorder::ProvideLogMetadata(uma_proto);
  // Add the system profile.
  ProvideSystemProfile(uma_proto.mutable_system_profile());
}

void AshStructuredMetricsRecorder::OnSystemProfileInitialized() {
  system_profile_initialized_ = true;
}

void AshStructuredMetricsRecorder::OnExternalMetricsCollected(
    const EventsProto& events) {
  SCOPED_UMA_HISTOGRAM_TIMER_MICROS(
      "StructuredMetrics.OnExternalMetricsCollectedDuration");
  DCHECK(base::CurrentUIThread::IsSet());
  if (!recording_enabled()) {
    return;
  }

  event_storage_->AddBatchEvents(events.events());

  for (const auto& event : events.events()) {
    NotifyEventRecorded(event);
  }

  // Only increment if new events were add.
  if (events.events_size()) {
    external_metrics_scans_ += 1;
  }
}

void AshStructuredMetricsRecorder::SetExternalMetricsDirForTest(
    const base::FilePath& dir) {
  external_metrics_ = std::make_unique<ExternalMetrics>(
      dir, GetExternalMetricsCollectionInterval(),
      base::BindRepeating(
          &AshStructuredMetricsRecorder::OnExternalMetricsCollected,
          weak_factory_.GetWeakPtr()));
}

void AshStructuredMetricsRecorder::ProfileAdded(const Profile& profile) {
  external_metrics_ = std::make_unique<ExternalMetrics>(
      base::FilePath(kExternalMetricsDir),
      GetExternalMetricsCollectionInterval(),
      base::BindRepeating(
          &AshStructuredMetricsRecorder::OnExternalMetricsCollected,
          weak_factory_.GetWeakPtr()));
  if (recording_enabled()) {
    external_metrics_->EnableRecording();
  }
}

void AshStructuredMetricsRecorder::ProvideSystemProfile(
    SystemProfileProto* system_profile) {
  // Populate the proto if the system profile has been initialized and
  // have a system profile provider.
  // The field may be populated if ChromeOSMetricsProvider has already run.
  if (system_profile_initialized_) {
    system_profile_provider_->ProvideSystemProfileMetrics(system_profile);
  }
}

}  // namespace metrics::structured