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