chromium/ash/webui/diagnostics_ui/backend/system/system_data_provider.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ash/webui/diagnostics_ui/backend/system/system_data_provider.h"

#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "ash/system/diagnostics/diagnostics_log_controller.h"
#include "ash/system/diagnostics/telemetry_log.h"
#include "ash/webui/diagnostics_ui/backend/common/histogram_util.h"
#include "ash/webui/diagnostics_ui/backend/system/cros_healthd_helpers.h"
#include "ash/webui/diagnostics_ui/backend/system/power_manager_client_conversions.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/i18n/time_formatting.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chromeos/ash/services/cros_healthd/public/cpp/service_connection.h"
#include "chromeos/ash/services/cros_healthd/public/mojom/cros_healthd_probe.mojom.h"
#include "chromeos/dbus/power_manager/power_supply_properties.pb.h"

namespace ash::diagnostics {

namespace {

namespace healthd = cros_healthd::mojom;
using PhysicalCpuInfos = std::vector<healthd::PhysicalCpuInfoPtr>;
using PowerSupplyProperties = power_manager::PowerSupplyProperties;
using ProbeCategories = healthd::ProbeCategoryEnum;

constexpr int kBatteryHealthRefreshIntervalInSeconds = 60;
constexpr int kChargeStatusRefreshIntervalInSeconds = 15;
constexpr int kCpuUsageRefreshIntervalInSeconds = 1;
constexpr int kMemoryUsageRefreshIntervalInSeconds = 10;
constexpr int kMilliampsInAnAmp = 1000;

void PopulateBoardName(const healthd::SystemInfo& system_info,
                       mojom::SystemInfo& out_system_info) {
  out_system_info.board_name = system_info.os_info->code_name;
}

void PopulateMarketingName(const healthd::SystemInfo& system_info,
                           mojom::SystemInfo& out_system_info) {
  const std::optional<std::string>& marketing_name =
      system_info.os_info->marketing_name;

  if (!marketing_name.has_value()) {
    DVLOG(1) << "No marketing name in SystemInfo response.";
    return;
  }

  out_system_info.marketing_name = marketing_name.value();
}

void PopulateCpuInfo(const healthd::CpuInfo& cpu_info,
                     mojom::SystemInfo& out_system_info) {
  const PhysicalCpuInfos& physical_cpus = cpu_info.physical_cpus;
  out_system_info.cpu_threads_count = cpu_info.num_total_threads;

  if (physical_cpus.empty()) {
    EmitSystemDataError(metrics::DataError::kExpectationNotMet);
    LOG(ERROR) << "No physical cpus in SystemInfo response.";
    return;
  }

  // If there is more than one physical cpu on the device, use the name of the
  // first CPU.
  out_system_info.cpu_model_name = physical_cpus[0]->model_name.value_or("");

  if (physical_cpus[0]->logical_cpus.empty()) {
    EmitSystemDataError(metrics::DataError::kExpectationNotMet);
    LOG(ERROR) << "Device reported having 0 logical CPUs.";
    return;
  }
  // Calculate `max_clock_speed_khz` as the average of all logical core clock
  // speeds until we decide the best way to consume the information in the UI.
  uint32_t total_max_ghz = 0;
  for (const auto& logical_cpu_ptr : physical_cpus[0]->logical_cpus) {
    total_max_ghz += logical_cpu_ptr->max_clock_speed_khz;
  }

  // Integer division.
  out_system_info.cpu_max_clock_speed_khz =
      total_max_ghz / physical_cpus[0]->logical_cpus.size();
}

void PopulateVersionInfo(const healthd::SystemInfo& system_info,
                         mojom::SystemInfo& out_system_info) {
  const std::string full_version =
      system_info.os_info->os_version->release_milestone + '.' +
      system_info.os_info->os_version->build_number + '.' +
      system_info.os_info->os_version->patch_number;
  out_system_info.version_info = mojom::VersionInfo::New(
      system_info.os_info->os_version->release_milestone, full_version);
}

void PopulateMemorySize(const healthd::MemoryInfo& memory_info,
                        mojom::SystemInfo& out_system_info) {
  out_system_info.total_memory_kib = memory_info.total_memory_kib;
}

bool DoesDeviceHaveBattery(
    const PowerSupplyProperties& power_supply_properties) {
  return power_supply_properties.battery_state() !=
         power_manager::PowerSupplyProperties_BatteryState_NOT_PRESENT;
}

bool DoesDeviceHaveBattery(const healthd::TelemetryInfo& telemetry_info) {
  return GetBatteryInfo(telemetry_info) != nullptr;
}

void PopulateDeviceCapabilities(const healthd::TelemetryInfo& telemetry_info,
                                mojom::SystemInfo& out_system_info) {
  mojom::DeviceCapabilitiesPtr capabilities = mojom::DeviceCapabilities::New();
  capabilities->has_battery = DoesDeviceHaveBattery(telemetry_info);
  out_system_info.device_capabilities = std::move(capabilities);
}

void PopulateBatteryInfo(const healthd::BatteryInfo& battery_info,
                         mojom::BatteryInfo& out_battery_info) {
  if (battery_info.charge_full_design == 0) {
    LOG(ERROR) << "charge_full_design from battery_info should not be zero.";
    EmitBatteryDataError(metrics::DataError::kExpectationNotMet);
  }
  out_battery_info.manufacturer = battery_info.vendor;
  out_battery_info.charge_full_design_milliamp_hours =
      battery_info.charge_full_design * kMilliampsInAnAmp;
}

void PopulatePowerInfo(const PowerSupplyProperties& power_supply_properties,
                       mojom::BatteryChargeStatus& out_charge_status) {
  const mojom::BatteryState battery_state =
      ConvertBatteryStateFromProto(power_supply_properties.battery_state());

  out_charge_status.battery_state = battery_state;
  out_charge_status.power_time =
      ConstructPowerTime(battery_state, power_supply_properties);
  out_charge_status.power_adapter_status =
      ConvertPowerSourceFromProto(power_supply_properties.external_power());
}

void PopulateBatteryChargeStatus(
    const healthd::BatteryInfo& battery_info,
    const PowerSupplyProperties& power_supply_properties,
    mojom::BatteryChargeStatus& out_charge_status) {
  PopulatePowerInfo(power_supply_properties, out_charge_status);

  out_charge_status.current_now_milliamps =
      battery_info.current_now * kMilliampsInAnAmp;
  out_charge_status.charge_now_milliamp_hours =
      battery_info.charge_now * kMilliampsInAnAmp;
}

void PopulateBatteryHealth(const healthd::BatteryInfo& battery_info,
                           mojom::BatteryHealth& out_battery_health) {
  out_battery_health.cycle_count = battery_info.cycle_count;

  if (battery_info.charge_full == 0) {
    LOG(ERROR) << "charge_full from battery_info should not be zero.";
    EmitBatteryDataError(metrics::DataError::kExpectationNotMet);
  }

  // Handle values in battery_info which could cause a SIGFPE. See b/227485637.
  if (isnan(battery_info.charge_full) ||
      isnan(battery_info.charge_full_design) ||
      battery_info.charge_full_design == 0) {
    LOG(ERROR) << "battery_info values could cause SIGFPE crash: { "
               << "charge_full_design: " << battery_info.charge_full_design
               << ", charge_full: " << battery_info.charge_full << " }";
    out_battery_health.charge_full_now_milliamp_hours = 0;
    out_battery_health.charge_full_design_milliamp_hours = 0;
    out_battery_health.battery_wear_percentage = 0;
    return;
  }

  out_battery_health.charge_full_now_milliamp_hours =
      battery_info.charge_full * kMilliampsInAnAmp;
  out_battery_health.charge_full_design_milliamp_hours =
      battery_info.charge_full_design * kMilliampsInAnAmp;
  out_battery_health.battery_wear_percentage =
      100 * out_battery_health.charge_full_now_milliamp_hours /
      out_battery_health.charge_full_design_milliamp_hours;
}

void PopulateMemoryUsage(const healthd::MemoryInfo& memory_info,
                         mojom::MemoryUsage& out_memory_usage) {
  out_memory_usage.total_memory_kib = memory_info.total_memory_kib;
  out_memory_usage.free_memory_kib = memory_info.free_memory_kib;
  out_memory_usage.available_memory_kib = memory_info.available_memory_kib;
}

CpuUsageData CalculateCpuUsage(
    const std::vector<healthd::LogicalCpuInfoPtr>& logical_cpu_infos) {
  CpuUsageData new_usage_data;

  DCHECK_GE(logical_cpu_infos.size(), 1u);
  for (const auto& logical_cpu_ptr : logical_cpu_infos) {
    new_usage_data += CpuUsageData(logical_cpu_ptr->user_time_user_hz,
                                   logical_cpu_ptr->system_time_user_hz,
                                   logical_cpu_ptr->idle_time_user_hz);
  }

  return new_usage_data;
}

void PopulateCpuUsagePercentages(const CpuUsageData& new_usage,
                                 const CpuUsageData& old_usage,
                                 mojom::CpuUsage& out_cpu_usage) {
  CpuUsageData delta = new_usage - old_usage;

  const uint64_t total_delta = delta.GetTotalTime();
  if (total_delta == 0) {
    EmitSystemDataError(metrics::DataError::kExpectationNotMet);
    return;
  }

  // Mulitply by 100 to convert to percentages.
  out_cpu_usage.percent_usage_user = 100 * delta.GetUserTime() / total_delta;
  out_cpu_usage.percent_usage_system =
      100 * delta.GetSystemTime() / total_delta;
  out_cpu_usage.percent_usage_free = 100 * delta.GetIdleTime() / total_delta;
}

void PopulateAverageCpuTemperature(const healthd::CpuInfo& cpu_info,
                                   mojom::CpuUsage& out_cpu_usage) {
  if (cpu_info.temperature_channels.empty()) {
    LOG(ERROR) << "Device reported having 0 temperature channels.";
    return;
  }

  uint32_t cumulative_total = 0;
  for (const auto& temp_channel_ptr : cpu_info.temperature_channels) {
    cumulative_total += temp_channel_ptr->temperature_celsius;
  }

  // Integer divison.
  out_cpu_usage.average_cpu_temp_celsius =
      cumulative_total / cpu_info.temperature_channels.size();
}

void PopulateAverageScaledClockSpeed(const healthd::CpuInfo& cpu_info,
                                     mojom::CpuUsage& out_cpu_usage) {
  if (cpu_info.physical_cpus.empty() ||
      cpu_info.physical_cpus[0]->logical_cpus.empty()) {
    LOG(ERROR) << "Device reported having 0 logical CPUs.";
    return;
  }

  uint32_t total_scaled_ghz = 0;
  for (const auto& logical_cpu_ptr : cpu_info.physical_cpus[0]->logical_cpus) {
    total_scaled_ghz += logical_cpu_ptr->scaling_current_frequency_khz;
  }

  // Integer division.
  out_cpu_usage.scaling_current_frequency_khz =
      total_scaled_ghz / cpu_info.physical_cpus[0]->logical_cpus.size();
}

bool IsLoggingEnabled() {
  return diagnostics::DiagnosticsLogController::IsInitialized();
}

}  // namespace

SystemDataProvider::SystemDataProvider() {
  battery_charge_status_timer_ = std::make_unique<base::RepeatingTimer>();
  battery_health_timer_ = std::make_unique<base::RepeatingTimer>();
  cpu_usage_timer_ = std::make_unique<base::RepeatingTimer>();
  memory_usage_timer_ = std::make_unique<base::RepeatingTimer>();
  chromeos::PowerManagerClient::Get()->AddObserver(this);
}

SystemDataProvider::~SystemDataProvider() {
  chromeos::PowerManagerClient::Get()->RemoveObserver(this);
}

void SystemDataProvider::GetSystemInfo(GetSystemInfoCallback callback) {
  BindCrosHealthdProbeServiceIfNeccessary();

  probe_service_->ProbeTelemetryInfo(
      {ProbeCategories::kBattery, ProbeCategories::kCpu,
       ProbeCategories::kMemory, ProbeCategories::kSystem},
      base::BindOnce(&SystemDataProvider::OnSystemInfoProbeResponse,
                     weak_factory_.GetWeakPtr(), std::move(callback)));
}

void SystemDataProvider::GetBatteryInfo(GetBatteryInfoCallback callback) {
  BindCrosHealthdProbeServiceIfNeccessary();

  probe_service_->ProbeTelemetryInfo(
      {ProbeCategories::kBattery},
      base::BindOnce(&SystemDataProvider::OnBatteryInfoProbeResponse,
                     weak_factory_.GetWeakPtr(), std::move(callback)));
}

void SystemDataProvider::ObserveBatteryChargeStatus(
    mojo::PendingRemote<mojom::BatteryChargeStatusObserver> observer) {
  battery_charge_status_observers_.Add(std::move(observer));

  if (!battery_charge_status_timer_->IsRunning()) {
    battery_charge_status_timer_->Start(
        FROM_HERE, base::Seconds(kChargeStatusRefreshIntervalInSeconds),
        base::BindRepeating(&SystemDataProvider::UpdateBatteryChargeStatus,
                            base::Unretained(this)));
  }
  UpdateBatteryChargeStatus();
}

void SystemDataProvider::ObserveBatteryHealth(
    mojo::PendingRemote<mojom::BatteryHealthObserver> observer) {
  battery_health_observers_.Add(std::move(observer));

  if (!battery_health_timer_->IsRunning()) {
    battery_health_timer_->Start(
        FROM_HERE, base::Seconds(kBatteryHealthRefreshIntervalInSeconds),
        base::BindRepeating(&SystemDataProvider::UpdateBatteryHealth,
                            base::Unretained(this)));
  }
  UpdateBatteryHealth();
}

void SystemDataProvider::ObserveMemoryUsage(
    mojo::PendingRemote<mojom::MemoryUsageObserver> observer) {
  memory_usage_observers_.Add(std::move(observer));

  if (!memory_usage_timer_->IsRunning()) {
    memory_usage_timer_->Start(
        FROM_HERE, base::Seconds(kMemoryUsageRefreshIntervalInSeconds),
        base::BindRepeating(&SystemDataProvider::UpdateMemoryUsage,
                            base::Unretained(this)));
  }
  UpdateMemoryUsage();
}

void SystemDataProvider::ObserveCpuUsage(
    mojo::PendingRemote<mojom::CpuUsageObserver> observer) {
  cpu_usage_observers_.Add(std::move(observer));
  if (!cpu_usage_timer_->IsRunning()) {
    previous_cpu_usage_data_ = CpuUsageData();
    cpu_usage_timer_->Start(
        FROM_HERE, base::Seconds(kCpuUsageRefreshIntervalInSeconds),
        base::BindRepeating(&SystemDataProvider::UpdateCpuUsage,
                            base::Unretained(this)));
  }

  UpdateCpuUsage();
}

void SystemDataProvider::PowerChanged(
    const power_manager::PowerSupplyProperties& proto) {
  if (battery_charge_status_observers_.empty()) {
    return;
  }

  // Fetch updated data from CrosHealthd
  BindCrosHealthdProbeServiceIfNeccessary();
  probe_service_->ProbeTelemetryInfo(
      {ProbeCategories::kBattery},
      base::BindOnce(&SystemDataProvider::OnBatteryChargeStatusUpdated,
                     weak_factory_.GetWeakPtr(), proto));
}

void SystemDataProvider::BindInterface(
    mojo::PendingReceiver<mojom::SystemDataProvider> pending_receiver) {
  receiver_.reset();
  receiver_.Bind(std::move(pending_receiver));
}

void SystemDataProvider::SetBatteryChargeStatusTimerForTesting(
    std::unique_ptr<base::RepeatingTimer> timer) {
  battery_charge_status_timer_ = std::move(timer);
}

void SystemDataProvider::SetBatteryHealthTimerForTesting(
    std::unique_ptr<base::RepeatingTimer> timer) {
  battery_health_timer_ = std::move(timer);
}

void SystemDataProvider::SetMemoryUsageTimerForTesting(
    std::unique_ptr<base::RepeatingTimer> timer) {
  memory_usage_timer_ = std::move(timer);
}

void SystemDataProvider::SetCpuUsageTimerForTesting(
    std::unique_ptr<base::RepeatingTimer> timer) {
  cpu_usage_timer_ = std::move(timer);
}

void SystemDataProvider::OnSystemInfoProbeResponse(
    GetSystemInfoCallback callback,
    healthd::TelemetryInfoPtr info_ptr) {
  mojom::SystemInfoPtr system_info = mojom::SystemInfo::New();
  system_info->version_info = mojom::VersionInfo::New();
  system_info->device_capabilities = mojom::DeviceCapabilities::New();

  if (info_ptr.is_null()) {
    LOG(ERROR) << "Null response from croshealthd::ProbeTelemetryInfo.";
    std::move(callback).Run(std::move(system_info));
    return;
  }

  const healthd::SystemInfo* system_info_ptr =
      diagnostics::GetSystemInfo(*info_ptr);
  if (system_info_ptr) {
    PopulateBoardName(*system_info_ptr, *system_info.get());
    PopulateMarketingName(*system_info_ptr, *system_info.get());
    PopulateVersionInfo(*system_info_ptr, *system_info.get());
  } else {
    LOG(ERROR)
        << "Expected SystemInfo in croshealthd::ProbeTelemetryInfo response";
    std::move(callback).Run(std::move(system_info));
    return;
  }

  const healthd::CpuInfo* cpu_info_ptr = GetCpuInfo(*info_ptr);
  if (cpu_info_ptr) {
    PopulateCpuInfo(*cpu_info_ptr, *system_info.get());
  } else {
    LOG(ERROR)
        << "Expected CpuInfo in croshealthd::ProbeTelemetryInfo response";
  }

  const healthd::MemoryInfo* memory_info_ptr = GetMemoryInfo(*info_ptr);
  if (memory_info_ptr) {
    PopulateMemorySize(*memory_info_ptr, *system_info.get());
  } else {
    LOG(ERROR)
        << "Expected MemoryInfo in croshealthd::ProbeTelemetryInfo response";
  }

  PopulateDeviceCapabilities(*info_ptr, *system_info.get());

  if (IsLoggingEnabled()) {
    DiagnosticsLogController::Get()->GetTelemetryLog().UpdateSystemInfo(
        system_info.Clone());
  }

  std::move(callback).Run(std::move(system_info));
}

void SystemDataProvider::OnBatteryInfoProbeResponse(
    GetBatteryInfoCallback callback,
    healthd::TelemetryInfoPtr info_ptr) {
  mojom::BatteryInfoPtr battery_info = mojom::BatteryInfo::New();

  if (info_ptr.is_null()) {
    LOG(ERROR) << "Null response from croshealthd::ProbeTelemetryInfo.";
    std::move(callback).Run(std::move(battery_info));
    return;
  }

  const healthd::BatteryInfo* battery_info_ptr =
      diagnostics::GetBatteryInfo(*info_ptr);
  if (!battery_info_ptr) {
    LOG(ERROR) << "BatteryInfo requested by device does not have a battery.";
    EmitBatteryDataError(metrics::DataError::kNoData);
    std::move(callback).Run(std::move(battery_info));
    return;
  }

  PopulateBatteryInfo(*battery_info_ptr, *battery_info.get());
  std::move(callback).Run(std::move(battery_info));
}

void SystemDataProvider::UpdateBatteryChargeStatus() {
  // Fetch updated data from PowerManagerClient
  std::optional<PowerSupplyProperties> properties =
      chromeos::PowerManagerClient::Get()->GetLastStatus();

  // Fetch updated data from CrosHealthd
  BindCrosHealthdProbeServiceIfNeccessary();

  probe_service_->ProbeTelemetryInfo(
      {ProbeCategories::kBattery},
      base::BindOnce(&SystemDataProvider::OnBatteryChargeStatusUpdated,
                     weak_factory_.GetWeakPtr(), properties));
}

void SystemDataProvider::UpdateBatteryHealth() {
  BindCrosHealthdProbeServiceIfNeccessary();

  probe_service_->ProbeTelemetryInfo(
      {ProbeCategories::kBattery},
      base::BindOnce(&SystemDataProvider::OnBatteryHealthUpdated,
                     weak_factory_.GetWeakPtr()));
}

void SystemDataProvider::UpdateMemoryUsage() {
  BindCrosHealthdProbeServiceIfNeccessary();

  probe_service_->ProbeTelemetryInfo(
      {ProbeCategories::kMemory},
      base::BindOnce(&SystemDataProvider::OnMemoryUsageUpdated,
                     weak_factory_.GetWeakPtr()));
}

void SystemDataProvider::UpdateCpuUsage() {
  BindCrosHealthdProbeServiceIfNeccessary();

  probe_service_->ProbeTelemetryInfo(
      {ProbeCategories::kCpu},
      base::BindOnce(&SystemDataProvider::OnCpuUsageUpdated,
                     weak_factory_.GetWeakPtr()));
}

void SystemDataProvider::OnBatteryChargeStatusUpdated(
    const std::optional<PowerSupplyProperties>& power_supply_properties,
    healthd::TelemetryInfoPtr info_ptr) {
  mojom::BatteryChargeStatusPtr battery_charge_status =
      mojom::BatteryChargeStatus::New();

  if (info_ptr.is_null()) {
    LOG(ERROR) << "Null response from croshealthd::ProbeTelemetryInfo.";
    NotifyBatteryChargeStatusObservers(battery_charge_status);
    return;
  }

  if (!power_supply_properties.has_value()) {
    LOG(ERROR) << "Null response from power_manager_client::GetLastStatus.";
    EmitBatteryDataError(metrics::DataError::kNoData);
    NotifyBatteryChargeStatusObservers(battery_charge_status);
    return;
  }

  if (!DoesDeviceHaveBattery(*info_ptr) ||
      !DoesDeviceHaveBattery(*power_supply_properties)) {
    if (DoesDeviceHaveBattery(*info_ptr) !=
        DoesDeviceHaveBattery(*power_supply_properties)) {
      LOG(ERROR)
          << "Sources should not disagree about whether there is a battery.";
      EmitBatteryDataError(metrics::DataError::kExpectationNotMet);
    }
    NotifyBatteryChargeStatusObservers(battery_charge_status);
    return;
  }

  PopulateBatteryChargeStatus(*diagnostics::GetBatteryInfo(*info_ptr),
                              *power_supply_properties,
                              *battery_charge_status.get());
  NotifyBatteryChargeStatusObservers(battery_charge_status);
}

void SystemDataProvider::OnBatteryHealthUpdated(
    healthd::TelemetryInfoPtr info_ptr) {
  mojom::BatteryHealthPtr battery_health = mojom::BatteryHealth::New();

  if (info_ptr.is_null()) {
    LOG(ERROR) << "Null response from croshealthd::ProbeTelemetryInfo.";
    NotifyBatteryHealthObservers(battery_health);
    return;
  }

  if (!DoesDeviceHaveBattery(*info_ptr)) {
    NotifyBatteryHealthObservers(battery_health);
    return;
  }

  PopulateBatteryHealth(*diagnostics::GetBatteryInfo(*info_ptr),
                        *battery_health.get());
  NotifyBatteryHealthObservers(battery_health);
}

void SystemDataProvider::OnMemoryUsageUpdated(
    healthd::TelemetryInfoPtr info_ptr) {
  mojom::MemoryUsagePtr memory_usage = mojom::MemoryUsage::New();

  if (info_ptr.is_null()) {
    LOG(ERROR) << "Null response from croshealthd::ProbeTelemetryInfo.";
    NotifyMemoryUsageObservers(memory_usage);
    return;
  }

  const healthd::MemoryInfo* memory_info = GetMemoryInfo(*info_ptr);
  if (memory_info == nullptr) {
    LOG(ERROR) << "No MemoryInfo in response from cros_healthd.";
    NotifyMemoryUsageObservers(memory_usage);
    return;
  }

  PopulateMemoryUsage(*memory_info, *memory_usage.get());
  NotifyMemoryUsageObservers(memory_usage);
}

void SystemDataProvider::OnCpuUsageUpdated(healthd::TelemetryInfoPtr info_ptr) {
  mojom::CpuUsagePtr cpu_usage = mojom::CpuUsage::New();

  if (info_ptr.is_null()) {
    LOG(ERROR) << "Null response from croshealthd::ProbeTelemetryInfo.";
    NotifyCpuUsageObservers(cpu_usage);
    return;
  }

  // TODO(ashleydp): Add metrics to track the occurrence of invalid cros_healthd
  // CpuInfo responses.
  const healthd::CpuInfo* cpu_info = GetCpuInfo(*info_ptr);
  if (cpu_info == nullptr) {
    LOG(ERROR) << "No CpuInfo in response from cros_healthd.";
    NotifyCpuUsageObservers(cpu_usage);
    return;
  }

  ComputeAndPopulateCpuUsage(*cpu_info, *cpu_usage.get());
  PopulateAverageCpuTemperature(*cpu_info, *cpu_usage.get());
  PopulateAverageScaledClockSpeed(*cpu_info, *cpu_usage.get());

  NotifyCpuUsageObservers(cpu_usage);
}

void SystemDataProvider::ComputeAndPopulateCpuUsage(
    const healthd::CpuInfo& cpu_info,
    mojom::CpuUsage& out_cpu_usage) {
  if (cpu_info.physical_cpus.empty()) {
    LOG(ERROR) << "Device reported having zero physical CPUs";
    return;
  }

  if (cpu_info.physical_cpus[0]->logical_cpus.empty()) {
    LOG(ERROR) << "Device reported having zero logical CPUs";
    return;
  }

  // For simplicity, assume that all devices have just one physical CPU, made
  // up of one or more virtual CPUs.

  // TODO(baileyberro): Handle devices with multiple physical CPUs.
  if (cpu_info.physical_cpus.size() > 1) {
    VLOG(1) << "Device has more than one physical CPU";
  }

  const healthd::PhysicalCpuInfoPtr& physical_cpu_ptr =
      cpu_info.physical_cpus[0];

  CpuUsageData new_usage_data =
      CalculateCpuUsage(physical_cpu_ptr->logical_cpus);

  // We use the first usage data we get back as a baseline. On subsequent
  // fetches, we use the previous cumulative data to calculate a delta.
  if (previous_cpu_usage_data_.IsInitialized()) {
    PopulateCpuUsagePercentages(new_usage_data, previous_cpu_usage_data_,
                                out_cpu_usage);
  }

  previous_cpu_usage_data_ = new_usage_data;
}

void SystemDataProvider::NotifyBatteryChargeStatusObservers(
    const mojom::BatteryChargeStatusPtr& battery_charge_status) {
  for (auto& observer : battery_charge_status_observers_) {
    observer->OnBatteryChargeStatusUpdated(battery_charge_status.Clone());
  }
  if (IsLoggingEnabled()) {
    DiagnosticsLogController::Get()
        ->GetTelemetryLog()
        .UpdateBatteryChargeStatus(battery_charge_status.Clone());
  }
}

void SystemDataProvider::NotifyBatteryHealthObservers(
    const mojom::BatteryHealthPtr& battery_health) {
  for (auto& observer : battery_health_observers_) {
    observer->OnBatteryHealthUpdated(battery_health.Clone());
  }
  if (IsLoggingEnabled()) {
    DiagnosticsLogController::Get()->GetTelemetryLog().UpdateBatteryHealth(
        battery_health.Clone());
  }
}

void SystemDataProvider::NotifyMemoryUsageObservers(
    const mojom::MemoryUsagePtr& memory_usage) {
  for (auto& observer : memory_usage_observers_) {
    observer->OnMemoryUsageUpdated(memory_usage.Clone());
  }
  if (IsLoggingEnabled()) {
    DiagnosticsLogController::Get()->GetTelemetryLog().UpdateMemoryUsage(
        memory_usage.Clone());
  }
}

void SystemDataProvider::NotifyCpuUsageObservers(
    const mojom::CpuUsagePtr& cpu_usage) {
  for (auto& observer : cpu_usage_observers_) {
    observer->OnCpuUsageUpdated(cpu_usage.Clone());
  }
  if (IsLoggingEnabled()) {
    DiagnosticsLogController::Get()->GetTelemetryLog().UpdateCpuUsage(
        cpu_usage.Clone());
  }
}

void SystemDataProvider::BindCrosHealthdProbeServiceIfNeccessary() {
  if (!probe_service_ || !probe_service_.is_connected()) {
    cros_healthd::ServiceConnection::GetInstance()->BindProbeService(
        probe_service_.BindNewPipeAndPassReceiver());
    probe_service_.set_disconnect_handler(
        base::BindOnce(&SystemDataProvider::OnProbeServiceDisconnect,
                       weak_factory_.GetWeakPtr()));
  }
}

void SystemDataProvider::OnProbeServiceDisconnect() {
  probe_service_.reset();
}

}  // namespace ash::diagnostics