mirror of
https://github.com/vmatare/thinkfan.git
synced 2024-08-17 07:20:45 +03:00
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:
parent
ab466ddd0e
commit
8a844b96cf
@ -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})
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
183
src/sensors.cpp
183
src/sensors.cpp
@ -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 */
|
||||
|
||||
}
|
||||
|
@ -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 */
|
||||
|
||||
}
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user