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