| // Copyright 2021 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "update_engine/common/cros_healthd.h" | |
| #include <memory> | |
| #include <string> | |
| #include <unordered_map> | |
| #include <unordered_set> | |
| #include <utility> | |
| #include <vector> | |
| #include <base/check.h> | |
| #include <base/files/scoped_file.h> | |
| #include <base/logging.h> | |
| #include <base/task/single_thread_task_runner.h> | |
| #include <base/time/time.h> | |
| #include <chromeos/mojo/service_constants.h> | |
| #include <mojo/public/cpp/bindings/callback_helpers.h> | |
| #include <mojo/public/cpp/platform/platform_channel.h> | |
| #include <mojo/public/cpp/system/invitation.h> | |
| #include <mojo_service_manager/lib/connect.h> | |
| namespace chromeos_update_engine { | |
| namespace { | |
| using ::ash::cros_healthd::mojom::ProbeCategoryEnum; | |
| // The timeout for connecting to the cros_healthd. This should not happen in the | |
| // normal case. | |
| constexpr base::TimeDelta kCrosHealthdConnectingTimeout = base::Minutes(3); | |
| #define SET_MOJO_VALUE(x) \ | |
| { TelemetryCategoryEnum::x, ProbeCategoryEnum::x } | |
| static const std::unordered_map<TelemetryCategoryEnum, ProbeCategoryEnum> | |
| kTelemetryMojoMapping{ | |
| SET_MOJO_VALUE(kBattery), | |
| SET_MOJO_VALUE(kNonRemovableBlockDevices), | |
| SET_MOJO_VALUE(kCpu), | |
| SET_MOJO_VALUE(kTimezone), | |
| SET_MOJO_VALUE(kMemory), | |
| SET_MOJO_VALUE(kBacklight), | |
| SET_MOJO_VALUE(kFan), | |
| SET_MOJO_VALUE(kStatefulPartition), | |
| SET_MOJO_VALUE(kBluetooth), | |
| SET_MOJO_VALUE(kSystem), | |
| SET_MOJO_VALUE(kNetwork), | |
| SET_MOJO_VALUE(kAudio), | |
| SET_MOJO_VALUE(kBootPerformance), | |
| SET_MOJO_VALUE(kBus), | |
| }; | |
| void PrintError(const ash::cros_healthd::mojom::ProbeErrorPtr& error, | |
| std::string info) { | |
| LOG(ERROR) << "Failed to get " << info << "," << " error_type=" << error->type | |
| << " error_msg=" << error->msg; | |
| } | |
| } // namespace | |
| std::unique_ptr<CrosHealthdInterface> CreateCrosHealthd() { | |
| auto cros_healthd = std::make_unique<CrosHealthd>(); | |
| // Call mojo bootstrap, instead of in constructor as testing/mocks don't | |
| // require the `BootstrapMojo()` call. | |
| cros_healthd->BootstrapMojo(); | |
| return cros_healthd; | |
| } | |
| void CrosHealthd::BootstrapMojo() { | |
| // TODO(b/264832802): Move these initialization to a new interface. | |
| // These operations (`mojo::core::Init()` and connecting to mojo service | |
| // manager) could be done in each process only once. If we want to add other | |
| // mojo services, we must reuse these mojo initiailizations. | |
| mojo::core::Init(); | |
| ipc_support_ = std::make_unique<mojo::core::ScopedIPCSupport>( | |
| base::SingleThreadTaskRunner:: | |
| GetCurrentDefault() /* io_thread_task_runner */, | |
| mojo::core::ScopedIPCSupport::ShutdownPolicy:: | |
| CLEAN /* blocking shutdown */); | |
| auto pending_remote = | |
| chromeos::mojo_service_manager::ConnectToMojoServiceManager(); | |
| if (!pending_remote) { | |
| LOG(ERROR) << "Failed to connect to mojo service manager."; | |
| return; | |
| } | |
| service_manager_.Bind(std::move(pending_remote)); | |
| service_manager_.set_disconnect_with_reason_handler( | |
| base::BindOnce([](uint32_t error, const std::string& message) { | |
| LOG(ERROR) << "Disconnected from mojo service manager. Error: " << error | |
| << ", message: " << message; | |
| })); | |
| service_manager_->Request( | |
| chromeos::mojo_services::kCrosHealthdProbe, kCrosHealthdConnectingTimeout, | |
| cros_healthd_probe_service_.BindNewPipeAndPassReceiver().PassPipe()); | |
| cros_healthd_probe_service_.set_disconnect_with_reason_handler( | |
| base::BindOnce([](uint32_t error, const std::string& message) { | |
| LOG(ERROR) << "Disconnected from cros_healthd probe service. Error: " | |
| << error << ", message: " << message; | |
| })); | |
| } | |
| TelemetryInfo* const CrosHealthd::GetTelemetryInfo() { | |
| return telemetry_info_.get(); | |
| } | |
| void CrosHealthd::ProbeTelemetryInfo( | |
| const std::unordered_set<TelemetryCategoryEnum>& categories, | |
| base::OnceClosure once_callback) { | |
| if (!cros_healthd_probe_service_.is_bound()) { | |
| LOG(WARNING) << "Skip probing because connection of cros_healthd is not " | |
| "initialized."; | |
| std::move(once_callback).Run(); | |
| return; | |
| } | |
| std::vector<ProbeCategoryEnum> categories_mojo; | |
| for (const auto& category : categories) { | |
| auto it = kTelemetryMojoMapping.find(category); | |
| if (it != kTelemetryMojoMapping.end()) | |
| categories_mojo.push_back(it->second); | |
| } | |
| auto callback = | |
| base::BindOnce(&CrosHealthd::OnProbeTelemetryInfo, | |
| weak_ptr_factory_.GetWeakPtr(), std::move(once_callback)); | |
| cros_healthd_probe_service_->ProbeTelemetryInfo( | |
| categories_mojo, mojo::WrapCallbackWithDefaultInvokeIfNotRun( | |
| std::move(callback), nullptr)); | |
| } | |
| void CrosHealthd::OnProbeTelemetryInfo( | |
| base::OnceClosure once_callback, | |
| ash::cros_healthd::mojom::TelemetryInfoPtr result) { | |
| if (!result) { | |
| LOG(WARNING) << "Failed to parse telemetry information."; | |
| std::move(once_callback).Run(); | |
| return; | |
| } | |
| LOG(INFO) << "Probed telemetry info from cros_healthd."; | |
| telemetry_info_ = std::make_unique<TelemetryInfo>(); | |
| if (!ParseSystemResult(&result, telemetry_info_.get())) | |
| LOG(WARNING) << "Failed to parse system information."; | |
| if (!ParseMemoryResult(&result, telemetry_info_.get())) | |
| LOG(WARNING) << "Failed to parse memory information."; | |
| if (!ParseNonRemovableBlockDeviceResult(&result, telemetry_info_.get())) | |
| LOG(WARNING) << "Failed to parse non-removable block device information."; | |
| if (!ParseCpuResult(&result, telemetry_info_.get())) | |
| LOG(WARNING) << "Failed to parse physical CPU information."; | |
| if (!ParseBusResult(&result, telemetry_info_.get())) | |
| LOG(WARNING) << "Failed to parse bus information."; | |
| std::move(once_callback).Run(); | |
| } | |
| bool CrosHealthd::ParseSystemResult( | |
| ash::cros_healthd::mojom::TelemetryInfoPtr* result, | |
| TelemetryInfo* telemetry_info) { | |
| const auto& system_result = (*result)->system_result; | |
| if (system_result) { | |
| if (system_result->is_error()) { | |
| PrintError(system_result->get_error(), "system information"); | |
| return false; | |
| } | |
| const auto& system_info = system_result->get_system_info(); | |
| const auto& dmi_info = system_info->dmi_info; | |
| if (dmi_info) { | |
| if (dmi_info->sys_vendor.has_value()) | |
| telemetry_info->system_info.dmi_info.sys_vendor = | |
| dmi_info->sys_vendor.value(); | |
| if (dmi_info->product_name.has_value()) | |
| telemetry_info->system_info.dmi_info.product_name = | |
| dmi_info->product_name.value(); | |
| if (dmi_info->product_version.has_value()) | |
| telemetry_info->system_info.dmi_info.product_version = | |
| dmi_info->product_version.value(); | |
| if (dmi_info->bios_version.has_value()) | |
| telemetry_info->system_info.dmi_info.bios_version = | |
| dmi_info->bios_version.value(); | |
| } | |
| const auto& os_info = system_info->os_info; | |
| if (os_info) { | |
| telemetry_info->system_info.os_info.boot_mode = | |
| static_cast<TelemetryInfo::SystemInfo::OsInfo::BootMode>( | |
| os_info->boot_mode); | |
| } | |
| } | |
| return true; | |
| } | |
| bool CrosHealthd::ParseMemoryResult( | |
| ash::cros_healthd::mojom::TelemetryInfoPtr* result, | |
| TelemetryInfo* telemetry_info) { | |
| const auto& memory_result = (*result)->memory_result; | |
| if (memory_result) { | |
| if (memory_result->is_error()) { | |
| PrintError(memory_result->get_error(), "memory information"); | |
| return false; | |
| } | |
| const auto& memory_info = memory_result->get_memory_info(); | |
| if (memory_info) { | |
| telemetry_info->memory_info.total_memory_kib = | |
| memory_info->total_memory_kib; | |
| } | |
| } | |
| return true; | |
| } | |
| bool CrosHealthd::ParseNonRemovableBlockDeviceResult( | |
| ash::cros_healthd::mojom::TelemetryInfoPtr* result, | |
| TelemetryInfo* telemetry_info) { | |
| const auto& non_removable_block_device_result = | |
| (*result)->block_device_result; | |
| if (non_removable_block_device_result) { | |
| if (non_removable_block_device_result->is_error()) { | |
| PrintError(non_removable_block_device_result->get_error(), | |
| "non-removable block device information"); | |
| return false; | |
| } | |
| const auto& non_removable_block_device_infos = | |
| non_removable_block_device_result->get_block_device_info(); | |
| for (const auto& non_removable_block_device_info : | |
| non_removable_block_device_infos) { | |
| telemetry_info->block_device_info.push_back({ | |
| .size = non_removable_block_device_info->size, | |
| }); | |
| } | |
| } | |
| return true; | |
| } | |
| bool CrosHealthd::ParseCpuResult( | |
| ash::cros_healthd::mojom::TelemetryInfoPtr* result, | |
| TelemetryInfo* telemetry_info) { | |
| const auto& cpu_result = (*result)->cpu_result; | |
| if (cpu_result) { | |
| if (cpu_result->is_error()) { | |
| PrintError(cpu_result->get_error(), "CPU information"); | |
| return false; | |
| } | |
| const auto& cpu_info = cpu_result->get_cpu_info(); | |
| for (const auto& physical_cpu : cpu_info->physical_cpus) { | |
| if (physical_cpu->model_name.has_value()) { | |
| telemetry_info->cpu_info.physical_cpus.push_back({ | |
| .model_name = physical_cpu->model_name.value(), | |
| }); | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| bool CrosHealthd::ParseBusResult( | |
| ash::cros_healthd::mojom::TelemetryInfoPtr* result, | |
| TelemetryInfo* telemetry_info) { | |
| const auto& bus_result = (*result)->bus_result; | |
| if (bus_result) { | |
| if (bus_result->is_error()) { | |
| PrintError(bus_result->get_error(), "bus information"); | |
| return false; | |
| } | |
| const auto& bus_devices = bus_result->get_bus_devices(); | |
| for (const auto& bus_device : bus_devices) { | |
| if (!bus_device->bus_info) | |
| continue; | |
| switch (bus_device->bus_info->which()) { | |
| case ash::cros_healthd::mojom::BusInfo::Tag::kPciBusInfo: { | |
| const auto& pci_bus_info = bus_device->bus_info->get_pci_bus_info(); | |
| telemetry_info->bus_devices.push_back({ | |
| .device_class = | |
| static_cast<TelemetryInfo::BusDevice::BusDeviceClass>( | |
| bus_device->device_class), | |
| .bus_type_info = | |
| TelemetryInfo::BusDevice::PciBusInfo{ | |
| .vendor_id = pci_bus_info->vendor_id, | |
| .device_id = pci_bus_info->device_id, | |
| .driver = pci_bus_info->driver.has_value() | |
| ? pci_bus_info->driver.value() | |
| : "", | |
| }, | |
| }); | |
| break; | |
| } | |
| case ash::cros_healthd::mojom::BusInfo::Tag::kUsbBusInfo: { | |
| const auto& usb_bus_info = bus_device->bus_info->get_usb_bus_info(); | |
| telemetry_info->bus_devices.push_back({ | |
| .device_class = | |
| static_cast<TelemetryInfo::BusDevice::BusDeviceClass>( | |
| bus_device->device_class), | |
| .bus_type_info = | |
| TelemetryInfo::BusDevice::UsbBusInfo{ | |
| .vendor_id = usb_bus_info->vendor_id, | |
| .product_id = usb_bus_info->product_id, | |
| }, | |
| }); | |
| break; | |
| } | |
| case ash::cros_healthd::mojom::BusInfo::Tag::kThunderboltBusInfo: { | |
| break; | |
| } | |
| case ash::cros_healthd::mojom::BusInfo::Tag::kUnmappedField: { | |
| LOG(ERROR) << "Get unmapped Mojo fields by retrieving bus info from " | |
| "cros_healthd"; | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| } // namespace chromeos_update_engine |