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