blob: 986d1838d6e87d2c74d2bd1a5b4db1612fbd52c2 [file] [log] [blame]
Patrick Ventureca44b2f2019-10-31 11:02:26 -07001#include "Thresholds.hpp"
2
3#include "VariantVisitors.hpp"
4#include "sensor.hpp"
5
James Feist6714a252018-09-10 15:26:18 -07006#include <boost/algorithm/string/replace.hpp>
Patrick Venture96e97db2019-10-31 13:44:38 -07007#include <boost/container/flat_map.hpp>
James Feist6714a252018-09-10 15:26:18 -07008#include <boost/lexical_cast.hpp>
James Feist38fb5982020-05-28 10:09:54 -07009
10#include <array>
James Feistdc6c55f2018-10-31 12:53:20 -070011#include <cmath>
James Feist6714a252018-09-10 15:26:18 -070012#include <fstream>
13#include <iostream>
Patrick Venture96e97db2019-10-31 13:44:38 -070014#include <memory>
15#include <stdexcept>
16#include <string>
Zbigniew Kurzynski0a4c4802020-04-01 11:22:27 +020017#include <tuple>
Patrick Venture96e97db2019-10-31 13:44:38 -070018#include <utility>
19#include <variant>
20#include <vector>
James Feist6714a252018-09-10 15:26:18 -070021
22static constexpr bool DEBUG = false;
James Feist6714a252018-09-10 15:26:18 -070023namespace thresholds
24{
James Feistd8705872019-02-08 13:26:09 -080025unsigned int toBusValue(const Level& level)
James Feist6714a252018-09-10 15:26:18 -070026{
27 switch (level)
28 {
29 case (Level::WARNING):
30 {
31 return 0;
32 }
33 case (Level::CRITICAL):
34 {
35 return 1;
36 }
37 default:
38 {
39 return -1;
40 }
41 }
42}
43
James Feistd8705872019-02-08 13:26:09 -080044std::string toBusValue(const Direction& direction)
James Feist6714a252018-09-10 15:26:18 -070045{
46 switch (direction)
47 {
48 case (Direction::LOW):
49 {
50 return "less than";
51 }
52 case (Direction::HIGH):
53 {
54 return "greater than";
55 }
56 default:
57 {
58 return "err";
59 }
60 }
61}
62
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -070063bool parseThresholdsFromConfig(
James Feistd8705872019-02-08 13:26:09 -080064 const SensorData& sensorData,
65 std::vector<thresholds::Threshold>& thresholdVector,
66 const std::string* matchLabel)
James Feist6714a252018-09-10 15:26:18 -070067{
James Feistd8705872019-02-08 13:26:09 -080068 for (const auto& item : sensorData)
James Feist6714a252018-09-10 15:26:18 -070069 {
70 if (item.first.find("Thresholds") == std::string::npos)
71 {
72 continue;
73 }
74 if (matchLabel != nullptr)
75 {
76 auto labelFind = item.second.find("Label");
77 if (labelFind == item.second.end())
78 continue;
James Feist3eb82622019-02-08 13:10:22 -080079 if (std::visit(VariantToStringVisitor(), labelFind->second) !=
80 *matchLabel)
James Feist6714a252018-09-10 15:26:18 -070081 continue;
82 }
83 auto directionFind = item.second.find("Direction");
84 auto severityFind = item.second.find("Severity");
85 auto valueFind = item.second.find("Value");
86 if (valueFind == item.second.end() ||
87 severityFind == item.second.end() ||
88 directionFind == item.second.end())
89 {
90 std::cerr << "Malformed threshold in configuration\n";
91 return false;
92 }
93 Level level;
94 Direction direction;
James Feist3eb82622019-02-08 13:10:22 -080095 if (std::visit(VariantToUnsignedIntVisitor(), severityFind->second) ==
96 0)
James Feist6714a252018-09-10 15:26:18 -070097 {
98 level = Level::WARNING;
99 }
100 else
101 {
102 level = Level::CRITICAL;
103 }
James Feist3eb82622019-02-08 13:10:22 -0800104 if (std::visit(VariantToStringVisitor(), directionFind->second) ==
105 "less than")
James Feist6714a252018-09-10 15:26:18 -0700106 {
107 direction = Direction::LOW;
108 }
109 else
110 {
111 direction = Direction::HIGH;
112 }
James Feist13f340b2019-03-07 16:36:11 -0800113 double val = std::visit(VariantToDoubleVisitor(), valueFind->second);
James Feist6714a252018-09-10 15:26:18 -0700114
115 thresholdVector.emplace_back(level, direction, val);
116 }
117 return true;
118}
119
James Feistd8705872019-02-08 13:26:09 -0800120void persistThreshold(const std::string& path, const std::string& baseInterface,
121 const thresholds::Threshold& threshold,
James Feista222ba72019-03-01 15:57:51 -0800122 std::shared_ptr<sdbusplus::asio::connection>& conn,
Cheng C Yang6b1247a2020-03-09 23:48:39 +0800123 size_t thresholdCount, const std::string& labelMatch)
James Feist6714a252018-09-10 15:26:18 -0700124{
Brad Bishopfbb44ad2019-11-08 09:42:37 -0500125 for (size_t ii = 0; ii < thresholdCount; ii++)
James Feist6714a252018-09-10 15:26:18 -0700126 {
127 std::string thresholdInterface =
128 baseInterface + ".Thresholds" + std::to_string(ii);
129 conn->async_method_call(
Cheng C Yang6b1247a2020-03-09 23:48:39 +0800130 [&, path, threshold, thresholdInterface, labelMatch](
James Feistd8705872019-02-08 13:26:09 -0800131 const boost::system::error_code& ec,
132 const boost::container::flat_map<std::string, BasicVariantType>&
133 result) {
James Feist6714a252018-09-10 15:26:18 -0700134 if (ec)
135 {
136 return; // threshold not supported
137 }
138
Cheng C Yang6b1247a2020-03-09 23:48:39 +0800139 if (!labelMatch.empty())
140 {
141 auto labelFind = result.find("Label");
142 if (labelFind == result.end())
143 {
144 std::cerr << "No label in threshold configuration\n";
145 return;
146 }
147 std::string label =
148 std::visit(VariantToStringVisitor(), labelFind->second);
149 if (label != labelMatch)
150 {
151 return;
152 }
153 }
154
James Feist6714a252018-09-10 15:26:18 -0700155 auto directionFind = result.find("Direction");
156 auto severityFind = result.find("Severity");
157 auto valueFind = result.find("Value");
158 if (valueFind == result.end() || severityFind == result.end() ||
159 directionFind == result.end())
160 {
161 std::cerr << "Malformed threshold in configuration\n";
162 return;
163 }
James Feist3eb82622019-02-08 13:10:22 -0800164 unsigned int level = std::visit(VariantToUnsignedIntVisitor(),
165 severityFind->second);
James Feist6714a252018-09-10 15:26:18 -0700166
James Feist3eb82622019-02-08 13:10:22 -0800167 std::string dir =
168 std::visit(VariantToStringVisitor(), directionFind->second);
James Feist6714a252018-09-10 15:26:18 -0700169 if ((toBusValue(threshold.level) != level) ||
170 (toBusValue(threshold.direction) != dir))
171 {
172 return; // not the droid we're looking for
173 }
174
James Feist3eb82622019-02-08 13:10:22 -0800175 std::variant<double> value(threshold.value);
James Feist6714a252018-09-10 15:26:18 -0700176 conn->async_method_call(
James Feistd8705872019-02-08 13:26:09 -0800177 [](const boost::system::error_code& ec) {
James Feist6714a252018-09-10 15:26:18 -0700178 if (ec)
179 {
180 std::cerr << "Error setting threshold " << ec
181 << "\n";
182 }
183 },
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700184 entityManagerName, path, "org.freedesktop.DBus.Properties",
185 "Set", thresholdInterface, "Value", value);
James Feist6714a252018-09-10 15:26:18 -0700186 },
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700187 entityManagerName, path, "org.freedesktop.DBus.Properties",
James Feist6714a252018-09-10 15:26:18 -0700188 "GetAll", thresholdInterface);
189 }
190}
191
Jae Hyun Yoo95b8a2d2019-02-25 20:15:09 -0800192void updateThresholds(Sensor* sensor)
193{
194 if (sensor->thresholds.empty())
195 {
196 return;
197 }
198
199 for (const auto& threshold : sensor->thresholds)
200 {
201 std::shared_ptr<sdbusplus::asio::dbus_interface> interface;
202 std::string property;
203 if (threshold.level == thresholds::Level::CRITICAL)
204 {
205 interface = sensor->thresholdInterfaceCritical;
206 if (threshold.direction == thresholds::Direction::HIGH)
207 {
208 property = "CriticalHigh";
209 }
210 else
211 {
212 property = "CriticalLow";
213 }
214 }
215 else if (threshold.level == thresholds::Level::WARNING)
216 {
217 interface = sensor->thresholdInterfaceWarning;
218 if (threshold.direction == thresholds::Direction::HIGH)
219 {
220 property = "WarningHigh";
221 }
222 else
223 {
224 property = "WarningLow";
225 }
226 }
227 else
228 {
229 continue;
230 }
231 if (!interface)
232 {
233 continue;
234 }
235 interface->set_property(property, threshold.value);
236 }
237}
238
Josh Lehan883fb3a2020-02-27 14:41:39 -0800239// Debugging counters
240static int cHiTrue = 0;
241static int cHiFalse = 0;
242static int cHiMidstate = 0;
243static int cLoTrue = 0;
244static int cLoFalse = 0;
245static int cLoMidstate = 0;
246static int cDebugThrottle = 0;
247
Zhikui Ren59b8b9e2020-06-26 18:34:22 -0700248struct ChangeParam
James Feist251c7822018-09-12 12:54:15 -0700249{
Zhikui Ren59b8b9e2020-06-26 18:34:22 -0700250 ChangeParam(Threshold whichThreshold, bool status, double value) :
251 threshold(whichThreshold), asserted(status), assertValue(value)
252 {}
253
254 Threshold threshold;
255 bool asserted;
256 double assertValue;
257};
258
259static std::vector<ChangeParam> checkThresholds(Sensor* sensor, double value)
260{
261 std::vector<ChangeParam> thresholdChanges;
James Feist251c7822018-09-12 12:54:15 -0700262 if (sensor->thresholds.empty())
263 {
James Feist46342ec2019-03-06 14:03:41 -0800264 return thresholdChanges;
James Feist251c7822018-09-12 12:54:15 -0700265 }
James Feist46342ec2019-03-06 14:03:41 -0800266
James Feistd8705872019-02-08 13:26:09 -0800267 for (auto& threshold : sensor->thresholds)
James Feist251c7822018-09-12 12:54:15 -0700268 {
Josh Lehan883fb3a2020-02-27 14:41:39 -0800269 // Use "Schmitt trigger" logic to avoid threshold trigger spam,
270 // if value is noisy while hovering very close to a threshold.
271 // When a threshold is crossed, indicate true immediately,
272 // but require more distance to be crossed the other direction,
273 // before resetting the indicator back to false.
James Feist46342ec2019-03-06 14:03:41 -0800274 if (threshold.direction == thresholds::Direction::HIGH)
James Feistdc6c55f2018-10-31 12:53:20 -0700275 {
James Feist551087a2019-12-09 11:17:12 -0800276 if (value >= threshold.value)
James Feist251c7822018-09-12 12:54:15 -0700277 {
Zhikui Ren59b8b9e2020-06-26 18:34:22 -0700278 thresholdChanges.emplace_back(threshold, true, value);
Zhikui Ren2456dde2020-09-11 10:33:30 -0700279 ++cHiTrue;
Josh Lehan883fb3a2020-02-27 14:41:39 -0800280 }
281 else if (value < (threshold.value - sensor->hysteresisTrigger))
282 {
Zhikui Ren59b8b9e2020-06-26 18:34:22 -0700283 thresholdChanges.emplace_back(threshold, false, value);
Josh Lehan883fb3a2020-02-27 14:41:39 -0800284 ++cHiFalse;
James Feist251c7822018-09-12 12:54:15 -0700285 }
Patrick Venture66235d42019-10-11 08:31:27 -0700286 else
James Feist251c7822018-09-12 12:54:15 -0700287 {
Josh Lehan883fb3a2020-02-27 14:41:39 -0800288 ++cHiMidstate;
James Feist251c7822018-09-12 12:54:15 -0700289 }
290 }
Josh Lehan883fb3a2020-02-27 14:41:39 -0800291 else if (threshold.direction == thresholds::Direction::LOW)
James Feist251c7822018-09-12 12:54:15 -0700292 {
James Feist551087a2019-12-09 11:17:12 -0800293 if (value <= threshold.value)
James Feist251c7822018-09-12 12:54:15 -0700294 {
Zhikui Ren59b8b9e2020-06-26 18:34:22 -0700295 thresholdChanges.emplace_back(threshold, true, value);
Zhikui Ren2456dde2020-09-11 10:33:30 -0700296 ++cLoTrue;
Josh Lehan883fb3a2020-02-27 14:41:39 -0800297 }
298 else if (value > (threshold.value + sensor->hysteresisTrigger))
299 {
Zhikui Ren59b8b9e2020-06-26 18:34:22 -0700300 thresholdChanges.emplace_back(threshold, false, value);
Josh Lehan883fb3a2020-02-27 14:41:39 -0800301 ++cLoFalse;
James Feist251c7822018-09-12 12:54:15 -0700302 }
Patrick Venture66235d42019-10-11 08:31:27 -0700303 else
James Feist251c7822018-09-12 12:54:15 -0700304 {
Josh Lehan883fb3a2020-02-27 14:41:39 -0800305 ++cLoMidstate;
James Feist251c7822018-09-12 12:54:15 -0700306 }
307 }
Josh Lehan883fb3a2020-02-27 14:41:39 -0800308 else
309 {
310 std::cerr << "Error determining threshold direction\n";
311 }
James Feist251c7822018-09-12 12:54:15 -0700312 }
Josh Lehan883fb3a2020-02-27 14:41:39 -0800313
314 if constexpr (DEBUG)
315 {
316 // Throttle debug output, so that it does not continuously spam
317 ++cDebugThrottle;
318 if (cDebugThrottle >= 1000)
319 {
320 cDebugThrottle = 0;
321 std::cerr << "checkThresholds: High T=" << cHiTrue
322 << " F=" << cHiFalse << " M=" << cHiMidstate
323 << ", Low T=" << cLoTrue << " F=" << cLoFalse
324 << " M=" << cLoMidstate << "\n";
325 }
326 }
327
James Feist46342ec2019-03-06 14:03:41 -0800328 return thresholdChanges;
329}
330
331bool checkThresholds(Sensor* sensor)
332{
James Feist7b18b1e2019-05-14 13:42:09 -0700333 bool status = true;
Zhikui Ren59b8b9e2020-06-26 18:34:22 -0700334 std::vector<ChangeParam> changes = checkThresholds(sensor, sensor->value);
335 for (const auto& change : changes)
James Feist46342ec2019-03-06 14:03:41 -0800336 {
Zhikui Ren59b8b9e2020-06-26 18:34:22 -0700337 assertThresholds(sensor, change.assertValue, change.threshold.level,
338 change.threshold.direction, change.asserted);
339 if (change.threshold.level == thresholds::Level::CRITICAL &&
340 change.asserted)
James Feist46342ec2019-03-06 14:03:41 -0800341 {
James Feist7b18b1e2019-05-14 13:42:09 -0700342 status = false;
James Feist46342ec2019-03-06 14:03:41 -0800343 }
344 }
345
James Feistdc6c55f2018-10-31 12:53:20 -0700346 return status;
James Feist251c7822018-09-12 12:54:15 -0700347}
348
James Feist46342ec2019-03-06 14:03:41 -0800349void checkThresholdsPowerDelay(Sensor* sensor, ThresholdTimer& thresholdTimer)
350{
351
Zhikui Ren59b8b9e2020-06-26 18:34:22 -0700352 std::vector<ChangeParam> changes = checkThresholds(sensor, sensor->value);
353 for (const auto& change : changes)
James Feist46342ec2019-03-06 14:03:41 -0800354 {
Zhikui Renbf7cbc82020-07-02 08:44:00 -0700355 // When CPU is powered off, some volatges are expected to
356 // go below low thresholds. Filter these events with thresholdTimer.
357 // 1. always delay the assertion of low events to see if they are
358 // caused by power off event.
359 // 2. conditional delay the de-assertion of low events if there is
360 // an existing timer for assertion.
361 // 3. no delays for de-assert of low events if there is an existing
362 // de-assert for low event. This means 2nd de-assert would happen
363 // first and when timer expires for the previous one, no additional
364 // signal will be logged.
365 // 4. no delays for all high events.
366 if (change.threshold.direction == thresholds::Direction::LOW)
James Feist46342ec2019-03-06 14:03:41 -0800367 {
Zhikui Renbf7cbc82020-07-02 08:44:00 -0700368 if (change.asserted || thresholdTimer.hasActiveTimer(
369 change.threshold, !change.asserted))
370 {
371 thresholdTimer.startTimer(change.threshold, change.asserted,
372 change.assertValue);
373 continue;
374 }
James Feist46342ec2019-03-06 14:03:41 -0800375 }
Zhikui Renbf7cbc82020-07-02 08:44:00 -0700376 assertThresholds(sensor, change.assertValue, change.threshold.level,
377 change.threshold.direction, change.asserted);
James Feist46342ec2019-03-06 14:03:41 -0800378 }
379}
380
Zhikui Ren59b8b9e2020-06-26 18:34:22 -0700381void assertThresholds(Sensor* sensor, double assertValue,
382 thresholds::Level level, thresholds::Direction direction,
383 bool assert)
James Feist251c7822018-09-12 12:54:15 -0700384{
385 std::string property;
386 std::shared_ptr<sdbusplus::asio::dbus_interface> interface;
387 if (level == thresholds::Level::WARNING &&
388 direction == thresholds::Direction::HIGH)
389 {
390 property = "WarningAlarmHigh";
391 interface = sensor->thresholdInterfaceWarning;
392 }
393 else if (level == thresholds::Level::WARNING &&
394 direction == thresholds::Direction::LOW)
395 {
396 property = "WarningAlarmLow";
397 interface = sensor->thresholdInterfaceWarning;
398 }
399 else if (level == thresholds::Level::CRITICAL &&
400 direction == thresholds::Direction::HIGH)
401 {
402 property = "CriticalAlarmHigh";
403 interface = sensor->thresholdInterfaceCritical;
404 }
405 else if (level == thresholds::Level::CRITICAL &&
406 direction == thresholds::Direction::LOW)
407 {
408 property = "CriticalAlarmLow";
409 interface = sensor->thresholdInterfaceCritical;
410 }
411 else
412 {
413 std::cerr << "Unknown threshold, level " << level << "direction "
414 << direction << "\n";
415 return;
416 }
417 if (!interface)
418 {
419 std::cout << "trying to set uninitialized interface\n";
420 return;
421 }
Zhikui Ren59b8b9e2020-06-26 18:34:22 -0700422
423 if (interface->set_property<bool, true>(property, assert))
424 {
425 try
426 {
427 // msg.get_path() is interface->get_object_path()
428 sdbusplus::message::message msg =
429 interface->new_signal("ThresholdAsserted");
430
431 msg.append(sensor->name, interface->get_interface_name(), property,
432 assert, assertValue);
433 msg.signal_send();
434 }
435 catch (const sdbusplus::exception::exception& e)
436 {
437 std::cerr
438 << "Failed to send thresholdAsserted signal with assertValue\n";
439 }
440 }
James Feist251c7822018-09-12 12:54:15 -0700441}
442
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700443bool parseThresholdsFromAttr(
James Feistd8705872019-02-08 13:26:09 -0800444 std::vector<thresholds::Threshold>& thresholdVector,
Vijay Khemka86dea2b2019-06-06 11:14:37 -0700445 const std::string& inputPath, const double& scaleFactor,
446 const double& offset)
James Feist6714a252018-09-10 15:26:18 -0700447{
Zbigniew Kurzynski0a4c4802020-04-01 11:22:27 +0200448 const boost::container::flat_map<
449 std::string, std::vector<std::tuple<const char*, thresholds::Level,
450 thresholds::Direction, double>>>
451 map = {
452 {"average",
453 {
454 std::make_tuple("average_min", Level::WARNING, Direction::LOW,
455 0.0),
456 std::make_tuple("average_max", Level::WARNING, Direction::HIGH,
457 0.0),
458 }},
459 {"input",
460 {
461 std::make_tuple("min", Level::WARNING, Direction::LOW, 0.0),
462 std::make_tuple("max", Level::WARNING, Direction::HIGH, 0.0),
463 std::make_tuple("lcrit", Level::CRITICAL, Direction::LOW, 0.0),
464 std::make_tuple("crit", Level::CRITICAL, Direction::HIGH,
465 offset),
466 }},
467 };
468
469 if (auto fileParts = splitFileName(inputPath))
James Feist6714a252018-09-10 15:26:18 -0700470 {
Zbigniew Kurzynski0a4c4802020-04-01 11:22:27 +0200471 auto [type, nr, item] = *fileParts;
472 if (map.count(item) != 0)
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700473 {
Zbigniew Kurzynski0a4c4802020-04-01 11:22:27 +0200474 for (const auto& t : map.at(item))
475 {
476 auto [suffix, level, direction, offset] = t;
477 auto attrPath =
478 boost::replace_all_copy(inputPath, item, suffix);
479 if (auto val = readFile(attrPath, scaleFactor))
480 {
481 *val += offset;
482 if (DEBUG)
483 {
484 std::cout << "Threshold: " << attrPath << ": " << *val
485 << "\n";
486 }
487 thresholdVector.emplace_back(level, direction, *val);
488 }
489 }
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700490 }
James Feist6714a252018-09-10 15:26:18 -0700491 }
James Feist6714a252018-09-10 15:26:18 -0700492 return true;
493}
494
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700495bool hasCriticalInterface(
James Feistd8705872019-02-08 13:26:09 -0800496 const std::vector<thresholds::Threshold>& thresholdVector)
James Feist6714a252018-09-10 15:26:18 -0700497{
James Feistd8705872019-02-08 13:26:09 -0800498 for (auto& threshold : thresholdVector)
James Feist6714a252018-09-10 15:26:18 -0700499 {
500 if (threshold.level == Level::CRITICAL)
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700501 {
James Feist6714a252018-09-10 15:26:18 -0700502 return true;
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700503 }
James Feist6714a252018-09-10 15:26:18 -0700504 }
505 return false;
506}
507
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700508bool hasWarningInterface(
James Feistd8705872019-02-08 13:26:09 -0800509 const std::vector<thresholds::Threshold>& thresholdVector)
James Feist6714a252018-09-10 15:26:18 -0700510{
James Feistd8705872019-02-08 13:26:09 -0800511 for (auto& threshold : thresholdVector)
James Feist6714a252018-09-10 15:26:18 -0700512 {
513 if (threshold.level == Level::WARNING)
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700514 {
James Feist6714a252018-09-10 15:26:18 -0700515 return true;
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700516 }
James Feist6714a252018-09-10 15:26:18 -0700517 }
518 return false;
519}
520} // namespace thresholds