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