xref: /openbmc/dbus-sensors/src/Thresholds.cpp (revision 091c92c0)
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         uint8_t 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 level = std::visit(VariantToUnsignedIntVisitor(),
185                                                 severityFind->second);
186 
187                 std::string dir =
188                     std::visit(VariantToStringVisitor(), directionFind->second);
189                 if (((findThresholdLevel(level, dir)) != threshold.level) ||
190                     ((findThresholdDirection(level, 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     if (sensor->thresholds.empty())
216     {
217         return;
218     }
219 
220     for (const auto& threshold : sensor->thresholds)
221     {
222         if (!findOrder(threshold.level, threshold.direction))
223         {
224             continue;
225         }
226         std::shared_ptr<sdbusplus::asio::dbus_interface> interface =
227             sensor->getThresholdInterface(threshold.level);
228 
229         if (!interface)
230         {
231             continue;
232         }
233 
234         std::string property =
235             sensor->propertyLevel(threshold.level, threshold.direction);
236         if (property.empty())
237         {
238             continue;
239         }
240         interface->set_property(property, threshold.value);
241     }
242 }
243 
244 // Debugging counters
245 static int cHiTrue = 0;
246 static int cHiFalse = 0;
247 static int cHiMidstate = 0;
248 static int cLoTrue = 0;
249 static int cLoFalse = 0;
250 static int cLoMidstate = 0;
251 static int cDebugThrottle = 0;
252 static constexpr int assertLogCount = 10;
253 
254 struct ChangeParam
255 {
256     ChangeParam(Threshold whichThreshold, bool status, double value) :
257         threshold(whichThreshold), asserted(status), assertValue(value)
258     {}
259 
260     Threshold threshold;
261     bool asserted;
262     double assertValue;
263 };
264 
265 static std::vector<ChangeParam> checkThresholds(Sensor* sensor, double value)
266 {
267     std::vector<ChangeParam> thresholdChanges;
268     if (sensor->thresholds.empty())
269     {
270         return thresholdChanges;
271     }
272 
273     for (auto& threshold : sensor->thresholds)
274     {
275         // Use "Schmitt trigger" logic to avoid threshold trigger spam,
276         // if value is noisy while hovering very close to a threshold.
277         // When a threshold is crossed, indicate true immediately,
278         // but require more distance to be crossed the other direction,
279         // before resetting the indicator back to false.
280         if (threshold.direction == thresholds::Direction::HIGH)
281         {
282             if (value >= threshold.value)
283             {
284                 thresholdChanges.emplace_back(threshold, true, value);
285                 if (++cHiTrue < assertLogCount)
286                 {
287                     std::cerr << "Sensor " << sensor->name << " high threshold "
288                               << threshold.value << " assert: value " << value
289                               << " raw data " << sensor->rawValue << "\n";
290                 }
291             }
292             else if (value < (threshold.value - threshold.hysteresis))
293             {
294                 thresholdChanges.emplace_back(threshold, false, value);
295                 ++cHiFalse;
296             }
297             else
298             {
299                 ++cHiMidstate;
300             }
301         }
302         else if (threshold.direction == thresholds::Direction::LOW)
303         {
304             if (value <= threshold.value)
305             {
306                 thresholdChanges.emplace_back(threshold, true, value);
307                 if (++cLoTrue < assertLogCount)
308                 {
309                     std::cerr << "Sensor " << sensor->name << " low threshold "
310                               << threshold.value << " assert: value "
311                               << sensor->value << " raw data "
312                               << sensor->rawValue << "\n";
313                 }
314             }
315             else if (value > (threshold.value + threshold.hysteresis))
316             {
317                 thresholdChanges.emplace_back(threshold, false, value);
318                 ++cLoFalse;
319             }
320             else
321             {
322                 ++cLoMidstate;
323             }
324         }
325         else
326         {
327             std::cerr << "Error determining threshold direction\n";
328         }
329     }
330 
331     // Throttle debug output, so that it does not continuously spam
332     ++cDebugThrottle;
333     if (cDebugThrottle >= 1000)
334     {
335         cDebugThrottle = 0;
336         if constexpr (debug)
337         {
338             std::cerr << "checkThresholds: High T=" << cHiTrue
339                       << " F=" << cHiFalse << " M=" << cHiMidstate
340                       << ", Low T=" << cLoTrue << " F=" << cLoFalse
341                       << " M=" << cLoMidstate << "\n";
342         }
343     }
344 
345     return thresholdChanges;
346 }
347 
348 void ThresholdTimer::startTimer(const std::weak_ptr<Sensor>& weakSensor,
349                                 const Threshold& threshold, bool assert,
350                                 double assertValue)
351 {
352     struct TimerUsed timerUsed = {};
353     constexpr const size_t waitTime = 5;
354     TimerPair* pair = nullptr;
355 
356     for (TimerPair& timer : timers)
357     {
358         if (!timer.first.used)
359         {
360             pair = &timer;
361             break;
362         }
363     }
364     if (pair == nullptr)
365     {
366         pair = &timers.emplace_back(timerUsed, boost::asio::deadline_timer(io));
367     }
368 
369     pair->first.used = true;
370     pair->first.level = threshold.level;
371     pair->first.direction = threshold.direction;
372     pair->first.assert = assert;
373     pair->second.expires_from_now(boost::posix_time::seconds(waitTime));
374     pair->second.async_wait([weakSensor, pair, threshold, assert,
375                              assertValue](boost::system::error_code ec) {
376         auto sensorPtr = weakSensor.lock();
377         if (!sensorPtr)
378         {
379             return; // owner sensor has been destructed
380         }
381         // pair is valid as long as sensor is valid
382         pair->first.used = false;
383 
384         if (ec == boost::asio::error::operation_aborted)
385         {
386             return; // we're being canceled
387         }
388         if (ec)
389         {
390             std::cerr << "timer error: " << ec.message() << "\n";
391             return;
392         }
393         if (sensorPtr->readingStateGood())
394         {
395             assertThresholds(sensorPtr.get(), assertValue, threshold.level,
396                              threshold.direction, assert);
397         }
398     });
399 }
400 
401 bool checkThresholds(Sensor* sensor)
402 {
403     bool status = true;
404     std::vector<ChangeParam> changes = checkThresholds(sensor, sensor->value);
405     for (const auto& change : changes)
406     {
407         assertThresholds(sensor, change.assertValue, change.threshold.level,
408                          change.threshold.direction, change.asserted);
409         if (change.threshold.level == thresholds::Level::CRITICAL &&
410             change.asserted)
411         {
412             status = false;
413         }
414     }
415 
416     return status;
417 }
418 
419 void checkThresholdsPowerDelay(const std::weak_ptr<Sensor>& weakSensor,
420                                ThresholdTimer& thresholdTimer)
421 {
422     auto sensorPtr = weakSensor.lock();
423     if (!sensorPtr)
424     {
425         return; // sensor is destructed, should never be here
426     }
427 
428     Sensor* sensor = sensorPtr.get();
429     std::vector<ChangeParam> changes = checkThresholds(sensor, sensor->value);
430     for (const auto& change : changes)
431     {
432         // When CPU is powered off, some volatges are expected to
433         // go below low thresholds. Filter these events with thresholdTimer.
434         // 1. always delay the assertion of low events to see if they are
435         //   caused by power off event.
436         // 2. conditional delay the de-assertion of low events if there is
437         //   an existing timer for assertion.
438         // 3. no delays for de-assert of low events if there is an existing
439         //   de-assert for low event. This means 2nd de-assert would happen
440         //   first and when timer expires for the previous one, no additional
441         //   signal will be logged.
442         // 4. no delays for all high events.
443         if (change.threshold.direction == thresholds::Direction::LOW)
444         {
445             if (change.asserted || thresholdTimer.hasActiveTimer(
446                                        change.threshold, !change.asserted))
447             {
448                 thresholdTimer.startTimer(weakSensor, change.threshold,
449                                           change.asserted, change.assertValue);
450                 continue;
451             }
452         }
453         assertThresholds(sensor, change.assertValue, change.threshold.level,
454                          change.threshold.direction, change.asserted);
455     }
456 }
457 
458 void assertThresholds(Sensor* sensor, double assertValue,
459                       thresholds::Level level, thresholds::Direction direction,
460                       bool assert)
461 {
462     if (!findOrder(level, direction))
463     {
464         return;
465     }
466 
467     std::shared_ptr<sdbusplus::asio::dbus_interface> interface =
468         sensor->getThresholdInterface(level);
469 
470     if (!interface)
471     {
472         std::cout << "trying to set uninitialized interface\n";
473         return;
474     }
475 
476     std::string property = sensor->propertyAlarm(level, direction);
477     if (property.empty())
478     {
479         std::cout << "Alarm property is empty \n";
480         return;
481     }
482     if (interface->set_property<bool, true>(property, assert))
483     {
484         try
485         {
486             // msg.get_path() is interface->get_object_path()
487             sdbusplus::message::message msg =
488                 interface->new_signal("ThresholdAsserted");
489 
490             msg.append(sensor->name, interface->get_interface_name(), property,
491                        assert, assertValue);
492             msg.signal_send();
493         }
494         catch (const sdbusplus::exception::exception& e)
495         {
496             std::cerr
497                 << "Failed to send thresholdAsserted signal with assertValue\n";
498         }
499     }
500 }
501 
502 bool parseThresholdsFromAttr(
503     std::vector<thresholds::Threshold>& thresholdVector,
504     const std::string& inputPath, const double& scaleFactor,
505     const double& offset)
506 {
507     const boost::container::flat_map<
508         std::string, std::vector<std::tuple<const char*, thresholds::Level,
509                                             thresholds::Direction, double>>>
510         map = {
511             {"average",
512              {
513                  std::make_tuple("average_min", Level::WARNING, Direction::LOW,
514                                  0.0),
515                  std::make_tuple("average_max", Level::WARNING, Direction::HIGH,
516                                  0.0),
517              }},
518             {"input",
519              {
520                  std::make_tuple("min", Level::WARNING, Direction::LOW, 0.0),
521                  std::make_tuple("max", Level::WARNING, Direction::HIGH, 0.0),
522                  std::make_tuple("lcrit", Level::CRITICAL, Direction::LOW, 0.0),
523                  std::make_tuple("crit", Level::CRITICAL, Direction::HIGH,
524                                  offset),
525              }},
526         };
527 
528     if (auto fileParts = splitFileName(inputPath))
529     {
530         auto& [type, nr, item] = *fileParts;
531         if (map.count(item) != 0)
532         {
533             for (const auto& t : map.at(item))
534             {
535                 auto& [suffix, level, direction, offset] = t;
536                 auto attrPath =
537                     boost::replace_all_copy(inputPath, item, suffix);
538                 if (auto val = readFile(attrPath, scaleFactor))
539                 {
540                     *val += offset;
541                     if (debug)
542                     {
543                         std::cout << "Threshold: " << attrPath << ": " << *val
544                                   << "\n";
545                     }
546                     thresholdVector.emplace_back(level, direction, *val);
547                 }
548             }
549         }
550     }
551     return true;
552 }
553 
554 std::string getInterface(const Level thresholdLevel)
555 {
556     std::string level;
557     switch (thresholdLevel)
558     {
559         case Level::WARNING:
560             level = "Warning";
561             break;
562         case Level::CRITICAL:
563             level = "Critical";
564             break;
565         case Level::SOFTSHUTDOWN:
566             level = "SoftShutDown";
567             break;
568         case Level::HARDSHUTDOWN:
569             level = "HardShutdown";
570             break;
571         case Level::ERROR:
572             level = "Error";
573             break;
574     }
575     std::string interface = "xyz.openbmc_project.Sensor.Threshold." + level;
576     return interface;
577 }
578 } // namespace thresholds
579