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