1 #include "config.h" 2 3 #include "health_metric_config.hpp" 4 5 #include <nlohmann/json.hpp> 6 #include <phosphor-logging/lg2.hpp> 7 8 #include <cmath> 9 #include <fstream> 10 #include <ranges> 11 #include <unordered_map> 12 #include <unordered_set> 13 #include <utility> 14 15 PHOSPHOR_LOG2_USING; 16 17 namespace phosphor::health::metric 18 { 19 namespace config 20 { 21 22 using json = nlohmann::json; 23 24 // Default health metric config 25 extern json defaultHealthMetricConfig; 26 27 // Valid thresholds from config 28 static const auto validThresholdTypesWithBound = 29 std::unordered_set<std::string>{"Critical_Lower", "Critical_Upper", 30 "Warning_Lower", "Warning_Upper"}; 31 32 static const auto validThresholdBounds = 33 std::unordered_map<std::string, ThresholdIntf::Bound>{ 34 {"Lower", ThresholdIntf::Bound::Lower}, 35 {"Upper", ThresholdIntf::Bound::Upper}}; 36 37 static const auto validThresholdTypes = 38 std::unordered_map<std::string, ThresholdIntf::Type>{ 39 {"HardShutdown", ThresholdIntf::Type::HardShutdown}, 40 {"SoftShutdown", ThresholdIntf::Type::SoftShutdown}, 41 {"PerformanceLoss", ThresholdIntf::Type::PerformanceLoss}, 42 {"Critical", ThresholdIntf::Type::Critical}, 43 {"Warning", ThresholdIntf::Type::Warning}}; 44 45 // Valid metrics from config 46 static const auto validTypes = 47 std::unordered_map<std::string, Type>{{"CPU", Type::cpu}, 48 {"Memory", Type::memory}, 49 {"Storage", Type::storage}, 50 {"Inode", Type::inode}}; 51 52 // Valid submetrics from config 53 static const auto validSubTypes = std::unordered_map<std::string, SubType>{ 54 {"CPU", SubType::cpuTotal}, 55 {"CPU_User", SubType::cpuUser}, 56 {"CPU_Kernel", SubType::cpuKernel}, 57 {"Memory", SubType::memoryTotal}, 58 {"Memory_Free", SubType::memoryFree}, 59 {"Memory_Available", SubType::memoryAvailable}, 60 {"Memory_Shared", SubType::memoryShared}, 61 {"Memory_Buffered_And_Cached", SubType::memoryBufferedAndCached}, 62 {"Storage_RW", SubType::NA}, 63 {"Storage_TMP", SubType::NA}}; 64 65 /** Deserialize a Threshold from JSON. */ 66 void from_json(const json& j, Threshold& self) 67 { 68 self.value = j.value("Value", 100.0); 69 self.log = j.value("Log", false); 70 self.target = j.value("Target", Threshold::defaults::target); 71 } 72 73 /** Deserialize a HealthMetric from JSON. */ 74 void from_json(const json& j, HealthMetric& self) 75 { 76 self.windowSize = j.value("Window_size", 77 HealthMetric::defaults::windowSize); 78 self.hysteresis = j.value("Hysteresis", HealthMetric::defaults::hysteresis); 79 // Path is only valid for storage 80 self.path = j.value("Path", ""); 81 82 auto thresholds = j.find("Threshold"); 83 if (thresholds == j.end()) 84 { 85 return; 86 } 87 88 for (auto& [key, value] : thresholds->items()) 89 { 90 if (!validThresholdTypesWithBound.contains(key)) 91 { 92 warning("Invalid ThresholdType: {TYPE}", "TYPE", key); 93 continue; 94 } 95 96 auto config = value.template get<Threshold>(); 97 if (!std::isfinite(config.value)) 98 { 99 throw std::invalid_argument("Invalid threshold value"); 100 } 101 102 static constexpr auto keyDelimiter = "_"; 103 std::string typeStr = key.substr(0, key.find_first_of(keyDelimiter)); 104 std::string boundStr = key.substr(key.find_last_of(keyDelimiter) + 1, 105 key.length()); 106 107 self.thresholds.emplace( 108 std::make_tuple(validThresholdTypes.at(typeStr), 109 validThresholdBounds.at(boundStr)), 110 config); 111 } 112 } 113 114 json parseConfigFile(std::string configFile) 115 { 116 std::ifstream jsonFile(configFile); 117 if (!jsonFile.is_open()) 118 { 119 info("config JSON file not found: {PATH}", "PATH", configFile); 120 return {}; 121 } 122 123 try 124 { 125 return json::parse(jsonFile, nullptr, true); 126 } 127 catch (const json::parse_error& e) 128 { 129 error("Failed to parse JSON config file {PATH}: {ERROR}", "PATH", 130 configFile, "ERROR", e); 131 } 132 133 return {}; 134 } 135 136 void printConfig(HealthMetric::map_t& configs) 137 { 138 for (auto& [type, configList] : configs) 139 { 140 for (auto& config : configList) 141 { 142 debug( 143 "TYPE={TYPE}, NAME={NAME} SUBTYPE={SUBTYPE} PATH={PATH}, WSIZE={WSIZE}, HYSTERESIS={HYSTERESIS}", 144 "TYPE", type, "NAME", config.name, "SUBTYPE", config.subType, 145 "PATH", config.path, "WSIZE", config.windowSize, "HYSTERESIS", 146 config.hysteresis); 147 148 for (auto& [key, threshold] : config.thresholds) 149 { 150 debug( 151 "THRESHOLD TYPE={TYPE} THRESHOLD BOUND={BOUND} VALUE={VALUE} LOG={LOG} TARGET={TARGET}", 152 "TYPE", get<ThresholdIntf::Type>(key), "BOUND", 153 get<ThresholdIntf::Bound>(key), "VALUE", threshold.value, 154 "LOG", threshold.log, "TARGET", threshold.target); 155 } 156 } 157 } 158 } 159 160 auto getHealthMetricConfigs() -> HealthMetric::map_t 161 { 162 json mergedConfig(defaultHealthMetricConfig); 163 164 if (auto platformConfig = parseConfigFile(HEALTH_CONFIG_FILE); 165 !platformConfig.empty()) 166 { 167 mergedConfig.merge_patch(platformConfig); 168 } 169 170 HealthMetric::map_t configs = {}; 171 for (auto& [name, metric] : mergedConfig.items()) 172 { 173 static constexpr auto nameDelimiter = "_"; 174 std::string typeStr = name.substr(0, name.find_first_of(nameDelimiter)); 175 176 auto type = validTypes.find(typeStr); 177 if (type == validTypes.end()) 178 { 179 warning("Invalid metric type: {TYPE}", "TYPE", typeStr); 180 continue; 181 } 182 183 auto config = metric.template get<HealthMetric>(); 184 config.name = name; 185 186 auto subType = validSubTypes.find(name); 187 config.subType = (subType != validSubTypes.end() ? subType->second 188 : SubType::NA); 189 190 configs[type->second].emplace_back(std::move(config)); 191 } 192 printConfig(configs); 193 return configs; 194 } 195 196 json defaultHealthMetricConfig = R"({ 197 "CPU": { 198 "Window_size": 120, 199 "Hysteresis": 1.0, 200 "Threshold": { 201 "Critical_Upper": { 202 "Value": 90.0, 203 "Log": true, 204 "Target": "" 205 }, 206 "Warning_Upper": { 207 "Value": 80.0, 208 "Log": false, 209 "Target": "" 210 } 211 } 212 }, 213 "CPU_User": { 214 "Window_size": 120, 215 "Hysteresis": 1.0 216 }, 217 "CPU_Kernel": { 218 "Window_size": 120, 219 "Hysteresis": 1.0 220 }, 221 "Memory": { 222 "Window_size": 120, 223 "Hysteresis": 1.0 224 }, 225 "Memory_Available": { 226 "Window_size": 120, 227 "Hysteresis": 1.0, 228 "Threshold": { 229 "Critical_Lower": { 230 "Value": 15.0, 231 "Log": true, 232 "Target": "" 233 } 234 } 235 }, 236 "Memory_Free": { 237 "Window_size": 120, 238 "Hysteresis": 1.0 239 }, 240 "Memory_Shared": { 241 "Window_size": 120, 242 "Hysteresis": 1.0, 243 "Threshold": { 244 "Critical_Upper": { 245 "Value": 85.0, 246 "Log": true, 247 "Target": "" 248 } 249 } 250 }, 251 "Memory_Buffered_And_Cached": { 252 "Window_size": 120, 253 "Hysteresis": 1.0 254 }, 255 "Storage_RW": { 256 "Path": "/run/initramfs/rw", 257 "Window_size": 120, 258 "Hysteresis": 1.0, 259 "Threshold": { 260 "Critical_Lower": { 261 "Value": 15.0, 262 "Log": true, 263 "Target": "" 264 } 265 } 266 }, 267 "Storage_TMP": { 268 "Path": "/tmp", 269 "Window_size": 120, 270 "Hysteresis": 1.0, 271 "Threshold": { 272 "Critical_Lower": { 273 "Value": 15.0, 274 "Log": true, 275 "Target": "" 276 } 277 } 278 } 279 })"_json; 280 281 } // namespace config 282 283 namespace details 284 { 285 auto reverse_map_search(const auto& m, auto v) 286 { 287 if (auto match = std::ranges::find_if( 288 m, [=](const auto& p) { return p.second == v; }); 289 match != std::end(m)) 290 { 291 return match->first; 292 } 293 return std::format("Enum({})", std::to_underlying(v)); 294 } 295 } // namespace details 296 297 // to_string specialization for Type. 298 auto to_string(Type t) -> std::string 299 { 300 return details::reverse_map_search(config::validTypes, t); 301 } 302 303 // to_string specializaiton for SubType. 304 auto to_string(SubType t) -> std::string 305 { 306 return details::reverse_map_search(config::validSubTypes, t); 307 } 308 309 } // namespace phosphor::health::metric 310