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