blob: fc252ac4218a17f6ec2eb77e8310c49568ce939c [file] [log] [blame]
#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 <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 <iostream>
#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())
{
std::cerr << "Malformed threshold on configuration interface "
<< intf << "\n";
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())
{
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 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)
{
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)
{
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)
{
std::cerr << "Sensor " << sensor->name << " high threshold "
<< threshold.value << " assert: value " << value
<< " raw data " << sensor->rawValue << "\n";
}
}
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)
{
std::cerr
<< "Sensor " << sensor->name << " low threshold "
<< threshold.value << " assert: value " << sensor->value
<< " raw data " << sensor->rawValue << "\n";
}
}
else if (value > (threshold.value + threshold.hysteresis))
{
thresholdChanges.emplace_back(threshold, false, value);
++cLoFalse;
}
else
{
++cLoMidstate;
}
}
else
{
std::cerr << "Error determining threshold direction\n";
}
}
// Throttle debug output, so that it does not continuously spam
++cDebugThrottle;
if (cDebugThrottle >= 1000)
{
cDebugThrottle = 0;
if constexpr (debug)
{
std::cerr << "checkThresholds: High T=" << cHiTrue
<< " F=" << cHiFalse << " M=" << cHiMidstate
<< ", Low T=" << cLoTrue << " F=" << cLoFalse
<< " M=" << cLoMidstate << "\n";
}
}
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)
{
std::cerr << "timer error: " << ec.message() << "\n";
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)
{
std::cout << "trying to set uninitialized interface\n";
return;
}
std::string property = Sensor::propertyAlarm(level, direction);
if (property.empty())
{
std::cout << "Alarm property is empty \n";
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)
{
std::cerr
<< "Failed to send thresholdAsserted signal with assertValue\n";
}
}
}
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.count(item) != 0)
{
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)
{
std::cout << "Threshold: " << attrPath << ": " << *val
<< "\n";
}
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