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(SubType subType) -> std::string 16 { 17 std::string path; 18 switch (subType) 19 { 20 case SubType::cpuTotal: 21 { 22 return std::string(BmcPath) + "/" + PathIntf::total_cpu; 23 } 24 case SubType::cpuKernel: 25 { 26 return std::string(BmcPath) + "/" + PathIntf::kernel_cpu; 27 } 28 case SubType::cpuUser: 29 { 30 return std::string(BmcPath) + "/" + PathIntf::user_cpu; 31 } 32 case SubType::memoryAvailable: 33 { 34 return std::string(BmcPath) + "/" + PathIntf::available_memory; 35 } 36 case SubType::memoryBufferedAndCached: 37 { 38 return std::string(BmcPath) + "/" + 39 PathIntf::buffered_and_cached_memory; 40 } 41 case SubType::memoryFree: 42 { 43 return std::string(BmcPath) + "/" + PathIntf::free_memory; 44 } 45 case SubType::memoryShared: 46 { 47 return std::string(BmcPath) + "/" + PathIntf::shared_memory; 48 } 49 case SubType::memoryTotal: 50 { 51 return std::string(BmcPath) + "/" + PathIntf::total_memory; 52 } 53 case SubType::storageReadWrite: 54 { 55 return std::string(BmcPath) + "/" + PathIntf::read_write_storage; 56 } 57 default: 58 { 59 error("Invalid Memory metric {TYPE}", "TYPE", 60 std::to_underlying(subType)); 61 return ""; 62 } 63 } 64 } 65 66 void HealthMetric::initProperties() 67 { 68 switch (config.subType) 69 { 70 case SubType::cpuTotal: 71 case SubType::cpuKernel: 72 case SubType::cpuUser: 73 { 74 ValueIntf::unit(ValueIntf::Unit::Percent, true); 75 ValueIntf::minValue(0.0, true); 76 ValueIntf::maxValue(100.0, true); 77 break; 78 } 79 case SubType::memoryAvailable: 80 case SubType::memoryBufferedAndCached: 81 case SubType::memoryFree: 82 case SubType::memoryShared: 83 case SubType::memoryTotal: 84 case SubType::storageReadWrite: 85 default: 86 { 87 ValueIntf::unit(ValueIntf::Unit::Bytes, true); 88 ValueIntf::minValue(0.0, true); 89 } 90 } 91 ValueIntf::value(std::numeric_limits<double>::quiet_NaN(), true); 92 93 using bound_map_t = std::map<ThresholdIntf::Bound, double>; 94 std::map<ThresholdIntf::Type, bound_map_t> thresholds; 95 for (const auto& [key, value] : config.thresholds) 96 { 97 auto type = std::get<ThresholdIntf::Type>(key); 98 auto bound = std::get<ThresholdIntf::Bound>(key); 99 auto threshold = thresholds.find(type); 100 if (threshold == thresholds.end()) 101 { 102 bound_map_t bounds; 103 bounds.emplace(bound, value.value); 104 thresholds.emplace(type, bounds); 105 } 106 else 107 { 108 threshold->second.emplace(bound, value.value); 109 } 110 } 111 ThresholdIntf::value(thresholds, true); 112 } 113 114 void HealthMetric::checkThreshold(ThresholdIntf::Type type, 115 ThresholdIntf::Bound bound, double value) 116 { 117 auto threshold = std::make_tuple(type, bound); 118 auto thresholds = ThresholdIntf::value(); 119 120 if (thresholds.contains(type) && thresholds[type].contains(bound)) 121 { 122 auto thresholdValue = thresholds[type][bound]; 123 auto assertions = ThresholdIntf::asserted(); 124 if (value > thresholdValue) 125 { 126 if (!assertions.contains(threshold)) 127 { 128 assertions.insert(threshold); 129 ThresholdIntf::asserted(assertions); 130 ThresholdIntf::assertionChanged(type, bound, true, value); 131 auto tConfig = config.thresholds.at(threshold); 132 if (tConfig.log) 133 { 134 error( 135 "ASSERT: Health Metric {METRIC} crossed {TYPE} upper threshold", 136 "METRIC", config.name, "TYPE", 137 sdbusplus::message::convert_to_string(type)); 138 startUnit(bus, tConfig.target); 139 } 140 } 141 return; 142 } 143 else if (assertions.contains(threshold)) 144 { 145 assertions.erase(threshold); 146 ThresholdIntf::asserted(assertions); 147 ThresholdIntf::assertionChanged(type, bound, false, value); 148 if (config.thresholds.find(threshold)->second.log) 149 { 150 info( 151 "DEASSERT: Health Metric {METRIC} is below {TYPE} upper threshold", 152 "METRIC", config.name, "TYPE", 153 sdbusplus::message::convert_to_string(type)); 154 } 155 } 156 } 157 } 158 159 void HealthMetric::checkThresholds(double value) 160 { 161 if (!ThresholdIntf::value().empty()) 162 { 163 for (auto type : 164 {ThresholdIntf::Type::HardShutdown, 165 ThresholdIntf::Type::SoftShutdown, 166 ThresholdIntf::Type::PerformanceLoss, 167 ThresholdIntf::Type::Critical, ThresholdIntf::Type::Warning}) 168 { 169 checkThreshold(type, ThresholdIntf::Bound::Upper, value); 170 } 171 } 172 } 173 174 void HealthMetric::update(MValue value) 175 { 176 // Maintain window size for metric 177 if (history.size() >= config.windowSize) 178 { 179 history.pop_front(); 180 } 181 history.push_back(value.user); 182 183 if (history.size() < config.windowSize) 184 { 185 // Wait for the metric to have enough samples to calculate average 186 info("Not enough samples to calculate average"); 187 return; 188 } 189 190 double average = (std::accumulate(history.begin(), history.end(), 0.0)) / 191 history.size(); 192 ValueIntf::value(average); 193 checkThresholds(value.monitor); 194 } 195 196 void HealthMetric::create(const paths_t& bmcPaths) 197 { 198 info("Create Health Metric: {METRIC}", "METRIC", config.name); 199 initProperties(); 200 201 std::vector<association_t> associations; 202 static constexpr auto forwardAssociation = "measuring"; 203 static constexpr auto reverseAssociation = "measured_by"; 204 for (const auto& bmcPath : bmcPaths) 205 { 206 /* 207 * This metric is "measuring" the health for the BMC at bmcPath 208 * The BMC at bmcPath is "measured_by" this metric. 209 */ 210 associations.push_back( 211 {forwardAssociation, reverseAssociation, bmcPath}); 212 } 213 AssociationIntf::associations(associations); 214 } 215 216 } // namespace phosphor::health::metric 217