#include "health_metric.hpp" #include #include #include PHOSPHOR_LOG2_USING; namespace phosphor::health::metric { using association_t = std::tuple; 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; } case SubType::storageTmp: { return std::string(BmcPath) + "/" + PathIntf::tmp_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::quiet_NaN(), true); using bound_map_t = std::map; std::map thresholds; for (const auto& [key, value] : config.thresholds) { auto type = std::get(key); auto bound = std::get(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, true); } bool didThresholdViolate(ThresholdIntf::Bound bound, double thresholdValue, double value) { switch (bound) { case ThresholdIntf::Bound::Lower: { return (value < thresholdValue); } case ThresholdIntf::Bound::Upper: { return (value > thresholdValue); } default: { error("Invalid threshold bound {BOUND}", "BOUND", std::to_underlying(bound)); return false; } } } 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 (didThresholdViolate(bound, thresholdValue, value)) { 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::Lower, value); 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 debug("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 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