chromium/chrome/browser/ash/policy/reporting/metrics_reporting/cros_healthd_sampler_handlers/cros_healthd_psr_sampler_handler.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/ash/policy/reporting/metrics_reporting/cros_healthd_sampler_handlers/cros_healthd_psr_sampler_handler.h"

#include <optional>
#include <utility>

#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/task_runner.h"
#include "base/time/time.h"
#include "base/types/cxx23_to_underlying.h"
#include "chromeos/ash/services/cros_healthd/public/cpp/service_connection.h"
#include "components/reporting/metrics/sampler.h"
#include "components/reporting/proto/synced/metric_data.pb.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"

namespace reporting {

namespace cros_healthd = ::ash::cros_healthd::mojom;

namespace {
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class EnterpriseReportingPsrResult {
  kOk = 0,
  kErrorGettingPsr = 1,
  kUnknownSystemResultType = 2,
  kNullPsrInfo = 3,
  kPsrUnsupported = 4,
  kPsrNotStarted = 5,
  kPsrStopped = 6,
  kUnknownPsrLogState = 7,
  kMaxValue = kUnknownPsrLogState
};

constexpr size_t kMaxRetries = 1u;

// Records PSR result to UMA.
void RecordPsrResult(EnterpriseReportingPsrResult result) {
  base::UmaHistogramEnumeration("Browser.ERP.PsrResult", result);
}

}  // namespace

CrosHealthdPsrSamplerHandler::CrosHealthdPsrSamplerHandler() = default;
CrosHealthdPsrSamplerHandler::~CrosHealthdPsrSamplerHandler() = default;

void CrosHealthdPsrSamplerHandler::HandleResult(
    OptionalMetricCallback callback,
    cros_healthd::TelemetryInfoPtr result) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  HandleResultImpl(std::move(callback), /*num_retries_left=*/kMaxRetries,
                   std::move(result));
}

void CrosHealthdPsrSamplerHandler::Retry(OptionalMetricCallback callback,
                                         size_t num_retries_left) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  auto healthd_callback = base::BindOnce(
      &CrosHealthdPsrSamplerHandler::HandleResultImpl,
      weak_ptr_factory_.GetWeakPtr(), std::move(callback), num_retries_left);
  ash::cros_healthd::ServiceConnection::GetInstance()
      ->GetProbeService()
      ->ProbeTelemetryInfo(
          std::vector<cros_healthd::ProbeCategoryEnum>{
              cros_healthd::ProbeCategoryEnum::kSystem},
          std::move(healthd_callback));
}

void CrosHealthdPsrSamplerHandler::HandleResultImpl(
    OptionalMetricCallback callback,
    size_t num_retries_left,
    cros_healthd::TelemetryInfoPtr result) const {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  std::optional<MetricData> metric_data;
  absl::Cleanup run_callback_on_return = [this, &callback, &metric_data,
                                          num_retries_left] {
    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
    if (!metric_data.has_value() && num_retries_left > 0) {
      // Failed to obtain PSR info, try again in 10 seconds. May be due to
      // some race condition in healthd when reading PSR devices.
      base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
          FROM_HERE,
          base::BindOnce(&CrosHealthdPsrSamplerHandler::Retry,
                         weak_ptr_factory_.GetWeakPtr(), std::move(callback),
                         num_retries_left - 1),
          wait_time_);
      return;
    }
    std::move(callback).Run(std::move(metric_data));
  };

  const auto& system_result = result->system_result;
  if (system_result.is_null()) {
    return;
  }

  if (system_result->which() != cros_healthd::SystemResult::Tag::kSystemInfo) {
    switch (system_result->which()) {
      case cros_healthd::SystemResult::Tag::kError:
        RecordPsrResult(EnterpriseReportingPsrResult::kErrorGettingPsr);
        LOG(ERROR) << "cros_healthd: Error getting PSR info: "
                   << system_result->get_error()->msg;
        return;
      default:
        RecordPsrResult(EnterpriseReportingPsrResult::kUnknownSystemResultType);
        LOG(ERROR) << "cros_healthd: Unknown system result type: "
                   << base::to_underlying(system_result->which());
        return;
    }
  }

  const auto& psr_info = system_result->get_system_info()->psr_info;
  if (psr_info.is_null()) {
    RecordPsrResult(EnterpriseReportingPsrResult::kNullPsrInfo);
    LOG(ERROR) << "Null PsrInfo from cros_healthd";
    return;
  }

  if (!psr_info->is_supported) {
    RecordPsrResult(EnterpriseReportingPsrResult::kPsrUnsupported);
    return;
  }

  if (psr_info->log_state != cros_healthd::PsrInfo::LogState::kStarted) {
    // Meaning log state is either kNotStarted (PSR was not started by OEM) or
    // kStopped (PSR log has been tampered and permanently stopped.). Don't
    // report anything as PSR would (no longer) run on this device.
    switch (psr_info->log_state) {
      case cros_healthd::PsrInfo::LogState::kNotStarted:
        RecordPsrResult(EnterpriseReportingPsrResult::kPsrNotStarted);
        break;
      case cros_healthd::PsrInfo::LogState::kStopped:
        RecordPsrResult(EnterpriseReportingPsrResult::kPsrStopped);
        break;
      default:
        LOG(ERROR) << "Unknown log state!";
        RecordPsrResult(EnterpriseReportingPsrResult::kUnknownPsrLogState);
    }
    return;
  }

  // Gather PSR info.
  metric_data = std::make_optional<MetricData>();
  auto* const runtime_counters_telemetry =
      metric_data->mutable_telemetry_data()
          ->mutable_runtime_counters_telemetry();

  runtime_counters_telemetry->set_uptime_runtime_seconds(
      psr_info->uptime_seconds);
  runtime_counters_telemetry->set_counter_enter_sleep(psr_info->s3_counter);
  runtime_counters_telemetry->set_counter_enter_hibernation(
      psr_info->s4_counter);
  runtime_counters_telemetry->set_counter_enter_poweroff(psr_info->s5_counter);
  RecordPsrResult(EnterpriseReportingPsrResult::kOk);
}
}  // namespace reporting