blob: 4c391cd7c95df8175e7a43d2dea1c90364ca304c [file] [log] [blame]
#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;
}
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<double>::quiet_NaN(), true);
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, std::numeric_limits<double>::quiet_NaN());
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, MValue value)
{
auto threshold = std::make_tuple(type, bound);
auto thresholds = ThresholdIntf::value();
if (thresholds.contains(type) && thresholds[type].contains(bound))
{
auto tConfig = config.thresholds.at(threshold);
auto thresholdValue = tConfig.value / 100 * value.total;
thresholds[type][bound] = thresholdValue;
ThresholdIntf::value(thresholds);
auto assertions = ThresholdIntf::asserted();
if (didThresholdViolate(bound, thresholdValue, value.current))
{
if (!assertions.contains(threshold))
{
assertions.insert(threshold);
ThresholdIntf::asserted(assertions);
ThresholdIntf::assertionChanged(type, bound, true,
value.current);
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.current);
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(MValue 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.current);
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);
}
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