#include "config.h" #include "health_metric_config.hpp" #include #include #include #include #include #include PHOSPHOR_LOG2_USING; namespace phosphor::health::metric::config { using json = nlohmann::json; // Default health metric config extern json defaultHealthMetricConfig; // Valid thresholds from config static const auto validThresholdTypes = std::unordered_map{ {"Critical", ThresholdIntf::Type::Critical}, {"Warning", ThresholdIntf::Type::Warning}}; // Valid metrics from config static const auto validTypes = std::unordered_map{{"CPU", Type::cpu}, {"Memory", Type::memory}, {"Storage", Type::storage}, {"Inode", Type::inode}}; // Valid submetrics from config static const auto validSubTypes = std::unordered_map{ {"CPU", SubType::cpuTotal}, {"CPU_User", SubType::cpuUser}, {"CPU_Kernel", SubType::cpuKernel}, {"Memory", SubType::memoryTotal}, {"Memory_Free", SubType::memoryFree}, {"Memory_Available", SubType::memoryAvailable}, {"Memory_Shared", SubType::memoryShared}, {"Memory_Buffered_And_Cached", SubType::memoryBufferedAndCached}, {"Storage_RW", SubType::storageReadWrite}, {"Storage_TMP", SubType::storageReadWrite}}; /** Deserialize a Threshold from JSON. */ void from_json(const json& j, Threshold& self) { self.value = j.value("Value", 100.0); self.log = j.value("Log", false); self.target = j.value("Target", Threshold::defaults::target); } /** Deserialize a HealthMetric from JSON. */ void from_json(const json& j, HealthMetric& self) { self.collectionFreq = std::chrono::seconds(j.value( "Frequency", std::chrono::seconds(HealthMetric::defaults::frequency).count())); self.windowSize = j.value("Window_size", HealthMetric::defaults::windowSize); // Path is only valid for storage self.path = j.value("Path", ""); auto thresholds = j.find("Threshold"); if (thresholds == j.end()) { return; } for (auto& [key, value] : thresholds->items()) { if (!validThresholdTypes.contains(key)) { warning("Invalid ThresholdType: {TYPE}", "TYPE", key); continue; } auto config = value.template get(); if (!std::isfinite(config.value)) { throw std::invalid_argument("Invalid threshold value"); } // ThresholdIntf::Bound::Upper is the only use case for // ThresholdIntf::Bound self.thresholds.emplace(std::make_tuple(validThresholdTypes.at(key), ThresholdIntf::Bound::Upper), config); } } json parseConfigFile(std::string configFile) { std::ifstream jsonFile(configFile); if (!jsonFile.is_open()) { info("config JSON file not found: {PATH}", "PATH", configFile); return {}; } try { return json::parse(jsonFile, nullptr, true); } catch (const json::parse_error& e) { error("Failed to parse JSON config file {PATH}: {ERROR}", "PATH", configFile, "ERROR", e); } return {}; } void printConfig(HealthMetric::map_t& configs) { for (auto& [type, configList] : configs) { for (auto& config : configList) { debug( "MTYPE={MTYPE}, MNAME={MNAME} MSTYPE={MSTYPE} PATH={PATH}, FREQ={FREQ}, WSIZE={WSIZE}", "MTYPE", std::to_underlying(type), "MNAME", config.name, "MSTYPE", std::to_underlying(config.subType), "PATH", config.path, "FREQ", config.collectionFreq.count(), "WSIZE", config.windowSize); for (auto& [key, threshold] : config.thresholds) { debug( "THRESHOLD TYPE={TYPE} THRESHOLD BOUND={BOUND} VALUE={VALUE} LOG={LOG} TARGET={TARGET}", "TYPE", std::to_underlying(get(key)), "BOUND", std::to_underlying(get(key)), "VALUE", threshold.value, "LOG", threshold.log, "TARGET", threshold.target); } } } } auto getHealthMetricConfigs() -> HealthMetric::map_t { json mergedConfig(defaultHealthMetricConfig); if (auto platformConfig = parseConfigFile(HEALTH_CONFIG_FILE); !platformConfig.empty()) { mergedConfig.merge_patch(platformConfig); } HealthMetric::map_t configs = {}; for (auto& [name, metric] : mergedConfig.items()) { static constexpr auto nameDelimiter = "_"; std::string typeStr = name.substr(0, name.find_first_of(nameDelimiter)); auto type = validTypes.find(typeStr); if (type == validTypes.end()) { warning("Invalid metric type: {TYPE}", "TYPE", typeStr); continue; } auto config = metric.template get(); auto subType = validSubTypes.find(name); config.subType = (subType != validSubTypes.end() ? subType->second : SubType::NA); configs[type->second].emplace_back(std::move(config)); } printConfig(configs); return configs; } json defaultHealthMetricConfig = R"({ "CPU": { "Frequency": 1, "Window_size": 120, "Threshold": { "Critical": { "Value": 90.0, "Log": true, "Target": "" }, "Warning": { "Value": 80.0, "Log": false, "Target": "" } } }, "CPU_User": { "Frequency": 1, "Window_size": 120, "Threshold": { "Critical": { "Value": 90.0, "Log": true, "Target": "" }, "Warning": { "Value": 80.0, "Log": false, "Target": "" } } }, "CPU_Kernel": { "Frequency": 1, "Window_size": 120, "Threshold": { "Critical": { "Value": 90.0, "Log": true, "Target": "" }, "Warning": { "Value": 80.0, "Log": false, "Target": "" } } }, "Memory_Available": { "Frequency": 1, "Window_size": 120, "Threshold": { "Critical": { "Value": 85.0, "Log": true, "Target": "" } } }, "Storage_RW": { "Path": "/run/initramfs/rw", "Frequency": 1, "Window_size": 120, "Threshold": { "Critical": { "Value": 85.0, "Log": true, "Target": "" } } }, "Storage_TMP": { "Path": "/tmp", "Frequency": 1, "Window_size": 120, "Threshold": { "Critical": { "Value": 85.0, "Log": true, "Target": "" } } } })"_json; } // namespace phosphor::health::metric::config