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