hardware: data readers can set up sensor thresholds
We are monitoring some hardware sensors. Some sensor values might be
considered dangerous (e.g. high temperatures). This commit introduces a
way of storing thresholds for data readers and validating measured
sensor values against those thresholds.
This is implemented in a way that each data reader must also implement a
method called thresholds() that returns a mapping of sensor XPath to
a Threshold object. These will be collected in the future by
IETFHardware and used to check obtained sensor values in order to raise
alarms.
Change-Id: I2d3854603f2ec04613906290633151f4d7b034f1
diff --git a/src/ietf-hardware/Factory.cpp b/src/ietf-hardware/Factory.cpp
index 572e320..860d6c4 100644
--- a/src/ietf-hardware/Factory.cpp
+++ b/src/ietf-hardware/Factory.cpp
@@ -48,16 +48,40 @@
"psu2",
std::make_shared<TransientI2C>(2, 0x59, "ym2151e"));
- ietfHardware->registerDataReader([pduGroup = std::move(pduGroup), psu1, psu2]() {
- auto psu1Reader = std::async(std::launch::async, [psu1] { return psu1->readValues(); });
- auto psu2Reader = std::async(std::launch::async, [psu2] { return psu2->readValues(); });
- auto pduReader = std::async(std::launch::async, [&pduGroup] { return pduGroup(); });
+ struct ParallelPDUReader {
+ Group pduGroup;
+ std::shared_ptr<velia::ietf_hardware::FspYhPsu> psu1;
+ std::shared_ptr<velia::ietf_hardware::FspYhPsu> psu2;
- auto res = psu1Reader.get();
- res.merge(psu2Reader.get());
- res.merge(pduReader.get());
- return res;
- });
+ ParallelPDUReader(Group&& pduGroup, std::shared_ptr<velia::ietf_hardware::FspYhPsu> psu1, std::shared_ptr<velia::ietf_hardware::FspYhPsu> psu2)
+ : pduGroup(std::move(pduGroup))
+ , psu1(std::move(psu1))
+ , psu2(std::move(psu2))
+ {
+ }
+
+ DataTree operator()()
+ {
+ auto psu1Reader = std::async(std::launch::async, [&] { return psu1->readValues(); });
+ auto psu2Reader = std::async(std::launch::async, [&] { return psu2->readValues(); });
+ auto pduReader = std::async(std::launch::async, [&] { return pduGroup(); });
+
+ auto res = psu1Reader.get();
+ res.merge(psu2Reader.get());
+ res.merge(pduReader.get());
+ return res;
+ }
+
+ ThresholdsBySensorPath thresholds() const
+ {
+ auto res = psu1->thresholds();
+ res.merge(psu2->thresholds());
+ res.merge(pduGroup.thresholds());
+ return res;
+ }
+ };
+
+ ietfHardware->registerDataReader(ParallelPDUReader(std::move(pduGroup), psu1, psu2));
}
std::shared_ptr<IETFHardware> create(const std::string& applianceName)
diff --git a/src/ietf-hardware/FspYhPsu.cpp b/src/ietf-hardware/FspYhPsu.cpp
index f41aca5..1a5c315 100644
--- a/src/ietf-hardware/FspYhPsu.cpp
+++ b/src/ietf-hardware/FspYhPsu.cpp
@@ -124,17 +124,24 @@
using velia::ietf_hardware::data_reader::SysfsValue;
using velia::ietf_hardware::data_reader::Fans;
using velia::ietf_hardware::data_reader::SensorType;
- m_properties.emplace_back(SysfsValue<SensorType::Temperature>(m_namePrefix + ":temperature-1", m_namePrefix, m_hwmon, 1));
- m_properties.emplace_back(SysfsValue<SensorType::Temperature>(m_namePrefix + ":temperature-2", m_namePrefix, m_hwmon, 2));
- m_properties.emplace_back(SysfsValue<SensorType::Current>(m_namePrefix + ":current-in", m_namePrefix, m_hwmon, 1));
- m_properties.emplace_back(SysfsValue<SensorType::Current>(m_namePrefix + ":current-12V", m_namePrefix, m_hwmon, 2));
- m_properties.emplace_back(SysfsValue<SensorType::VoltageAC>(m_namePrefix + ":voltage-in", m_namePrefix, m_hwmon, 1));
- m_properties.emplace_back(SysfsValue<SensorType::VoltageDC>(m_namePrefix + ":voltage-12V", m_namePrefix, m_hwmon, 2));
- m_properties.emplace_back(SysfsValue<SensorType::Power>(m_namePrefix + ":power-in", m_namePrefix, m_hwmon, 1));
- m_properties.emplace_back(SysfsValue<SensorType::Power>(m_namePrefix + ":power-out", m_namePrefix, m_hwmon, 2));
- m_properties.emplace_back(Fans(m_namePrefix + ":fan", m_namePrefix, m_hwmon, 1));
- m_properties.emplace_back(SysfsValue<SensorType::Current>(m_namePrefix + ":current-5Vsb", m_namePrefix, m_hwmon, 3));
- m_properties.emplace_back(SysfsValue<SensorType::VoltageDC>(m_namePrefix + ":voltage-5Vsb", m_namePrefix, m_hwmon, 3));
+
+
+ auto registerReader = [&]<typename DataReaderType>(DataReaderType&& reader) {
+ m_thresholds.merge(reader.thresholds());
+ m_properties.emplace_back(reader);
+ };
+
+ registerReader(SysfsValue<SensorType::Temperature>(m_namePrefix + ":temperature-1", m_namePrefix, m_hwmon, 1));
+ registerReader(SysfsValue<SensorType::Temperature>(m_namePrefix + ":temperature-2", m_namePrefix, m_hwmon, 2));
+ registerReader(SysfsValue<SensorType::Current>(m_namePrefix + ":current-in", m_namePrefix, m_hwmon, 1));
+ registerReader(SysfsValue<SensorType::Current>(m_namePrefix + ":current-12V", m_namePrefix, m_hwmon, 2));
+ registerReader(SysfsValue<SensorType::VoltageAC>(m_namePrefix + ":voltage-in", m_namePrefix, m_hwmon, 1));
+ registerReader(SysfsValue<SensorType::VoltageDC>(m_namePrefix + ":voltage-12V", m_namePrefix, m_hwmon, 2));
+ registerReader(SysfsValue<SensorType::Power>(m_namePrefix + ":power-in", m_namePrefix, m_hwmon, 1));
+ registerReader(SysfsValue<SensorType::Power>(m_namePrefix + ":power-out", m_namePrefix, m_hwmon, 2));
+ registerReader(Fans(m_namePrefix + ":fan", m_namePrefix, m_hwmon, 1));
+ registerReader(SysfsValue<SensorType::Current>(m_namePrefix + ":current-5Vsb", m_namePrefix, m_hwmon, 3));
+ registerReader(SysfsValue<SensorType::VoltageDC>(m_namePrefix + ":voltage-5Vsb", m_namePrefix, m_hwmon, 3));
}
DataTree FspYhPsu::readValues()
@@ -170,4 +177,9 @@
return res;
}
+
+ThresholdsBySensorPath FspYhPsu::thresholds() const
+{
+ return m_thresholds;
+}
}
diff --git a/src/ietf-hardware/FspYhPsu.h b/src/ietf-hardware/FspYhPsu.h
index 27b9952..04004ee 100644
--- a/src/ietf-hardware/FspYhPsu.h
+++ b/src/ietf-hardware/FspYhPsu.h
@@ -32,6 +32,8 @@
FspYhPsu(const std::filesystem::path& hwmonDir, const std::string& psuName, std::shared_ptr<TransientI2C> i2c);
~FspYhPsu();
velia::ietf_hardware::DataTree readValues();
+ ThresholdsBySensorPath thresholds() const;
+
private:
std::mutex m_mtx;
std::mutex m_condMtx;
@@ -47,7 +49,9 @@
std::string m_namePrefix;
velia::ietf_hardware::DataTree m_staticData;
+
std::vector<std::function<velia::ietf_hardware::DataTree()>> m_properties;
+ ThresholdsBySensorPath m_thresholds;
void createPower();
};
diff --git a/src/ietf-hardware/IETFHardware.cpp b/src/ietf-hardware/IETFHardware.cpp
index 0e7d7ad..95ceb3d 100644
--- a/src/ietf-hardware/IETFHardware.cpp
+++ b/src/ietf-hardware/IETFHardware.cpp
@@ -64,11 +64,6 @@
return res;
}
-void IETFHardware::registerDataReader(const DataReader& callable)
-{
- m_callbacks.push_back(callable);
-}
-
/** @brief A namespace containing predefined data readers for IETFHardware class.
* @see IETFHardware for more information
*/
@@ -95,11 +90,13 @@
}
DataTree StaticData::operator()() const { return m_staticData; }
+ThresholdsBySensorPath StaticData::thresholds() const { return {}; }
-Fans::Fans(std::string componentName, std::optional<std::string> parent, std::shared_ptr<sysfs::HWMon> hwmon, unsigned fanChannelsCount)
+Fans::Fans(std::string componentName, std::optional<std::string> parent, std::shared_ptr<sysfs::HWMon> hwmon, unsigned fanChannelsCount, Thresholds<int64_t> thresholds)
: DataReader(std::move(componentName), std::move(parent))
, m_hwmon(std::move(hwmon))
, m_fanChannelsCount(fanChannelsCount)
+ , m_thresholds(std::move(thresholds))
{
// fans
addComponent(m_staticData,
@@ -146,6 +143,17 @@
return res;
}
+ThresholdsBySensorPath Fans::thresholds() const
+{
+ ThresholdsBySensorPath res;
+
+ for (unsigned i = 1; i <= m_fanChannelsCount; i++) {
+ res.emplace(xpathForComponent(m_componentName + ":fan" + std::to_string(i) + ":rpm") + "sensor-data/value", m_thresholds);
+ }
+
+ return res;
+}
+
std::string getSysfsFilename(const SensorType type, int sysfsChannelNr)
{
switch (type) {
@@ -205,10 +213,11 @@
{"sensor-data/oper-status", "ok"}};
template <SensorType TYPE>
-SysfsValue<TYPE>::SysfsValue(std::string componentName, std::optional<std::string> parent, std::shared_ptr<sysfs::HWMon> hwmon, int sysfsChannelNr)
+SysfsValue<TYPE>::SysfsValue(std::string componentName, std::optional<std::string> parent, std::shared_ptr<sysfs::HWMon> hwmon, int sysfsChannelNr, Thresholds<int64_t> thresholds)
: DataReader(std::move(componentName), std::move(parent))
, m_hwmon(std::move(hwmon))
, m_sysfsFile(getSysfsFilename(TYPE, sysfsChannelNr))
+ , m_thresholds(std::move(thresholds))
{
addComponent(m_staticData,
m_componentName,
@@ -227,15 +236,22 @@
return res;
}
+template <SensorType TYPE>
+ThresholdsBySensorPath SysfsValue<TYPE>::thresholds() const
+{
+ return {{xpathForComponent(m_componentName) + "sensor-data/value", m_thresholds}};
+}
+
template struct SysfsValue<SensorType::Current>;
template struct SysfsValue<SensorType::Power>;
template struct SysfsValue<SensorType::Temperature>;
template struct SysfsValue<SensorType::VoltageAC>;
template struct SysfsValue<SensorType::VoltageDC>;
-EMMC::EMMC(std::string componentName, std::optional<std::string> parent, std::shared_ptr<sysfs::EMMC> emmc)
+EMMC::EMMC(std::string componentName, std::optional<std::string> parent, std::shared_ptr<sysfs::EMMC> emmc, Thresholds<int64_t> thresholds)
: DataReader(std::move(componentName), std::move(parent))
, m_emmc(std::move(emmc))
+ , m_thresholds(std::move(thresholds))
{
auto emmcAttrs = m_emmc->attributes();
@@ -280,9 +296,9 @@
return res;
}
-void Group::registerDataReader(const IETFHardware::DataReader& callable)
+ThresholdsBySensorPath EMMC::thresholds() const
{
- m_readers.emplace_back(callable);
+ return {{xpathForComponent(m_componentName + ":lifetime") + "sensor-data/value", m_thresholds}};
}
DataTree Group::operator()() const
@@ -294,5 +310,9 @@
return res;
}
+ThresholdsBySensorPath Group::thresholds() const
+{
+ return m_thresholds;
+}
}
}
diff --git a/src/ietf-hardware/IETFHardware.h b/src/ietf-hardware/IETFHardware.h
index d807fe8..c1265b3 100644
--- a/src/ietf-hardware/IETFHardware.h
+++ b/src/ietf-hardware/IETFHardware.h
@@ -13,6 +13,7 @@
#include <utility>
#include "ietf-hardware/sysfs/EMMC.h"
#include "ietf-hardware/sysfs/HWMon.h"
+#include "ietf-hardware/thresholds.h"
#include "utils/log-fwd.h"
using namespace std::literals;
@@ -20,6 +21,7 @@
namespace velia::ietf_hardware {
using DataTree = std::map<std::string, std::string>;
+using ThresholdsBySensorPath = std::map<std::string, Thresholds<int64_t>>;
/**
* @brief Readout of hardware-state related data according to RFC 8348 (App. A)
@@ -49,7 +51,12 @@
IETFHardware();
~IETFHardware();
- void registerDataReader(const DataReader& callable);
+
+ template <class DataReaderType>
+ void registerDataReader(const DataReaderType& callable)
+ {
+ m_callbacks.push_back(callable);
+ }
DataTree process();
@@ -89,6 +96,7 @@
struct StaticData : private DataReader {
StaticData(std::string propertyPrefix, std::optional<std::string> parent, DataTree tree);
DataTree operator()() const;
+ ThresholdsBySensorPath thresholds() const;
};
/** @brief Manages fans component. Data is provided by a sysfs::HWMon object. */
@@ -96,10 +104,12 @@
private:
std::shared_ptr<sysfs::HWMon> m_hwmon;
unsigned m_fanChannelsCount;
+ Thresholds<int64_t> m_thresholds;
public:
- Fans(std::string propertyPrefix, std::optional<std::string> parent, std::shared_ptr<sysfs::HWMon> hwmon, unsigned fanChannelsCount);
+ Fans(std::string propertyPrefix, std::optional<std::string> parent, std::shared_ptr<sysfs::HWMon> hwmon, unsigned fanChannelsCount, Thresholds<int64_t> thresholds = {});
DataTree operator()() const;
+ ThresholdsBySensorPath thresholds() const;
};
enum class SensorType {
@@ -117,20 +127,24 @@
private:
std::shared_ptr<sysfs::HWMon> m_hwmon;
std::string m_sysfsFile;
+ Thresholds<int64_t> m_thresholds;
public:
- SysfsValue(std::string propertyPrefix, std::optional<std::string> parent, std::shared_ptr<sysfs::HWMon> hwmon, int sysfsChannelNr);
+ SysfsValue(std::string propertyPrefix, std::optional<std::string> parent, std::shared_ptr<sysfs::HWMon> hwmon, int sysfsChannelNr, Thresholds<int64_t> thresholds = {});
DataTree operator()() const;
+ ThresholdsBySensorPath thresholds() const;
};
/** @brief Manages a single eMMC block device hardware component. Data is provided by a sysfs::EMMC object. */
struct EMMC : private DataReader {
private:
std::shared_ptr<sysfs::EMMC> m_emmc;
+ Thresholds<int64_t> m_thresholds;
public:
- EMMC(std::string propertyPrefix, std::optional<std::string> parent, std::shared_ptr<sysfs::EMMC> emmc);
+ EMMC(std::string propertyPrefix, std::optional<std::string> parent, std::shared_ptr<sysfs::EMMC> emmc, Thresholds<int64_t> thresholds = {});
DataTree operator()() const;
+ ThresholdsBySensorPath thresholds() const;
};
/** @brief Use this when you want to wrap reading several properties in one go and still use it as a single DataReader instance (e.g. in on thread)
@@ -138,10 +152,18 @@
struct Group {
private:
std::vector<IETFHardware::DataReader> m_readers;
+ ThresholdsBySensorPath m_thresholds;
public:
- void registerDataReader(const IETFHardware::DataReader& callable);
DataTree operator()() const;
+ ThresholdsBySensorPath thresholds() const;
+
+ template <class DataReaderType>
+ void registerDataReader(const DataReaderType& callable)
+ {
+ m_readers.push_back(callable);
+ m_thresholds.merge(callable.thresholds());
+ }
};
}
diff --git a/tests/sysrepo_ietf-hardware.cpp b/tests/sysrepo_ietf-hardware.cpp
index 605729d..e97aade 100644
--- a/tests/sysrepo_ietf-hardware.cpp
+++ b/tests/sysrepo_ietf-hardware.cpp
@@ -119,6 +119,11 @@
return res;
}
+
+ velia::ietf_hardware::ThresholdsBySensorPath thresholds() const
+ {
+ return {{"/ietf-hardware:hardware/component[name='ne:psu:child']/sensor-data/value", {}}};
+ }
};
ietfHardware->registerDataReader(PsuDataReader{psuActive});