blob: 04ee169a75e16a4d4462a3e8e2699cebe92733ff [file] [log] [blame]
Jagpal Singh Gill7e11ab02023-12-08 11:41:41 -08001#include "config.h"
2
3#include "health_metric_config.hpp"
4
5#include <nlohmann/json.hpp>
6#include <phosphor-logging/lg2.hpp>
7
Jagpal Singh Gill23f091e2023-12-10 15:23:19 -08008#include <cmath>
Jagpal Singh Gill7e11ab02023-12-08 11:41:41 -08009#include <fstream>
10#include <unordered_map>
11#include <utility>
12
13PHOSPHOR_LOG2_USING;
14
15namespace phosphor::health::metric::config
16{
17
18using json = nlohmann::json;
19
20// Default health metric config
21extern json defaultHealthMetricConfig;
22
23// Valid thresholds from config
24static 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
30static 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
37static 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},
Jagpal Singh Gill7f3fd6e2024-02-12 16:20:00 -080046 {"Storage_RW", SubType::storageReadWrite},
Jagpal Singh Gilldfe839f2024-02-16 09:54:02 -080047 {"Storage_TMP", SubType::storageTmp}};
Jagpal Singh Gill7e11ab02023-12-08 11:41:41 -080048
49/** Deserialize a Threshold from JSON. */
50void 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. */
58void 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>();
Jagpal Singh Gill23f091e2023-12-10 15:23:19 -080084 if (!std::isfinite(config.value))
85 {
86 throw std::invalid_argument("Invalid threshold value");
87 }
Jagpal Singh Gill7e11ab02023-12-08 11:41:41 -080088
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
97json 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
119void 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
145auto 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>();
Jagpal Singh Gill1f920052024-02-16 09:57:18 -0800169 config.name = name;
Jagpal Singh Gill7e11ab02023-12-08 11:41:41 -0800170
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
181json 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 },
Jagpal Singh Gillc6897812024-02-16 09:59:47 -0800241 "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 },
Jagpal Singh Gill7e11ab02023-12-08 11:41:41 -0800263 "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 }
Jagpal Singh Gill7f3fd6e2024-02-12 16:20:00 -0800274 },
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 }
Jagpal Singh Gill7e11ab02023-12-08 11:41:41 -0800286 }
287})"_json;
288
289} // namespace phosphor::health::metric::config