blob: a6f855cb1ef7f281a394de822d188fe70b2b895c [file] [log] [blame]
// Copyright 2019 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 <stdlib.h>
#include <iostream>
#include <iterator>
#include <map>
#include <string>
#include <vector>
#include <base/at_exit.h>
#include <base/logging.h>
#include <base/message_loop/message_loop.h>
#include <base/no_destructor.h>
#include <base/threading/platform_thread.h>
#include <base/time/time.h>
#include <brillo/flag_helper.h>
#include "diagnostics/common/mojo_utils.h"
#include "diagnostics/cros_healthd_mojo_adapter/cros_healthd_mojo_adapter.h"
#include "mojo/cros_healthd.mojom.h"
#include "mojo/cros_healthd_diagnostics.mojom.h"
namespace mojo_ipc = ::chromeos::cros_healthd::mojom;
namespace {
// Poll interval while waiting for a routine to finish.
constexpr base::TimeDelta kRoutinePollIntervalTimeDelta =
base::TimeDelta::FromMilliseconds(100);
// Maximum time we're willing to wait for a routine to finish.
constexpr base::TimeDelta kMaximumRoutineExecutionTimeDelta =
base::TimeDelta::FromSeconds(60);
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}};
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}};
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;
}
bool RunRoutineAndProcessResult(int32_t id,
diagnostics::CrosHealthdMojoAdapter* adapter) {
auto response = adapter->GetRoutineUpdate(
id, mojo_ipc::DiagnosticRoutineCommandEnum::kGetStatus,
true /* include_output */);
const base::TimeTicks start_time = base::TimeTicks::Now();
while (!response.is_null() &&
response->routine_update_union->is_noninteractive_update() &&
response->routine_update_union->get_noninteractive_update()->status ==
mojo_ipc::DiagnosticRoutineStatusEnum::kRunning &&
base::TimeTicks::Now() <
start_time + kMaximumRoutineExecutionTimeDelta) {
base::PlatformThread::Sleep(kRoutinePollIntervalTimeDelta);
std::cout << "Progress: " << response->progress_percent << std::endl;
response = adapter->GetRoutineUpdate(
id, mojo_ipc::DiagnosticRoutineCommandEnum::kGetStatus,
true /* include_output */);
}
if (response.is_null()) {
std::cout << "No GetRoutineUpdateResponse received." << std::endl;
return false;
}
// 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.
if (response->routine_update_union->is_interactive_update()) {
bool user_message_found = false;
mojo_ipc::DiagnosticRoutineUserMessageEnum user_message =
response->routine_update_union->get_interactive_update()->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;
}
}
LOG_IF(FATAL, !user_message_found)
<< "No readable message found for DiagnosticRoutineUserMessageEnum: "
<< user_message;
std::string dummy;
std::getline(std::cin, dummy);
response = adapter->GetRoutineUpdate(
id, mojo_ipc::DiagnosticRoutineCommandEnum::kContinue,
false /* include_output */);
return RunRoutineAndProcessResult(id, adapter);
}
// Noninteractive routines without a status of kRunning must have terminated
// in some form. Print the update to the console to let the user know.
if (response->output.is_valid()) {
auto shared_memory = diagnostics::GetReadOnlySharedMemoryFromMojoHandle(
std::move(response->output));
if (shared_memory) {
std::cout << "Output: "
<< std::string(
static_cast<const char*>(shared_memory->memory()),
shared_memory->mapped_size())
<< std::endl;
} else {
LOG(ERROR) << "Failed to read output.";
return false;
}
}
std::cout << "Progress: " << response->progress_percent << std::endl;
bool status_found = false;
mojo_ipc::DiagnosticRoutineStatusEnum status =
response->routine_update_union->get_noninteractive_update()->status;
for (const auto& item : kDiagnosticRoutineReadableStatuses) {
if (item.status == status) {
status_found = true;
std::cout << "Status: " << item.readable_status << std::endl;
break;
}
}
LOG_IF(FATAL, !status_found)
<< "Invalid readable status lookup with status: " << status;
std::cout << "Status message: "
<< response->routine_update_union->get_noninteractive_update()
->status_message
<< std::endl;
if (status != mojo_ipc::DiagnosticRoutineStatusEnum::kFailedToStart) {
response = adapter->GetRoutineUpdate(
id, mojo_ipc::DiagnosticRoutineCommandEnum::kRemove,
false /* include_output */);
if (response.is_null() ||
!response->routine_update_union->is_noninteractive_update() ||
response->routine_update_union->get_noninteractive_update()->status !=
mojo_ipc::DiagnosticRoutineStatusEnum::kRemoved) {
std::cout << "Failed to remove routine." << std::endl;
return false;
}
}
return true;
}
bool ActionGetRoutines() {
diagnostics::CrosHealthdMojoAdapter adapter;
auto reply = adapter.GetAvailableRoutines();
for (auto routine : reply) {
std::cout << "Available routine: " << GetSwitchFromRoutine(routine)
<< std::endl;
}
return true;
}
bool ActionRunBatteryCapacityRoutine(int low_mah, int high_mah) {
diagnostics::CrosHealthdMojoAdapter adapter;
auto response = adapter.RunBatteryCapacityRoutine(low_mah, high_mah);
CHECK(response) << "No RunRoutineResponse received.";
return RunRoutineAndProcessResult(response->id, &adapter);
}
bool ActionRunBatteryHealthRoutine(int maximum_cycle_count,
int percent_battery_wear_allowed) {
diagnostics::CrosHealthdMojoAdapter adapter;
auto response = adapter.RunBatteryHealthRoutine(maximum_cycle_count,
percent_battery_wear_allowed);
CHECK(response) << "No RunRoutineResponse received.";
return RunRoutineAndProcessResult(response->id, &adapter);
}
bool ActionRunUrandomRoutine(int length_seconds) {
diagnostics::CrosHealthdMojoAdapter adapter;
auto response = adapter.RunUrandomRoutine(length_seconds);
CHECK(response) << "No RunRoutineResponse received.";
return RunRoutineAndProcessResult(response->id, &adapter);
}
bool ActionRunSmartctlCheckRoutine() {
diagnostics::CrosHealthdMojoAdapter adapter;
auto response = adapter.RunSmartctlCheckRoutine();
CHECK(response) << "No RunRoutineResponse received.";
return RunRoutineAndProcessResult(response->id, &adapter);
}
bool ActionRunAcPowerRoutine(bool is_connected, const std::string& power_type) {
diagnostics::CrosHealthdMojoAdapter adapter;
chromeos::cros_healthd::mojom::AcPowerStatusEnum expected_status =
is_connected
? chromeos::cros_healthd::mojom::AcPowerStatusEnum::kConnected
: chromeos::cros_healthd::mojom::AcPowerStatusEnum::kDisconnected;
const base::Optional<std::string> optional_power_type =
(power_type == "") ? base::nullopt
: base::Optional<std::string>{power_type};
auto response =
adapter.RunAcPowerRoutine(expected_status, optional_power_type);
CHECK(response) << "No RunRoutineResponse received.";
return RunRoutineAndProcessResult(response->id, &adapter);
}
} // namespace
// 'diag' command-line tool:
//
// Test driver for libdiag. Only supports running a single diagnostic routine
// at a time.
int main(int argc, char** argv) {
DEFINE_string(action, "",
"Action to perform. Options are:\n\tget_routines - retrieve "
"available routines.\n\trun_routine - run specified routine.");
DEFINE_string(routine, "",
"Diagnostic routine to run. For a list of available routines, "
"run 'diag --action=get_routines'.");
DEFINE_int32(low_mah, 1000, "Lower bound for the battery routine, in mAh.");
DEFINE_int32(high_mah, 10000, "Upper bound for the battery routine, in mAh.");
DEFINE_int32(
maximum_cycle_count, 0,
"Maximum cycle count allowed for the battery_sysfs routine to pass.");
DEFINE_int32(percent_battery_wear_allowed, 100,
"Maximum percent battery wear allowed for the battery_sysfs "
"routine to pass.");
DEFINE_int32(length_seconds, 10,
"Number of seconds to run the urandom routine for.");
DEFINE_bool(ac_power_is_connected, true,
"Whether or not the AC power routine expects the power supply to "
"be connected.");
DEFINE_string(
expected_power_type, "",
"Optional type of power supply expected for the AC power routine.");
brillo::FlagHelper::Init(argc, argv, "diag - Device diagnostic tool.");
logging::InitLogging(logging::LoggingSettings());
base::AtExitManager at_exit_manager;
base::MessageLoopForIO message_loop;
if (FLAGS_action == "") {
std::cout << "--action must be specified. Use --help for help on usage."
<< std::endl;
return EXIT_FAILURE;
}
if (FLAGS_action == "get_routines")
return ActionGetRoutines() ? EXIT_SUCCESS : EXIT_FAILURE;
if (FLAGS_action == "run_routine") {
std::map<std::string, mojo_ipc::DiagnosticRoutineEnum>
switch_to_diagnostic_routine;
for (const auto& item : kDiagnosticRoutineSwitches)
switch_to_diagnostic_routine[item.switch_name] = item.routine;
auto itr = switch_to_diagnostic_routine.find(FLAGS_routine);
if (itr == switch_to_diagnostic_routine.end()) {
std::cout << "Unknown routine: " << FLAGS_routine << std::endl;
return EXIT_FAILURE;
}
bool routine_result;
switch (itr->second) {
case mojo_ipc::DiagnosticRoutineEnum::kBatteryCapacity:
routine_result =
ActionRunBatteryCapacityRoutine(FLAGS_low_mah, FLAGS_high_mah);
break;
case mojo_ipc::DiagnosticRoutineEnum::kBatteryHealth:
routine_result = ActionRunBatteryHealthRoutine(
FLAGS_maximum_cycle_count, FLAGS_percent_battery_wear_allowed);
break;
case mojo_ipc::DiagnosticRoutineEnum::kUrandom:
routine_result = ActionRunUrandomRoutine(FLAGS_length_seconds);
break;
case mojo_ipc::DiagnosticRoutineEnum::kSmartctlCheck:
routine_result = ActionRunSmartctlCheckRoutine();
break;
case mojo_ipc::DiagnosticRoutineEnum::kAcPower:
routine_result = ActionRunAcPowerRoutine(FLAGS_ac_power_is_connected,
FLAGS_expected_power_type);
break;
default:
std::cout << "Unsupported routine: " << FLAGS_routine << std::endl;
return EXIT_FAILURE;
}
return routine_result ? EXIT_SUCCESS : EXIT_FAILURE;
}
std::cout << "Unknown action: " << FLAGS_action << std::endl;
return EXIT_FAILURE;
}