From a67f56b0ec6994ee1e34d3e38a87c44b2933eeb2 Mon Sep 17 00:00:00 2001 From: Hayden Briese Date: Fri, 14 Apr 2017 14:10:05 +1000 Subject: [PATCH] Fan point temperature no longer require a prefix ('['); fixed fahrenheit temperature support regression; narrowed point search space; changed in-config documentation; fixed suggestions not being outputted; exit failure when launching a competing instance; refactoring; CMakeLists fix; updated README install instructions --- CMakeLists.txt | 2 +- README.md | 15 ++--- TODO.md | 8 +-- src/Config.cpp | 124 ++++++++++++++++++----------------- src/Config.hpp | 16 ++--- src/Controller.cpp | 109 +++++++++++++++--------------- src/Controller.hpp | 16 ++--- src/FanInterface.cpp | 23 ++++--- src/FanInterface.hpp | 7 +- src/main.cpp | 153 ++++++++++++++++++++++--------------------- src/main.hpp | 3 +- 11 files changed, 243 insertions(+), 233 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ce4d963..c794162 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -94,7 +94,7 @@ target_link_libraries(${PROJECT_NAME} ${LIBS}) ## Run lint if debug build option(LINT "Run linter with debug build" OFF) -if (${CMAKE_BUILD_TYPE} STREQUAL "Debug" AND LINT) +if (CMAKE_BUILD_TYPE STREQUAL "Debug" AND LINT) set(LINT_CXX_FLAGS "${CMAKE_CXX_FLAGS_DEBUG} -std=c++${CMAKE_CXX_STANDARD}") set(LINT_CHECKS "*,-clang-diagnostic-unused-command-line-argument,\ -*readability-braces-around-statements,-google-runtime-int") diff --git a/README.md b/README.md index 1ef3ae6..0db3353 100644 --- a/README.md +++ b/README.md @@ -18,31 +18,30 @@ Low overhead and easy, meaningful configuration are the main goals of fancon, th ### Installation +Download latest appropriate release from github.com/hbriese/fancon/releases + ###### Debian/Ubuntu ```sh -$ wget https://github.com/hbriese/fancon/releases/download/0.10.1/fancon_0.10.1_amd64.deb -$ sudo dpkg -i ./fancon_0.10.1_amd64.deb +$ sudo dpkg -i ./fancon*.deb ``` ###### Fedora ```sh -$ wget https://github.com/hbriese/fancon/releases/download/0.10.1/fancon-0.10.1-2.x86_64.rpm -$ sudo yum –nogpgcheck install ./fancon-0.10.1-2.x86_64.rpm +$ sudo yum –nogpgcheck install ./fancon*.rpm ``` ##### Build from source: Tested with both gcc & clang ```sh -$ sudo apt-get install gcc cmake libgcc-6-dev libc6-dev linux-libc-dev libc++-helpers lm-sensors libsensors4-dev libboost-system-dev libboost-filesystem-dev libboost-log-dev libpthread-stubs0-dev libpstreams-dev libsm-dev -$ sudo apt-get install libxnvctrl-dev libx11-dev -$ git clone https://github.com/HBriese/fancon.git && cd fancon +$ sudo apt-get install gcc cmake libgcc-6-dev libc6-dev linux-libc-dev libc++-helpers lm-sensors libsensors4-dev libboost-system-dev libboost-filesystem-dev libboost-log-dev libpthread-stubs0-dev libpstreams-dev +$ git clone https://github.com/hbriese/fancon.git && cd fancon $ mkdir build; cd build && cmake -DCMAKE_BUILD_TYPE=Release .. && make -j && sudo make install ``` | CMake Option | Default | Description | -|------------------|---------|---------------------------------------------------------------------------------------------| +|:-----------------|:-------:| :-------------------------------------------------------------------------------------------| | NVIDIA_SUPPORT | ON | Support for NVIDIA GPUs | | STATIC_LIBSTDC++ | OFF | Statically link libstdc++ - useful for binary distribution | | OPTIMIZE_DEBUG | OFF | Enable compiler optimizations on debug build | diff --git a/TODO.md b/TODO.md index 9ba5df5..27661c6 100644 --- a/TODO.md +++ b/TODO.md @@ -4,7 +4,7 @@ ### Bugs -- Get xauth file & xdisplay from files in /etc/fancon.d/ +- Fan PWM writes fail once after reload ### Features @@ -17,8 +17,8 @@ - Remove fan when control has been lost (maybe??) - Add 'precise' option for more accurate RPM control - Auto-config option - copy current controller configuration -- Remove '[' and ']' requirement from fan point - Check for NVIDIA hardware, and recommend libs for nvidia support +- Update interval as a float ### Performance @@ -36,5 +36,5 @@ ## Watch -- NVML API - several functions missing before it can be used to replace XNVCtrl -- CMake 3.8 release \ No newline at end of file +- NVML: docs.nvidia.com/deploy/nvml-api/change-log.html#change-log for XNVCtrl functionality +- C++17: GCC 7 & Clang 4 \ No newline at end of file diff --git a/src/Config.cpp b/src/Config.cpp index 73cce55..02d1a7b 100644 --- a/src/Config.cpp +++ b/src/Config.cpp @@ -4,99 +4,96 @@ using fancon::InputValue; namespace controller = fancon::controller; namespace fan = fancon::fan; -InputValue::InputValue(string &input, const string &sep, std::function predicate) { - beg = search(input.begin(), input.end(), sep.begin(), sep.end()); - if (beg != input.end()) - beg = next(beg, sep.size()); +// TODO: review beg != end +InputValue::InputValue(string &input, string::iterator &&begin, std::function predicate) + : beg(begin), end(std::find_if_not(beg, input.end(), predicate)), found(beg != input.end() && beg != end) {} - finishConstruction(input, move(predicate)); -} +InputValue::InputValue(string &input, const string &sep, std::function predicate) + : InputValue(input, afterSeperator(input.begin(), input.end(), sep), move(predicate)) {} + +InputValue::InputValue(string &input, const char &sep, std::function predicate) + : InputValue(input, afterSeperator(input.begin(), input.end(), sep), move(predicate)) {} -InputValue::InputValue(string &input, const char &sep, std::function predicate) { - beg = find(input.begin(), input.end(), sep); - if (beg != input.end()) - ++beg; +string::iterator +InputValue::afterSeperator(const string::iterator &&beg, const string::iterator &&end, const char &sep) { + auto ret = find(beg, end, sep); + if (ret != end) + ++ret; - finishConstruction(input, move(predicate)); + return ret; } -void InputValue::finishConstruction(string &input, std::function predicate) { - end = find_if(beg, input.end(), predicate); - found = (beg != input.end() && beg != end); +string::iterator +InputValue::afterSeperator(const string::iterator &&beg, const string::iterator &&end, const string &sep) { + auto ret = search(beg, end, sep.begin(), sep.end()); + if (ret != end) + std::advance(ret, sep.size()); + + return ret; } ostream &controller::operator<<(ostream &os, const controller::Config &c) { using namespace fancon::serialization_constants::controller_config; - os << update_prefix << c.update_interval.count() << ' ' << threads_prefix << c.max_threads - << ' ' << dynamic_prefix << ((c.dynamic) ? "true" : "false"); + os << interval_prefix << c.update_interval.count() << ' ' + << threads_prefix << c.max_threads << ' ' + << dynamic_prefix << ((c.dynamic) ? "true" : "false"); + return os; } istream &controller::operator>>(istream &is, controller::Config &c) { - // Read whole line from istream - std::istreambuf_iterator eos; - string in(std::istreambuf_iterator(is), eos); + string in; + std::getline(is, in); using namespace fancon::serialization_constants::controller_config; - InputValue dynamicVal(in, dynamic_prefix, [](const char &ch) { return !std::isalpha(ch); }); - InputValue updateVal(in, update_prefix, [](const char &ch) { return !std::isdigit(ch); }); - InputValue threadsVal(in, threads_prefix, [](const char &ch) { return !std::isdigit(ch); }); - InputValue updateValDeprecated(in, update_prefix_deprecated, [](const char &ch) { return !std::isdigit(ch); }); + InputValue dynamic(in, dynamic_prefix, ::isalpha); + InputValue interval(in, interval_prefix, ::isdigit); + InputValue threads(in, threads_prefix, ::isdigit); + InputValue update(in, update_prefix_deprecated, ::isdigit); /// <\deprecated Use interval // Fail if no values are found - if (!dynamicVal.found && !updateVal.found && !threadsVal.found) { + if (!dynamic.found && !interval.found && !threads.found && !update.found) { // Set invalid values - see valid() c.update_interval = seconds(0); c.max_threads = 0; return is; } - if (dynamicVal.found) { + if (dynamic.found) { // Convert string to bool string dynamicStr; - dynamicVal.setIfValid(dynamicStr); + dynamic.setIfValid(dynamicStr); c.dynamic = (dynamicStr != "false" || dynamicStr != "0"); } - if (updateVal.found || updateValDeprecated.found) { + if (interval.found || update.found) { // chrono::duration doesn't define operator>> decltype(c.update_interval.count()) update_interval{0}; - if (updateVal.found) - updateVal.setIfValid(update_interval); + if (interval.found) + interval.setIfValid(update_interval); else { - updateValDeprecated.setIfValid(update_interval); + update.setIfValid(update_interval); LOG(llvl::warning) << update_prefix_deprecated << " in " << Util::config_path - << " is deprecated, and WILL BE REMOVED. Use " << update_prefix; + << " is deprecated, and WILL BE REMOVED. Use " << interval_prefix; } if (update_interval > 0) c.update_interval = seconds(update_interval); } - if (threadsVal.found) - threadsVal.setIfValid(c.max_threads); + if (threads.found) + threads.setIfValid(c.max_threads); return is; } -fan::Point &fan::Point::operator=(const fan::Point &other) { - temp = other.temp; - rpm = other.rpm; - pwm = other.pwm; - - return *this; -} - ostream &fan::operator<<(ostream &os, const fan::Point &p) { using namespace fancon::serialization_constants::point; - string rpmOut, pwmOut; - if (p.validRPM()) - rpmOut += rpm_separator + to_string(p.rpm); - if (p.validPWM()) - pwmOut += pwm_separator + to_string(p.pwm); + os << p.temp + << (p.validRPM() ? string() + rpm_separator + to_string(p.rpm) : "") + << (p.validPWM() ? string() + pwm_separator + to_string(p.pwm) : ""); - os << temp_separator << p.temp << rpmOut << pwmOut << end_separator; return os; } @@ -106,28 +103,33 @@ istream &fan::operator>>(istream &is, fan::Point &p) { is >> std::skipws >> in; if (in.empty()) return is; -// std::remove_if(in.begin(), in.end(), [](auto &c) { return isspace(c); }); - auto notDigit = [](const char &c) { return !std::isdigit(c); }; - InputValue tempVal(in, temp_separator, notDigit); - InputValue pwmVal(in, pwm_separator, notDigit); - InputValue rpmVal(in, rpm_separator, notDigit); + InputValue pwm(in, pwm_separator, ::isdigit); + InputValue rpm(in, rpm_separator, ::isdigit); + + // Temp must be before (first of) PWM & RPM + auto &&tempEnd = std::prev((rpm.found && rpm.beg < pwm.beg) ? rpm.beg : pwm.beg); + InputValue temp(in, find_if(in.begin(), tempEnd, ::isdigit), ::isdigit); // Must contain temp, and either a rpm or pwm value - if (!tempVal.found || (!rpmVal.found & !pwmVal.found)) { + if (!temp.found || (!rpm.found & !pwm.found)) { LOG(llvl::error) << "Invalid fan config: " << in; return is; } - if (tempVal.found) - tempVal.setIfValid(p.temp); + // Set values if they are found & valid + if (temp.found) { + temp.setIfValid(p.temp); + if (temp.end != in.end() && std::tolower(*temp.end) == fahrenheit) + p.temp = static_cast((p.temp - 32) / 1.8); + } - if (pwmVal.found) - pwmVal.setIfValid(p.pwm); - else if (rpmVal.found) { - rpmVal.setIfValid(p.rpm); - if (rpmVal.end != in.end()) - p.is_rpm_percent = (std::tolower(*rpmVal.end) == percent); + if (pwm.found) + pwm.setIfValid(p.pwm); + else if (rpm.found) { + rpm.setIfValid(p.rpm); + if (rpm.end != in.end()) + p.is_rpm_percent = (std::tolower(*rpm.end) == percent); } return is; diff --git a/src/Config.hpp b/src/Config.hpp index 52c4468..b0a3f5f 100644 --- a/src/Config.hpp +++ b/src/Config.hpp @@ -29,11 +29,12 @@ using temp_t = int; class InputValue { public: + InputValue(string &input, string::iterator &&begin, std::function predicate); InputValue(string &input, const string &sep, std::function predicate); InputValue(string &input, const char &sep, std::function predicate); - string::iterator beg, end; - bool found; + const string::iterator beg, end; + const bool found; template void setIfValid(T &value) { @@ -48,7 +49,8 @@ class InputValue { } private: - void finishConstruction(string &input, std::function predicate); + string::iterator afterSeperator(const string::iterator &&beg, const string::iterator &&end, const char &sep); + string::iterator afterSeperator(const string::iterator &&beg, const string::iterator &&end, const string &sep); }; namespace controller { @@ -96,8 +98,6 @@ class Point { Point(temp_t temp = 0, rpm_t rpm = (rpm_min_abs - 1), pwm_t pwm = (pwm_min_abs - 1), bool isRpmPercent = false) : temp(temp), rpm(rpm), pwm(pwm), is_rpm_percent(isRpmPercent) {} - Point &operator=(const Point &other); - temp_t temp; rpm_t rpm; // TODO: C++17: replace with std::variant (tagged union) pwm_t pwm; @@ -137,17 +137,15 @@ namespace serialization_constants { // TODO: Review name namespace controller_config { const string dynamic_prefix = "dynamic=", - update_prefix = "interval=", + interval_prefix = "interval=", threads_prefix = "threads="; -const string update_prefix_deprecated = "update="; /// <\deprecated Use update_prefix // TODO: remove 08/17 +const string update_prefix_deprecated = "update="; /// <\deprecated Use interval_prefix // TODO: remove 08/17 } namespace point { constexpr const char - temp_separator = '[', // TODO: Rename, and refactor rpm_separator = ':', pwm_separator = ';', - end_separator = ']', fahrenheit = 'f', percent = '%'; } diff --git a/src/Controller.cpp b/src/Controller.cpp index 3241a6a..d2253d9 100644 --- a/src/Controller.cpp +++ b/src/Controller.cpp @@ -4,7 +4,7 @@ using namespace fancon; namespace fancon { -ControllerState controller_state; +ControllerState controller_state{ControllerState::stop}; } /// \param configPath Path to a file containing the ControllerConfig and user fan configurations @@ -85,38 +85,26 @@ ControllerState Controller::run() { sigaction(s, &act, nullptr); // Start sensor & fan threads defered - to avoid data races - controller_state = ControllerState::defered_start; - - auto sensorThreadTasks2 = Util::distributeTasks(conf.max_threads, sensors); - for (auto &tasks : sensorThreadTasks2) - threads.emplace_back(thread([this, &tasks] { - deferStart(sensors_wakeup); - readSensors(tasks); - })); - - auto fanThreadTasks2 = Util::distributeTasks(conf.max_threads, fans); - for (auto &tasks : fanThreadTasks2) - threads.emplace_back(thread([this, &tasks] { - deferStart(fans_wakeup); - updateFans(tasks); - })); + vector threads; + + auto sensorTasks = Util::distributeTasks(conf.max_threads, sensors); + for (auto &tasks : sensorTasks) + threads.emplace_back(thread(&Controller::updateSensors, this, std::ref(tasks))); + + auto fanTasks = Util::distributeTasks(conf.max_threads, fans); + for (auto &tasks : fanTasks) + threads.emplace_back(thread(&Controller::updateFans, this, std::ref(tasks))); threads.shrink_to_fit(); - LOG(llvl::debug) << "Started with " << threads.size() << " threads"; + LOG(llvl::debug) << "Started with " << (threads.size() + 1) << " threads"; // Synchronize thread wake-up times - startThreads(); - while (controller_state == ControllerState::run) { - updateWakeupTimes(); - sleep_until(main_wakeup); - } + syncWakeups(); for (auto &t : threads) if (t.joinable()) t.join(); - else - LOG(llvl::debug) << "Unable to join thread ID: " << t.get_id(); return controller_state; } @@ -126,30 +114,9 @@ void Controller::reload(const string &configPath) { *this = Controller(configPath); } -void Controller::signalHandler(int sig) { - switch (sig) { - case SIGTERM: - case SIGINT: - case SIGABRT: controller_state = ControllerState::stop; - break; - case SIGHUP: controller_state = ControllerState::reload; - break; - default: LOG(llvl::warning) << "Unknown signal caught (" << sig << "): " << strsignal(sig); - } -} - -/// \return True if the line doesn't start with '#' and isn't just spaces/tabs -bool Controller::validConfigLine(const string &line) { - // Skip spaces, tabs & whitespace - auto beg = find_if(line.begin(), line.end(), [](const char &c) { return !std::isspace(c); }); +void Controller::updateSensors(vector &sensors) { + deferStart(sensors_wakeup); - if (beg == line.end()) - return false; - else - return *beg != '#'; -} - -void Controller::readSensors(vector &sensors) { while (controller_state == ControllerState::run) { for (auto &it : sensors) (*it)->refresh(); @@ -159,6 +126,8 @@ void Controller::readSensors(vector &sensors) { } void Controller::updateFans(vector &fans) { + deferStart(fans_wakeup); + while (controller_state == ControllerState::run) { // Update fan speed if sensor's temperature has changed for (auto &it : fans) @@ -169,22 +138,52 @@ void Controller::updateFans(vector &fans) { } } -void Controller::startThreads() { +void Controller::syncWakeups() { + // Start threads main_wakeup = chrono::time_point_cast(chrono::steady_clock::now()); + updateWakeups(); controller_state = ControllerState::run; - updateWakeupTimes(); + + while (controller_state == ControllerState::run) { + updateWakeups(); + sleep_until(main_wakeup); + } } -void Controller::updateWakeupTimes() { - main_wakeup += conf.update_interval; - sensors_wakeup = main_wakeup + milliseconds(20); - fans_wakeup = sensors_wakeup + milliseconds(100); +/// \brief Sets controller_state if appropriate signals are recieved +void Controller::signalHandler(int sig) { + switch (sig) { + case SIGTERM: + case SIGINT: + case SIGABRT: controller_state = ControllerState::stop; + break; + case SIGHUP: controller_state = ControllerState::reload; + break; + default: LOG(llvl::warning) << "Unknown signal caught (" << sig << "): " << strsignal(sig); + } +} + +/// \return True if the line doesn't start with '#' and isn't just spaces/tabs +bool Controller::validConfigLine(const string &line) { + // Skip spaces, tabs & whitespace + auto beg = find_if(line.begin(), line.end(), [](const char &c) { return !std::isspace(c); }); + + if (beg == line.end()) + return false; + else + return *beg != '#'; } /// \brief Lock while start is defered, then sleep until the given time point void Controller::deferStart(chrono::time_point &timePoint) { - while (controller_state == ControllerState::defered_start) - sleep_for(milliseconds(1)); + while (controller_state != ControllerState::run) + sleep_for(milliseconds(10)); sleep_until(timePoint); +} + +void Controller::updateWakeups() { + main_wakeup += conf.update_interval; + sensors_wakeup = main_wakeup + milliseconds(20); + fans_wakeup = sensors_wakeup + milliseconds(100); } \ No newline at end of file diff --git a/src/Controller.hpp b/src/Controller.hpp index 19febda..f3619f8 100644 --- a/src/Controller.hpp +++ b/src/Controller.hpp @@ -21,7 +21,7 @@ using sensor_container_t = vector>; using fan_container_t = vector; enum class ControllerState { - run, stop = SIGTERM, reload = SIGHUP, defered_start + run, stop = SIGTERM, reload = SIGHUP } extern controller_state; class Controller { @@ -29,7 +29,6 @@ class Controller { Controller(const string &configPath); controller::Config conf; - vector threads; chrono::time_point main_wakeup, sensors_wakeup, fans_wakeup; @@ -39,18 +38,17 @@ class Controller { ControllerState run(); void reload(const string &configPath); - static void signalHandler(int sig); + void updateSensors(vector &sensors); + void updateFans(vector &fans); + void syncWakeups(); + + static void signalHandler(int sig); static bool validConfigLine(const string &line); private: - void readSensors(vector &sensors); - void updateFans(vector &fans); - - void startThreads(); - void updateWakeupTimes(); - void deferStart(chrono::time_point &timePoint); + void updateWakeups(); }; struct MappedFan { diff --git a/src/FanInterface.cpp b/src/FanInterface.cpp index c911687..d492558 100644 --- a/src/FanInterface.cpp +++ b/src/FanInterface.cpp @@ -25,6 +25,7 @@ FanInterface::FanInterface(const UID &uid, const fan::Config &c, bool dynamic, // Removed invalid points & sort by temperature verifyPoints(uid); std::sort(points.begin(), points.end(), [](const Point &lhs, const Point &rhs) { return lhs.temp < rhs.temp; }); + prev_it = points.begin(); points.shrink_to_fit(); } @@ -41,14 +42,20 @@ bool FanInterface::recoverControl(const string &deviceLabel) { } void FanInterface::update(const temp_t temp) { - // lower_bound: first element that is greater-or-equal; can return end - auto it = std::lower_bound(points.begin(), points.end(), temp, - [](const Point &p1, const Point &p2) { return p1.temp < p2.temp; }); + auto comparePoints = [](const Point &p1, const Point &p2) { return p1.temp < p2.temp; }; + decltype(points)::iterator + it; - // Find the element that is less-or-equal (unless last point) - previous element - bool previouslyEnd = (it == points.end()); - if (previouslyEnd || (it->temp != temp && it != points.begin())) - it = prev(it); + // Narrow search space by comparing to the previous iterator + if (temp < prev_it->temp) + it = std::upper_bound(points.begin(), prev_it, temp, comparePoints); + else + it = std::upper_bound(prev_it, points.end(), temp, comparePoints); + + // std::upper_bound returns element greater-than + // Therefore the previous element is less-or-equal (unless last point) + if (it != points.begin()) + std::advance(it, -1); pwm_t newPwm{it->pwm}; decltype(it) nextIt; @@ -57,7 +64,7 @@ void FanInterface::update(const temp_t temp) { // Start fan if stopped, or calculate dynamic if (readRPM() == 0) newPwm = pwm_start; - else if (dynamic && (!previouslyEnd && (nextIt = next(it)) != points.end())) // Can't be highest element + else if (dynamic && (nextIt = next(it)) != points.end()) // Can't be highest element newPwm += ((nextIt->pwm - newPwm) / (nextIt->temp - it->temp)); } diff --git a/src/FanInterface.hpp b/src/FanInterface.hpp index ca6d887..a6ff41d 100644 --- a/src/FanInterface.hpp +++ b/src/FanInterface.hpp @@ -35,12 +35,17 @@ class FanInterface { virtual ~FanInterface() {} vector points; + decltype(points)::iterator + prev_it; bool tested = false; // Characteristic variables written rpm_t rpm_min, rpm_max; // TODO: remove with testPWM pwm_t pwm_min, pwm_start; // ^^ milliseconds wait_time; + const enable_mode_t manual_enable_mode; + enable_mode_t driver_enable_mode; + // TODO: Functions don't need to be public virtual rpm_t readRPM() = 0; virtual rpm_t readPWM() = 0; @@ -54,8 +59,6 @@ class FanInterface { static void writeTestResult(const UID &uid, const FanTestResult &result, DeviceType devType); protected: - const enable_mode_t manual_enable_mode; - enable_mode_t driver_enable_mode; const int hw_id; const string hw_id_str; diff --git a/src/main.cpp b/src/main.cpp index 1a6494f..cd1bb92 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -60,7 +60,7 @@ void f::suggestions(const char *fanconDir, const char *configPath) { } LOG(llvl::info) << bold << setw(maxSize) << std::setfill('-') << left << '-' << '\n' - << resetFont << messages.rdbuf() << resetFont + << resetFont << messages.str() << resetFont << bold << setw(maxSize) << std::setfill('-') << left << '-' << '\n' << resetFont; } } @@ -121,71 +121,12 @@ void f::listSensors() { LOG(llvl::info) << ss.rdbuf(); } -void f::testFans(uint testRetries, bool singleThreaded) { - umask(default_umask); - appendConfig(config_path); - - auto fanUIDs = Find::getFanUIDs(); - if (fanUIDs.empty()) { - LOG(llvl::warning) << "No fans were detected, try running 'sudo sensors-detect' first"; - return; - } - - if (!exists(Util::fancon_dir)) - create_directory(Util::fancon_dir); - - LOG(llvl::info) << "Starting tests. This may take some time (to ensure accurate results)"; - vector threads; - for (auto it = fanUIDs.begin(); it != fanUIDs.end(); ++it) { - auto dir = Util::getDir(to_string(it->hw_id), it->type); - if (!exists(dir)) - create_directory(dir); - - threads.emplace_back(thread(&f::testFan, std::ref(*it), Find::getFan(*it), testRetries)); - - if (singleThreaded) { - threads.back().join(); - threads.pop_back(); - } - } - - // Rejoin threads - for (auto &t : threads) - if (t.joinable()) - t.join(); - else - t.detach(); -} - -void f::testFan(const UID &uid, unique_ptr &&fan, uint retries) { - FanTestResult res; - stringstream ss; - for (; retries > 0; --retries) { - res = fan->test(); - - if (res.testable() && res.valid()) { - FanInterface::writeTestResult(uid, res, uid.type); - ss << uid << " passed"; - break; - } else if (uid.type == DeviceType::fan_nv) - LOG(llvl::error) - << "NVIDIA manual fan control coolbit is not set. " - << "Please run 'sudo nvidia-xconfig --cool-bits=4', restart your X server (or reboot) and retry test"; - } - - if (retries <= 0) - ss << uid << ((res.testable()) ? " results are invalid, consider running with more --retries (default is 4)" - : " cannot be tested, driver unresponsive"); - LOG(llvl::info) << ss.rdbuf(); -} - -/// \bug ifstream failbit, or badbit is set during read, consequently reporting a fail to the user void f::appendConfig(const string &path) { umask(default_umask); std::ifstream ifs(path); auto allUIDs = Find::getFanUIDs(); - vector existingUIDs; + vector existingUIDs; bool pExists = exists(path); bool controllerConfigFound = false; @@ -220,23 +161,26 @@ void f::appendConfig(const string &path) { auto writeTop = [](ostream &os) { os << "# Interval is the seconds between fan speed changes\n" - << "# Dynamic enables interpolation between points, e.g. [30:20%] [40:30%], @ 35°C, RPM is 25%\n" + << "# Dynamic enables interpolation between points, e.g. 30:20% 40:30%, @ 35°C, RPM is 25%\n" << controller::Config() << "\n\n" << "# Missing 's are appended with 'fancon write-config' or manually with 'fancon list-fans'\n" - << "# 's can be enumerated with 'fancon list-sensors'\n" - << "# 's are comprised of one or more points\n" - << "# Point syntax: [TEMP (f) :RPM (%) ;PWM] - 'f' & '%' are optional\n" + << "# s can be enumerated with 'fancon list-sensors'\n" + << "# s are comprised of one or more points\n" + << "#\n" + << "# Note. 'fancon test' MUST be run for RPM & percentage speed control\n" + << "# Point syntax: Temperature(f):RPM(%);PWM\n" + << "# REQUIRED: Temperature, and RPM or PWM\n" + << "# OPTIONAL: 'f' for temperature in fahrenheit\n" + << "# '%' for percentage of max RPM control - 1% being the slowest spinning speed\n" << "#\n" << "# Example:\n" - << "# it8728/2:fan1 coretemp/0:temp2 [0:0%] [86f:10%] [45:30%] [55:1000] [65:1200] [90:100%]\n" - << "# [0:0%] (or [0;0]) -> fan stopped below 30°C\n" - << "# [86f:10%] (or [30:10%]) -> 10% of max fan speed @ 30°C (86 fahrenheit)\n" - << "# [65:1200] (or [65;180]) -> 1200 RPM @ 65°C -- where 1200 RPM is reached at 180 PWM\n" - << "# [90:100%] (or [90;255]) -> max fan speed @ 90°C\n" + << "# it8728/2:fan1 coretemp/0:temp2 0:0% 25:10% 113f:30% 55:50% 65:1000 80:1400 190:100%\n" + << "# 0:0% (or 32f;0) -> fan stopped below 25°C\n" + << "# 25:10% (or 77f:5%) -> fan starts -- 10% of max speed @ 25°C (77 fahrenheit)\n" + << "# 113f:30% (or 45:10%) -> 30% @ 30°C (113 fahrenheit)\n" + << "# 65:1000 (or 65;180) -> 1000 RPM @ 65°C -- where 1000 RPM is reached at 180 PWM\n" + << "# 90:100% (or 90;255) -> 100% (max) @ 90°C\n" << "#\n" - << "# 'fancon test' MUST be run before use of RPM for speed control. PWM control can still be used without\n" - << "# Append 'f' for temperature in fahrenheit e.g. [86f:10%]\n" - << "# '%' for percentage of max RPM control - 1% being the slowest speed whilst still spinning\n\n" << "# \n"; }; @@ -255,6 +199,64 @@ void f::appendConfig(const string &path) { LOG(llvl::error) << "Failed to write config: " << path; } +void f::testFans(uint testRetries, bool singleThreaded) { + umask(default_umask); + appendConfig(config_path); + + auto fanUIDs = Find::getFanUIDs(); + if (fanUIDs.empty()) { + LOG(llvl::warning) << "No fans were detected, try running 'sudo sensors-detect' first"; + return; + } + + if (!exists(Util::fancon_dir)) + create_directory(Util::fancon_dir); + + LOG(llvl::info) << "Starting tests. This may take some time (to ensure accurate results)"; + vector threads; + for (auto it = fanUIDs.begin(); it != fanUIDs.end(); ++it) { + auto dir = Util::getDir(to_string(it->hw_id), it->type); + if (!exists(dir)) + create_directory(dir); + + threads.emplace_back(thread(&f::testFan, std::ref(*it), Find::getFan(*it), testRetries)); + + if (singleThreaded) { + threads.back().join(); + threads.pop_back(); + } + } + + // Rejoin threads + for (auto &t : threads) + if (t.joinable()) + t.join(); + else + t.detach(); +} + +void f::testFan(const UID &uid, unique_ptr &&fan, uint retries) { + FanTestResult res; + stringstream ss; + for (; retries > 0; --retries) { + res = fan->test(); + + if (res.testable() && res.valid()) { + FanInterface::writeTestResult(uid, res, uid.type); + ss << uid << " passed"; + break; + } else if (uid.type == DeviceType::fan_nv) + LOG(llvl::error) + << "NVIDIA manual fan control coolbit is not set. " + << "Please run 'sudo nvidia-xconfig --cool-bits=4', restart your X server (or reboot) and retry test"; + } + + if (retries <= 0) + ss << uid << ((res.testable()) ? " results are invalid, consider running with more --retries (default is 4)" + : " cannot be tested, driver unresponsive"); + LOG(llvl::info) << ss.rdbuf(); +} + void f::start(const bool fork_) { if (fork_) { LOG(llvl::info) << "Starting fancond forked; see 'fancon -help' for logging info"; @@ -416,9 +418,10 @@ int main(int argc, char *argv[]) { const auto &com = c.get(); // Command requirements must be met before running - if (com.lock && !Util::try_lock()) + if (com.lock && !Util::try_lock()) { LOG(llvl::warning) << "A fancon process is already running\n"; - else if (com.require_root && getuid() != 0) + exit(EXIT_FAILURE); // Exiting failing so init system does not restart + } else if (com.require_root && getuid() != 0) LOG(llvl::error) << "Please run with sudo, or as root for command: " << com.name << '\n'; else com.func(); diff --git a/src/main.hpp b/src/main.hpp index bfdedfe..7ea279a 100644 --- a/src/main.hpp +++ b/src/main.hpp @@ -41,9 +41,10 @@ constexpr size_t strlength(const char *s) { return (*s == 0) ? 0 : strlength(s + void listFans(); void listSensors(); +void appendConfig(const string &configPath); + void testFans(uint testRetries, bool singleThreaded); void testFan(const UID &uid, unique_ptr &&fan, uint retries); -void appendConfig(const string &configPath); void start(const bool fork); void sendSignal(ControllerState state);