Added support for LM Sensors.

This uses `libsensors` as a source of temperature values, avoiding many issues with the current hwmon driver class.
This commit is contained in:
Koutheir Attouchi 2021-10-16 03:41:46 -04:00 committed by Victor Mataré
parent ab466ddd0e
commit 8a844b96cf
6 changed files with 331 additions and 0 deletions

View File

@ -20,6 +20,9 @@ endif()
pkg_check_modules(ATASMART "libatasmart")
find_library(LM_SENSORS_LIB NAMES "libsensors.so" "libsensors.so.5")
find_path(LM_SENSORS_INC NAMES "sensors/sensors.h")
if(SYSTEMD_FOUND)
set(PID_FILE "/run/thinkfan.pid")
else()
@ -39,6 +42,11 @@ option(USE_ATASMART "Enable reading temperatures from HDDs via S.M.A.R.T" OFF)
#
option(USE_NVML "Get temperatures directly from nVidia GPUs via their proprietary NVML API" ON)
#
# Defaults to ON.
#
option(USE_LM_SENSORS "Get temperatures from LM sensors" ON)
#
# The shiny new YAML config parser. Depends on yaml-cpp.
#
@ -100,6 +108,18 @@ if(USE_NVML)
target_link_libraries(thinkfan PRIVATE dl)
endif(USE_NVML)
if(USE_LM_SENSORS)
if(LM_SENSORS_LIB MATCHES "LM_SENSORS_LIB-NOTFOUND")
message(FATAL_ERROR "USE_LM_SENSORS enabled but libsensors not found. Please install libsensors-dev!")
elseif(LM_SENSORS_INC MATCHES "LM_SENSORS_INC-NOTFOUND")
message(FATAL_ERROR "USE_LM_SENSORS enabled but sensors/sensors.h not found. Please install libsensors-dev!")
else()
target_compile_definitions(thinkfan PRIVATE -DUSE_LM_SENSORS)
target_include_directories(thinkfan PRIVATE ${LM_SENSORS_INC})
target_link_libraries(thinkfan PRIVATE ${LM_SENSORS_LIB})
endif()
endif(USE_LM_SENSORS)
if(USE_YAML)
target_compile_definitions(thinkfan PRIVATE -DUSE_YAML)
target_include_directories(thinkfan PRIVATE ${YAML_CPP_INCLUDE_DIRS})

View File

@ -53,6 +53,11 @@ To compile thinkfan, you will need to have the following things installed:
this only when you really need it, since libatasmart is unreasonably
CPU-intensive.
`USE_LM_SENSORS:BOOL` (default: `ON`)
Use LM sensors to read temperatures directly from Linux drivers.
The `libsensors` library needs to be installed for this feature, probably
with required headers and development files (e.g., `libsensors-dev`).
`USE_YAML:BOOL` (default: `ON`)
Support config file in the new, more flexible YAML format. The old
config format will be deprecated after the thinkfan 1.0 release. New

View File

@ -28,6 +28,34 @@
# sensor.
sensors:
# LM Sensors
# ==========
# Temperatures can be read directly from Linux drivers through the LM sensors.
#
# To configure this, install "lm-sensors" and "libsensors", then
# run "sensors-detect", then run "sensors".
# To build thinkfan from sources, you'll also need to install "libsensors-dev"
# or equivalent package for your distribution.
#
# For example, the following output of "sensors":
# ...
# thinkpad-isa-0000
# Adapter: ISA adapter
# fan1: 2618 RPM
# fan2: 2553 RPM
# CPU: +63.0 C
# GPU 1: +55.0 C
# temp3: +68.0 C
# temp4: +0.0 C
# temp5: +60.0 C
# temp6: +64.0 C
# temp7: +67.0 C
# temp8: +0.0 C
# ...
# would result in the following configuration:
- chip: thinkpad-isa-0000
ids: [ CPU, "GPU 1", temp3, temp4, temp5, temp6, temp7, temp8 ]
# hwmon: Full path to a temperature file (single sensor).
# =======================================================
# Disadvantage is that the index in "hwmon0" depends on the load order of

View File

