1 #include "health_metric.hpp"
2 
3 #include <phosphor-logging/lg2.hpp>
4 
5 #include <numeric>
6 #include <unordered_map>
7 
8 PHOSPHOR_LOG2_USING;
9 
10 namespace phosphor::health::metric
11 {
12 
13 using association_t = std::tuple<std::string, std::string, std::string>;
14 
15 auto 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         case SubType::storageTmp:
58         {
59             return std::string(BmcPath) + "/" + PathIntf::tmp_storage;
60         }
61         default:
62         {
63             error("Invalid Memory metric {TYPE}", "TYPE",
64                   std::to_underlying(subType));
65             return "";
66         }
67     }
68 }
69 
70 void HealthMetric::initProperties()
71 {
72     switch (config.subType)
73     {
74         case SubType::cpuTotal:
75         case SubType::cpuKernel:
76         case SubType::cpuUser:
77         {
78             ValueIntf::unit(ValueIntf::Unit::Percent, true);
79             ValueIntf::minValue(0.0, true);
80             ValueIntf::maxValue(100.0, true);
81             break;
82         }
83         case SubType::memoryAvailable:
84         case SubType::memoryBufferedAndCached:
85         case SubType::memoryFree:
86         case SubType::memoryShared:
87         case SubType::memoryTotal:
88         case SubType::storageReadWrite:
89         default:
90         {
91             ValueIntf::unit(ValueIntf::Unit::Bytes, true);
92             ValueIntf::minValue(0.0, true);
93         }
94     }
95     ValueIntf::value(std::numeric_limits<double>::quiet_NaN(), true);
96 
97     using bound_map_t = std::map<ThresholdIntf::Bound, double>;
98     std::map<ThresholdIntf::Type, bound_map_t> thresholds;
99     for (const auto& [key, value] : config.thresholds)
100     {
101         auto type = std::get<ThresholdIntf::Type>(key);
102         auto bound = std::get<ThresholdIntf::Bound>(key);
103         auto threshold = thresholds.find(type);
104         if (threshold == thresholds.end())
105         {
106             bound_map_t bounds;
107             bounds.emplace(bound, std::numeric_limits<double>::quiet_NaN());
108             thresholds.emplace(type, bounds);
109         }
110         else
111         {
112             threshold->second.emplace(bound, value.value);
113         }
114     }
115     ThresholdIntf::value(thresholds, true);
116 }
117 
118 bool didThresholdViolate(ThresholdIntf::Bound bound, double thresholdValue,
119                          double value)
120 {
121     switch (bound)
122     {
123         case ThresholdIntf::Bound::Lower:
124         {
125             return (value < thresholdValue);
126         }
127         case ThresholdIntf::Bound::Upper:
128         {
129             return (value > thresholdValue);
130         }
131         default:
132         {
133             error("Invalid threshold bound {BOUND}", "BOUND",
134                   std::to_underlying(bound));
135             return false;
136         }
137     }
138 }
139 
140 void HealthMetric::checkThreshold(ThresholdIntf::Type type,
141                                   ThresholdIntf::Bound bound, MValue value)
142 {
143     auto threshold = std::make_tuple(type, bound);
144     auto thresholds = ThresholdIntf::value();
145 
146     if (thresholds.contains(type) && thresholds[type].contains(bound))
147     {
148         auto tConfig = config.thresholds.at(threshold);
149         auto thresholdValue = tConfig.value / 100 * value.total;
150         thresholds[type][bound] = thresholdValue;
151         ThresholdIntf::value(thresholds);
152         auto assertions = ThresholdIntf::asserted();
153         if (didThresholdViolate(bound, thresholdValue, value.current))
154         {
155             if (!assertions.contains(threshold))
156             {
157                 assertions.insert(threshold);
158                 ThresholdIntf::asserted(assertions);
159                 ThresholdIntf::assertionChanged(type, bound, true,
160                                                 value.current);
161                 if (tConfig.log)
162                 {
163                     error(
164                         "ASSERT: Health Metric {METRIC} crossed {TYPE} upper threshold",
165                         "METRIC", config.name, "TYPE",
166                         sdbusplus::message::convert_to_string(type));
167                     startUnit(bus, tConfig.target);
168                 }
169             }
170             return;
171         }
172         else if (assertions.contains(threshold))
173         {
174             assertions.erase(threshold);
175             ThresholdIntf::asserted(assertions);
176             ThresholdIntf::assertionChanged(type, bound, false, value.current);
177             if (config.thresholds.find(threshold)->second.log)
178             {
179                 info(
180                     "DEASSERT: Health Metric {METRIC} is below {TYPE} upper threshold",
181                     "METRIC", config.name, "TYPE",
182                     sdbusplus::message::convert_to_string(type));
183             }
184         }
185     }
186 }
187 
188 void HealthMetric::checkThresholds(MValue value)
189 {
190     if (!ThresholdIntf::value().empty())
191     {
192         for (auto type :
193              {ThresholdIntf::Type::HardShutdown,
194               ThresholdIntf::Type::SoftShutdown,
195               ThresholdIntf::Type::PerformanceLoss,
196               ThresholdIntf::Type::Critical, ThresholdIntf::Type::Warning})
197         {
198             checkThreshold(type, ThresholdIntf::Bound::Lower, value);
199             checkThreshold(type, ThresholdIntf::Bound::Upper, value);
200         }
201     }
202 }
203 
204 void HealthMetric::update(MValue value)
205 {
206     // Maintain window size for metric
207     if (history.size() >= config.windowSize)
208     {
209         history.pop_front();
210     }
211     history.push_back(value.current);
212 
213     if (history.size() < config.windowSize)
214     {
215         // Wait for the metric to have enough samples to calculate average
216         debug("Not enough samples to calculate average");
217         return;
218     }
219 
220     double average = (std::accumulate(history.begin(), history.end(), 0.0)) /
221                      history.size();
222     ValueIntf::value(average);
223     checkThresholds(value);
224 }
225 
226 void HealthMetric::create(const paths_t& bmcPaths)
227 {
228     info("Create Health Metric: {METRIC}", "METRIC", config.name);
229     initProperties();
230 
231     std::vector<association_t> associations;
232     static constexpr auto forwardAssociation = "measuring";
233     static constexpr auto reverseAssociation = "measured_by";
234     for (const auto& bmcPath : bmcPaths)
235     {
236         /*
237          * This metric is "measuring" the health for the BMC at bmcPath
238          * The BMC at bmcPath is "measured_by" this metric.
239          */
240         associations.push_back(
241             {forwardAssociation, reverseAssociation, bmcPath});
242     }
243     AssociationIntf::associations(associations);
244 }
245 
246 } // namespace phosphor::health::metric
247