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::storageReadWrite}}; 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 170 auto subType = validSubTypes.find(name); 171 config.subType = (subType != validSubTypes.end() ? subType->second 172 : SubType::NA); 173 174 configs[type->second].emplace_back(std::move(config)); 175 } 176 printConfig(configs); 177 return configs; 178 } 179 180 json defaultHealthMetricConfig = R"({ 181 "CPU": { 182 "Frequency": 1, 183 "Window_size": 120, 184 "Threshold": { 185 "Critical": { 186 "Value": 90.0, 187 "Log": true, 188 "Target": "" 189 }, 190 "Warning": { 191 "Value": 80.0, 192 "Log": false, 193 "Target": "" 194 } 195 } 196 }, 197 "CPU_User": { 198 "Frequency": 1, 199 "Window_size": 120, 200 "Threshold": { 201 "Critical": { 202 "Value": 90.0, 203 "Log": true, 204 "Target": "" 205 }, 206 "Warning": { 207 "Value": 80.0, 208 "Log": false, 209 "Target": "" 210 } 211 } 212 }, 213 "CPU_Kernel": { 214 "Frequency": 1, 215 "Window_size": 120, 216 "Threshold": { 217 "Critical": { 218 "Value": 90.0, 219 "Log": true, 220 "Target": "" 221 }, 222 "Warning": { 223 "Value": 80.0, 224 "Log": false, 225 "Target": "" 226 } 227 } 228 }, 229 "Memory_Available": { 230 "Frequency": 1, 231 "Window_size": 120, 232 "Threshold": { 233 "Critical": { 234 "Value": 85.0, 235 "Log": true, 236 "Target": "" 237 } 238 } 239 }, 240 "Storage_RW": { 241 "Path": "/run/initramfs/rw", 242 "Frequency": 1, 243 "Window_size": 120, 244 "Threshold": { 245 "Critical": { 246 "Value": 85.0, 247 "Log": true, 248 "Target": "" 249 } 250 } 251 }, 252 "Storage_TMP": { 253 "Path": "/tmp", 254 "Frequency": 1, 255 "Window_size": 120, 256 "Threshold": { 257 "Critical": { 258 "Value": 85.0, 259 "Log": true, 260 "Target": "" 261 } 262 } 263 } 264 })"_json; 265 266 } // namespace phosphor::health::metric::config 267