| #include "Thresholds.hpp" |
| |
| #include "VariantVisitors.hpp" |
| #include "sensor.hpp" |
| |
| #include <array> |
| #include <boost/algorithm/string/replace.hpp> |
| #include <boost/container/flat_map.hpp> |
| #include <boost/lexical_cast.hpp> |
| #include <cmath> |
| #include <fstream> |
| #include <iostream> |
| #include <memory> |
| #include <stdexcept> |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| static constexpr bool DEBUG = false; |
| namespace thresholds |
| { |
| unsigned int toBusValue(const Level& level) |
| { |
| switch (level) |
| { |
| case (Level::WARNING): |
| { |
| return 0; |
| } |
| case (Level::CRITICAL): |
| { |
| return 1; |
| } |
| default: |
| { |
| return -1; |
| } |
| } |
| } |
| |
| std::string toBusValue(const Direction& direction) |
| { |
| switch (direction) |
| { |
| case (Direction::LOW): |
| { |
| return "less than"; |
| } |
| case (Direction::HIGH): |
| { |
| return "greater than"; |
| } |
| default: |
| { |
| return "err"; |
| } |
| } |
| } |
| |
| bool parseThresholdsFromConfig( |
| const SensorData& sensorData, |
| std::vector<thresholds::Threshold>& thresholdVector, |
| const std::string* matchLabel) |
| { |
| for (const auto& item : sensorData) |
| { |
| if (item.first.find("Thresholds") == std::string::npos) |
| { |
| continue; |
| } |
| if (matchLabel != nullptr) |
| { |
| auto labelFind = item.second.find("Label"); |
| if (labelFind == item.second.end()) |
| continue; |
| if (std::visit(VariantToStringVisitor(), labelFind->second) != |
| *matchLabel) |
| continue; |
| } |
| auto directionFind = item.second.find("Direction"); |
| auto severityFind = item.second.find("Severity"); |
| auto valueFind = item.second.find("Value"); |
| if (valueFind == item.second.end() || |
| severityFind == item.second.end() || |
| directionFind == item.second.end()) |
| { |
| std::cerr << "Malformed threshold in configuration\n"; |
| return false; |
| } |
| Level level; |
| Direction direction; |
| if (std::visit(VariantToUnsignedIntVisitor(), severityFind->second) == |
| 0) |
| { |
| level = Level::WARNING; |
| } |
| else |
| { |
| level = Level::CRITICAL; |
| } |
| if (std::visit(VariantToStringVisitor(), directionFind->second) == |
| "less than") |
| { |
| direction = Direction::LOW; |
| } |
| else |
| { |
| direction = Direction::HIGH; |
| } |
| double val = std::visit(VariantToDoubleVisitor(), valueFind->second); |
| |
| thresholdVector.emplace_back(level, direction, val); |
| } |
| return true; |
| } |
| |
| void persistThreshold(const std::string& path, const std::string& baseInterface, |
| const thresholds::Threshold& threshold, |
| std::shared_ptr<sdbusplus::asio::connection>& conn, |
| size_t thresholdCount, const std::string& labelMatch) |
| { |
| for (size_t ii = 0; ii < thresholdCount; ii++) |
| { |
| std::string thresholdInterface = |
| baseInterface + ".Thresholds" + std::to_string(ii); |
| conn->async_method_call( |
| [&, path, threshold, thresholdInterface, labelMatch]( |
| const boost::system::error_code& ec, |
| const boost::container::flat_map<std::string, BasicVariantType>& |
| result) { |
| if (ec) |
| { |
| return; // threshold not supported |
| } |
| |
| if (!labelMatch.empty()) |
| { |
| auto labelFind = result.find("Label"); |
| if (labelFind == result.end()) |
| { |
| std::cerr << "No label in threshold configuration\n"; |
| return; |
| } |
| std::string label = |
| std::visit(VariantToStringVisitor(), labelFind->second); |
| if (label != labelMatch) |
| { |
| return; |
| } |
| } |
| |
| auto directionFind = result.find("Direction"); |
| auto severityFind = result.find("Severity"); |
| auto valueFind = result.find("Value"); |
| if (valueFind == result.end() || severityFind == result.end() || |
| directionFind == result.end()) |
| { |
| std::cerr << "Malformed threshold in configuration\n"; |
| return; |
| } |
| unsigned int level = std::visit(VariantToUnsignedIntVisitor(), |
| severityFind->second); |
| |
| std::string dir = |
| std::visit(VariantToStringVisitor(), directionFind->second); |
| if ((toBusValue(threshold.level) != level) || |
| (toBusValue(threshold.direction) != dir)) |
| { |
| return; // not the droid we're looking for |
| } |
| |
| std::variant<double> value(threshold.value); |
| conn->async_method_call( |
| [](const boost::system::error_code& ec) { |
| if (ec) |
| { |
| std::cerr << "Error setting threshold " << ec |
| << "\n"; |
| } |
| }, |
| entityManagerName, path, "org.freedesktop.DBus.Properties", |
| "Set", thresholdInterface, "Value", value); |
| }, |
| entityManagerName, path, "org.freedesktop.DBus.Properties", |
| "GetAll", thresholdInterface); |
| } |
| } |
| |
| void updateThresholds(Sensor* sensor) |
| { |
| if (sensor->thresholds.empty()) |
| { |
| return; |
| } |
| |
| for (const auto& threshold : sensor->thresholds) |
| { |
| std::shared_ptr<sdbusplus::asio::dbus_interface> interface; |
| std::string property; |
| if (threshold.level == thresholds::Level::CRITICAL) |
| { |
| interface = sensor->thresholdInterfaceCritical; |
| if (threshold.direction == thresholds::Direction::HIGH) |
| { |
| property = "CriticalHigh"; |
| } |
| else |
| { |
| property = "CriticalLow"; |
| } |
| } |
| else if (threshold.level == thresholds::Level::WARNING) |
| { |
| interface = sensor->thresholdInterfaceWarning; |
| if (threshold.direction == thresholds::Direction::HIGH) |
| { |
| property = "WarningHigh"; |
| } |
| else |
| { |
| property = "WarningLow"; |
| } |
| } |
| else |
| { |
| continue; |
| } |
| if (!interface) |
| { |
| continue; |
| } |
| interface->set_property(property, threshold.value); |
| } |
| } |
| |
| // Debugging counters |
| static int cHiTrue = 0; |
| static int cHiFalse = 0; |
| static int cHiMidstate = 0; |
| static int cLoTrue = 0; |
| static int cLoFalse = 0; |
| static int cLoMidstate = 0; |
| static int cDebugThrottle = 0; |
| |
| static std::vector<std::pair<Threshold, bool>> checkThresholds(Sensor* sensor, |
| double value) |
| { |
| std::vector<std::pair<Threshold, bool>> thresholdChanges; |
| if (sensor->thresholds.empty()) |
| { |
| return thresholdChanges; |
| } |
| |
| for (auto& threshold : sensor->thresholds) |
| { |
| // Use "Schmitt trigger" logic to avoid threshold trigger spam, |
| // if value is noisy while hovering very close to a threshold. |
| // When a threshold is crossed, indicate true immediately, |
| // but require more distance to be crossed the other direction, |
| // before resetting the indicator back to false. |
| if (threshold.direction == thresholds::Direction::HIGH) |
| { |
| if (value >= threshold.value) |
| { |
| thresholdChanges.push_back(std::make_pair(threshold, true)); |
| ++cHiTrue; |
| } |
| else if (value < (threshold.value - sensor->hysteresisTrigger)) |
| { |
| thresholdChanges.push_back(std::make_pair(threshold, false)); |
| ++cHiFalse; |
| } |
| else |
| { |
| ++cHiMidstate; |
| } |
| } |
| else if (threshold.direction == thresholds::Direction::LOW) |
| { |
| if (value <= threshold.value) |
| { |
| thresholdChanges.push_back(std::make_pair(threshold, true)); |
| ++cLoTrue; |
| } |
| else if (value > (threshold.value + sensor->hysteresisTrigger)) |
| { |
| thresholdChanges.push_back(std::make_pair(threshold, false)); |
| ++cLoFalse; |
| } |
| else |
| { |
| ++cLoMidstate; |
| } |
| } |
| else |
| { |
| std::cerr << "Error determining threshold direction\n"; |
| } |
| } |
| |
| if constexpr (DEBUG) |
| { |
| // Throttle debug output, so that it does not continuously spam |
| ++cDebugThrottle; |
| if (cDebugThrottle >= 1000) |
| { |
| cDebugThrottle = 0; |
| std::cerr << "checkThresholds: High T=" << cHiTrue |
| << " F=" << cHiFalse << " M=" << cHiMidstate |
| << ", Low T=" << cLoTrue << " F=" << cLoFalse |
| << " M=" << cLoMidstate << "\n"; |
| } |
| } |
| |
| return thresholdChanges; |
| } |
| |
| bool checkThresholds(Sensor* sensor) |
| { |
| bool status = true; |
| std::vector<std::pair<Threshold, bool>> changes = |
| checkThresholds(sensor, sensor->value); |
| for (const auto& [threshold, asserted] : changes) |
| { |
| assertThresholds(sensor, threshold.level, threshold.direction, |
| asserted); |
| if (threshold.level == thresholds::Level::CRITICAL && asserted) |
| { |
| status = false; |
| } |
| } |
| |
| return status; |
| } |
| |
| void checkThresholdsPowerDelay(Sensor* sensor, ThresholdTimer& thresholdTimer) |
| { |
| |
| std::vector<std::pair<Threshold, bool>> changes = |
| checkThresholds(sensor, sensor->value); |
| for (const auto& [threshold, asserted] : changes) |
| { |
| if (asserted) |
| { |
| thresholdTimer.startTimer(threshold); |
| } |
| else |
| { |
| assertThresholds(sensor, threshold.level, threshold.direction, |
| false); |
| } |
| } |
| } |
| |
| void assertThresholds(Sensor* sensor, thresholds::Level level, |
| thresholds::Direction direction, bool assert) |
| { |
| std::string property; |
| std::shared_ptr<sdbusplus::asio::dbus_interface> interface; |
| if (level == thresholds::Level::WARNING && |
| direction == thresholds::Direction::HIGH) |
| { |
| property = "WarningAlarmHigh"; |
| interface = sensor->thresholdInterfaceWarning; |
| } |
| else if (level == thresholds::Level::WARNING && |
| direction == thresholds::Direction::LOW) |
| { |
| property = "WarningAlarmLow"; |
| interface = sensor->thresholdInterfaceWarning; |
| } |
| else if (level == thresholds::Level::CRITICAL && |
| direction == thresholds::Direction::HIGH) |
| { |
| property = "CriticalAlarmHigh"; |
| interface = sensor->thresholdInterfaceCritical; |
| } |
| else if (level == thresholds::Level::CRITICAL && |
| direction == thresholds::Direction::LOW) |
| { |
| property = "CriticalAlarmLow"; |
| interface = sensor->thresholdInterfaceCritical; |
| } |
| else |
| { |
| std::cerr << "Unknown threshold, level " << level << "direction " |
| << direction << "\n"; |
| return; |
| } |
| if (!interface) |
| { |
| std::cout << "trying to set uninitialized interface\n"; |
| return; |
| } |
| interface->set_property(property, assert); |
| } |
| |
| std::optional<double> readFile(const std::string& thresholdFile, |
| const double& scaleFactor) |
| { |
| std::string line; |
| std::ifstream labelFile(thresholdFile); |
| if (labelFile.good()) |
| { |
| std::getline(labelFile, line); |
| labelFile.close(); |
| |
| try |
| { |
| return std::stod(line) / scaleFactor; |
| } |
| catch (const std::invalid_argument&) |
| { |
| return std::nullopt; |
| } |
| } |
| return std::nullopt; |
| } |
| |
| std::optional<std::tuple<std::string, std::string, std::string>> |
| splitFileName(const std::filesystem::path& filePath) |
| { |
| if (filePath.has_filename()) |
| { |
| const auto fileName = filePath.filename().string(); |
| const std::regex rx(R"((\w+)(\d+)_(.*))"); |
| std::smatch mr; |
| |
| if (std::regex_search(fileName, mr, rx)) |
| { |
| if (mr.size() == 4) |
| { |
| return std::make_optional(std::make_tuple(mr[1], mr[2], mr[3])); |
| } |
| } |
| } |
| return std::nullopt; |
| } |
| |
| bool parseThresholdsFromAttr( |
| std::vector<thresholds::Threshold>& thresholdVector, |
| const std::string& inputPath, const double& scaleFactor, |
| const double& offset) |
| { |
| const boost::container::flat_map< |
| std::string, std::vector<std::tuple<const char*, thresholds::Level, |
| thresholds::Direction, double>>> |
| map = { |
| {"average", |
| { |
| std::make_tuple("average_min", Level::WARNING, Direction::LOW, |
| 0.0), |
| std::make_tuple("average_max", Level::WARNING, Direction::HIGH, |
| 0.0), |
| }}, |
| {"input", |
| { |
| std::make_tuple("min", Level::WARNING, Direction::LOW, 0.0), |
| std::make_tuple("max", Level::WARNING, Direction::HIGH, 0.0), |
| std::make_tuple("lcrit", Level::CRITICAL, Direction::LOW, 0.0), |
| std::make_tuple("crit", Level::CRITICAL, Direction::HIGH, |
| offset), |
| }}, |
| }; |
| |
| if (auto fileParts = splitFileName(inputPath)) |
| { |
| auto [type, nr, item] = *fileParts; |
| if (map.count(item) != 0) |
| { |
| for (const auto& t : map.at(item)) |
| { |
| auto [suffix, level, direction, offset] = t; |
| auto attrPath = |
| boost::replace_all_copy(inputPath, item, suffix); |
| if (auto val = readFile(attrPath, scaleFactor)) |
| { |
| *val += offset; |
| if (DEBUG) |
| { |
| std::cout << "Threshold: " << attrPath << ": " << *val |
| << "\n"; |
| } |
| thresholdVector.emplace_back(level, direction, *val); |
| } |
| } |
| } |
| } |
| return true; |
| } |
| |
| bool hasCriticalInterface( |
| const std::vector<thresholds::Threshold>& thresholdVector) |
| { |
| for (auto& threshold : thresholdVector) |
| { |
| if (threshold.level == Level::CRITICAL) |
| { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool hasWarningInterface( |
| const std::vector<thresholds::Threshold>& thresholdVector) |
| { |
| for (auto& threshold : thresholdVector) |
| { |
| if (threshold.level == Level::WARNING) |
| { |
| return true; |
| } |
| } |
| return false; |
| } |
| } // namespace thresholds |