Added discrete threshold trigger support
Implemented discrete threshold logic
Discrete trigger with empty threshold array is handled as 'onChange'
Added unit tests coverage for discrete trigger
Supported scenarios:
-discrete threshold with value and dwell time
-discrete threshold with value, without dwell time
-discrete trigger without threshold ('onChange')
Tests:
-Unit tests passed
Change-Id: Id60a48f4113bd955d97e154888c00d1b6e5490af
Signed-off-by: Szymon Dompke <szymon.dompke@intel.com>
diff --git a/src/discrete_threshold.cpp b/src/discrete_threshold.cpp
new file mode 100644
index 0000000..a93f682
--- /dev/null
+++ b/src/discrete_threshold.cpp
@@ -0,0 +1,103 @@
+#include "discrete_threshold.hpp"
+
+#include <phosphor-logging/log.hpp>
+
+DiscreteThreshold::DiscreteThreshold(
+ boost::asio::io_context& ioc,
+ std::vector<std::shared_ptr<interfaces::Sensor>> sensorsIn,
+ std::vector<std::string> sensorNames,
+ std::vector<std::unique_ptr<interfaces::TriggerAction>> actionsIn,
+ std::chrono::milliseconds dwellTimeIn, double thresholdValueIn,
+ std::string name) :
+ ioc(ioc),
+ sensors(std::move(sensorsIn)), actions(std::move(actionsIn)),
+ dwellTime(dwellTimeIn), thresholdValue(thresholdValueIn), name(name)
+{
+ details.reserve(sensors.size());
+ for (size_t i = 0; i < sensors.size(); i++)
+ {
+ details.emplace_back(sensorNames[i], false, ioc);
+ }
+}
+
+void DiscreteThreshold::initialize()
+{
+ for (auto& sensor : sensors)
+ {
+ sensor->registerForUpdates(weak_from_this());
+ }
+}
+
+DiscreteThreshold::ThresholdDetail&
+ DiscreteThreshold::getDetails(interfaces::Sensor& sensor)
+{
+ auto it =
+ std::find_if(sensors.begin(), sensors.end(),
+ [&sensor](const auto& x) { return &sensor == x.get(); });
+ auto index = std::distance(sensors.begin(), it);
+ return details.at(index);
+}
+
+void DiscreteThreshold::sensorUpdated(interfaces::Sensor& sensor,
+ uint64_t timestamp)
+{}
+
+void DiscreteThreshold::sensorUpdated(interfaces::Sensor& sensor,
+ uint64_t timestamp, double value)
+{
+ auto& [sensorName, dwell, timer] = getDetails(sensor);
+
+ if (thresholdValue)
+ {
+ if (dwell && value != thresholdValue)
+ {
+ timer.cancel();
+ dwell = false;
+ }
+ else if (value == thresholdValue)
+ {
+ startTimer(sensor, timestamp, value);
+ }
+ }
+}
+
+void DiscreteThreshold::startTimer(interfaces::Sensor& sensor,
+ uint64_t timestamp, double value)
+{
+ auto& [sensorName, dwell, timer] = getDetails(sensor);
+ if (dwellTime == std::chrono::milliseconds::zero())
+ {
+ commit(sensorName, timestamp, value);
+ }
+ else
+ {
+ dwell = true;
+ timer.expires_after(dwellTime);
+ timer.async_wait([this, &sensor, timestamp,
+ value](const boost::system::error_code ec) {
+ if (ec)
+ {
+ phosphor::logging::log<phosphor::logging::level::DEBUG>(
+ "Timer has been canceled");
+ return;
+ }
+ auto& [sensorName, dwell, timer] = getDetails(sensor);
+ commit(sensorName, timestamp, value);
+ dwell = false;
+ });
+ }
+}
+
+void DiscreteThreshold::commit(const std::string& sensorName,
+ uint64_t timestamp, double value)
+{
+ for (const auto& action : actions)
+ {
+ action->commit(sensorName, timestamp, value);
+ }
+}
+
+const char* DiscreteThreshold::getName() const
+{
+ return name.c_str();
+}
diff --git a/src/discrete_threshold.hpp b/src/discrete_threshold.hpp
new file mode 100644
index 0000000..100b88d
--- /dev/null
+++ b/src/discrete_threshold.hpp
@@ -0,0 +1,63 @@
+#pragma once
+
+#include "interfaces/sensor.hpp"
+#include "interfaces/sensor_listener.hpp"
+#include "interfaces/threshold.hpp"
+#include "interfaces/trigger_action.hpp"
+#include "interfaces/trigger_types.hpp"
+
+#include <boost/asio/steady_timer.hpp>
+
+#include <chrono>
+#include <memory>
+#include <vector>
+
+class DiscreteThreshold :
+ public interfaces::Threshold,
+ public interfaces::SensorListener,
+ public std::enable_shared_from_this<DiscreteThreshold>
+{
+ public:
+ DiscreteThreshold(
+ boost::asio::io_context& ioc,
+ std::vector<std::shared_ptr<interfaces::Sensor>> sensors,
+ std::vector<std::string> sensorNames,
+ std::vector<std::unique_ptr<interfaces::TriggerAction>> actions,
+ std::chrono::milliseconds dwellTime, double thresholdValue,
+ std::string name);
+ DiscreteThreshold(const DiscreteThreshold&) = delete;
+ DiscreteThreshold(DiscreteThreshold&&) = delete;
+ ~DiscreteThreshold()
+ {}
+
+ void initialize() override;
+ void sensorUpdated(interfaces::Sensor&, uint64_t) override;
+ void sensorUpdated(interfaces::Sensor&, uint64_t, double) override;
+ const char* getName() const;
+
+ private:
+ boost::asio::io_context& ioc;
+ const std::vector<std::shared_ptr<interfaces::Sensor>> sensors;
+ const std::vector<std::unique_ptr<interfaces::TriggerAction>> actions;
+ const std::chrono::milliseconds dwellTime;
+ const double thresholdValue;
+ const std::string name;
+
+ struct ThresholdDetail
+ {
+ std::string sensorName;
+ bool dwell;
+ boost::asio::steady_timer timer;
+
+ ThresholdDetail(const std::string& name, bool dwell,
+ boost::asio::io_context& ioc) :
+ sensorName(name),
+ dwell(dwell), timer(ioc)
+ {}
+ };
+ std::vector<ThresholdDetail> details;
+
+ void startTimer(interfaces::Sensor&, uint64_t, double);
+ void commit(const std::string&, uint64_t, double);
+ ThresholdDetail& getDetails(interfaces::Sensor& sensor);
+};
diff --git a/src/interfaces/trigger_types.hpp b/src/interfaces/trigger_types.hpp
index f99cd3c..90c673d 100644
--- a/src/interfaces/trigger_types.hpp
+++ b/src/interfaces/trigger_types.hpp
@@ -37,8 +37,7 @@
return std::string(utils::enumToString(details::convDataSeverity, v));
}
-using ThresholdParam =
- std::tuple<std::string, std::string, std::variant<double>, uint64_t>;
+using ThresholdParam = std::tuple<std::string, std::string, uint64_t, double>;
} // namespace discrete
namespace numeric
diff --git a/src/on_change_threshold.cpp b/src/on_change_threshold.cpp
new file mode 100644
index 0000000..0365ef4
--- /dev/null
+++ b/src/on_change_threshold.cpp
@@ -0,0 +1,42 @@
+#include "on_change_threshold.hpp"
+
+#include <phosphor-logging/log.hpp>
+
+OnChangeThreshold::OnChangeThreshold(
+ std::vector<std::shared_ptr<interfaces::Sensor>> sensorsIn,
+ std::vector<std::string> sensorNamesIn,
+ std::vector<std::unique_ptr<interfaces::TriggerAction>> actionsIn) :
+ sensors(std::move(sensorsIn)),
+ sensorNames(std::move(sensorNamesIn)), actions(std::move(actionsIn))
+{}
+
+void OnChangeThreshold::initialize()
+{
+ for (auto& sensor : sensors)
+ {
+ sensor->registerForUpdates(weak_from_this());
+ }
+}
+
+void OnChangeThreshold::sensorUpdated(interfaces::Sensor& sensor,
+ uint64_t timestamp)
+{}
+
+void OnChangeThreshold::sensorUpdated(interfaces::Sensor& sensor,
+ uint64_t timestamp, double value)
+{
+ auto it =
+ std::find_if(sensors.begin(), sensors.end(),
+ [&sensor](const auto& x) { return &sensor == x.get(); });
+ auto index = std::distance(sensors.begin(), it);
+ commit(sensorNames.at(index), timestamp, value);
+}
+
+void OnChangeThreshold::commit(const std::string& sensorName,
+ uint64_t timestamp, double value)
+{
+ for (const auto& action : actions)
+ {
+ action->commit(sensorName, timestamp, value);
+ }
+}
diff --git a/src/on_change_threshold.hpp b/src/on_change_threshold.hpp
new file mode 100644
index 0000000..0d0f841
--- /dev/null
+++ b/src/on_change_threshold.hpp
@@ -0,0 +1,38 @@
+#pragma once
+
+#include "interfaces/sensor.hpp"
+#include "interfaces/sensor_listener.hpp"
+#include "interfaces/threshold.hpp"
+#include "interfaces/trigger_action.hpp"
+#include "interfaces/trigger_types.hpp"
+
+#include <boost/asio/steady_timer.hpp>
+
+#include <chrono>
+#include <memory>
+#include <vector>
+
+class OnChangeThreshold :
+ public interfaces::Threshold,
+ public interfaces::SensorListener,
+ public std::enable_shared_from_this<OnChangeThreshold>
+{
+ public:
+ OnChangeThreshold(
+ std::vector<std::shared_ptr<interfaces::Sensor>> sensors,
+ std::vector<std::string> sensorNames,
+ std::vector<std::unique_ptr<interfaces::TriggerAction>> actions);
+ ~OnChangeThreshold()
+ {}
+
+ void initialize() override;
+ void sensorUpdated(interfaces::Sensor&, uint64_t) override;
+ void sensorUpdated(interfaces::Sensor&, uint64_t, double) override;
+
+ private:
+ const std::vector<std::shared_ptr<interfaces::Sensor>> sensors;
+ const std::vector<std::string> sensorNames;
+ const std::vector<std::unique_ptr<interfaces::TriggerAction>> actions;
+
+ void commit(const std::string&, uint64_t, double);
+};
diff --git a/src/trigger_actions.cpp b/src/trigger_actions.cpp
index 26fa703..8f2cf82 100644
--- a/src/trigger_actions.cpp
+++ b/src/trigger_actions.cpp
@@ -7,6 +7,25 @@
namespace action
{
+namespace
+{
+std::string timestampToString(uint64_t timestamp)
+{
+ std::time_t t = static_cast<time_t>(timestamp);
+ std::array<char, sizeof("YYYY-MM-DDThh:mm:ssZ")> buf = {};
+ size_t size =
+ std::strftime(buf.data(), buf.size(), "%FT%TZ", std::gmtime(&t));
+ if (size == 0)
+ {
+ throw std::runtime_error("Failed to parse timestamp to string");
+ }
+ return std::string(buf.data(), size);
+}
+} // namespace
+
+namespace numeric
+{
+
static const char* getDirection(double value, double threshold)
{
if (value < threshold)
@@ -24,13 +43,13 @@
{
switch (type)
{
- case numeric::Type::upperCritical:
+ case ::numeric::Type::upperCritical:
return "UpperCritical";
- case numeric::Type::lowerCritical:
+ case ::numeric::Type::lowerCritical:
return "LowerCritical";
- case numeric::Type::upperWarning:
+ case ::numeric::Type::upperWarning:
return "UpperWarning";
- case numeric::Type::lowerWarning:
+ case ::numeric::Type::lowerWarning:
return "LowerWarning";
}
throw std::runtime_error("Invalid type");
@@ -39,19 +58,10 @@
void LogToJournal::commit(const std::string& sensorName, uint64_t timestamp,
double value)
{
- std::time_t t = static_cast<time_t>(timestamp);
- std::array<char, sizeof("YYYY-MM-DDThh:mm:ssZ")> buf = {};
- size_t size =
- std::strftime(buf.data(), buf.size(), "%FT%TZ", std::gmtime(&t));
- if (size == 0)
- {
- throw std::runtime_error("Failed to parse timestamp to string");
- }
-
std::string msg = std::string(getType()) +
" numeric threshold condition is met on sensor " +
sensorName + ", recorded value " + std::to_string(value) +
- ", timestamp " + std::string(buf.data(), size) +
+ ", timestamp " + timestampToString(timestamp) +
", direction " +
std::string(getDirection(value, threshold));
@@ -62,13 +72,13 @@
{
switch (type)
{
- case numeric::Type::upperCritical:
+ case ::numeric::Type::upperCritical:
return "OpenBMC.0.1.0.NumericThresholdUpperCritical";
- case numeric::Type::lowerCritical:
+ case ::numeric::Type::lowerCritical:
return "OpenBMC.0.1.0.NumericThresholdLowerCritical";
- case numeric::Type::upperWarning:
+ case ::numeric::Type::upperWarning:
return "OpenBMC.0.1.0.NumericThresholdUpperWarning";
- case numeric::Type::lowerWarning:
+ case ::numeric::Type::lowerWarning:
return "OpenBMC.0.1.0.NumericThresholdLowerWarning";
}
throw std::runtime_error("Invalid type");
@@ -85,6 +95,84 @@
getDirection(value, threshold)));
}
+} // namespace numeric
+
+namespace discrete
+{
+const char* LogToJournal::getSeverity() const
+{
+ switch (severity)
+ {
+ case ::discrete::Severity::ok:
+ return "OK";
+ case ::discrete::Severity::warning:
+ return "Warning";
+ case ::discrete::Severity::critical:
+ return "Critical";
+ }
+ throw std::runtime_error("Invalid severity");
+}
+
+void LogToJournal::commit(const std::string& sensorName, uint64_t timestamp,
+ double value)
+{
+ std::string msg = std::string(getSeverity()) +
+ " discrete threshold condition is met on sensor " +
+ sensorName + ", recorded value " + std::to_string(value) +
+ ", timestamp " + timestampToString(timestamp);
+
+ phosphor::logging::log<phosphor::logging::level::INFO>(msg.c_str());
+}
+
+const char* LogToRedfish::getMessageId() const
+{
+ switch (severity)
+ {
+ case ::discrete::Severity::ok:
+ return "OpenBMC.0.1.0.DiscreteThresholdOk";
+ case ::discrete::Severity::warning:
+ return "OpenBMC.0.1.0.DiscreteThresholdWarning";
+ case ::discrete::Severity::critical:
+ return "OpenBMC.0.1.0.DiscreteThresholdCritical";
+ }
+ throw std::runtime_error("Invalid severity");
+}
+
+void LogToRedfish::commit(const std::string& sensorName, uint64_t timestamp,
+ double value)
+{
+ phosphor::logging::log<phosphor::logging::level::INFO>(
+ "Discrete treshold condition is met",
+ phosphor::logging::entry("REDFISH_MESSAGE_ID=%s", getMessageId()),
+ phosphor::logging::entry("REDFISH_MESSAGE_ARGS=%s,%f,%llu",
+ sensorName.c_str(), value, timestamp));
+}
+
+namespace onChange
+{
+void LogToJournal::commit(const std::string& sensorName, uint64_t timestamp,
+ double value)
+{
+ std::string msg = "Value changed on sensor " + sensorName +
+ ", recorded value " + std::to_string(value) +
+ ", timestamp " + timestampToString(timestamp);
+
+ phosphor::logging::log<phosphor::logging::level::INFO>(msg.c_str());
+}
+
+void LogToRedfish::commit(const std::string& sensorName, uint64_t timestamp,
+ double value)
+{
+ const char* messageId = "OpenBMC.0.1.0.DiscreteThresholdOnChange";
+ phosphor::logging::log<phosphor::logging::level::INFO>(
+ "Uncondtional discrete threshold triggered",
+ phosphor::logging::entry("REDFISH_MESSAGE_ID=%s", messageId),
+ phosphor::logging::entry("REDFISH_MESSAGE_ARGS=%s,%f,%llu",
+ sensorName.c_str(), value, timestamp));
+}
+} // namespace onChange
+} // namespace discrete
+
void UpdateReport::commit(const std::string&, uint64_t, double)
{
for (const auto& name : reportNames)
diff --git a/src/trigger_actions.hpp b/src/trigger_actions.hpp
index 2c8db69..408bcb5 100644
--- a/src/trigger_actions.hpp
+++ b/src/trigger_actions.hpp
@@ -7,17 +7,19 @@
namespace action
{
+namespace numeric
+{
class LogToJournal : public interfaces::TriggerAction
{
public:
- LogToJournal(numeric::Type type, double val) : type(type), threshold(val)
+ LogToJournal(::numeric::Type type, double val) : type(type), threshold(val)
{}
void commit(const std::string& id, uint64_t timestamp,
double value) override;
private:
- numeric::Type type;
+ ::numeric::Type type;
double threshold;
const char* getType() const;
@@ -26,18 +28,76 @@
class LogToRedfish : public interfaces::TriggerAction
{
public:
- LogToRedfish(numeric::Type type, double val) : type(type), threshold(val)
+ LogToRedfish(::numeric::Type type, double val) : type(type), threshold(val)
{}
void commit(const std::string& id, uint64_t timestamp,
double value) override;
private:
- numeric::Type type;
+ ::numeric::Type type;
double threshold;
const char* getMessageId() const;
};
+} // namespace numeric
+
+namespace discrete
+{
+class LogToJournal : public interfaces::TriggerAction
+{
+ public:
+ LogToJournal(::discrete::Severity severity) : severity(severity)
+ {}
+
+ void commit(const std::string& id, uint64_t timestamp,
+ double value) override;
+
+ private:
+ ::discrete::Severity severity;
+
+ const char* getSeverity() const;
+};
+
+class LogToRedfish : public interfaces::TriggerAction
+{
+ public:
+ LogToRedfish(::discrete::Severity severity) : severity(severity)
+ {}
+
+ void commit(const std::string& id, uint64_t timestamp,
+ double value) override;
+
+ private:
+ ::discrete::Severity severity;
+
+ const char* getMessageId() const;
+};
+
+namespace onChange
+{
+class LogToJournal : public interfaces::TriggerAction
+{
+ public:
+ LogToJournal()
+ {}
+
+ void commit(const std::string& id, uint64_t timestamp,
+ double value) override;
+};
+
+class LogToRedfish : public interfaces::TriggerAction
+{
+ public:
+ LogToRedfish()
+ {}
+
+ void commit(const std::string& id, uint64_t timestamp,
+ double value) override;
+};
+} // namespace onChange
+
+} // namespace discrete
class UpdateReport : public interfaces::TriggerAction
{
diff --git a/src/trigger_factory.cpp b/src/trigger_factory.cpp
index 7953cd9..3114e9a 100644
--- a/src/trigger_factory.cpp
+++ b/src/trigger_factory.cpp
@@ -1,6 +1,8 @@
#include "trigger_factory.hpp"
+#include "discrete_threshold.hpp"
#include "numeric_threshold.hpp"
+#include "on_change_threshold.hpp"
#include "sensor.hpp"
#include "trigger.hpp"
#include "trigger_actions.hpp"
@@ -24,40 +26,96 @@
const TriggerThresholdParams& thresholdParams,
interfaces::TriggerManager& triggerManager) const
{
- if (isDiscrete)
- {
- throw std::runtime_error("Not implemented!");
- }
-
auto [sensors, sensorNames] = getSensors(yield, sensorPaths);
std::vector<std::shared_ptr<interfaces::Threshold>> thresholds;
- const auto& params =
- std::get<std::vector<numeric::ThresholdParam>>(thresholdParams);
- for (const auto& [typeStr, dwellTime, directionStr, value] : params)
+ if (isDiscrete)
{
- numeric::Type type = numeric::stringToType(typeStr);
- std::vector<std::unique_ptr<interfaces::TriggerAction>> actions;
- if (logToJournal)
+ const auto& params =
+ std::get<std::vector<discrete::ThresholdParam>>(thresholdParams);
+ for (const auto& [thresholdName, severityStr, dwellTime,
+ thresholdValue] : params)
{
- actions.emplace_back(
- std::make_unique<action::LogToJournal>(type, value));
- }
- if (logToRedfish)
- {
- actions.emplace_back(
- std::make_unique<action::LogToRedfish>(type, value));
- }
- if (updateReport)
- {
- actions.emplace_back(std::make_unique<action::UpdateReport>(
- reportManager, reportNames));
- }
+ discrete::Severity severity =
+ discrete::stringToSeverity(severityStr);
+ std::vector<std::unique_ptr<interfaces::TriggerAction>> actions;
+ if (logToJournal)
+ {
+ actions.emplace_back(
+ std::make_unique<action::discrete::LogToJournal>(severity));
+ }
+ if (logToRedfish)
+ {
+ actions.emplace_back(
+ std::make_unique<action::discrete::LogToRedfish>(severity));
+ }
+ if (updateReport)
+ {
+ actions.emplace_back(std::make_unique<action::UpdateReport>(
+ reportManager, reportNames));
+ }
- thresholds.emplace_back(std::make_shared<NumericThreshold>(
- bus->get_io_context(), sensors, sensorNames, std::move(actions),
- std::chrono::milliseconds(dwellTime),
- numeric::stringToDirection(directionStr), value));
+ thresholds.emplace_back(std::make_shared<DiscreteThreshold>(
+ bus->get_io_context(), sensors, sensorNames, std::move(actions),
+ std::chrono::milliseconds(dwellTime), thresholdValue,
+ thresholdName));
+ }
+ if (params.empty())
+ {
+ std::vector<std::unique_ptr<interfaces::TriggerAction>> actions;
+ if (logToJournal)
+ {
+ actions.emplace_back(
+ std::make_unique<
+ action::discrete::onChange::LogToJournal>());
+ }
+ if (logToRedfish)
+ {
+ actions.emplace_back(
+ std::make_unique<
+ action::discrete::onChange::LogToRedfish>());
+ }
+ if (updateReport)
+ {
+ actions.emplace_back(std::make_unique<action::UpdateReport>(
+ reportManager, reportNames));
+ }
+
+ thresholds.emplace_back(std::make_shared<OnChangeThreshold>(
+ sensors, sensorNames, std::move(actions)));
+ }
+ }
+ else
+ {
+ const auto& params =
+ std::get<std::vector<numeric::ThresholdParam>>(thresholdParams);
+ for (const auto& [typeStr, dwellTime, directionStr, value] : params)
+ {
+ numeric::Type type = numeric::stringToType(typeStr);
+ std::vector<std::unique_ptr<interfaces::TriggerAction>> actions;
+ if (logToJournal)
+ {
+ actions.emplace_back(
+ std::make_unique<action::numeric::LogToJournal>(type,
+ value));
+ }
+ if (logToRedfish)
+ {
+ actions.emplace_back(
+ std::make_unique<action::numeric::LogToRedfish>(type,
+ value));
+ }
+ if (updateReport)
+ {
+ actions.emplace_back(std::make_unique<action::UpdateReport>(
+ reportManager, reportNames));
+ }
+
+ thresholds.emplace_back(std::make_shared<NumericThreshold>(
+ bus->get_io_context(), sensors, sensorNames, std::move(actions),
+ std::chrono::milliseconds(dwellTime),
+ numeric::stringToDirection(directionStr), value));
+ }
}
return std::make_unique<Trigger>(
diff --git a/src/trigger_manager.cpp b/src/trigger_manager.cpp
index 4191738..bf372c2 100644
--- a/src/trigger_manager.cpp
+++ b/src/trigger_manager.cpp
@@ -17,13 +17,6 @@
std::string>>& sensors,
const std::vector<std::string>& reportNames,
const TriggerThresholdParams& thresholds) {
- if (isDiscrete)
- {
- throw sdbusplus::exception::SdBusError(
- static_cast<int>(std::errc::not_supported),
- "Only numeric threshold is supported");
- }
-
if (triggers.size() >= maxTriggers)
{
throw sdbusplus::exception::SdBusError(