blob: f7a59613cc859d8b08a0e9c653b2ddd5953f482b [file] [log] [blame]
// Copyright 2020 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "diagnostics/cros_health_tool/diag/diag_actions.h"
#include <cstdint>
#include <iostream>
#include <iterator>
#include <map>
#include <string>
#include <utility>
#include <base/logging.h>
#include <base/no_destructor.h>
#include <base/run_loop.h>
#include <base/threading/thread_task_runner_handle.h>
#include <base/time/time.h>
#include "diagnostics/common/mojo_utils.h"
#include "diagnostics/cros_healthd_mojo_adapter/cros_healthd_mojo_adapter.h"
#include "mojo/cros_healthd_diagnostics.mojom.h"
namespace diagnostics {
namespace mojo_ipc = ::chromeos::cros_healthd::mojom;
namespace {
const struct {
const char* switch_name;
mojo_ipc::DiagnosticRoutineEnum routine;
} kDiagnosticRoutineSwitches[] = {
{"battery_capacity", mojo_ipc::DiagnosticRoutineEnum::kBatteryCapacity},
{"battery_health", mojo_ipc::DiagnosticRoutineEnum::kBatteryHealth},
{"urandom", mojo_ipc::DiagnosticRoutineEnum::kUrandom},
{"smartctl_check", mojo_ipc::DiagnosticRoutineEnum::kSmartctlCheck},
{"ac_power", mojo_ipc::DiagnosticRoutineEnum::kAcPower},
{"cpu_cache", mojo_ipc::DiagnosticRoutineEnum::kCpuCache},
{"cpu_stress", mojo_ipc::DiagnosticRoutineEnum::kCpuStress},
{"floating_point_accuracy",
mojo_ipc::DiagnosticRoutineEnum::kFloatingPointAccuracy},
{"nvme_wear_level", mojo_ipc::DiagnosticRoutineEnum::kNvmeWearLevel},
{"nvme_self_test", mojo_ipc::DiagnosticRoutineEnum::kNvmeSelfTest},
{"disk_read", mojo_ipc::DiagnosticRoutineEnum::kDiskRead},
{"prime_search", mojo_ipc::DiagnosticRoutineEnum::kPrimeSearch},
{"battery_discharge", mojo_ipc::DiagnosticRoutineEnum::kBatteryDischarge}};
const struct {
const char* readable_status;
mojo_ipc::DiagnosticRoutineStatusEnum status;
} kDiagnosticRoutineReadableStatuses[] = {
{"Ready", mojo_ipc::DiagnosticRoutineStatusEnum::kReady},
{"Running", mojo_ipc::DiagnosticRoutineStatusEnum::kRunning},
{"Waiting", mojo_ipc::DiagnosticRoutineStatusEnum::kWaiting},
{"Passed", mojo_ipc::DiagnosticRoutineStatusEnum::kPassed},
{"Failed", mojo_ipc::DiagnosticRoutineStatusEnum::kFailed},
{"Error", mojo_ipc::DiagnosticRoutineStatusEnum::kError},
{"Cancelled", mojo_ipc::DiagnosticRoutineStatusEnum::kCancelled},
{"Failed to start", mojo_ipc::DiagnosticRoutineStatusEnum::kFailedToStart},
{"Removed", mojo_ipc::DiagnosticRoutineStatusEnum::kRemoved},
{"Cancelling", mojo_ipc::DiagnosticRoutineStatusEnum::kCancelling},
{"Unsupported", mojo_ipc::DiagnosticRoutineStatusEnum::kUnsupported}};
const struct {
const char* readable_user_message;
mojo_ipc::DiagnosticRoutineUserMessageEnum user_message_enum;
} kDiagnosticRoutineReadableUserMessages[] = {
{"Unplug the AC adapter.",
mojo_ipc::DiagnosticRoutineUserMessageEnum::kUnplugACPower},
{"Plug in the AC adapter.",
mojo_ipc::DiagnosticRoutineUserMessageEnum::kPlugInACPower}};
std::string GetSwitchFromRoutine(mojo_ipc::DiagnosticRoutineEnum routine) {
static base::NoDestructor<
std::map<mojo_ipc::DiagnosticRoutineEnum, std::string>>
diagnostic_routine_to_switch;
if (diagnostic_routine_to_switch->empty()) {
for (const auto& item : kDiagnosticRoutineSwitches) {
diagnostic_routine_to_switch->insert(
std::make_pair(item.routine, item.switch_name));
}
}
auto routine_itr = diagnostic_routine_to_switch->find(routine);
LOG_IF(FATAL, routine_itr == diagnostic_routine_to_switch->end())
<< "Invalid routine to switch lookup with routine: " << routine;
return routine_itr->second;
}
} // namespace
DiagActions::DiagActions(base::TimeDelta polling_interval,
base::TimeDelta maximum_execution_time,
const base::TickClock* tick_clock)
: adapter_(CrosHealthdMojoAdapter::Create()),
kPollingInterval(polling_interval),
kMaximumExecutionTime(maximum_execution_time) {
DCHECK(adapter_);
if (tick_clock) {
tick_clock_ = tick_clock;
} else {
default_tick_clock_ = std::make_unique<base::DefaultTickClock>();
tick_clock_ = default_tick_clock_.get();
}
DCHECK(tick_clock_);
}
DiagActions::~DiagActions() = default;
bool DiagActions::ActionGetRoutines() {
auto reply = adapter_->GetAvailableRoutines();
for (auto routine : reply) {
std::cout << "Available routine: " << GetSwitchFromRoutine(routine)
<< std::endl;
}
return true;
}
bool DiagActions::ActionRunAcPowerRoutine(
mojo_ipc::AcPowerStatusEnum expected_status,
const base::Optional<std::string>& expected_power_type) {
auto response =
adapter_->RunAcPowerRoutine(expected_status, expected_power_type);
CHECK(response) << "No RunRoutineResponse received.";
id_ = response->id;
return PollRoutineAndProcessResult();
}
bool DiagActions::ActionRunBatteryCapacityRoutine(uint32_t low_mah,
uint32_t high_mah) {
auto response = adapter_->RunBatteryCapacityRoutine(low_mah, high_mah);
CHECK(response) << "No RunRoutineResponse received.";
id_ = response->id;
return PollRoutineAndProcessResult();
}
bool DiagActions::ActionRunBatteryDischargeRoutine(
base::TimeDelta exec_duration, uint32_t maximum_discharge_percent_allowed) {
auto response = adapter_->RunBatteryDischargeRoutine(
exec_duration, maximum_discharge_percent_allowed);
CHECK(response) << "No RunRoutineResponse received.";
id_ = response->id;
return PollRoutineAndProcessResult();
}
bool DiagActions::ActionRunBatteryHealthRoutine(
uint32_t maximum_cycle_count, uint32_t percent_battery_wear_allowed) {
auto response = adapter_->RunBatteryHealthRoutine(
maximum_cycle_count, percent_battery_wear_allowed);
CHECK(response) << "No RunRoutineResponse received.";
id_ = response->id;
return PollRoutineAndProcessResult();
}
bool DiagActions::ActionRunCpuCacheRoutine(base::TimeDelta exec_duration) {
auto response = adapter_->RunCpuCacheRoutine(exec_duration);
CHECK(response) << "No RunRoutineResponse received.";
id_ = response->id;
return PollRoutineAndProcessResult();
}
bool DiagActions::ActionRunCpuStressRoutine(base::TimeDelta exec_duration) {
auto response = adapter_->RunCpuStressRoutine(exec_duration);
CHECK(response) << "No RunRoutineResponse received.";
id_ = response->id;
return PollRoutineAndProcessResult();
}
bool DiagActions::ActionRunDiskReadRoutine(
mojo_ipc::DiskReadRoutineTypeEnum type,
base::TimeDelta exec_duration,
uint32_t file_size_mb) {
auto response =
adapter_->RunDiskReadRoutine(type, exec_duration, file_size_mb);
id_ = response->id;
CHECK(response) << "No RunRoutineResponse received.";
return PollRoutineAndProcessResult();
}
bool DiagActions::ActionRunFloatingPointAccuracyRoutine(
base::TimeDelta exec_duration) {
auto response = adapter_->RunFloatingPointAccuracyRoutine(exec_duration);
CHECK(response) << "No RunRoutineResponse received.";
id_ = response->id;
return PollRoutineAndProcessResult();
}
bool DiagActions::ActionRunNvmeSelfTestRoutine(
mojo_ipc::NvmeSelfTestTypeEnum nvme_self_test_type) {
auto response = adapter_->RunNvmeSelfTestRoutine(nvme_self_test_type);
CHECK(response) << "No RunRoutineResponse received.";
id_ = response->id;
return PollRoutineAndProcessResult();
}
bool DiagActions::ActionRunNvmeWearLevelRoutine(uint32_t wear_level_threshold) {
auto response = adapter_->RunNvmeWearLevelRoutine(wear_level_threshold);
CHECK(response) << "No RunRoutineResponse received.";
id_ = response->id;
return PollRoutineAndProcessResult();
}
bool DiagActions::ActionRunPrimeSearchRoutine(base::TimeDelta exec_duration,
uint64_t max_num) {
auto response = adapter_->RunPrimeSearchRoutine(exec_duration, max_num);
id_ = response->id;
CHECK(response) << "No RunRoutineResponse received.";
return PollRoutineAndProcessResult();
}
bool DiagActions::ActionRunSmartctlCheckRoutine() {
auto response = adapter_->RunSmartctlCheckRoutine();
CHECK(response) << "No RunRoutineResponse received.";
id_ = response->id;
return PollRoutineAndProcessResult();
}
bool DiagActions::ActionRunUrandomRoutine(uint32_t length_seconds) {
auto response = adapter_->RunUrandomRoutine(length_seconds);
CHECK(response) << "No RunRoutineResponse received.";
id_ = response->id;
return PollRoutineAndProcessResult();
}
void DiagActions::ForceCancelAtPercent(uint32_t percent) {
CHECK_LE(percent, 100) << "Percent must be <= 100.";
force_cancel_ = true;
cancellation_percent_ = percent;
}
bool DiagActions::PollRoutineAndProcessResult() {
mojo_ipc::RoutineUpdatePtr response;
const base::TimeTicks start_time = tick_clock_->NowTicks();
do {
// Poll the routine until it's either interactive and requires user input,
// or it's noninteractive but no longer running.
response = adapter_->GetRoutineUpdate(
id_, mojo_ipc::DiagnosticRoutineCommandEnum::kGetStatus,
true /* include_output */);
std::cout << "Progress: " << response->progress_percent << std::endl;
if (force_cancel_ && !response.is_null() &&
response->progress_percent >= cancellation_percent_) {
response = adapter_->GetRoutineUpdate(
id_, mojo_ipc::DiagnosticRoutineCommandEnum::kCancel,
true /* include_output */);
force_cancel_ = false;
}
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, run_loop.QuitClosure(), kPollingInterval);
run_loop.Run();
} while (
!response.is_null() &&
response->routine_update_union->is_noninteractive_update() &&
response->routine_update_union->get_noninteractive_update()->status ==
mojo_ipc::DiagnosticRoutineStatusEnum::kRunning &&
tick_clock_->NowTicks() < start_time + kMaximumExecutionTime);
if (response.is_null()) {
std::cout << "No GetRoutineUpdateResponse received." << std::endl;
return false;
}
if (response->routine_update_union->is_interactive_update()) {
return ProcessInteractiveResultAndContinue(
std::move(response->routine_update_union->get_interactive_update()));
}
// Noninteractive routines without a status of kRunning must have terminated
// in some form. Print the update to the console to let the user know.
std::cout << "Progress: " << response->progress_percent << std::endl;
if (response->output.is_valid()) {
auto shm_mapping =
diagnostics::GetReadOnlySharedMemoryMappingFromMojoHandle(
std::move(response->output));
if (shm_mapping.IsValid()) {
std::cout << "Output: "
<< std::string(shm_mapping.GetMemoryAs<const char>(),
shm_mapping.mapped_size())
<< std::endl;
} else {
LOG(ERROR) << "Failed to read output.";
return false;
}
}
return ProcessNonInteractiveResultAndEnd(
std::move(response->routine_update_union->get_noninteractive_update()));
}
bool DiagActions::ProcessInteractiveResultAndContinue(
mojo_ipc::InteractiveRoutineUpdatePtr interactive_result) {
// Interactive updates require us to print out instructions to the user on the
// console. Once the user responds by pressing the ENTER key, we need to send
// a continue command to the routine and restart waiting for results.
bool user_message_found = false;
mojo_ipc::DiagnosticRoutineUserMessageEnum user_message =
interactive_result->user_message;
for (const auto& item : kDiagnosticRoutineReadableUserMessages) {
if (item.user_message_enum == user_message) {
user_message_found = true;
std::cout << item.readable_user_message << std::endl
<< "Press ENTER to continue." << std::endl;
break;
}
}
if (!user_message_found) {
LOG(ERROR) << "No human-readable string for user message: "
<< static_cast<int>(user_message);
RemoveRoutine();
return false;
}
std::string dummy;
std::getline(std::cin, dummy);
auto response = adapter_->GetRoutineUpdate(
id_, mojo_ipc::DiagnosticRoutineCommandEnum::kContinue,
false /* include_output */);
return PollRoutineAndProcessResult();
}
bool DiagActions::ProcessNonInteractiveResultAndEnd(
mojo_ipc::NonInteractiveRoutineUpdatePtr noninteractive_result) {
bool status_found = false;
mojo_ipc::DiagnosticRoutineStatusEnum status = noninteractive_result->status;
// Clean up the routine if necessary - if the routine never started, then we
// don't need to remove it.
if (status != mojo_ipc::DiagnosticRoutineStatusEnum::kFailedToStart)
RemoveRoutine();
for (const auto& item : kDiagnosticRoutineReadableStatuses) {
if (item.status == status) {
status_found = true;
std::cout << "Status: " << item.readable_status << std::endl;
break;
}
}
if (!status_found) {
LOG(ERROR) << "No human-readable string for status: "
<< static_cast<int>(status);
return false;
}
std::cout << "Status message: " << noninteractive_result->status_message
<< std::endl;
return true;
}
void DiagActions::RemoveRoutine() {
auto response = adapter_->GetRoutineUpdate(
id_, mojo_ipc::DiagnosticRoutineCommandEnum::kRemove,
false /* include_output */);
// Reset |id_|, because it's no longer valid after the routine has been
// removed.
id_ = mojo_ipc::kFailedToStartId;
if (response.is_null() ||
!response->routine_update_union->is_noninteractive_update() ||
response->routine_update_union->get_noninteractive_update()->status !=
mojo_ipc::DiagnosticRoutineStatusEnum::kRemoved) {
LOG(ERROR) << "Failed to remove routine: " << id_;
}
}
} // namespace diagnostics