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