1 #include "health_metric.hpp"
2
3 #include <phosphor-logging/lg2.hpp>
4
5 #include <cmath>
6 #include <numeric>
7 #include <unordered_map>
8
9 PHOSPHOR_LOG2_USING;
10
11 namespace phosphor::health::metric
12 {
13
14 using association_t = std::tuple<std::string, std::string, std::string>;
15
getPath(MType type,std::string name,SubType subType)16 auto HealthMetric::getPath(MType type, std::string name, SubType subType)
17 -> std::string
18 {
19 std::string path;
20 switch (subType)
21 {
22 case SubType::cpuTotal:
23 {
24 return std::string(BmcPath) + "/" + PathIntf::total_cpu;
25 }
26 case SubType::cpuKernel:
27 {
28 return std::string(BmcPath) + "/" + PathIntf::kernel_cpu;
29 }
30 case SubType::cpuUser:
31 {
32 return std::string(BmcPath) + "/" + PathIntf::user_cpu;
33 }
34 case SubType::memoryAvailable:
35 {
36 return std::string(BmcPath) + "/" + PathIntf::available_memory;
37 }
38 case SubType::memoryBufferedAndCached:
39 {
40 return std::string(BmcPath) + "/" +
41 PathIntf::buffered_and_cached_memory;
42 }
43 case SubType::memoryFree:
44 {
45 return std::string(BmcPath) + "/" + PathIntf::free_memory;
46 }
47 case SubType::memoryShared:
48 {
49 return std::string(BmcPath) + "/" + PathIntf::shared_memory;
50 }
51 case SubType::memoryTotal:
52 {
53 return std::string(BmcPath) + "/" + PathIntf::total_memory;
54 }
55 case SubType::NA:
56 {
57 if (type == MType::storage)
58 {
59 static constexpr auto nameDelimiter = "_";
60 auto storageType = name.substr(
61 name.find_last_of(nameDelimiter) + 1, name.length());
62 std::ranges::for_each(storageType, [](auto& c) {
63 c = std::tolower(c);
64 });
65 return std::string(BmcPath) + "/" + PathIntf::storage + "/" +
66 storageType;
67 }
68 else
69 {
70 error("Invalid metric {SUBTYPE} for metric {TYPE}", "SUBTYPE",
71 subType, "TYPE", type);
72 return "";
73 }
74 }
75 default:
76 {
77 error("Invalid metric {SUBTYPE}", "SUBTYPE", subType);
78 return "";
79 }
80 }
81 }
82
initProperties()83 void HealthMetric::initProperties()
84 {
85 switch (type)
86 {
87 case MType::cpu:
88 {
89 ValueIntf::unit(ValueIntf::Unit::Percent, true);
90 ValueIntf::minValue(0.0, true);
91 ValueIntf::maxValue(100.0, true);
92 break;
93 }
94 case MType::memory:
95 case MType::storage:
96 {
97 ValueIntf::unit(ValueIntf::Unit::Bytes, true);
98 ValueIntf::minValue(0.0, true);
99 break;
100 }
101 case MType::inode:
102 case MType::unknown:
103 default:
104 {
105 throw std::invalid_argument("Invalid metric type");
106 }
107 }
108 ValueIntf::value(std::numeric_limits<double>::quiet_NaN(), true);
109
110 using bound_map_t = std::map<Bound, double>;
111 std::map<Type, bound_map_t> thresholds;
112 for (const auto& [key, value] : config.thresholds)
113 {
114 auto type = std::get<Type>(key);
115 auto bound = std::get<Bound>(key);
116 auto threshold = thresholds.find(type);
117 if (threshold == thresholds.end())
118 {
119 bound_map_t bounds;
120 bounds.emplace(bound, std::numeric_limits<double>::quiet_NaN());
121 thresholds.emplace(type, bounds);
122 }
123 else
124 {
125 threshold->second.emplace(bound, value.value);
126 }
127 }
128 ThresholdIntf::value(thresholds, true);
129 }
130
didThresholdViolate(ThresholdIntf::Bound bound,double thresholdValue,double value)131 bool didThresholdViolate(ThresholdIntf::Bound bound, double thresholdValue,
132 double value)
133 {
134 switch (bound)
135 {
136 case ThresholdIntf::Bound::Lower:
137 {
138 return (value < thresholdValue);
139 }
140 case ThresholdIntf::Bound::Upper:
141 {
142 return (value > thresholdValue);
143 }
144 default:
145 {
146 error("Invalid threshold bound {BOUND}", "BOUND", bound);
147 return false;
148 }
149 }
150 }
151
checkThreshold(Type type,Bound bound,MValue value)152 void HealthMetric::checkThreshold(Type type, Bound bound, MValue value)
153 {
154 auto threshold = std::make_tuple(type, bound);
155 auto thresholds = ThresholdIntf::value();
156
157 if (thresholds.contains(type) && thresholds[type].contains(bound))
158 {
159 auto tConfig = config.thresholds.at(threshold);
160 auto thresholdValue = tConfig.value / 100 * value.total;
161 thresholds[type][bound] = thresholdValue;
162 ThresholdIntf::value(thresholds);
163 auto assertions = ThresholdIntf::asserted();
164 if (didThresholdViolate(bound, thresholdValue, value.current))
165 {
166 if (!assertions.contains(threshold))
167 {
168 assertions.insert(threshold);
169 ThresholdIntf::asserted(assertions);
170 ThresholdIntf::assertionChanged(type, bound, true,
171 value.current);
172 if (tConfig.log)
173 {
174 error(
175 "ASSERT: Health Metric {METRIC} crossed {TYPE} upper threshold",
176 "METRIC", config.name, "TYPE", type);
177 startUnit(bus, tConfig.target);
178 }
179 }
180 return;
181 }
182 else if (assertions.contains(threshold))
183 {
184 assertions.erase(threshold);
185 ThresholdIntf::asserted(assertions);
186 ThresholdIntf::assertionChanged(type, bound, false, value.current);
187 if (config.thresholds.find(threshold)->second.log)
188 {
189 info(
190 "DEASSERT: Health Metric {METRIC} is below {TYPE} upper threshold",
191 "METRIC", config.name, "TYPE", type);
192 }
193 }
194 }
195 }
196
checkThresholds(MValue value)197 void HealthMetric::checkThresholds(MValue value)
198 {
199 if (!ThresholdIntf::value().empty())
200 {
201 for (auto type : {Type::HardShutdown, Type::SoftShutdown,
202 Type::PerformanceLoss, Type::Critical, Type::Warning})
203 {
204 checkThreshold(type, Bound::Lower, value);
205 checkThreshold(type, Bound::Upper, value);
206 }
207 }
208 }
209
shouldNotify(MValue value)210 auto HealthMetric::shouldNotify(MValue value) -> bool
211 {
212 if (std::isnan(value.current))
213 {
214 return true;
215 }
216 auto changed = std::abs(
217 (value.current - lastNotifiedValue) / lastNotifiedValue * 100.0);
218 if (changed >= config.hysteresis)
219 {
220 lastNotifiedValue = value.current;
221 return true;
222 }
223 return false;
224 }
225
update(MValue value)226 void HealthMetric::update(MValue value)
227 {
228 ValueIntf::value(value.current, !shouldNotify(value));
229
230 // Maintain window size for threshold calculation
231 if (history.size() >= config.windowSize)
232 {
233 history.pop_front();
234 }
235 history.push_back(value.current);
236
237 if (history.size() < config.windowSize)
238 {
239 // Wait for the metric to have enough samples to calculate average
240 return;
241 }
242
243 double average =
244 (std::accumulate(history.begin(), history.end(), 0.0)) / history.size();
245 value.current = average;
246 checkThresholds(value);
247 }
248
create(const paths_t & bmcPaths)249 void HealthMetric::create(const paths_t& bmcPaths)
250 {
251 info("Create Health Metric: {METRIC}", "METRIC", config.name);
252 initProperties();
253
254 std::vector<association_t> associations;
255 static constexpr auto forwardAssociation = "measuring";
256 static constexpr auto reverseAssociation = "measured_by";
257 for (const auto& bmcPath : bmcPaths)
258 {
259 /*
260 * This metric is "measuring" the health for the BMC at bmcPath
261 * The BMC at bmcPath is "measured_by" this metric.
262 */
263 associations.push_back(
264 {forwardAssociation, reverseAssociation, bmcPath});
265 }
266 AssociationIntf::associations(associations);
267 }
268
269 } // namespace phosphor::health::metric
270