add health_metric implementation
Add the interface and implementation for the health_metric to be used
in the rewrite for phosphor-health-monitor.
This change is in relation to following design and D-Bus interface
update -
https://gerrit.openbmc.org/c/openbmc/docs/+/64917
https://gerrit.openbmc.org/c/openbmc/phosphor-dbus-interfaces/+/64914
gtest added for UT.
Change-Id: Iffcc52f9dff712890377b1222fd7e7d5d6661eaf
Signed-off-by: Jagpal Singh Gill <paligill@gmail.com>
diff --git a/health_metric.cpp b/health_metric.cpp
new file mode 100644
index 0000000..2c82fea
--- /dev/null
+++ b/health_metric.cpp
@@ -0,0 +1,216 @@
+#include "health_metric.hpp"
+
+#include <phosphor-logging/lg2.hpp>
+
+#include <numeric>
+#include <unordered_map>
+
+PHOSPHOR_LOG2_USING;
+
+namespace phosphor::health::metric
+{
+
+using association_t = std::tuple<std::string, std::string, std::string>;
+
+auto HealthMetric::getPath(SubType subType) -> std::string
+{
+ std::string path;
+ switch (subType)
+ {
+ case SubType::cpuTotal:
+ {
+ return std::string(BmcPath) + "/" + PathIntf::total_cpu;
+ }
+ case SubType::cpuKernel:
+ {
+ return std::string(BmcPath) + "/" + PathIntf::kernel_cpu;
+ }
+ case SubType::cpuUser:
+ {
+ return std::string(BmcPath) + "/" + PathIntf::user_cpu;
+ }
+ case SubType::memoryAvailable:
+ {
+ return std::string(BmcPath) + "/" + PathIntf::available_memory;
+ }
+ case SubType::memoryBufferedAndCached:
+ {
+ return std::string(BmcPath) + "/" +
+ PathIntf::buffered_and_cached_memory;
+ }
+ case SubType::memoryFree:
+ {
+ return std::string(BmcPath) + "/" + PathIntf::free_memory;
+ }
+ case SubType::memoryShared:
+ {
+ return std::string(BmcPath) + "/" + PathIntf::shared_memory;
+ }
+ case SubType::memoryTotal:
+ {
+ return std::string(BmcPath) + "/" + PathIntf::total_memory;
+ }
+ case SubType::storageReadWrite:
+ {
+ return std::string(BmcPath) + "/" + PathIntf::read_write_storage;
+ }
+ default:
+ {
+ error("Invalid Memory metric {TYPE}", "TYPE",
+ std::to_underlying(subType));
+ return "";
+ }
+ }
+}
+
+void HealthMetric::initProperties()
+{
+ switch (config.subType)
+ {
+ case SubType::cpuTotal:
+ case SubType::cpuKernel:
+ case SubType::cpuUser:
+ {
+ ValueIntf::unit(ValueIntf::Unit::Percent, true);
+ ValueIntf::minValue(0.0, true);
+ ValueIntf::maxValue(100.0, true);
+ break;
+ }
+ case SubType::memoryAvailable:
+ case SubType::memoryBufferedAndCached:
+ case SubType::memoryFree:
+ case SubType::memoryShared:
+ case SubType::memoryTotal:
+ case SubType::storageReadWrite:
+ default:
+ {
+ ValueIntf::unit(ValueIntf::Unit::Bytes, true);
+ ValueIntf::minValue(0.0, true);
+ }
+ }
+ ValueIntf::value(std::numeric_limits<double>::quiet_NaN());
+
+ using bound_map_t = std::map<ThresholdIntf::Bound, double>;
+ std::map<ThresholdIntf::Type, bound_map_t> thresholds;
+ for (const auto& [key, value] : config.thresholds)
+ {
+ auto type = std::get<ThresholdIntf::Type>(key);
+ auto bound = std::get<ThresholdIntf::Bound>(key);
+ auto threshold = thresholds.find(type);
+ if (threshold == thresholds.end())
+ {
+ bound_map_t bounds;
+ bounds.emplace(bound, value.value);
+ thresholds.emplace(type, bounds);
+ }
+ else
+ {
+ threshold->second.emplace(bound, value.value);
+ }
+ }
+ ThresholdIntf::value(thresholds);
+}
+
+void HealthMetric::checkThreshold(ThresholdIntf::Type type,
+ ThresholdIntf::Bound bound, double value)
+{
+ auto threshold = std::make_tuple(type, bound);
+ auto thresholds = ThresholdIntf::value();
+
+ if (thresholds.contains(type) && thresholds[type].contains(bound))
+ {
+ auto thresholdValue = thresholds[type][bound];
+ auto assertions = ThresholdIntf::asserted();
+ if (value > thresholdValue)
+ {
+ if (!assertions.contains(threshold))
+ {
+ assertions.insert(threshold);
+ ThresholdIntf::asserted(assertions);
+ ThresholdIntf::assertionChanged(type, bound, true, value);
+ auto tConfig = config.thresholds.at(threshold);
+ if (tConfig.log)
+ {
+ error(
+ "ASSERT: Health Metric {METRIC} crossed {TYPE} upper threshold",
+ "METRIC", config.name, "TYPE",
+ sdbusplus::message::convert_to_string(type));
+ startUnit(bus, tConfig.target);
+ }
+ }
+ return;
+ }
+ else if (assertions.contains(threshold))
+ {
+ assertions.erase(threshold);
+ ThresholdIntf::asserted(assertions);
+ ThresholdIntf::assertionChanged(type, bound, false, value);
+ if (config.thresholds.find(threshold)->second.log)
+ {
+ info(
+ "DEASSERT: Health Metric {METRIC} is below {TYPE} upper threshold",
+ "METRIC", config.name, "TYPE",
+ sdbusplus::message::convert_to_string(type));
+ }
+ }
+ }
+}
+
+void HealthMetric::checkThresholds(double value)
+{
+ if (!ThresholdIntf::value().empty())
+ {
+ for (auto type :
+ {ThresholdIntf::Type::HardShutdown,
+ ThresholdIntf::Type::SoftShutdown,
+ ThresholdIntf::Type::PerformanceLoss,
+ ThresholdIntf::Type::Critical, ThresholdIntf::Type::Warning})
+ {
+ checkThreshold(type, ThresholdIntf::Bound::Upper, value);
+ }
+ }
+}
+
+void HealthMetric::update(MValue value)
+{
+ // Maintain window size for metric
+ if (history.size() >= config.windowSize)
+ {
+ history.pop_front();
+ }
+ history.push_back(value.user);
+
+ if (history.size() < config.windowSize)
+ {
+ // Wait for the metric to have enough samples to calculate average
+ info("Not enough samples to calculate average");
+ return;
+ }
+
+ double average = (std::accumulate(history.begin(), history.end(), 0.0)) /
+ history.size();
+ ValueIntf::value(average);
+ checkThresholds(value.monitor);
+}
+
+void HealthMetric::create(const paths_t& bmcPaths)
+{
+ info("Create Health Metric: {METRIC}", "METRIC", config.name);
+ initProperties();
+
+ std::vector<association_t> associations;
+ static constexpr auto forwardAssociation = "measuring";
+ static constexpr auto reverseAssociation = "measured_by";
+ for (const auto& bmcPath : bmcPaths)
+ {
+ /*
+ * This metric is "measuring" the health for the BMC at bmcPath
+ * The BMC at bmcPath is "measured_by" this metric.
+ */
+ associations.push_back(
+ {forwardAssociation, reverseAssociation, bmcPath});
+ }
+ AssociationIntf::associations(associations);
+}
+
+} // namespace phosphor::health::metric