// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/ash/components/sparky/system_info_delegate_impl.h"
#include <memory>
#include <optional>
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "chromeos/ash/components/system_info/battery_health.h"
#include "chromeos/ash/components/system_info/memory_data.h"
#include "chromeos/ash/components/system_info/system_info_util.h"
#include "chromeos/ash/services/cros_healthd/public/cpp/service_connection.h"
#include "chromeos/dbus/power/power_manager_client.h"
#include "components/manta/sparky/system_info_delegate.h"
#include "ui/base/text/bytes_formatting.h"
namespace sparky {
namespace {
using ProbeCategories = ::ash::cros_healthd::mojom::ProbeCategoryEnum;
using ::ash::cros_healthd::mojom::BatteryInfo;
using ::ash::cros_healthd::mojom::CpuInfo;
using ::ash::cros_healthd::mojom::PhysicalCpuInfoPtr;
using ::ash::cros_healthd::mojom::TelemetryInfoPtr;
constexpr int kCpuUsageRefreshIntervalInMilliseconds = 200;
double ConvertKBtoGB(uint32_t amount) {
return static_cast<double>(amount) / 1024 / 1024;
}
void PopulatePowerStatus(const power_manager::PowerSupplyProperties& proto,
system_info::BatteryHealth& battery_health) {
bool calculating = proto.is_calculating_battery_time();
int percent = system_info::GetRoundedBatteryPercent(proto.battery_percent());
CHECK(percent <= 100 && percent >= 0);
if (!calculating) {
bool charging =
proto.battery_state() == power_manager::PowerSupplyProperties::CHARGING;
base::TimeDelta time_left =
base::Seconds(charging ? proto.battery_time_to_full_sec()
: proto.battery_time_to_empty_sec());
if (system_info::ShouldDisplayBatteryTime(time_left)) {
// TODO (b:342609231) replace with a translation string.
battery_health.SetPowerTime(
charging ? system_info::GetBatteryTimeText(time_left) + u" until full"
: system_info::GetBatteryTimeText(time_left) + u" left");
}
}
battery_health.SetBatteryPercentage(percent);
}
} // namespace
SystemInfoDelegateImpl::SystemInfoDelegateImpl()
: cpu_usage_timer_(std::make_unique<base::RepeatingTimer>()) {}
SystemInfoDelegateImpl::~SystemInfoDelegateImpl() {}
void SystemInfoDelegateImpl::ObtainDiagnostics(
const std::vector<manta::Diagnostics>& diagnostics,
manta::DiagnosticsDataCallback diagnostics_callback) {
// Invalidate weak pointers to cancel existing searches.
weak_factory_.InvalidateWeakPtrs();
diagnostics_callback_ = std::move(diagnostics_callback);
diagnostics_error_ = false;
for (manta::Diagnostics diagnostics_option : diagnostics) {
switch (diagnostics_option) {
case manta::Diagnostics::kMemory: {
UpdateMemoryUsage();
break;
}
case manta::Diagnostics::kBattery: {
UpdateBatteryInfo();
break;
}
case manta::Diagnostics::kCpu: {
cpu_refreshes_left_ = 2;
cpu_usage_timer_->Start(
FROM_HERE,
base::Milliseconds(kCpuUsageRefreshIntervalInMilliseconds),
base::BindRepeating(&SystemInfoDelegateImpl::UpdateCpuUsage,
weak_factory_.GetWeakPtr()));
break;
}
case manta::Diagnostics::kStorage: {
// TODO (b:340963863) This field will be handled within the Sparky
// Delegate Impl as it requires a profile to obtain the storage data.
break;
}
}
}
diagnostics_requested_ = diagnostics;
}
void SystemInfoDelegateImpl::OnDiagnosticsUpdated() {
if (diagnostics_error_) { // TODO (b:343072278) add in a test case for
// diagnostics error.
return;
}
// Check if all of the requested diagnostics fields have been calculated yet.
// Only return the results once all of the values are found.
for (auto diagnostics_type : diagnostics_requested_) {
if (diagnostics_type == manta::Diagnostics::kMemory && !memory_data_) {
return;
}
if (diagnostics_type == manta::Diagnostics::kCpu && !cpu_data_) {
return;
}
if (diagnostics_type == manta::Diagnostics::kBattery && !battery_data_) {
return;
}
}
if (diagnostics_callback_) {
std::move(diagnostics_callback_)
.Run(std::make_unique<manta::DiagnosticsData>(
battery_data_ ? std::optional<manta::BatteryData>(*battery_data_)
: std::nullopt,
cpu_data_ ? std::optional<manta::CpuData>(*cpu_data_)
: std::nullopt,
memory_data_ ? std::optional<manta::MemoryData>(*memory_data_)
: std::nullopt,
std::nullopt));
}
}
void SystemInfoDelegateImpl::ReturnWithNullptr() {
diagnostics_error_ = true;
if (diagnostics_callback_) {
std::move(diagnostics_callback_).Run(nullptr);
}
// Invalidate weak pointers to cancel existing searches.
weak_factory_.InvalidateWeakPtrs();
}
void SystemInfoDelegateImpl::UpdateMemoryUsage() {
auto* probe_service =
ash::cros_healthd::ServiceConnection::GetInstance()->GetProbeService();
probe_service->ProbeTelemetryInfo(
{ProbeCategories::kMemory},
base::BindOnce(&SystemInfoDelegateImpl::OnMemoryUsageUpdated,
weak_factory_.GetWeakPtr()));
}
void SystemInfoDelegateImpl::UpdateCpuUsage() {
auto* probe_service =
ash::cros_healthd::ServiceConnection::GetInstance()->GetProbeService();
probe_service->ProbeTelemetryInfo(
{ProbeCategories::kCpu},
base::BindOnce(&SystemInfoDelegateImpl::OnCpuUsageUpdated,
weak_factory_.GetWeakPtr()));
}
void SystemInfoDelegateImpl::UpdateBatteryInfo() {
auto* probe_service =
ash::cros_healthd::ServiceConnection::GetInstance()->GetProbeService();
probe_service->ProbeTelemetryInfo(
{ProbeCategories::kBattery},
base::BindOnce(&SystemInfoDelegateImpl::OnBatteryInfoUpdated,
weak_factory_.GetWeakPtr()));
}
void SystemInfoDelegateImpl::OnMemoryUsageUpdated(TelemetryInfoPtr info_ptr) {
auto* memory_info = system_info::GetMemoryInfo(*info_ptr, "");
if (!memory_info) {
LOG(ERROR) << "Memory information not provided by croshealthd";
ReturnWithNullptr();
return;
}
double available_memory_gb = ConvertKBtoGB(memory_info->available_memory_kib);
double total_memory_gb = ConvertKBtoGB(memory_info->total_memory_kib);
memory_data_ =
std::make_unique<manta::MemoryData>(available_memory_gb, total_memory_gb);
OnDiagnosticsUpdated();
}
void SystemInfoDelegateImpl::OnCpuUsageUpdated(
ash::cros_healthd::mojom::TelemetryInfoPtr info_ptr) {
const CpuInfo* cpu_info = system_info::GetCpuInfo(*info_ptr, "");
if (cpu_info == nullptr) {
LOG(ERROR) << "No CpuInfo in response from cros_healthd.";
ReturnWithNullptr();
return;
}
if (cpu_info->physical_cpus.empty()) {
LOG(ERROR) << "Device reported having zero physical CPUs.";
ReturnWithNullptr();
return;
}
if (cpu_info->physical_cpus[0]->logical_cpus.empty()) {
LOG(ERROR) << "Device reported having zero logical CPUs.";
ReturnWithNullptr();
return;
}
// For simplicity, assumes that all devices have just one physical CPU, made
// up of one or more virtual CPUs.
if (cpu_info->physical_cpus.size() > 1) {
VLOG(1) << "Device has more than one physical CPU.";
}
const PhysicalCpuInfoPtr& physical_cpu_ptr = cpu_info->physical_cpus[0];
system_info::CpuUsageData new_cpu_usage_data =
system_info::CalculateCpuUsage(physical_cpu_ptr->logical_cpus);
std::unique_ptr<system_info::CpuData> new_cpu_usage =
std::make_unique<system_info::CpuData>();
system_info::PopulateCpuUsage(new_cpu_usage_data, previous_cpu_usage_data_,
*new_cpu_usage.get());
system_info::PopulateAverageCpuTemperature(*cpu_info, *new_cpu_usage.get());
system_info::PopulateAverageScaledClockSpeed(*cpu_info, *new_cpu_usage.get());
previous_cpu_usage_data_ = new_cpu_usage_data;
cpu_refreshes_left_--;
if (cpu_refreshes_left_ == 0) {
int percentage;
base::StringToInt(new_cpu_usage->GetPercentUsageTotalString(), &percentage);
cpu_data_ = std::make_unique<manta::CpuData>(
percentage, new_cpu_usage->GetAverageCpuTempCelsius(),
static_cast<double>(
new_cpu_usage->GetScalingAverageCurrentFrequencyKhz() / 10000) /
100);
cpu_usage_timer_->Stop();
OnDiagnosticsUpdated();
}
}
void SystemInfoDelegateImpl::OnBatteryInfoUpdated(
ash::cros_healthd::mojom::TelemetryInfoPtr info_ptr) {
const BatteryInfo* battery_info_ptr =
system_info::GetBatteryInfo(*info_ptr, "", "");
if (!battery_info_ptr) {
LOG(ERROR) << "BatteryInfo requested by device does not have a battery.";
ReturnWithNullptr();
return;
}
std::unique_ptr<system_info::BatteryHealth> new_battery_health =
std::make_unique<system_info::BatteryHealth>();
system_info::PopulateBatteryHealth(*battery_info_ptr,
*new_battery_health.get());
const std::optional<power_manager::PowerSupplyProperties>& proto =
chromeos::PowerManagerClient::Get()->GetLastStatus();
if (!proto) {
LOG(ERROR) << "No data from Power Manager.";
ReturnWithNullptr();
return;
}
PopulatePowerStatus(proto.value(), *new_battery_health.get());
battery_data_ = std::make_unique<manta::BatteryData>(
new_battery_health->GetCycleCount(),
new_battery_health->GetBatteryWearPercentage(),
base::UTF16ToUTF8(new_battery_health->GetPowerTime()),
new_battery_health->GetBatteryPercentage());
OnDiagnosticsUpdated();
}
} // namespace sparky