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