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::storageReadWrite}};
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 
170         auto subType = validSubTypes.find(name);
171         config.subType = (subType != validSubTypes.end() ? subType->second
172                                                          : SubType::NA);
173 
174         configs[type->second].emplace_back(std::move(config));
175     }
176     printConfig(configs);
177     return configs;
178 }
179 
180 json defaultHealthMetricConfig = R"({
181     "CPU": {
182         "Frequency": 1,
183         "Window_size": 120,
184         "Threshold": {
185             "Critical": {
186                 "Value": 90.0,
187                 "Log": true,
188                 "Target": ""
189             },
190             "Warning": {
191                 "Value": 80.0,
192                 "Log": false,
193                 "Target": ""
194             }
195         }
196     },
197     "CPU_User": {
198         "Frequency": 1,
199         "Window_size": 120,
200         "Threshold": {
201             "Critical": {
202                 "Value": 90.0,
203                 "Log": true,
204                 "Target": ""
205             },
206             "Warning": {
207                 "Value": 80.0,
208                 "Log": false,
209                 "Target": ""
210             }
211         }
212     },
213     "CPU_Kernel": {
214         "Frequency": 1,
215         "Window_size": 120,
216         "Threshold": {
217             "Critical": {
218                 "Value": 90.0,
219                 "Log": true,
220                 "Target": ""
221             },
222             "Warning": {
223                 "Value": 80.0,
224                 "Log": false,
225                 "Target": ""
226             }
227         }
228     },
229     "Memory_Available": {
230         "Frequency": 1,
231         "Window_size": 120,
232         "Threshold": {
233             "Critical": {
234                 "Value": 85.0,
235                 "Log": true,
236                 "Target": ""
237             }
238         }
239     },
240     "Storage_RW": {
241         "Path": "/run/initramfs/rw",
242         "Frequency": 1,
243         "Window_size": 120,
244         "Threshold": {
245             "Critical": {
246                 "Value": 85.0,
247                 "Log": true,
248                 "Target": ""
249             }
250         }
251     },
252     "Storage_TMP": {
253         "Path": "/tmp",
254         "Frequency": 1,
255         "Window_size": 120,
256         "Threshold": {
257             "Critical": {
258                 "Value": 85.0,
259                 "Log": true,
260                 "Target": ""
261             }
262         }
263     }
264 })"_json;
265 
266 } // namespace phosphor::health::metric::config
267