xref: /openbmc/dbus-sensors/src/Thresholds.cpp (revision b669b6b5)
1 #include "Thresholds.hpp"
2 
3 #include "VariantVisitors.hpp"
4 #include "sensor.hpp"
5 
6 #include <array>
7 #include <boost/algorithm/string/replace.hpp>
8 #include <boost/container/flat_map.hpp>
9 #include <boost/lexical_cast.hpp>
10 #include <cmath>
11 #include <fstream>
12 #include <iostream>
13 #include <memory>
14 #include <stdexcept>
15 #include <string>
16 #include <utility>
17 #include <variant>
18 #include <vector>
19 
20 static constexpr bool DEBUG = false;
21 namespace thresholds
22 {
23 unsigned int toBusValue(const Level& level)
24 {
25     switch (level)
26     {
27         case (Level::WARNING):
28         {
29             return 0;
30         }
31         case (Level::CRITICAL):
32         {
33             return 1;
34         }
35         default:
36         {
37             return -1;
38         }
39     }
40 }
41 
42 std::string toBusValue(const Direction& direction)
43 {
44     switch (direction)
45     {
46         case (Direction::LOW):
47         {
48             return "less than";
49         }
50         case (Direction::HIGH):
51         {
52             return "greater than";
53         }
54         default:
55         {
56             return "err";
57         }
58     }
59 }
60 
61 bool parseThresholdsFromConfig(
62     const SensorData& sensorData,
63     std::vector<thresholds::Threshold>& thresholdVector,
64     const std::string* matchLabel)
65 {
66     for (const auto& item : sensorData)
67     {
68         if (item.first.find("Thresholds") == std::string::npos)
69         {
70             continue;
71         }
72         if (matchLabel != nullptr)
73         {
74             auto labelFind = item.second.find("Label");
75             if (labelFind == item.second.end())
76                 continue;
77             if (std::visit(VariantToStringVisitor(), labelFind->second) !=
78                 *matchLabel)
79                 continue;
80         }
81         auto directionFind = item.second.find("Direction");
82         auto severityFind = item.second.find("Severity");
83         auto valueFind = item.second.find("Value");
84         if (valueFind == item.second.end() ||
85             severityFind == item.second.end() ||
86             directionFind == item.second.end())
87         {
88             std::cerr << "Malformed threshold in configuration\n";
89             return false;
90         }
91         Level level;
92         Direction direction;
93         if (std::visit(VariantToUnsignedIntVisitor(), severityFind->second) ==
94             0)
95         {
96             level = Level::WARNING;
97         }
98         else
99         {
100             level = Level::CRITICAL;
101         }
102         if (std::visit(VariantToStringVisitor(), directionFind->second) ==
103             "less than")
104         {
105             direction = Direction::LOW;
106         }
107         else
108         {
109             direction = Direction::HIGH;
110         }
111         double val = std::visit(VariantToDoubleVisitor(), valueFind->second);
112 
113         thresholdVector.emplace_back(level, direction, val);
114     }
115     return true;
116 }
117 
118 void persistThreshold(const std::string& path, const std::string& baseInterface,
119                       const thresholds::Threshold& threshold,
120                       std::shared_ptr<sdbusplus::asio::connection>& conn,
121                       size_t thresholdCount)
122 {
123     for (size_t ii = 0; ii < thresholdCount; ii++)
124     {
125         std::string thresholdInterface =
126             baseInterface + ".Thresholds" + std::to_string(ii);
127         conn->async_method_call(
128             [&, path, threshold, thresholdInterface](
129                 const boost::system::error_code& ec,
130                 const boost::container::flat_map<std::string, BasicVariantType>&
131                     result) {
132                 if (ec)
133                 {
134                     return; // threshold not supported
135                 }
136 
137                 auto directionFind = result.find("Direction");
138                 auto severityFind = result.find("Severity");
139                 auto valueFind = result.find("Value");
140                 if (valueFind == result.end() || severityFind == result.end() ||
141                     directionFind == result.end())
142                 {
143                     std::cerr << "Malformed threshold in configuration\n";
144                     return;
145                 }
146                 unsigned int level = std::visit(VariantToUnsignedIntVisitor(),
147                                                 severityFind->second);
148 
149                 std::string dir =
150                     std::visit(VariantToStringVisitor(), directionFind->second);
151                 if ((toBusValue(threshold.level) != level) ||
152                     (toBusValue(threshold.direction) != dir))
153                 {
154                     return; // not the droid we're looking for
155                 }
156 
157                 std::variant<double> value(threshold.value);
158                 conn->async_method_call(
159                     [](const boost::system::error_code& ec) {
160                         if (ec)
161                         {
162                             std::cerr << "Error setting threshold " << ec
163                                       << "\n";
164                         }
165                     },
166                     entityManagerName, path, "org.freedesktop.DBus.Properties",
167                     "Set", thresholdInterface, "Value", value);
168             },
169             entityManagerName, path, "org.freedesktop.DBus.Properties",
170             "GetAll", thresholdInterface);
171     }
172 }
173 
174 void updateThresholds(Sensor* sensor)
175 {
176     if (sensor->thresholds.empty())
177     {
178         return;
179     }
180 
181     for (const auto& threshold : sensor->thresholds)
182     {
183         std::shared_ptr<sdbusplus::asio::dbus_interface> interface;
184         std::string property;
185         if (threshold.level == thresholds::Level::CRITICAL)
186         {
187             interface = sensor->thresholdInterfaceCritical;
188             if (threshold.direction == thresholds::Direction::HIGH)
189             {
190                 property = "CriticalHigh";
191             }
192             else
193             {
194                 property = "CriticalLow";
195             }
196         }
197         else if (threshold.level == thresholds::Level::WARNING)
198         {
199             interface = sensor->thresholdInterfaceWarning;
200             if (threshold.direction == thresholds::Direction::HIGH)
201             {
202                 property = "WarningHigh";
203             }
204             else
205             {
206                 property = "WarningLow";
207             }
208         }
209         else
210         {
211             continue;
212         }
213         if (!interface)
214         {
215             continue;
216         }
217         interface->set_property(property, threshold.value);
218     }
219 }
220 
221 static std::vector<std::pair<Threshold, bool>> checkThresholds(Sensor* sensor,
222                                                                double value)
223 {
224     std::vector<std::pair<Threshold, bool>> thresholdChanges;
225     if (sensor->thresholds.empty())
226     {
227         return thresholdChanges;
228     }
229 
230     for (auto& threshold : sensor->thresholds)
231     {
232         if (threshold.direction == thresholds::Direction::HIGH)
233         {
234             if (value >= threshold.value)
235             {
236                 thresholdChanges.push_back(std::make_pair(threshold, true));
237             }
238             else
239             {
240                 thresholdChanges.push_back(std::make_pair(threshold, false));
241             }
242         }
243         else
244         {
245             if (value <= threshold.value)
246             {
247                 thresholdChanges.push_back(std::make_pair(threshold, true));
248             }
249             else
250             {
251                 thresholdChanges.push_back(std::make_pair(threshold, false));
252             }
253         }
254     }
255     return thresholdChanges;
256 }
257 
258 bool checkThresholds(Sensor* sensor)
259 {
260     bool status = true;
261     std::vector<std::pair<Threshold, bool>> changes =
262         checkThresholds(sensor, sensor->value);
263     for (const auto& [threshold, asserted] : changes)
264     {
265         assertThresholds(sensor, threshold.level, threshold.direction,
266                          asserted);
267         if (threshold.level == thresholds::Level::CRITICAL && asserted)
268         {
269             status = false;
270         }
271     }
272 
273     return status;
274 }
275 
276 void checkThresholdsPowerDelay(Sensor* sensor, ThresholdTimer& thresholdTimer)
277 {
278 
279     std::vector<std::pair<Threshold, bool>> changes =
280         checkThresholds(sensor, sensor->value);
281     for (const auto& [threshold, asserted] : changes)
282     {
283         if (asserted)
284         {
285             thresholdTimer.startTimer(threshold);
286         }
287         else
288         {
289             assertThresholds(sensor, threshold.level, threshold.direction,
290                              false);
291         }
292     }
293 }
294 
295 void assertThresholds(Sensor* sensor, thresholds::Level level,
296                       thresholds::Direction direction, bool assert)
297 {
298     std::string property;
299     std::shared_ptr<sdbusplus::asio::dbus_interface> interface;
300     if (level == thresholds::Level::WARNING &&
301         direction == thresholds::Direction::HIGH)
302     {
303         property = "WarningAlarmHigh";
304         interface = sensor->thresholdInterfaceWarning;
305     }
306     else if (level == thresholds::Level::WARNING &&
307              direction == thresholds::Direction::LOW)
308     {
309         property = "WarningAlarmLow";
310         interface = sensor->thresholdInterfaceWarning;
311     }
312     else if (level == thresholds::Level::CRITICAL &&
313              direction == thresholds::Direction::HIGH)
314     {
315         property = "CriticalAlarmHigh";
316         interface = sensor->thresholdInterfaceCritical;
317     }
318     else if (level == thresholds::Level::CRITICAL &&
319              direction == thresholds::Direction::LOW)
320     {
321         property = "CriticalAlarmLow";
322         interface = sensor->thresholdInterfaceCritical;
323     }
324     else
325     {
326         std::cerr << "Unknown threshold, level " << level << "direction "
327                   << direction << "\n";
328         return;
329     }
330     if (!interface)
331     {
332         std::cout << "trying to set uninitialized interface\n";
333         return;
334     }
335     interface->set_property(property, assert);
336 }
337 
338 static constexpr std::array<const char*, 4> attrTypes = {"lcrit", "min", "max",
339                                                          "crit"};
340 
341 bool parseThresholdsFromAttr(
342     std::vector<thresholds::Threshold>& thresholdVector,
343     const std::string& inputPath, const double& scaleFactor,
344     const double& offset)
345 {
346     for (const std::string& type : attrTypes)
347     {
348         auto attrPath = boost::replace_all_copy(inputPath, "input", type);
349         std::ifstream attrFile(attrPath);
350         if (!attrFile.good())
351         {
352             continue;
353         }
354         std::string attr;
355         std::getline(attrFile, attr);
356         attrFile.close();
357 
358         Level level;
359         Direction direction;
360         double val;
361         try
362         {
363             val = std::stod(attr) / scaleFactor;
364         }
365         catch (const std::invalid_argument&)
366         {
367             return false;
368         }
369 
370         if (type == "min" || type == "max")
371         {
372             level = Level::WARNING;
373         }
374         else
375         {
376             level = Level::CRITICAL;
377         }
378         if (type == "min" || type == "lcrit")
379         {
380             direction = Direction::LOW;
381         }
382         else
383         {
384             direction = Direction::HIGH;
385         }
386 
387         if (type == "crit")
388         {
389             val += offset;
390         }
391 
392         if (DEBUG)
393         {
394             std::cout << "Threshold: " << attrPath << ": " << val << "\n";
395         }
396 
397         thresholdVector.emplace_back(level, direction, val);
398     }
399     // no thresholds is allowed, not an error so return true.
400     return true;
401 }
402 
403 bool hasCriticalInterface(
404     const std::vector<thresholds::Threshold>& thresholdVector)
405 {
406     for (auto& threshold : thresholdVector)
407     {
408         if (threshold.level == Level::CRITICAL)
409         {
410             return true;
411         }
412     }
413     return false;
414 }
415 
416 bool hasWarningInterface(
417     const std::vector<thresholds::Threshold>& thresholdVector)
418 {
419     for (auto& threshold : thresholdVector)
420     {
421         if (threshold.level == Level::WARNING)
422         {
423             return true;
424         }
425     }
426     return false;
427 }
428 } // namespace thresholds
429