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