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, value.value);
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, double 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 thresholdValue = thresholds[type][bound];
149         auto assertions = ThresholdIntf::asserted();
150         if (didThresholdViolate(bound, thresholdValue, value))
151         {
152             if (!assertions.contains(threshold))
153             {
154                 assertions.insert(threshold);
155                 ThresholdIntf::asserted(assertions);
156                 ThresholdIntf::assertionChanged(type, bound, true, value);
157                 auto tConfig = config.thresholds.at(threshold);
158                 if (tConfig.log)
159                 {
160                     error(
161                         "ASSERT: Health Metric {METRIC} crossed {TYPE} upper threshold",
162                         "METRIC", config.name, "TYPE",
163                         sdbusplus::message::convert_to_string(type));
164                     startUnit(bus, tConfig.target);
165                 }
166             }
167             return;
168         }
169         else if (assertions.contains(threshold))
170         {
171             assertions.erase(threshold);
172             ThresholdIntf::asserted(assertions);
173             ThresholdIntf::assertionChanged(type, bound, false, value);
174             if (config.thresholds.find(threshold)->second.log)
175             {
176                 info(
177                     "DEASSERT: Health Metric {METRIC} is below {TYPE} upper threshold",
178                     "METRIC", config.name, "TYPE",
179                     sdbusplus::message::convert_to_string(type));
180             }
181         }
182     }
183 }
184 
185 void HealthMetric::checkThresholds(double value)
186 {
187     if (!ThresholdIntf::value().empty())
188     {
189         for (auto type :
190              {ThresholdIntf::Type::HardShutdown,
191               ThresholdIntf::Type::SoftShutdown,
192               ThresholdIntf::Type::PerformanceLoss,
193               ThresholdIntf::Type::Critical, ThresholdIntf::Type::Warning})
194         {
195             checkThreshold(type, ThresholdIntf::Bound::Lower, value);
196             checkThreshold(type, ThresholdIntf::Bound::Upper, value);
197         }
198     }
199 }
200 
201 void HealthMetric::update(MValue value)
202 {
203     // Maintain window size for metric
204     if (history.size() >= config.windowSize)
205     {
206         history.pop_front();
207     }
208     history.push_back(value.user);
209 
210     if (history.size() < config.windowSize)
211     {
212         // Wait for the metric to have enough samples to calculate average
213         info("Not enough samples to calculate average");
214         return;
215     }
216 
217     double average = (std::accumulate(history.begin(), history.end(), 0.0)) /
218                      history.size();
219     ValueIntf::value(average);
220     checkThresholds(value.monitor);
221 }
222 
223 void HealthMetric::create(const paths_t& bmcPaths)
224 {
225     info("Create Health Metric: {METRIC}", "METRIC", config.name);
226     initProperties();
227 
228     std::vector<association_t> associations;
229     static constexpr auto forwardAssociation = "measuring";
230     static constexpr auto reverseAssociation = "measured_by";
231     for (const auto& bmcPath : bmcPaths)
232     {
233         /*
234          * This metric is "measuring" the health for the BMC at bmcPath
235          * The BMC at bmcPath is "measured_by" this metric.
236          */
237         associations.push_back(
238             {forwardAssociation, reverseAssociation, bmcPath});
239     }
240     AssociationIntf::associations(associations);
241 }
242 
243 } // namespace phosphor::health::metric
244