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