|  | #include "Thresholds.hpp" | 
|  |  | 
|  | #include "Utils.hpp" | 
|  | #include "VariantVisitors.hpp" | 
|  | #include "sensor.hpp" | 
|  |  | 
|  | #include <boost/algorithm/string/replace.hpp> | 
|  | #include <boost/asio/error.hpp> | 
|  | #include <boost/asio/steady_timer.hpp> | 
|  | #include <boost/container/flat_map.hpp> | 
|  | #include <phosphor-logging/lg2.hpp> | 
|  | #include <sdbusplus/asio/connection.hpp> | 
|  | #include <sdbusplus/asio/object_server.hpp> | 
|  | #include <sdbusplus/exception.hpp> | 
|  | #include <sdbusplus/message.hpp> | 
|  |  | 
|  | #include <array> | 
|  | #include <chrono> | 
|  | #include <cstddef> | 
|  | #include <cstdint> | 
|  | #include <limits> | 
|  | #include <memory> | 
|  | #include <string> | 
|  | #include <tuple> | 
|  | #include <utility> | 
|  | #include <variant> | 
|  | #include <vector> | 
|  |  | 
|  | static constexpr bool debug = false; | 
|  | namespace thresholds | 
|  | { | 
|  | Level findThresholdLevel(uint8_t sev) | 
|  | { | 
|  | for (const ThresholdDefinition& prop : thresProp) | 
|  | { | 
|  | if (prop.sevOrder == sev) | 
|  | { | 
|  | return prop.level; | 
|  | } | 
|  | } | 
|  | return Level::ERROR; | 
|  | } | 
|  |  | 
|  | Direction findThresholdDirection(const std::string& direct) | 
|  | { | 
|  | if (direct == "greater than") | 
|  | { | 
|  | return Direction::HIGH; | 
|  | } | 
|  | if (direct == "less than") | 
|  | { | 
|  | return Direction::LOW; | 
|  | } | 
|  | return Direction::ERROR; | 
|  | } | 
|  |  | 
|  | bool parseThresholdsFromConfig( | 
|  | const SensorData& sensorData, | 
|  | std::vector<thresholds::Threshold>& thresholdVector, | 
|  | const std::string* matchLabel, const int* sensorIndex) | 
|  | { | 
|  | for (const auto& [intf, cfg] : sensorData) | 
|  | { | 
|  | if (intf.find("Thresholds") == std::string::npos) | 
|  | { | 
|  | continue; | 
|  | } | 
|  | if (matchLabel != nullptr) | 
|  | { | 
|  | auto labelFind = cfg.find("Label"); | 
|  | if (labelFind == cfg.end()) | 
|  | { | 
|  | continue; | 
|  | } | 
|  | if (std::visit(VariantToStringVisitor(), labelFind->second) != | 
|  | *matchLabel) | 
|  | { | 
|  | continue; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (sensorIndex != nullptr) | 
|  | { | 
|  | auto indexFind = cfg.find("Index"); | 
|  |  | 
|  | // If we're checking for index 1, a missing Index is OK. | 
|  | if ((indexFind == cfg.end()) && (*sensorIndex != 1)) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if ((indexFind != cfg.end()) && | 
|  | (std::visit(VariantToIntVisitor(), indexFind->second) != | 
|  | *sensorIndex)) | 
|  | { | 
|  | continue; | 
|  | } | 
|  | } | 
|  |  | 
|  | double hysteresis = std::numeric_limits<double>::quiet_NaN(); | 
|  | auto hysteresisFind = cfg.find("Hysteresis"); | 
|  | if (hysteresisFind != cfg.end()) | 
|  | { | 
|  | hysteresis = | 
|  | std::visit(VariantToDoubleVisitor(), hysteresisFind->second); | 
|  | } | 
|  |  | 
|  | auto directionFind = cfg.find("Direction"); | 
|  | auto severityFind = cfg.find("Severity"); | 
|  | auto valueFind = cfg.find("Value"); | 
|  | if (valueFind == cfg.end() || severityFind == cfg.end() || | 
|  | directionFind == cfg.end()) | 
|  | { | 
|  | lg2::error( | 
|  | "Malformed threshold on configuration interface: '{INTERFACE}'", | 
|  | "INTERFACE", intf); | 
|  | return false; | 
|  | } | 
|  | unsigned int severity = | 
|  | std::visit(VariantToUnsignedIntVisitor(), severityFind->second); | 
|  |  | 
|  | std::string directions = | 
|  | std::visit(VariantToStringVisitor(), directionFind->second); | 
|  |  | 
|  | Level level = findThresholdLevel(severity); | 
|  | Direction direction = findThresholdDirection(directions); | 
|  |  | 
|  | if ((level == Level::ERROR) || (direction == Direction::ERROR)) | 
|  | { | 
|  | continue; | 
|  | } | 
|  | double val = std::visit(VariantToDoubleVisitor(), valueFind->second); | 
|  |  | 
|  | thresholdVector.emplace_back(level, direction, val, hysteresis); | 
|  | } | 
|  | 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 SensorBaseConfigMap& result) { | 
|  | if (ec) | 
|  | { | 
|  | return; // threshold not supported | 
|  | } | 
|  |  | 
|  | if (!labelMatch.empty()) | 
|  | { | 
|  | auto labelFind = result.find("Label"); | 
|  | if (labelFind == result.end()) | 
|  | { | 
|  | lg2::error("No label in threshold configuration"); | 
|  | 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()) | 
|  | { | 
|  | lg2::error("Malformed threshold in configuration"); | 
|  | return; | 
|  | } | 
|  | unsigned int severity = std::visit( | 
|  | VariantToUnsignedIntVisitor(), severityFind->second); | 
|  |  | 
|  | std::string dir = | 
|  | std::visit(VariantToStringVisitor(), directionFind->second); | 
|  | if ((findThresholdLevel(severity) != threshold.level) || | 
|  | (findThresholdDirection(dir) != threshold.direction)) | 
|  | { | 
|  | 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) | 
|  | { | 
|  | lg2::error( | 
|  | "Error setting threshold: '{ERROR_MESSAGE}'", | 
|  | "ERROR_MESSAGE", ec.message()); | 
|  | } | 
|  | }, | 
|  | entityManagerName, path, "org.freedesktop.DBus.Properties", | 
|  | "Set", thresholdInterface, "Value", value); | 
|  | }, | 
|  | entityManagerName, path, "org.freedesktop.DBus.Properties", | 
|  | "GetAll", thresholdInterface); | 
|  | } | 
|  | } | 
|  |  | 
|  | void updateThresholds(Sensor* sensor) | 
|  | { | 
|  | for (const auto& threshold : sensor->thresholds) | 
|  | { | 
|  | std::shared_ptr<sdbusplus::asio::dbus_interface> interface = | 
|  | sensor->getThresholdInterface(threshold.level); | 
|  |  | 
|  | if (!interface) | 
|  | { | 
|  | continue; | 
|  | } | 
|  |  | 
|  | std::string property = | 
|  | Sensor::propertyLevel(threshold.level, threshold.direction); | 
|  | if (property.empty()) | 
|  | { | 
|  | 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 constexpr int assertLogCount = 10; | 
|  |  | 
|  | struct ChangeParam | 
|  | { | 
|  | ChangeParam(Threshold whichThreshold, bool status, double value) : | 
|  | threshold(whichThreshold), asserted(status), assertValue(value) | 
|  | {} | 
|  |  | 
|  | Threshold threshold; | 
|  | bool asserted; | 
|  | double assertValue; | 
|  | }; | 
|  |  | 
|  | static std::vector<ChangeParam> checkThresholds(Sensor* sensor, double value) | 
|  | { | 
|  | std::vector<ChangeParam> 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.emplace_back(threshold, true, value); | 
|  | if (++cHiTrue < assertLogCount) | 
|  | { | 
|  | lg2::info( | 
|  | "Sensor name: {NAME}, high threshold: {THRESHOLD}, " | 
|  | "assert value: {VALUE}, raw data: {RAW_DATA}", | 
|  | "NAME", sensor->name, "THRESHOLD", threshold.value, | 
|  | "VALUE", value, "RAW_DATA", sensor->rawValue); | 
|  | } | 
|  | } | 
|  | else if (value < (threshold.value - threshold.hysteresis)) | 
|  | { | 
|  | thresholdChanges.emplace_back(threshold, false, value); | 
|  | ++cHiFalse; | 
|  | } | 
|  | else | 
|  | { | 
|  | ++cHiMidstate; | 
|  | } | 
|  | } | 
|  | else if (threshold.direction == thresholds::Direction::LOW) | 
|  | { | 
|  | if (value <= threshold.value) | 
|  | { | 
|  | thresholdChanges.emplace_back(threshold, true, value); | 
|  | if (++cLoTrue < assertLogCount) | 
|  | { | 
|  | lg2::info( | 
|  | "Sensor name: {NAME}, low threshold: {THRESHOLD}, " | 
|  | "assert value: {VALUE}, raw data: {RAW_DATA}", | 
|  | "NAME", sensor->name, "THRESHOLD", threshold.value, | 
|  | "VALUE", value, "RAW_DATA", sensor->rawValue); | 
|  | } | 
|  | } | 
|  | else if (value > (threshold.value + threshold.hysteresis)) | 
|  | { | 
|  | thresholdChanges.emplace_back(threshold, false, value); | 
|  | ++cLoFalse; | 
|  | } | 
|  | else | 
|  | { | 
|  | ++cLoMidstate; | 
|  | } | 
|  | } | 
|  | else | 
|  | { | 
|  | lg2::error("Error determining threshold direction"); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Throttle debug output, so that it does not continuously spam | 
|  | ++cDebugThrottle; | 
|  | if (cDebugThrottle >= 1000) | 
|  | { | 
|  | cDebugThrottle = 0; | 
|  | if constexpr (debug) | 
|  | { | 
|  | lg2::error("checkThresholds: High T= {HIGH_TRUE}, F= {HIGH_FALSE}," | 
|  | " M= {HIGH_MIDSTATE}, Low T= {LOW_TRUE}, F= {LOW_FALSE}," | 
|  | " M= {LOW_MIDSTATE}", | 
|  | "HIGH_TRUE", cHiTrue, "HIGH_FALSE", cHiFalse, | 
|  | "HIGH_MIDSTATE", cHiMidstate, "LOW_TRUE", cLoTrue, | 
|  | "LOW_FALSE", cLoFalse, "LOW_MIDSTATE", cLoMidstate); | 
|  | } | 
|  | } | 
|  |  | 
|  | return thresholdChanges; | 
|  | } | 
|  |  | 
|  | void ThresholdTimer::startTimer(const std::weak_ptr<Sensor>& weakSensor, | 
|  | const Threshold& threshold, bool assert, | 
|  | double assertValue) | 
|  | { | 
|  | struct TimerUsed timerUsed = {}; | 
|  | constexpr const size_t waitTime = 5; | 
|  | TimerPair* pair = nullptr; | 
|  |  | 
|  | for (TimerPair& timer : timers) | 
|  | { | 
|  | if (!timer.first.used) | 
|  | { | 
|  | pair = &timer; | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (pair == nullptr) | 
|  | { | 
|  | pair = &timers.emplace_back(timerUsed, boost::asio::steady_timer(io)); | 
|  | } | 
|  |  | 
|  | pair->first.used = true; | 
|  | pair->first.level = threshold.level; | 
|  | pair->first.direction = threshold.direction; | 
|  | pair->first.assert = assert; | 
|  | pair->second.expires_after(std::chrono::seconds(waitTime)); | 
|  | pair->second.async_wait([weakSensor, pair, threshold, assert, | 
|  | assertValue](boost::system::error_code ec) { | 
|  | auto sensorPtr = weakSensor.lock(); | 
|  | if (!sensorPtr) | 
|  | { | 
|  | return; // owner sensor has been destructed | 
|  | } | 
|  | // pair is valid as long as sensor is valid | 
|  | pair->first.used = false; | 
|  |  | 
|  | if (ec == boost::asio::error::operation_aborted) | 
|  | { | 
|  | return; // we're being canceled | 
|  | } | 
|  | if (ec) | 
|  | { | 
|  | lg2::error("timer error: '{ERROR_MESSAGE}'", "ERROR_MESSAGE", | 
|  | ec.message()); | 
|  | return; | 
|  | } | 
|  | if (sensorPtr->readingStateGood()) | 
|  | { | 
|  | assertThresholds(sensorPtr.get(), assertValue, threshold.level, | 
|  | threshold.direction, assert); | 
|  | } | 
|  | }); | 
|  | } | 
|  |  | 
|  | bool checkThresholds(Sensor* sensor) | 
|  | { | 
|  | bool status = true; | 
|  | std::vector<ChangeParam> changes = checkThresholds(sensor, sensor->value); | 
|  | for (const auto& change : changes) | 
|  | { | 
|  | assertThresholds(sensor, change.assertValue, change.threshold.level, | 
|  | change.threshold.direction, change.asserted); | 
|  | if (change.threshold.level == thresholds::Level::CRITICAL && | 
|  | change.asserted) | 
|  | { | 
|  | status = false; | 
|  | } | 
|  | } | 
|  |  | 
|  | return status; | 
|  | } | 
|  |  | 
|  | void checkThresholdsPowerDelay(const std::weak_ptr<Sensor>& weakSensor, | 
|  | ThresholdTimer& thresholdTimer) | 
|  | { | 
|  | auto sensorPtr = weakSensor.lock(); | 
|  | if (!sensorPtr) | 
|  | { | 
|  | return; // sensor is destructed, should never be here | 
|  | } | 
|  |  | 
|  | Sensor* sensor = sensorPtr.get(); | 
|  | std::vector<ChangeParam> changes = checkThresholds(sensor, sensor->value); | 
|  | for (const auto& change : changes) | 
|  | { | 
|  | // When CPU is powered off, some volatges are expected to | 
|  | // go below low thresholds. Filter these events with thresholdTimer. | 
|  | // 1. always delay the assertion of low events to see if they are | 
|  | //   caused by power off event. | 
|  | // 2. conditional delay the de-assertion of low events if there is | 
|  | //   an existing timer for assertion. | 
|  | // 3. no delays for de-assert of low events if there is an existing | 
|  | //   de-assert for low event. This means 2nd de-assert would happen | 
|  | //   first and when timer expires for the previous one, no additional | 
|  | //   signal will be logged. | 
|  | // 4. no delays for all high events. | 
|  | if (change.threshold.direction == thresholds::Direction::LOW) | 
|  | { | 
|  | if (change.asserted || thresholdTimer.hasActiveTimer( | 
|  | change.threshold, !change.asserted)) | 
|  | { | 
|  | thresholdTimer.startTimer(weakSensor, change.threshold, | 
|  | change.asserted, change.assertValue); | 
|  | continue; | 
|  | } | 
|  | } | 
|  | assertThresholds(sensor, change.assertValue, change.threshold.level, | 
|  | change.threshold.direction, change.asserted); | 
|  | } | 
|  | } | 
|  |  | 
|  | void assertThresholds(Sensor* sensor, double assertValue, | 
|  | thresholds::Level level, thresholds::Direction direction, | 
|  | bool assert) | 
|  | { | 
|  | std::shared_ptr<sdbusplus::asio::dbus_interface> interface = | 
|  | sensor->getThresholdInterface(level); | 
|  |  | 
|  | if (!interface) | 
|  | { | 
|  | lg2::info("trying to set uninitialized interface"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | std::string property = Sensor::propertyAlarm(level, direction); | 
|  | if (property.empty()) | 
|  | { | 
|  | lg2::info("Alarm property is empty"); | 
|  | return; | 
|  | } | 
|  | if (interface->set_property<bool, true>(property, assert)) | 
|  | { | 
|  | try | 
|  | { | 
|  | // msg.get_path() is interface->get_object_path() | 
|  | sdbusplus::message_t msg = | 
|  | interface->new_signal("ThresholdAsserted"); | 
|  |  | 
|  | msg.append(sensor->name, interface->get_interface_name(), property, | 
|  | assert, assertValue); | 
|  | msg.signal_send(); | 
|  | } | 
|  | catch (const sdbusplus::exception_t& e) | 
|  | { | 
|  | lg2::error( | 
|  | "Failed to send thresholdAsserted signal with assertValue"); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | bool parseThresholdsFromAttr( | 
|  | std::vector<thresholds::Threshold>& thresholdVector, | 
|  | const std::string& inputPath, const double& scaleFactor, | 
|  | const double& offset, const double& hysteresis) | 
|  | { | 
|  | 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.contains(item)) | 
|  | { | 
|  | for (const auto& t : map.at(item)) | 
|  | { | 
|  | const 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) | 
|  | { | 
|  | lg2::info("Threshold: '{PATH}': '{VALUE}'", "PATH", | 
|  | attrPath, "VALUE", *val); | 
|  | } | 
|  | thresholdVector.emplace_back(level, direction, *val, | 
|  | hysteresis); | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | std::string getInterface(const Level thresholdLevel) | 
|  | { | 
|  | for (const ThresholdDefinition& thresh : thresProp) | 
|  | { | 
|  | if (thresh.level == thresholdLevel) | 
|  | { | 
|  | return std::string("xyz.openbmc_project.Sensor.Threshold.") + | 
|  | thresh.levelName; | 
|  | } | 
|  | } | 
|  | return ""; | 
|  | } | 
|  | } // namespace thresholds |