blob: ed969d990f3752a787fb9bb5a74c3f68469c7e3b [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 "diagnostics/routines/battery_sysfs/battery_sysfs.h"
#include <utility>
#include <base/files/file_util.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_util.h>
namespace diagnostics {
namespace {
int CalculateProgressPercent(grpc_api::DiagnosticRoutineStatus status) {
// Since the battery_sysfs routine cannot be paused or cancelled, the
// progress percent can only be 0 or 100.
if (status == grpc_api::ROUTINE_STATUS_PASSED ||
status == grpc_api::ROUTINE_STATUS_FAILED)
return 100;
return 0;
}
const struct {
const char* battery_log_key;
const char* relative_file_path;
} kBatteryLogKeyPaths[] = {
{"Manufacturer", kBatterySysfsManufacturerPath},
{"Current Now", kBatterySysfsCurrentNowPath},
{"Present", kBatterySysfsPresentPath},
{"Status", kBatterySysfsStatusPath},
{"Voltage Now", kBatterySysfsVoltageNowPath},
{"Charge Full", kBatterySysfsChargeFullPath},
{"Charge Full Design", kBatterySysfsChargeFullDesignPath},
{"Charge Now", kBatterySysfsChargeNowPath}};
bool TryReadFileToString(const base::FilePath& absolute_file_path,
std::string* file_contents) {
DCHECK(file_contents);
if (!base::ReadFileToString(absolute_file_path, file_contents))
return false;
base::TrimWhitespaceASCII(*file_contents, base::TRIM_TRAILING, file_contents);
return true;
}
bool TryReadFileToInt(const base::FilePath& absolute_file_path, int* output) {
DCHECK(output);
std::string file_contents;
if (!base::ReadFileToString(absolute_file_path, &file_contents))
return false;
base::TrimWhitespaceASCII(file_contents, base::TRIM_TRAILING, &file_contents);
if (!base::StringToInt(file_contents, output))
return false;
return true;
}
} // namespace
const char kBatterySysfsPath[] = "sys/class/power_supply/BAT0/";
const char kBatterySysfsChargeFullPath[] = "charge_full";
const char kBatterySysfsChargeFullDesignPath[] = "charge_full_design";
const char kBatterySysfsEnergyFullPath[] = "energy_full";
const char kBatterySysfsEnergyFullDesignPath[] = "energy_full_design";
const char kBatterySysfsCycleCountPath[] = "cycle_count";
const char kBatterySysfsManufacturerPath[] = "manufacturer";
const char kBatterySysfsCurrentNowPath[] = "current_now";
const char kBatterySysfsPresentPath[] = "present";
const char kBatterySysfsStatusPath[] = "status";
const char kBatterySysfsVoltageNowPath[] = "voltage_now";
const char kBatterySysfsChargeNowPath[] = "charge_now";
const char kBatterySysfsFailedCalculatingWearPercentageMessage[] =
"Could not get wear percentage.";
const char kBatterySysfsExcessiveWearMessage[] = "Battery is over-worn.";
const char kBatterySysfsFailedReadingCycleCountMessage[] =
"Could not get cycle count.";
const char kBatterySysfsExcessiveCycleCountMessage[] =
"Battery cycle count is too high.";
const char kBatterySysfsRoutinePassedMessage[] = "Routine passed.";
BatterySysfsRoutine::BatterySysfsRoutine(
const grpc_api::BatterySysfsRoutineParameters& parameters)
: status_(grpc_api::ROUTINE_STATUS_READY), parameters_(parameters) {}
BatterySysfsRoutine::~BatterySysfsRoutine() = default;
void BatterySysfsRoutine::Start() {
DCHECK_EQ(status_, grpc_api::ROUTINE_STATUS_READY);
if (!RunBatterySysfsRoutine())
LOG(ERROR) << "Routine failed: " << status_message_;
}
// The battery_sysfs routine can only be started.
void BatterySysfsRoutine::Pause() {}
void BatterySysfsRoutine::Resume() {}
void BatterySysfsRoutine::Cancel() {}
void BatterySysfsRoutine::PopulateStatusUpdate(
grpc_api::GetRoutineUpdateResponse* response, bool include_output) {
// Because the battery routine is non-interactive, we will never include a
// user message.
response->set_status(status_);
response->set_status_message(status_message_);
response->set_progress_percent(CalculateProgressPercent(status_));
if (include_output) {
std::string output;
for (const auto& key_val : battery_sysfs_log_)
output += key_val.first + ": " + key_val.second + "\n";
response->set_output(output);
}
}
grpc_api::DiagnosticRoutineStatus BatterySysfsRoutine::GetStatus() {
return status_;
}
void BatterySysfsRoutine::set_root_dir_for_testing(
const base::FilePath& root_dir) {
root_dir_ = root_dir;
}
bool BatterySysfsRoutine::RunBatterySysfsRoutine() {
for (const auto& item : kBatteryLogKeyPaths) {
std::string file_contents;
base::FilePath absolute_file_path(
root_dir_.AppendASCII(kBatterySysfsPath)
.AppendASCII(item.relative_file_path));
if (!TryReadFileToString(absolute_file_path, &file_contents)) {
// Failing to read and log a file should not cause the routine to fail,
// but we should record the event.
LOG(WARNING) << "Battery attribute unavailable: " << item.battery_log_key;
continue;
}
battery_sysfs_log_[item.battery_log_key] = file_contents;
}
if (!TestWearPercentage() || !TestCycleCount())
return false;
status_message_ = kBatterySysfsRoutinePassedMessage;
status_ = grpc_api::ROUTINE_STATUS_PASSED;
return true;
}
bool BatterySysfsRoutine::ReadBatteryCapacities(int* capacity,
int* design_capacity) {
DCHECK(capacity);
DCHECK(design_capacity);
base::FilePath absolute_charge_full_path(
root_dir_.AppendASCII(kBatterySysfsPath)
.AppendASCII(kBatterySysfsChargeFullPath));
base::FilePath absolute_charge_full_design_path(
root_dir_.AppendASCII(kBatterySysfsPath)
.AppendASCII(kBatterySysfsChargeFullDesignPath));
if (!TryReadFileToInt(absolute_charge_full_path, capacity) ||
!TryReadFileToInt(absolute_charge_full_design_path, design_capacity)) {
// No charge values, check for energy-reporting batteries.
base::FilePath absolute_energy_full_path(
root_dir_.AppendASCII(kBatterySysfsPath)
.AppendASCII(kBatterySysfsEnergyFullPath));
base::FilePath absolute_energy_full_design_path(
root_dir_.AppendASCII(kBatterySysfsPath)
.AppendASCII(kBatterySysfsEnergyFullDesignPath));
if (!TryReadFileToInt(absolute_energy_full_path, capacity) ||
!TryReadFileToInt(absolute_energy_full_design_path, design_capacity)) {
return false;
}
}
return true;
}
bool BatterySysfsRoutine::ReadCycleCount(int* cycle_count) {
DCHECK(cycle_count);
base::FilePath absolute_cycle_count_path(
root_dir_.AppendASCII(kBatterySysfsPath)
.AppendASCII(kBatterySysfsCycleCountPath));
if (!TryReadFileToInt(absolute_cycle_count_path, cycle_count))
return false;
return true;
}
bool BatterySysfsRoutine::TestWearPercentage() {
int percent_battery_wear_allowed = parameters_.percent_battery_wear_allowed();
int capacity;
int design_capacity;
if (!ReadBatteryCapacities(&capacity, &design_capacity) || capacity < 0 ||
design_capacity < 0 || capacity > design_capacity) {
status_message_ = kBatterySysfsFailedCalculatingWearPercentageMessage;
status_ = grpc_api::ROUTINE_STATUS_ERROR;
return false;
}
int wear_percentage = 100 - capacity * 100 / design_capacity;
battery_sysfs_log_["Wear Percentage"] = std::to_string(wear_percentage);
if (wear_percentage > percent_battery_wear_allowed) {
status_message_ = kBatterySysfsExcessiveWearMessage;
status_ = grpc_api::ROUTINE_STATUS_FAILED;
return false;
}
return true;
}
bool BatterySysfsRoutine::TestCycleCount() {
int maximum_cycle_count = parameters_.maximum_cycle_count();
int cycle_count;
if (!ReadCycleCount(&cycle_count) || cycle_count < 0) {
status_message_ = kBatterySysfsFailedReadingCycleCountMessage;
status_ = grpc_api::ROUTINE_STATUS_ERROR;
return false;
}
battery_sysfs_log_["Cycle Count"] = std::to_string(cycle_count);
if (cycle_count > maximum_cycle_count) {
status_message_ = kBatterySysfsExcessiveCycleCountMessage;
status_ = grpc_api::ROUTINE_STATUS_FAILED;
return false;
}
return true;
}
} // namespace diagnostics