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         default:
58         {
59             error("Invalid Memory metric {TYPE}", "TYPE",
60                   std::to_underlying(subType));
61             return "";
62         }
63     }
64 }
65 
66 void 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(), true);
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, true);
112 }
113 
114 void 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 
159 void 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 
174 void 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 
196 void 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
217