xref: /openbmc/dbus-sensors/src/Thresholds.cpp (revision 89be6147e5a7ffa86d88d8f3e27eba8eb2c3a9da)
1 #include "Thresholds.hpp"
2 
3 #include "Utils.hpp"
4 #include "VariantVisitors.hpp"
5 #include "sensor.hpp"
6 
7 #include <boost/algorithm/string/replace.hpp>
8 #include <boost/asio/error.hpp>
9 #include <boost/asio/steady_timer.hpp>
10 #include <boost/container/flat_map.hpp>
11 #include <phosphor-logging/lg2.hpp>
12 #include <sdbusplus/asio/connection.hpp>
13 #include <sdbusplus/asio/object_server.hpp>
14 #include <sdbusplus/exception.hpp>
15 #include <sdbusplus/message.hpp>
16 
17 #include <array>
18 #include <chrono>
19 #include <cstddef>
20 #include <cstdint>
21 #include <limits>
22 #include <memory>
23 #include <string>
24 #include <tuple>
25 #include <utility>
26 #include <variant>
27 #include <vector>
28 
29 namespace thresholds
30 {
findThresholdLevel(uint8_t sev)31 Level findThresholdLevel(uint8_t sev)
32 {
33     for (const ThresholdDefinition& prop : thresProp)
34     {
35         if (prop.sevOrder == sev)
36         {
37             return prop.level;
38         }
39     }
40     return Level::ERROR;
41 }
42 
findThresholdDirection(const std::string & direct)43 Direction findThresholdDirection(const std::string& direct)
44 {
45     if (direct == "greater than")
46     {
47         return Direction::HIGH;
48     }
49     if (direct == "less than")
50     {
51         return Direction::LOW;
52     }
53     return Direction::ERROR;
54 }
55 
parseThresholdsFromConfig(const SensorData & sensorData,std::vector<thresholds::Threshold> & thresholdVector,const std::string * matchLabel,const int * sensorIndex)56 bool parseThresholdsFromConfig(
57     const SensorData& sensorData,
58     std::vector<thresholds::Threshold>& thresholdVector,
59     const std::string* matchLabel, const int* sensorIndex)
60 {
61     for (const auto& [intf, cfg] : sensorData)
62     {
63         if (intf.find("Thresholds") == std::string::npos)
64         {
65             continue;
66         }
67         if (matchLabel != nullptr)
68         {
69             auto labelFind = cfg.find("Label");
70             if (labelFind == cfg.end())
71             {
72                 continue;
73             }
74             if (std::visit(VariantToStringVisitor(), labelFind->second) !=
75                 *matchLabel)
76             {
77                 continue;
78             }
79         }
80 
81         if (sensorIndex != nullptr)
82         {
83             auto indexFind = cfg.find("Index");
84 
85             // If we're checking for index 1, a missing Index is OK.
86             if ((indexFind == cfg.end()) && (*sensorIndex != 1))
87             {
88                 continue;
89             }
90 
91             if ((indexFind != cfg.end()) &&
92                 (std::visit(VariantToIntVisitor(), indexFind->second) !=
93                  *sensorIndex))
94             {
95                 continue;
96             }
97         }
98 
99         double hysteresis = std::numeric_limits<double>::quiet_NaN();
100         auto hysteresisFind = cfg.find("Hysteresis");
101         if (hysteresisFind != cfg.end())
102         {
103             hysteresis =
104                 std::visit(VariantToDoubleVisitor(), hysteresisFind->second);
105         }
106 
107         auto directionFind = cfg.find("Direction");
108         auto severityFind = cfg.find("Severity");
109         auto valueFind = cfg.find("Value");
110         if (valueFind == cfg.end() || severityFind == cfg.end() ||
111             directionFind == cfg.end())
112         {
113             lg2::error(
114                 "Malformed threshold on configuration interface: '{INTERFACE}'",
115                 "INTERFACE", intf);
116             return false;
117         }
118         unsigned int severity =
119             std::visit(VariantToUnsignedIntVisitor(), severityFind->second);
120 
121         std::string directions =
122             std::visit(VariantToStringVisitor(), directionFind->second);
123 
124         Level level = findThresholdLevel(severity);
125         Direction direction = findThresholdDirection(directions);
126 
127         if ((level == Level::ERROR) || (direction == Direction::ERROR))
128         {
129             continue;
130         }
131         double val = std::visit(VariantToDoubleVisitor(), valueFind->second);
132 
133         thresholdVector.emplace_back(level, direction, val, hysteresis);
134     }
135     return true;
136 }
137 
persistThreshold(const std::string & path,const std::string & baseInterface,const thresholds::Threshold & threshold,std::shared_ptr<sdbusplus::asio::connection> & conn,size_t thresholdCount,const std::string & labelMatch)138 void persistThreshold(const std::string& path, const std::string& baseInterface,
139                       const thresholds::Threshold& threshold,
140                       std::shared_ptr<sdbusplus::asio::connection>& conn,
141                       size_t thresholdCount, const std::string& labelMatch)
142 {
143     for (size_t ii = 0; ii < thresholdCount; ii++)
144     {
145         std::string thresholdInterface =
146             baseInterface + ".Thresholds" + std::to_string(ii);
147         conn->async_method_call(
148             [&, path, threshold, thresholdInterface,
149              labelMatch](const boost::system::error_code& ec,
150                          const SensorBaseConfigMap& result) {
151                 if (ec)
152                 {
153                     return; // threshold not supported
154                 }
155 
156                 if (!labelMatch.empty())
157                 {
158                     auto labelFind = result.find("Label");
159                     if (labelFind == result.end())
160                     {
161                         lg2::error("No label in threshold configuration");
162                         return;
163                     }
164                     std::string label =
165                         std::visit(VariantToStringVisitor(), labelFind->second);
166                     if (label != labelMatch)
167                     {
168                         return;
169                     }
170                 }
171 
172                 auto directionFind = result.find("Direction");
173                 auto severityFind = result.find("Severity");
174                 auto valueFind = result.find("Value");
175                 if (valueFind == result.end() || severityFind == result.end() ||
176                     directionFind == result.end())
177                 {
178                     lg2::error("Malformed threshold in configuration");
179                     return;
180                 }
181                 unsigned int severity = std::visit(
182                     VariantToUnsignedIntVisitor(), severityFind->second);
183 
184                 std::string dir =
185                     std::visit(VariantToStringVisitor(), directionFind->second);
186                 if ((findThresholdLevel(severity) != threshold.level) ||
187                     (findThresholdDirection(dir) != threshold.direction))
188                 {
189                     return; // not the droid we're looking for
190                 }
191 
192                 std::variant<double> value(threshold.value);
193                 conn->async_method_call(
194                     [](const boost::system::error_code& ec) {
195                         if (ec)
196                         {
197                             lg2::error(
198                                 "Error setting threshold: '{ERROR_MESSAGE}'",
199                                 "ERROR_MESSAGE", ec.message());
200                         }
201                     },
202                     entityManagerName, path, "org.freedesktop.DBus.Properties",
203                     "Set", thresholdInterface, "Value", value);
204             },
205             entityManagerName, path, "org.freedesktop.DBus.Properties",
206             "GetAll", thresholdInterface);
207     }
208 }
209 
updateThresholds(Sensor * sensor)210 void updateThresholds(Sensor* sensor)
211 {
212     for (const auto& threshold : sensor->thresholds)
213     {
214         std::shared_ptr<sdbusplus::asio::dbus_interface> interface =
215             sensor->getThresholdInterface(threshold.level);
216 
217         if (!interface)
218         {
219             continue;
220         }
221 
222         std::string property =
223             Sensor::propertyLevel(threshold.level, threshold.direction);
224         if (property.empty())
225         {
226             continue;
227         }
228         interface->set_property(property, threshold.value);
229     }
230 }
231 
232 // Debugging counters
233 static int cHiTrue = 0;
234 static int cHiFalse = 0;
235 static int cHiMidstate = 0;
236 static int cLoTrue = 0;
237 static int cLoFalse = 0;
238 static int cLoMidstate = 0;
239 static int cDebugThrottle = 0;
240 static constexpr int assertLogCount = 10;
241 
242 struct ChangeParam
243 {
ChangeParamthresholds::ChangeParam244     ChangeParam(Threshold whichThreshold, bool status, double value) :
245         threshold(whichThreshold), asserted(status), assertValue(value)
246     {}
247 
248     Threshold threshold;
249     bool asserted;
250     double assertValue;
251 };
252 
checkThresholds(Sensor * sensor,double value)253 static std::vector<ChangeParam> checkThresholds(Sensor* sensor, double value)
254 {
255     std::vector<ChangeParam> thresholdChanges;
256     if (sensor->thresholds.empty())
257     {
258         return thresholdChanges;
259     }
260 
261     for (auto& threshold : sensor->thresholds)
262     {
263         // Use "Schmitt trigger" logic to avoid threshold trigger spam,
264         // if value is noisy while hovering very close to a threshold.
265         // When a threshold is crossed, indicate true immediately,
266         // but require more distance to be crossed the other direction,
267         // before resetting the indicator back to false.
268         if (threshold.direction == thresholds::Direction::HIGH)
269         {
270             if (value >= threshold.value)
271             {
272                 thresholdChanges.emplace_back(threshold, true, value);
273                 if (++cHiTrue < assertLogCount)
274                 {
275                     lg2::info(
276                         "Sensor name: {NAME}, high threshold: {THRESHOLD}, "
277                         "assert value: {VALUE}, raw data: {RAW_DATA}",
278                         "NAME", sensor->name, "THRESHOLD", threshold.value,
279                         "VALUE", value, "RAW_DATA", sensor->rawValue);
280                 }
281             }
282             else if (value < (threshold.value - threshold.hysteresis))
283             {
284                 thresholdChanges.emplace_back(threshold, false, value);
285                 ++cHiFalse;
286             }
287             else
288             {
289                 ++cHiMidstate;
290             }
291         }
292         else if (threshold.direction == thresholds::Direction::LOW)
293         {
294             if (value <= threshold.value)
295             {
296                 thresholdChanges.emplace_back(threshold, true, value);
297                 if (++cLoTrue < assertLogCount)
298                 {
299                     lg2::info(
300                         "Sensor name: {NAME}, low threshold: {THRESHOLD}, "
301                         "assert value: {VALUE}, raw data: {RAW_DATA}",
302                         "NAME", sensor->name, "THRESHOLD", threshold.value,
303                         "VALUE", value, "RAW_DATA", sensor->rawValue);
304                 }
305             }
306             else if (value > (threshold.value + threshold.hysteresis))
307             {
308                 thresholdChanges.emplace_back(threshold, false, value);
309                 ++cLoFalse;
310             }
311             else
312             {
313                 ++cLoMidstate;
314             }
315         }
316         else
317         {
318             lg2::error("Error determining threshold direction");
319         }
320     }
321 
322     // Throttle debug output, so that it does not continuously spam
323     ++cDebugThrottle;
324     if (cDebugThrottle >= 1000)
325     {
326         cDebugThrottle = 0;
327         lg2::debug("checkThresholds: High T= {HIGH_TRUE}, F= {HIGH_FALSE},"
328                    " M= {HIGH_MIDSTATE}, Low T= {LOW_TRUE}, F= {LOW_FALSE},"
329                    " M= {LOW_MIDSTATE}",
330                    "HIGH_TRUE", cHiTrue, "HIGH_FALSE", cHiFalse,
331                    "HIGH_MIDSTATE", cHiMidstate, "LOW_TRUE", cLoTrue,
332                    "LOW_FALSE", cLoFalse, "LOW_MIDSTATE", cLoMidstate);
333     }
334 
335     return thresholdChanges;
336 }
337 
startTimer(const std::weak_ptr<Sensor> & weakSensor,const Threshold & threshold,bool assert,double assertValue)338 void ThresholdTimer::startTimer(const std::weak_ptr<Sensor>& weakSensor,
339                                 const Threshold& threshold, bool assert,
340                                 double assertValue)
341 {
342     struct TimerUsed timerUsed = {};
343     constexpr const size_t waitTime = 5;
344     TimerPair* pair = nullptr;
345 
346     for (TimerPair& timer : timers)
347     {
348         if (!timer.first.used)
349         {
350             pair = &timer;
351             break;
352         }
353     }
354     if (pair == nullptr)
355     {
356         pair = &timers.emplace_back(timerUsed, boost::asio::steady_timer(io));
357     }
358 
359     pair->first.used = true;
360     pair->first.level = threshold.level;
361     pair->first.direction = threshold.direction;
362     pair->first.assert = assert;
363     pair->second.expires_after(std::chrono::seconds(waitTime));
364     pair->second.async_wait([weakSensor, pair, threshold, assert,
365                              assertValue](boost::system::error_code ec) {
366         auto sensorPtr = weakSensor.lock();
367         if (!sensorPtr)
368         {
369             return; // owner sensor has been destructed
370         }
371         // pair is valid as long as sensor is valid
372         pair->first.used = false;
373 
374         if (ec == boost::asio::error::operation_aborted)
375         {
376             return; // we're being canceled
377         }
378         if (ec)
379         {
380             lg2::error("timer error: '{ERROR_MESSAGE}'", "ERROR_MESSAGE",
381                        ec.message());
382             return;
383         }
384         if (sensorPtr->readingStateGood())
385         {
386             assertThresholds(sensorPtr.get(), assertValue, threshold.level,
387                              threshold.direction, assert);
388         }
389     });
390 }
391 
checkThresholds(Sensor * sensor)392 bool checkThresholds(Sensor* sensor)
393 {
394     bool status = true;
395     std::vector<ChangeParam> changes = checkThresholds(sensor, sensor->value);
396     for (const auto& change : changes)
397     {
398         assertThresholds(sensor, change.assertValue, change.threshold.level,
399                          change.threshold.direction, change.asserted);
400         if (change.threshold.level == thresholds::Level::CRITICAL &&
401             change.asserted)
402         {
403             status = false;
404         }
405     }
406 
407     return status;
408 }
409 
checkThresholdsPowerDelay(const std::weak_ptr<Sensor> & weakSensor,ThresholdTimer & thresholdTimer)410 void checkThresholdsPowerDelay(const std::weak_ptr<Sensor>& weakSensor,
411                                ThresholdTimer& thresholdTimer)
412 {
413     auto sensorPtr = weakSensor.lock();
414     if (!sensorPtr)
415     {
416         return; // sensor is destructed, should never be here
417     }
418 
419     Sensor* sensor = sensorPtr.get();
420     std::vector<ChangeParam> changes = checkThresholds(sensor, sensor->value);
421     for (const auto& change : changes)
422     {
423         // When CPU is powered off, some volatges are expected to
424         // go below low thresholds. Filter these events with thresholdTimer.
425         // 1. always delay the assertion of low events to see if they are
426         //   caused by power off event.
427         // 2. conditional delay the de-assertion of low events if there is
428         //   an existing timer for assertion.
429         // 3. no delays for de-assert of low events if there is an existing
430         //   de-assert for low event. This means 2nd de-assert would happen
431         //   first and when timer expires for the previous one, no additional
432         //   signal will be logged.
433         // 4. no delays for all high events.
434         if (change.threshold.direction == thresholds::Direction::LOW)
435         {
436             if (change.asserted || thresholdTimer.hasActiveTimer(
437                                        change.threshold, !change.asserted))
438             {
439                 thresholdTimer.startTimer(weakSensor, change.threshold,
440                                           change.asserted, change.assertValue);
441                 continue;
442             }
443         }
444         assertThresholds(sensor, change.assertValue, change.threshold.level,
445                          change.threshold.direction, change.asserted);
446     }
447 }
448 
assertThresholds(Sensor * sensor,double assertValue,thresholds::Level level,thresholds::Direction direction,bool assert)449 void assertThresholds(Sensor* sensor, double assertValue,
450                       thresholds::Level level, thresholds::Direction direction,
451                       bool assert)
452 {
453     std::shared_ptr<sdbusplus::asio::dbus_interface> interface =
454         sensor->getThresholdInterface(level);
455 
456     if (!interface)
457     {
458         lg2::info("trying to set uninitialized interface");
459         return;
460     }
461 
462     std::string property = Sensor::propertyAlarm(level, direction);
463     if (property.empty())
464     {
465         lg2::info("Alarm property is empty");
466         return;
467     }
468     if (interface->set_property<bool, true>(property, assert))
469     {
470         try
471         {
472             // msg.get_path() is interface->get_object_path()
473             sdbusplus::message_t msg =
474                 interface->new_signal("ThresholdAsserted");
475 
476             msg.append(sensor->name, interface->get_interface_name(), property,
477                        assert, assertValue);
478             msg.signal_send();
479         }
480         catch (const sdbusplus::exception_t& e)
481         {
482             lg2::error(
483                 "Failed to send thresholdAsserted signal with assertValue");
484         }
485     }
486 }
487 
parseThresholdsFromAttr(std::vector<thresholds::Threshold> & thresholdVector,const std::string & inputPath,const double & scaleFactor,const double & offset,const double & hysteresis)488 bool parseThresholdsFromAttr(
489     std::vector<thresholds::Threshold>& thresholdVector,
490     const std::string& inputPath, const double& scaleFactor,
491     const double& offset, const double& hysteresis)
492 {
493     const boost::container::flat_map<
494         std::string, std::vector<std::tuple<const char*, thresholds::Level,
495                                             thresholds::Direction, double>>>
496         map = {
497             {"average",
498              {
499                  std::make_tuple("average_min", Level::WARNING, Direction::LOW,
500                                  0.0),
501                  std::make_tuple("average_max", Level::WARNING, Direction::HIGH,
502                                  0.0),
503              }},
504             {"input",
505              {
506                  std::make_tuple("min", Level::WARNING, Direction::LOW, 0.0),
507                  std::make_tuple("max", Level::WARNING, Direction::HIGH, 0.0),
508                  std::make_tuple("lcrit", Level::CRITICAL, Direction::LOW, 0.0),
509                  std::make_tuple("crit", Level::CRITICAL, Direction::HIGH,
510                                  offset),
511              }},
512         };
513 
514     if (auto fileParts = splitFileName(inputPath))
515     {
516         auto& [type, nr, item] = *fileParts;
517         if (map.contains(item))
518         {
519             for (const auto& t : map.at(item))
520             {
521                 const auto& [suffix, level, direction, offset] = t;
522                 auto attrPath =
523                     boost::replace_all_copy(inputPath, item, suffix);
524                 if (auto val = readFile(attrPath, scaleFactor))
525                 {
526                     *val += offset;
527                     lg2::debug("Threshold: '{PATH}': '{VALUE}'", "PATH",
528                                attrPath, "VALUE", *val);
529                     thresholdVector.emplace_back(level, direction, *val,
530                                                  hysteresis);
531                 }
532             }
533         }
534     }
535     return true;
536 }
537 
getInterface(const Level thresholdLevel)538 std::string getInterface(const Level thresholdLevel)
539 {
540     for (const ThresholdDefinition& thresh : thresProp)
541     {
542         if (thresh.level == thresholdLevel)
543         {
544             return std::string("xyz.openbmc_project.Sensor.Threshold.") +
545                    thresh.levelName;
546         }
547     }
548     return "";
549 }
550 } // namespace thresholds
551