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