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