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 48 /** Deserialize a Threshold from JSON. */ 49 void from_json(const json& j, Threshold& self) 50 { 51 self.value = j.value("Value", 100.0); 52 self.log = j.value("Log", false); 53 self.target = j.value("Target", Threshold::defaults::target); 54 } 55 56 /** Deserialize a HealthMetric from JSON. */ 57 void from_json(const json& j, HealthMetric& self) 58 { 59 self.collectionFreq = std::chrono::seconds(j.value( 60 "Frequency", 61 std::chrono::seconds(HealthMetric::defaults::frequency).count())); 62 63 self.windowSize = j.value("Window_size", 64 HealthMetric::defaults::windowSize); 65 // Path is only valid for storage 66 self.path = j.value("Path", ""); 67 68 auto thresholds = j.find("Threshold"); 69 if (thresholds == j.end()) 70 { 71 return; 72 } 73 74 for (auto& [key, value] : thresholds->items()) 75 { 76 if (!validThresholdTypes.contains(key)) 77 { 78 warning("Invalid ThresholdType: {TYPE}", "TYPE", key); 79 continue; 80 } 81 82 auto config = value.template get<Threshold>(); 83 if (!std::isfinite(config.value)) 84 { 85 throw std::invalid_argument("Invalid threshold value"); 86 } 87 88 // ThresholdIntf::Bound::Upper is the only use case for 89 // ThresholdIntf::Bound 90 self.thresholds.emplace(std::make_tuple(validThresholdTypes.at(key), 91 ThresholdIntf::Bound::Upper), 92 config); 93 } 94 } 95 96 json parseConfigFile(std::string configFile) 97 { 98 std::ifstream jsonFile(configFile); 99 if (!jsonFile.is_open()) 100 { 101 info("config JSON file not found: {PATH}", "PATH", configFile); 102 return {}; 103 } 104 105 try 106 { 107 return json::parse(jsonFile, nullptr, true); 108 } 109 catch (const json::parse_error& e) 110 { 111 error("Failed to parse JSON config file {PATH}: {ERROR}", "PATH", 112 configFile, "ERROR", e); 113 } 114 115 return {}; 116 } 117 118 void printConfig(HealthMetric::map_t& configs) 119 { 120 for (auto& [type, configList] : configs) 121 { 122 for (auto& config : configList) 123 { 124 debug( 125 "MTYPE={MTYPE}, MNAME={MNAME} MSTYPE={MSTYPE} PATH={PATH}, FREQ={FREQ}, WSIZE={WSIZE}", 126 "MTYPE", std::to_underlying(type), "MNAME", config.name, 127 "MSTYPE", std::to_underlying(config.subType), "PATH", 128 config.path, "FREQ", config.collectionFreq.count(), "WSIZE", 129 config.windowSize); 130 131 for (auto& [key, threshold] : config.thresholds) 132 { 133 debug( 134 "THRESHOLD TYPE={TYPE} THRESHOLD BOUND={BOUND} VALUE={VALUE} LOG={LOG} TARGET={TARGET}", 135 "TYPE", std::to_underlying(get<ThresholdIntf::Type>(key)), 136 "BOUND", std::to_underlying(get<ThresholdIntf::Bound>(key)), 137 "VALUE", threshold.value, "LOG", threshold.log, "TARGET", 138 threshold.target); 139 } 140 } 141 } 142 } 143 144 auto getHealthMetricConfigs() -> HealthMetric::map_t 145 { 146 json mergedConfig(defaultHealthMetricConfig); 147 148 if (auto platformConfig = parseConfigFile(HEALTH_CONFIG_FILE); 149 !platformConfig.empty()) 150 { 151 mergedConfig.merge_patch(platformConfig); 152 } 153 154 HealthMetric::map_t configs = {}; 155 for (auto& [name, metric] : mergedConfig.items()) 156 { 157 static constexpr auto nameDelimiter = "_"; 158 std::string typeStr = name.substr(0, name.find_first_of(nameDelimiter)); 159 160 auto type = validTypes.find(typeStr); 161 if (type == validTypes.end()) 162 { 163 warning("Invalid metric type: {TYPE}", "TYPE", typeStr); 164 continue; 165 } 166 167 auto config = metric.template get<HealthMetric>(); 168 169 auto subType = validSubTypes.find(name); 170 config.subType = (subType != validSubTypes.end() ? subType->second 171 : SubType::NA); 172 173 configs[type->second].emplace_back(std::move(config)); 174 } 175 printConfig(configs); 176 return configs; 177 } 178 179 json defaultHealthMetricConfig = R"({ 180 "CPU": { 181 "Frequency": 1, 182 "Window_size": 120, 183 "Threshold": { 184 "Critical": { 185 "Value": 90.0, 186 "Log": true, 187 "Target": "" 188 }, 189 "Warning": { 190 "Value": 80.0, 191 "Log": false, 192 "Target": "" 193 } 194 } 195 }, 196 "CPU_User": { 197 "Frequency": 1, 198 "Window_size": 120, 199 "Threshold": { 200 "Critical": { 201 "Value": 90.0, 202 "Log": true, 203 "Target": "" 204 }, 205 "Warning": { 206 "Value": 80.0, 207 "Log": false, 208 "Target": "" 209 } 210 } 211 }, 212 "CPU_Kernel": { 213 "Frequency": 1, 214 "Window_size": 120, 215 "Threshold": { 216 "Critical": { 217 "Value": 90.0, 218 "Log": true, 219 "Target": "" 220 }, 221 "Warning": { 222 "Value": 80.0, 223 "Log": false, 224 "Target": "" 225 } 226 } 227 }, 228 "Memory_Available": { 229 "Frequency": 1, 230 "Window_size": 120, 231 "Threshold": { 232 "Critical": { 233 "Value": 85.0, 234 "Log": true, 235 "Target": "" 236 } 237 } 238 }, 239 "Storage_RW": { 240 "Path": "/run/initramfs/rw", 241 "Frequency": 1, 242 "Window_size": 120, 243 "Threshold": { 244 "Critical": { 245 "Value": 85.0, 246 "Log": true, 247 "Target": "" 248 } 249 } 250 } 251 })"_json; 252 253 } // namespace phosphor::health::metric::config 254