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     {"Storage_TMP", SubType::storageTmp}};
48 
49 /** Deserialize a Threshold from JSON. */
50 void from_json(const json& j, Threshold& self)
51 {
52     self.value = j.value("Value", 100.0);
53     self.log = j.value("Log", false);
54     self.target = j.value("Target", Threshold::defaults::target);
55 }
56 
57 /** Deserialize a HealthMetric from JSON. */
58 void from_json(const json& j, HealthMetric& self)
59 {
60     self.collectionFreq = std::chrono::seconds(j.value(
61         "Frequency",
62         std::chrono::seconds(HealthMetric::defaults::frequency).count()));
63 
64     self.windowSize = j.value("Window_size",
65                               HealthMetric::defaults::windowSize);
66     // Path is only valid for storage
67     self.path = j.value("Path", "");
68 
69     auto thresholds = j.find("Threshold");
70     if (thresholds == j.end())
71     {
72         return;
73     }
74 
75     for (auto& [key, value] : thresholds->items())
76     {
77         if (!validThresholdTypes.contains(key))
78         {
79             warning("Invalid ThresholdType: {TYPE}", "TYPE", key);
80             continue;
81         }
82 
83         auto config = value.template get<Threshold>();
84         if (!std::isfinite(config.value))
85         {
86             throw std::invalid_argument("Invalid threshold value");
87         }
88 
89         // ThresholdIntf::Bound::Upper is the only use case for
90         // ThresholdIntf::Bound
91         self.thresholds.emplace(std::make_tuple(validThresholdTypes.at(key),
92                                                 ThresholdIntf::Bound::Upper),
93                                 config);
94     }
95 }
96 
97 json parseConfigFile(std::string configFile)
98 {
99     std::ifstream jsonFile(configFile);
100     if (!jsonFile.is_open())
101     {
102         info("config JSON file not found: {PATH}", "PATH", configFile);
103         return {};
104     }
105 
106     try
107     {
108         return json::parse(jsonFile, nullptr, true);
109     }
110     catch (const json::parse_error& e)
111     {
112         error("Failed to parse JSON config file {PATH}: {ERROR}", "PATH",
113               configFile, "ERROR", e);
114     }
115 
116     return {};
117 }
118 
119 void printConfig(HealthMetric::map_t& configs)
120 {
121     for (auto& [type, configList] : configs)
122     {
123         for (auto& config : configList)
124         {
125             debug(
126                 "MTYPE={MTYPE}, MNAME={MNAME} MSTYPE={MSTYPE} PATH={PATH}, FREQ={FREQ}, WSIZE={WSIZE}",
127                 "MTYPE", std::to_underlying(type), "MNAME", config.name,
128                 "MSTYPE", std::to_underlying(config.subType), "PATH",
129                 config.path, "FREQ", config.collectionFreq.count(), "WSIZE",
130                 config.windowSize);
131 
132             for (auto& [key, threshold] : config.thresholds)
133             {
134                 debug(
135                     "THRESHOLD TYPE={TYPE} THRESHOLD BOUND={BOUND} VALUE={VALUE} LOG={LOG} TARGET={TARGET}",
136                     "TYPE", std::to_underlying(get<ThresholdIntf::Type>(key)),
137                     "BOUND", std::to_underlying(get<ThresholdIntf::Bound>(key)),
138                     "VALUE", threshold.value, "LOG", threshold.log, "TARGET",
139                     threshold.target);
140             }
141         }
142     }
143 }
144 
145 auto getHealthMetricConfigs() -> HealthMetric::map_t
146 {
147     json mergedConfig(defaultHealthMetricConfig);
148 
149     if (auto platformConfig = parseConfigFile(HEALTH_CONFIG_FILE);
150         !platformConfig.empty())
151     {
152         mergedConfig.merge_patch(platformConfig);
153     }
154 
155     HealthMetric::map_t configs = {};
156     for (auto& [name, metric] : mergedConfig.items())
157     {
158         static constexpr auto nameDelimiter = "_";
159         std::string typeStr = name.substr(0, name.find_first_of(nameDelimiter));
160 
161         auto type = validTypes.find(typeStr);
162         if (type == validTypes.end())
163         {
164             warning("Invalid metric type: {TYPE}", "TYPE", typeStr);
165             continue;
166         }
167 
168         auto config = metric.template get<HealthMetric>();
169         config.name = name;
170 
171         auto subType = validSubTypes.find(name);
172         config.subType = (subType != validSubTypes.end() ? subType->second
173                                                          : SubType::NA);
174 
175         configs[type->second].emplace_back(std::move(config));
176     }
177     printConfig(configs);
178     return configs;
179 }
180 
181 json defaultHealthMetricConfig = R"({
182     "CPU": {
183         "Frequency": 1,
184         "Window_size": 120,
185         "Threshold": {
186             "Critical": {
187                 "Value": 90.0,
188                 "Log": true,
189                 "Target": ""
190             },
191             "Warning": {
192                 "Value": 80.0,
193                 "Log": false,
194                 "Target": ""
195             }
196         }
197     },
198     "CPU_User": {
199         "Frequency": 1,
200         "Window_size": 120,
201         "Threshold": {
202             "Critical": {
203                 "Value": 90.0,
204                 "Log": true,
205                 "Target": ""
206             },
207             "Warning": {
208                 "Value": 80.0,
209                 "Log": false,
210                 "Target": ""
211             }
212         }
213     },
214     "CPU_Kernel": {
215         "Frequency": 1,
216         "Window_size": 120,
217         "Threshold": {
218             "Critical": {
219                 "Value": 90.0,
220                 "Log": true,
221                 "Target": ""
222             },
223             "Warning": {
224                 "Value": 80.0,
225                 "Log": false,
226                 "Target": ""
227             }
228         }
229     },
230     "Memory_Available": {
231         "Frequency": 1,
232         "Window_size": 120,
233         "Threshold": {
234             "Critical": {
235                 "Value": 85.0,
236                 "Log": true,
237                 "Target": ""
238             }
239         }
240     },
241     "Memory_Shared": {
242         "Frequency": 1,
243         "Window_size": 120,
244         "Threshold": {
245             "Critical": {
246                 "Value": 85.0,
247                 "Log": true,
248                 "Target": ""
249             }
250         }
251     },
252     "Memory_Buffered_And_Cached": {
253         "Frequency": 1,
254         "Window_size": 120,
255         "Threshold": {
256             "Critical": {
257                 "Value": 85.0,
258                 "Log": true,
259                 "Target": ""
260             }
261         }
262     },
263     "Storage_RW": {
264         "Path": "/run/initramfs/rw",
265         "Frequency": 1,
266         "Window_size": 120,
267         "Threshold": {
268             "Critical": {
269                 "Value": 85.0,
270                 "Log": true,
271                 "Target": ""
272             }
273         }
274     },
275     "Storage_TMP": {
276         "Path": "/tmp",
277         "Frequency": 1,
278         "Window_size": 120,
279         "Threshold": {
280             "Critical": {
281                 "Value": 85.0,
282                 "Log": true,
283                 "Target": ""
284             }
285         }
286     }
287 })"_json;
288 
289 } // namespace phosphor::health::metric::config
290