blob: 2c82feadb501b9d7a3be2484538dff451b8e5fc1 [file] [log] [blame]
Jagpal Singh Gill23f091e2023-12-10 15:23:19 -08001#include "health_metric.hpp"
2
3#include <phosphor-logging/lg2.hpp>
4
5#include <numeric>
6#include <unordered_map>
7
8PHOSPHOR_LOG2_USING;
9
10namespace phosphor::health::metric
11{
12
13using association_t = std::tuple<std::string, std::string, std::string>;
14
15auto HealthMetric::getPath(SubType subType) -> std::string
16{
17 std::string path;
18 switch (subType)
19 {
20 case SubType::cpuTotal:
21 {
22 return std::string(BmcPath) + "/" + PathIntf::total_cpu;
23 }
24 case SubType::cpuKernel:
25 {
26 return std::string(BmcPath) + "/" + PathIntf::kernel_cpu;
27 }
28 case SubType::cpuUser:
29 {
30 return std::string(BmcPath) + "/" + PathIntf::user_cpu;
31 }
32 case SubType::memoryAvailable:
33 {
34 return std::string(BmcPath) + "/" + PathIntf::available_memory;
35 }
36 case SubType::memoryBufferedAndCached:
37 {
38 return std::string(BmcPath) + "/" +
39 PathIntf::buffered_and_cached_memory;
40 }
41 case SubType::memoryFree:
42 {
43 return std::string(BmcPath) + "/" + PathIntf::free_memory;
44 }
45 case SubType::memoryShared:
46 {
47 return std::string(BmcPath) + "/" + PathIntf::shared_memory;
48 }
49 case SubType::memoryTotal:
50 {
51 return std::string(BmcPath) + "/" + PathIntf::total_memory;
52 }
53 case SubType::storageReadWrite:
54 {
55 return std::string(BmcPath) + "/" + PathIntf::read_write_storage;
56 }
57 default:
58 {
59 error("Invalid Memory metric {TYPE}", "TYPE",
60 std::to_underlying(subType));
61 return "";
62 }
63 }
64}
65
66void HealthMetric::initProperties()
67{
68 switch (config.subType)
69 {
70 case SubType::cpuTotal:
71 case SubType::cpuKernel:
72 case SubType::cpuUser:
73 {
74 ValueIntf::unit(ValueIntf::Unit::Percent, true);
75 ValueIntf::minValue(0.0, true);
76 ValueIntf::maxValue(100.0, true);
77 break;
78 }
79 case SubType::memoryAvailable:
80 case SubType::memoryBufferedAndCached:
81 case SubType::memoryFree:
82 case SubType::memoryShared:
83 case SubType::memoryTotal:
84 case SubType::storageReadWrite:
85 default:
86 {
87 ValueIntf::unit(ValueIntf::Unit::Bytes, true);
88 ValueIntf::minValue(0.0, true);
89 }
90 }
91 ValueIntf::value(std::numeric_limits<double>::quiet_NaN());
92
93 using bound_map_t = std::map<ThresholdIntf::Bound, double>;
94 std::map<ThresholdIntf::Type, bound_map_t> thresholds;
95 for (const auto& [key, value] : config.thresholds)
96 {
97 auto type = std::get<ThresholdIntf::Type>(key);
98 auto bound = std::get<ThresholdIntf::Bound>(key);
99 auto threshold = thresholds.find(type);
100 if (threshold == thresholds.end())
101 {
102 bound_map_t bounds;
103 bounds.emplace(bound, value.value);
104 thresholds.emplace(type, bounds);
105 }
106 else
107 {
108 threshold->second.emplace(bound, value.value);
109 }
110 }
111 ThresholdIntf::value(thresholds);
112}
113
114void HealthMetric::checkThreshold(ThresholdIntf::Type type,
115 ThresholdIntf::Bound bound, double value)
116{
117 auto threshold = std::make_tuple(type, bound);
118 auto thresholds = ThresholdIntf::value();
119
120 if (thresholds.contains(type) && thresholds[type].contains(bound))
121 {
122 auto thresholdValue = thresholds[type][bound];
123 auto assertions = ThresholdIntf::asserted();
124 if (value > thresholdValue)
125 {
126 if (!assertions.contains(threshold))
127 {
128 assertions.insert(threshold);
129 ThresholdIntf::asserted(assertions);
130 ThresholdIntf::assertionChanged(type, bound, true, value);
131 auto tConfig = config.thresholds.at(threshold);
132 if (tConfig.log)
133 {
134 error(
135 "ASSERT: Health Metric {METRIC} crossed {TYPE} upper threshold",
136 "METRIC", config.name, "TYPE",
137 sdbusplus::message::convert_to_string(type));
138 startUnit(bus, tConfig.target);
139 }
140 }
141 return;
142 }
143 else if (assertions.contains(threshold))
144 {
145 assertions.erase(threshold);
146 ThresholdIntf::asserted(assertions);
147 ThresholdIntf::assertionChanged(type, bound, false, value);
148 if (config.thresholds.find(threshold)->second.log)
149 {
150 info(
151 "DEASSERT: Health Metric {METRIC} is below {TYPE} upper threshold",
152 "METRIC", config.name, "TYPE",
153 sdbusplus::message::convert_to_string(type));
154 }
155 }
156 }
157}
158
159void HealthMetric::checkThresholds(double value)
160{
161 if (!ThresholdIntf::value().empty())
162 {
163 for (auto type :
164 {ThresholdIntf::Type::HardShutdown,
165 ThresholdIntf::Type::SoftShutdown,
166 ThresholdIntf::Type::PerformanceLoss,
167 ThresholdIntf::Type::Critical, ThresholdIntf::Type::Warning})
168 {
169 checkThreshold(type, ThresholdIntf::Bound::Upper, value);
170 }
171 }
172}
173
174void HealthMetric::update(MValue value)
175{
176 // Maintain window size for metric
177 if (history.size() >= config.windowSize)
178 {
179 history.pop_front();
180 }
181 history.push_back(value.user);
182
183 if (history.size() < config.windowSize)
184 {
185 // Wait for the metric to have enough samples to calculate average
186 info("Not enough samples to calculate average");
187 return;
188 }
189
190 double average = (std::accumulate(history.begin(), history.end(), 0.0)) /
191 history.size();
192 ValueIntf::value(average);
193 checkThresholds(value.monitor);
194}
195
196void HealthMetric::create(const paths_t& bmcPaths)
197{
198 info("Create Health Metric: {METRIC}", "METRIC", config.name);
199 initProperties();
200
201 std::vector<association_t> associations;
202 static constexpr auto forwardAssociation = "measuring";
203 static constexpr auto reverseAssociation = "measured_by";
204 for (const auto& bmcPath : bmcPaths)
205 {
206 /*
207 * This metric is "measuring" the health for the BMC at bmcPath
208 * The BMC at bmcPath is "measured_by" this metric.
209 */
210 associations.push_back(
211 {forwardAssociation, reverseAssociation, bmcPath});
212 }
213 AssociationIntf::associations(associations);
214}
215
216} // namespace phosphor::health::metric