@ -341,4 +341,187 @@ void NvmlSensorDriver::read_temps_(TemperatureState &global_temps) const
#endif /* USE_NVML */
#ifdef USE_LM_SENSORS
/*----------------------------------------------------------------------------
| LMSensorsDriver: Driver for sensors provided by LM sensors's `libsensors`. |
----------------------------------------------------------------------------*/
// Closest integer value to zero Kelvin.
static const int MIN_CELSIUS_TEMP = -273;
std::once_flag LMSensorsDriver::lm_sensors_once_init_;
LMSensorsDriver::LMSensorsDriver(
string chip_name, std::vector<string> feature_names,
bool optional, std::vector<int> correction)
: SensorDriver(optional),
chip_name_(chip_name), chip_(nullptr), feature_names_(feature_names)
{
LMSensorsDriver::ensure_lm_sensors_is_initialized();
chip_ = LMSensorsDriver::find_chip_by_name(chip_name_);
path_ = chip_->path;
size_t len = feature_names_.size();
for (size_t i = 0; i < len; ++i) {
const string& feature_name = feature_names_[i];
auto feature = LMSensorsDriver::find_feature_by_name(
*chip_, chip_name_, feature_name);
if (!feature) {
throw SystemError("LM sensors chip '" + chip_name
+ "' does not have the feature '" + feature_name + "'");
}
features_.push_back(feature);
auto sub_feature = ::sensors_get_subfeature(
chip_, feature, ::SENSORS_SUBFEATURE_TEMP_INPUT);
if (!sub_feature) {
throw SystemError("LM sensors feature '" + feature_name
+ "' of the chip '" + chip_name_
+ "' does not have a temperature input sensor");
}
sub_features_.push_back(sub_feature);
log(TF_DBG) << "Initialized LM sensors temperature input of feature '"
+ feature_name + "' of chip '" + chip_name_ + "'." << flush;
}
if (correction.empty()) {
correction.resize(feature_names_.size(), 0);
}
set_num_temps(feature_names_.size());
set_correction(correction);
}
LMSensorsDriver::~LMSensorsDriver() noexcept(false) {}
void LMSensorsDriver::ensure_lm_sensors_is_initialized() {
int r = 0;
std::call_once(LMSensorsDriver::lm_sensors_once_init_,
LMSensorsDriver::initialize_lm_sensors, &r);
if (r != 0) {
const char *msg = ::sensors_strerror(r);
throw SystemError(string("Failed to initialize LM sensors driver: ") + msg);
}
}
void LMSensorsDriver::initialize_lm_sensors(int* result) {
::sensors_parse_error = LMSensorsDriver::parse_error_call_back;
::sensors_parse_error_wfn = LMSensorsDriver::parse_error_wfn_call_back;
::sensors_fatal_error = LMSensorsDriver::fatal_error_call_back;
if ((*result = ::sensors_init(nullptr)) == 0) {
atexit(::sensors_cleanup);
}
log(TF_DBG) << "Initialized LM sensors." << flush;
}
const ::sensors_chip_name* LMSensorsDriver::find_chip_by_name(
const string& chip_name)
{
int state = 0;
for (;;) {
auto chip = ::sensors_get_detected_chips(nullptr, &state);
if (!chip) break;
if (chip_name == LMSensorsDriver::get_chip_name(*chip)) return chip;
}
throw SystemError("LM sensors chip '" + chip_name + "' was not found");
}
string LMSensorsDriver::get_chip_name(const ::sensors_chip_name& chip) {
int len = sensors_snprintf_chip_name(nullptr, 0, &chip);
if (len < 0) {
const char *msg = ::sensors_strerror(len);
throw SystemError(string("Failed to get LM sensors chip name: ") + msg);
}
std::vector<char> buffer((size_t)(len + 1));
int r = sensors_snprintf_chip_name(buffer.data(), (size_t)(len + 1), &chip);
if (r < 0) {
const char *msg = ::sensors_strerror(r);
throw SystemError(string("Failed to get LM sensors chip name: ") + msg);
} else if (r >= (len + 1)) {
throw SystemError("LM sensors chip name is too long");
}
return string(buffer.data(), r);
}
const ::sensors_feature* LMSensorsDriver::find_feature_by_name(
const ::sensors_chip_name& chip, const string& chip_name,
const string& feature_name)
{
int state = 0;
for (;;) {
auto feature = ::sensors_get_features(&chip, &state);
if (!feature) break;
auto label = ::sensors_get_label(&chip, feature);
bool label_matches = (feature_name == label);
free(label);
if (label_matches) return feature;
}
return nullptr;
}
void LMSensorsDriver::parse_error_call_back(const char *err, int line_no) {
log(TF_ERR) << "LM sensors parsing error: " << err << " in line "
<< std::to_string(line_no);
}
void LMSensorsDriver::parse_error_wfn_call_back(
const char *err, const char *file_name, int line_no)
{
log(TF_ERR) << "LM sensors parsing error: " << err << " in file '"
<< file_name << "' at line " << std::to_string(line_no);
}
void LMSensorsDriver::fatal_error_call_back(const char *proc, const char *err) {
log(TF_ERR) << "LM sensors fatal error in " << proc << ": " << err;
exit(EXIT_FAILURE);
}
void LMSensorsDriver::read_temps_(TemperatureState &global_temps) const {
size_t len = sub_features_.size();
for (size_t i = 0; i < len; ++i) {
auto sub_feature = sub_features_[i];
double real_value = MIN_CELSIUS_TEMP;
int r = ::sensors_get_value(chip_, sub_feature->number, &real_value);
int integer_value;
if (r == 0) {
integer_value = int(real_value) + correction_[i];
} else {
// NOTICE:
// If this happens, then all remaining temperature sources reported
// by the current driver instance are skipped.
//
// Sources of temperatures that are not always available should be
// configured on their own "- chip" entry, and marked optional.
integer_value = MIN_CELSIUS_TEMP;
const char *msg = ::sensors_strerror(r);
throw SystemError(
string("temperature input value of feature '")
+ feature_names_[i] + "' of chip '" + chip_name_
+ "' is unavailable: " + msg);
}
if (integer_value < MIN_CELSIUS_TEMP) {
// Make sure the reported value is physically valid.
integer_value = MIN_CELSIUS_TEMP;
}
global_temps.add_temp(integer_value);
}
}
#endif /* USE_LM_SENSORS */
}

