1 #include "health_metric.hpp" 2 3 #include <phosphor-logging/lg2.hpp> 4 5 #include <cmath> 6 #include <numeric> 7 #include <unordered_map> 8 9 PHOSPHOR_LOG2_USING; 10 11 namespace phosphor::health::metric 12 { 13 14 using association_t = std::tuple<std::string, std::string, std::string>; 15 16 auto HealthMetric::getPath(MType type, std::string name, SubType subType) 17 -> std::string 18 { 19 std::string path; 20 switch (subType) 21 { 22 case SubType::cpuTotal: 23 { 24 return std::string(BmcPath) + "/" + PathIntf::total_cpu; 25 } 26 case SubType::cpuKernel: 27 { 28 return std::string(BmcPath) + "/" + PathIntf::kernel_cpu; 29 } 30 case SubType::cpuUser: 31 { 32 return std::string(BmcPath) + "/" + PathIntf::user_cpu; 33 } 34 case SubType::memoryAvailable: 35 { 36 return std::string(BmcPath) + "/" + PathIntf::available_memory; 37 } 38 case SubType::memoryBufferedAndCached: 39 { 40 return std::string(BmcPath) + "/" + 41 PathIntf::buffered_and_cached_memory; 42 } 43 case SubType::memoryFree: 44 { 45 return std::string(BmcPath) + "/" + PathIntf::free_memory; 46 } 47 case SubType::memoryShared: 48 { 49 return std::string(BmcPath) + "/" + PathIntf::shared_memory; 50 } 51 case SubType::memoryTotal: 52 { 53 return std::string(BmcPath) + "/" + PathIntf::total_memory; 54 } 55 case SubType::NA: 56 { 57 if (type == MType::storage) 58 { 59 static constexpr auto nameDelimiter = "_"; 60 auto storageType = name.substr( 61 name.find_last_of(nameDelimiter) + 1, name.length()); 62 std::ranges::for_each(storageType, 63 [](auto& c) { c = std::tolower(c); }); 64 return std::string(BmcPath) + "/" + PathIntf::storage + "/" + 65 storageType; 66 } 67 else 68 { 69 error("Invalid metric {SUBTYPE} for metric {TYPE}", "SUBTYPE", 70 subType, "TYPE", type); 71 return ""; 72 } 73 } 74 default: 75 { 76 error("Invalid metric {SUBTYPE}", "SUBTYPE", subType); 77 return ""; 78 } 79 } 80 } 81 82 void HealthMetric::initProperties() 83 { 84 switch (type) 85 { 86 case MType::cpu: 87 { 88 ValueIntf::unit(ValueIntf::Unit::Percent, true); 89 ValueIntf::minValue(0.0, true); 90 ValueIntf::maxValue(100.0, true); 91 break; 92 } 93 case MType::memory: 94 case MType::storage: 95 { 96 ValueIntf::unit(ValueIntf::Unit::Bytes, true); 97 ValueIntf::minValue(0.0, true); 98 break; 99 } 100 case MType::inode: 101 case MType::unknown: 102 default: 103 { 104 throw std::invalid_argument("Invalid metric type"); 105 } 106 } 107 ValueIntf::value(std::numeric_limits<double>::quiet_NaN(), true); 108 109 using bound_map_t = std::map<Bound, double>; 110 std::map<Type, bound_map_t> thresholds; 111 for (const auto& [key, value] : config.thresholds) 112 { 113 auto type = std::get<Type>(key); 114 auto bound = std::get<Bound>(key); 115 auto threshold = thresholds.find(type); 116 if (threshold == thresholds.end()) 117 { 118 bound_map_t bounds; 119 bounds.emplace(bound, std::numeric_limits<double>::quiet_NaN()); 120 thresholds.emplace(type, bounds); 121 } 122 else 123 { 124 threshold->second.emplace(bound, value.value); 125 } 126 } 127 ThresholdIntf::value(thresholds, true); 128 } 129 130 bool didThresholdViolate(ThresholdIntf::Bound bound, double thresholdValue, 131 double value) 132 { 133 switch (bound) 134 { 135 case ThresholdIntf::Bound::Lower: 136 { 137 return (value < thresholdValue); 138 } 139 case ThresholdIntf::Bound::Upper: 140 { 141 return (value > thresholdValue); 142 } 143 default: 144 { 145 error("Invalid threshold bound {BOUND}", "BOUND", bound); 146 return false; 147 } 148 } 149 } 150 151 void HealthMetric::checkThreshold(Type type, Bound bound, MValue value) 152 { 153 auto threshold = std::make_tuple(type, bound); 154 auto thresholds = ThresholdIntf::value(); 155 156 if (thresholds.contains(type) && thresholds[type].contains(bound)) 157 { 158 auto tConfig = config.thresholds.at(threshold); 159 auto thresholdValue = tConfig.value / 100 * value.total; 160 thresholds[type][bound] = thresholdValue; 161 ThresholdIntf::value(thresholds); 162 auto assertions = ThresholdIntf::asserted(); 163 if (didThresholdViolate(bound, thresholdValue, value.current)) 164 { 165 if (!assertions.contains(threshold)) 166 { 167 assertions.insert(threshold); 168 ThresholdIntf::asserted(assertions); 169 ThresholdIntf::assertionChanged(type, bound, true, 170 value.current); 171 if (tConfig.log) 172 { 173 error( 174 "ASSERT: Health Metric {METRIC} crossed {TYPE} upper threshold", 175 "METRIC", config.name, "TYPE", type); 176 startUnit(bus, tConfig.target); 177 } 178 } 179 return; 180 } 181 else if (assertions.contains(threshold)) 182 { 183 assertions.erase(threshold); 184 ThresholdIntf::asserted(assertions); 185 ThresholdIntf::assertionChanged(type, bound, false, value.current); 186 if (config.thresholds.find(threshold)->second.log) 187 { 188 info( 189 "DEASSERT: Health Metric {METRIC} is below {TYPE} upper threshold", 190 "METRIC", config.name, "TYPE", type); 191 } 192 } 193 } 194 } 195 196 void HealthMetric::checkThresholds(MValue value) 197 { 198 if (!ThresholdIntf::value().empty()) 199 { 200 for (auto type : {Type::HardShutdown, Type::SoftShutdown, 201 Type::PerformanceLoss, Type::Critical, Type::Warning}) 202 { 203 checkThreshold(type, Bound::Lower, value); 204 checkThreshold(type, Bound::Upper, value); 205 } 206 } 207 } 208 209 auto HealthMetric::shouldNotify(MValue value) -> bool 210 { 211 if (std::isnan(value.current)) 212 { 213 return true; 214 } 215 auto changed = std::abs((value.current - lastNotifiedValue) / 216 lastNotifiedValue * 100.0); 217 if (changed >= config.hysteresis) 218 { 219 lastNotifiedValue = value.current; 220 return true; 221 } 222 return false; 223 } 224 225 void HealthMetric::update(MValue value) 226 { 227 ValueIntf::value(value.current, !shouldNotify(value)); 228 229 // Maintain window size for threshold calculation 230 if (history.size() >= config.windowSize) 231 { 232 history.pop_front(); 233 } 234 history.push_back(value.current); 235 236 if (history.size() < config.windowSize) 237 { 238 // Wait for the metric to have enough samples to calculate average 239 return; 240 } 241 242 double average = (std::accumulate(history.begin(), history.end(), 0.0)) / 243 history.size(); 244 value.current = average; 245 checkThresholds(value); 246 } 247 248 void HealthMetric::create(const paths_t& bmcPaths) 249 { 250 info("Create Health Metric: {METRIC}", "METRIC", config.name); 251 initProperties(); 252 253 std::vector<association_t> associations; 254 static constexpr auto forwardAssociation = "measuring"; 255 static constexpr auto reverseAssociation = "measured_by"; 256 for (const auto& bmcPath : bmcPaths) 257 { 258 /* 259 * This metric is "measuring" the health for the BMC at bmcPath 260 * The BMC at bmcPath is "measured_by" this metric. 261 */ 262 associations.push_back( 263 {forwardAssociation, reverseAssociation, bmcPath}); 264 } 265 AssociationIntf::associations(associations); 266 } 267 268 } // namespace phosphor::health::metric 269