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 // Path is only valid for storage 79 self.path = j.value("Path", ""); 80 81 auto thresholds = j.find("Threshold"); 82 if (thresholds == j.end()) 83 { 84 return; 85 } 86 87 for (auto& [key, value] : thresholds->items()) 88 { 89 if (!validThresholdTypesWithBound.contains(key)) 90 { 91 warning("Invalid ThresholdType: {TYPE}", "TYPE", key); 92 continue; 93 } 94 95 auto config = value.template get<Threshold>(); 96 if (!std::isfinite(config.value)) 97 { 98 throw std::invalid_argument("Invalid threshold value"); 99 } 100 101 static constexpr auto keyDelimiter = "_"; 102 std::string typeStr = key.substr(0, key.find_first_of(keyDelimiter)); 103 std::string boundStr = key.substr(key.find_last_of(keyDelimiter) + 1, 104 key.length()); 105 106 self.thresholds.emplace( 107 std::make_tuple(validThresholdTypes.at(typeStr), 108 validThresholdBounds.at(boundStr)), 109 config); 110 } 111 } 112 113 json parseConfigFile(std::string configFile) 114 { 115 std::ifstream jsonFile(configFile); 116 if (!jsonFile.is_open()) 117 { 118 info("config JSON file not found: {PATH}", "PATH", configFile); 119 return {}; 120 } 121 122 try 123 { 124 return json::parse(jsonFile, nullptr, true); 125 } 126 catch (const json::parse_error& e) 127 { 128 error("Failed to parse JSON config file {PATH}: {ERROR}", "PATH", 129 configFile, "ERROR", e); 130 } 131 132 return {}; 133 } 134 135 void printConfig(HealthMetric::map_t& configs) 136 { 137 for (auto& [type, configList] : configs) 138 { 139 for (auto& config : configList) 140 { 141 debug( 142 "TYPE={TYPE}, NAME={NAME} SUBTYPE={SUBTYPE} PATH={PATH}, WSIZE={WSIZE}", 143 "TYPE", type, "NAME", config.name, "SUBTYPE", config.subType, 144 "PATH", config.path, "WSIZE", config.windowSize); 145 146 for (auto& [key, threshold] : config.thresholds) 147 { 148 debug( 149 "THRESHOLD TYPE={TYPE} THRESHOLD BOUND={BOUND} VALUE={VALUE} LOG={LOG} TARGET={TARGET}", 150 "TYPE", get<ThresholdIntf::Type>(key), "BOUND", 151 get<ThresholdIntf::Bound>(key), "VALUE", threshold.value, 152 "LOG", threshold.log, "TARGET", threshold.target); 153 } 154 } 155 } 156 } 157 158 auto getHealthMetricConfigs() -> HealthMetric::map_t 159 { 160 json mergedConfig(defaultHealthMetricConfig); 161 162 if (auto platformConfig = parseConfigFile(HEALTH_CONFIG_FILE); 163 !platformConfig.empty()) 164 { 165 mergedConfig.merge_patch(platformConfig); 166 } 167 168 HealthMetric::map_t configs = {}; 169 for (auto& [name, metric] : mergedConfig.items()) 170 { 171 static constexpr auto nameDelimiter = "_"; 172 std::string typeStr = name.substr(0, name.find_first_of(nameDelimiter)); 173 174 auto type = validTypes.find(typeStr); 175 if (type == validTypes.end()) 176 { 177 warning("Invalid metric type: {TYPE}", "TYPE", typeStr); 178 continue; 179 } 180 181 auto config = metric.template get<HealthMetric>(); 182 config.name = name; 183 184 auto subType = validSubTypes.find(name); 185 config.subType = (subType != validSubTypes.end() ? subType->second 186 : SubType::NA); 187 188 configs[type->second].emplace_back(std::move(config)); 189 } 190 printConfig(configs); 191 return configs; 192 } 193 194 json defaultHealthMetricConfig = R"({ 195 "CPU": { 196 "Window_size": 120, 197 "Threshold": { 198 "Critical_Upper": { 199 "Value": 90.0, 200 "Log": true, 201 "Target": "" 202 }, 203 "Warning_Upper": { 204 "Value": 80.0, 205 "Log": false, 206 "Target": "" 207 } 208 } 209 }, 210 "CPU_User": { 211 "Window_size": 120 212 }, 213 "CPU_Kernel": { 214 "Window_size": 120 215 }, 216 "Memory": { 217 "Window_size": 120 218 }, 219 "Memory_Available": { 220 "Window_size": 120, 221 "Threshold": { 222 "Critical_Lower": { 223 "Value": 15.0, 224 "Log": true, 225 "Target": "" 226 } 227 } 228 }, 229 "Memory_Free": { 230 "Window_size": 120 231 }, 232 "Memory_Shared": { 233 "Window_size": 120, 234 "Threshold": { 235 "Critical_Upper": { 236 "Value": 85.0, 237 "Log": true, 238 "Target": "" 239 } 240 } 241 }, 242 "Memory_Buffered_And_Cached": { 243 "Window_size": 120 244 }, 245 "Storage_RW": { 246 "Path": "/run/initramfs/rw", 247 "Window_size": 120, 248 "Threshold": { 249 "Critical_Lower": { 250 "Value": 15.0, 251 "Log": true, 252 "Target": "" 253 } 254 } 255 }, 256 "Storage_TMP": { 257 "Path": "/tmp", 258 "Window_size": 120, 259 "Threshold": { 260 "Critical_Lower": { 261 "Value": 15.0, 262 "Log": true, 263 "Target": "" 264 } 265 } 266 } 267 })"_json; 268 269 } // namespace config 270 271 namespace details 272 { 273 auto reverse_map_search(const auto& m, auto v) 274 { 275 if (auto match = std::ranges::find_if( 276 m, [=](const auto& p) { return p.second == v; }); 277 match != std::end(m)) 278 { 279 return match->first; 280 } 281 return std::format("Enum({})", std::to_underlying(v)); 282 } 283 } // namespace details 284 285 // to_string specialization for Type. 286 auto to_string(Type t) -> std::string 287 { 288 return details::reverse_map_search(config::validTypes, t); 289 } 290 291 // to_string specializaiton for SubType. 292 auto to_string(SubType t) -> std::string 293 { 294 return details::reverse_map_search(config::validSubTypes, t); 295 } 296 297 } // namespace phosphor::health::metric 298