View File

@ -32,6 +32,13 @@
#include <nvidia/gdk/nvml.h>
#endif /* USE_NVML */
#ifdef USE_LM_SENSORS
#include <sensors/sensors.h>
#include <sensors/error.h>
#include <atomic>
#include <mutex>
#endif /* USE_LM_SENSORS */
namespace thinkfan {
class ExpectedError;
@ -141,5 +148,44 @@ private:
#endif /* USE_NVML */
#ifdef USE_LM_SENSORS
class LMSensorsDriver : public SensorDriver {
public:
LMSensorsDriver(string chip_name, std::vector<string> feature_names,
bool optional, std::vector<int> correction = {});
virtual ~LMSensorsDriver();
protected:
virtual void read_temps_(TemperatureState &global_temps) const override;
// LM sensors helpers.
static void ensure_lm_sensors_is_initialized();
static void initialize_lm_sensors(int* result);
static const ::sensors_chip_name* find_chip_by_name(
const string& chip_name);
static const ::sensors_feature* find_feature_by_name(
const ::sensors_chip_name& chip, const string& chip_name,
const string& feature_name);
static string get_chip_name(const ::sensors_chip_name& chip);
// LM sensors call backs.
static void parse_error_call_back(const char *err, int line_no);
static void parse_error_wfn_call_back(const char *err, const char *file_name, int line_no);
static void fatal_error_call_back(const char *proc, const char *err);
private:
const string chip_name_;
const ::sensors_chip_name* chip_;
const std::vector<string> feature_names_;
std::vector<const ::sensors_feature*> features_;
std::vector<const ::sensors_subfeature*> sub_features_;
static std::once_flag lm_sensors_once_init_;
};
#endif /* USE_LM_SENSORS */
}

View File

@ -29,6 +29,10 @@ static const string kw_nvidia("nvml");
#ifdef USE_ATASMART
static const string kw_atasmart("atasmart");
#endif
#ifdef USE_LM_SENSORS
static const string kw_chip("chip");
static const string kw_ids("ids");
#endif
static const string kw_speed("speed");
static const string kw_upper("upper_limit");
static const string kw_lower("lower_limit");
@ -391,6 +395,45 @@ struct convert<wtf_ptr<AtasmartSensorDriver>> {
#endif //USE_ATASMART
#ifdef USE_LM_SENSORS
template<>
struct convert<wtf_ptr<LMSensorsDriver>> {
static bool decode(const Node &node, wtf_ptr<LMSensorsDriver> &sensor)
{
if (!node[kw_chip])
return false;
if (!node[kw_ids]) {
throw YamlError(get_mark_compat(node[kw_ids]), "No temperature inputs were specified.");
}
string chip_name = node[kw_chip].as<string>();
vector<string> feature_names = node[kw_ids].as<vector<string>>();
bool optional = node[kw_optional] ? node[kw_optional].as<bool>() : false;
vector<int> correction;
if (node[kw_correction]) {
correction = node[kw_correction].as<vector<int>>();
}
if (!correction.empty()) {
if (correction.size() != feature_names.size()) {
throw YamlError(
get_mark_compat(node[kw_ids]),
MSG_CONF_CORRECTION_LEN(chip_name, correction.size(), feature_names.size()));
}
}
sensor = make_wtf<LMSensorsDriver>(
chip_name, feature_names, optional, correction);
return true;
}
};
#endif // USE_LM_SENSORS
template<>
struct convert<vector<wtf_ptr<SensorDriver>>> {
@ -419,6 +462,12 @@ struct convert<vector<wtf_ptr<SensorDriver>>> {
sensors.push_back(std::move(tmp));
}
#endif //USE_ATASMART
#ifdef USE_LM_SENSORS
else if ((*it)[kw_chip]) {
wtf_ptr<LMSensorsDriver> tmp = it->as<wtf_ptr<LMSensorsDriver>>();
sensors.push_back(std::move(tmp));
}
#endif // USE_LM_SENSORS
else
throw YamlError(get_mark_compat(*it), "Invalid sensor entry");
}