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