blob: 965b626fc439550a1c783d8bdbb23c77e729d5f8 [file] [log] [blame]
Patrick Ventureca44b2f2019-10-31 11:02:26 -07001#include "Thresholds.hpp"
2
3#include "VariantVisitors.hpp"
4#include "sensor.hpp"
5
Patrick Venture96e97db2019-10-31 13:44:38 -07006#include <array>
James Feist6714a252018-09-10 15:26:18 -07007#include <boost/algorithm/string/replace.hpp>
Patrick Venture96e97db2019-10-31 13:44:38 -07008#include <boost/container/flat_map.hpp>
James Feist6714a252018-09-10 15:26:18 -07009#include <boost/lexical_cast.hpp>
James Feistdc6c55f2018-10-31 12:53:20 -070010#include <cmath>
James Feist6714a252018-09-10 15:26:18 -070011#include <fstream>
12#include <iostream>
Patrick Venture96e97db2019-10-31 13:44:38 -070013#include <memory>
14#include <stdexcept>
15#include <string>
Zbigniew Kurzynski0a4c4802020-04-01 11:22:27 +020016#include <tuple>
Patrick Venture96e97db2019-10-31 13:44:38 -070017#include <utility>
18#include <variant>
19#include <vector>
James Feist6714a252018-09-10 15:26:18 -070020
21static constexpr bool DEBUG = false;
James Feist6714a252018-09-10 15:26:18 -070022namespace thresholds
23{
James Feistd8705872019-02-08 13:26:09 -080024unsigned int toBusValue(const Level& level)
James Feist6714a252018-09-10 15:26:18 -070025{
26 switch (level)
27 {
28 case (Level::WARNING):
29 {
30 return 0;
31 }
32 case (Level::CRITICAL):
33 {
34 return 1;
35 }
36 default:
37 {
38 return -1;
39 }
40 }
41}
42
James Feistd8705872019-02-08 13:26:09 -080043std::string toBusValue(const Direction& direction)
James Feist6714a252018-09-10 15:26:18 -070044{
45 switch (direction)
46 {
47 case (Direction::LOW):
48 {
49 return "less than";
50 }
51 case (Direction::HIGH):
52 {
53 return "greater than";
54 }
55 default:
56 {
57 return "err";
58 }
59 }
60}
61
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -070062bool parseThresholdsFromConfig(
James Feistd8705872019-02-08 13:26:09 -080063 const SensorData& sensorData,
64 std::vector<thresholds::Threshold>& thresholdVector,
65 const std::string* matchLabel)
James Feist6714a252018-09-10 15:26:18 -070066{
James Feistd8705872019-02-08 13:26:09 -080067 for (const auto& item : sensorData)
James Feist6714a252018-09-10 15:26:18 -070068 {
69 if (item.first.find("Thresholds") == std::string::npos)
70 {
71 continue;
72 }
73 if (matchLabel != nullptr)
74 {
75 auto labelFind = item.second.find("Label");
76 if (labelFind == item.second.end())
77 continue;
James Feist3eb82622019-02-08 13:10:22 -080078 if (std::visit(VariantToStringVisitor(), labelFind->second) !=
79 *matchLabel)
James Feist6714a252018-09-10 15:26:18 -070080 continue;
81 }
82 auto directionFind = item.second.find("Direction");
83 auto severityFind = item.second.find("Severity");
84 auto valueFind = item.second.find("Value");
85 if (valueFind == item.second.end() ||
86 severityFind == item.second.end() ||
87 directionFind == item.second.end())
88 {
89 std::cerr << "Malformed threshold in configuration\n";
90 return false;
91 }
92 Level level;
93 Direction direction;
James Feist3eb82622019-02-08 13:10:22 -080094 if (std::visit(VariantToUnsignedIntVisitor(), severityFind->second) ==
95 0)
James Feist6714a252018-09-10 15:26:18 -070096 {
97 level = Level::WARNING;
98 }
99 else
100 {
101 level = Level::CRITICAL;
102 }
James Feist3eb82622019-02-08 13:10:22 -0800103 if (std::visit(VariantToStringVisitor(), directionFind->second) ==
104 "less than")
James Feist6714a252018-09-10 15:26:18 -0700105 {
106 direction = Direction::LOW;
107 }
108 else
109 {
110 direction = Direction::HIGH;
111 }
James Feist13f340b2019-03-07 16:36:11 -0800112 double val = std::visit(VariantToDoubleVisitor(), valueFind->second);
James Feist6714a252018-09-10 15:26:18 -0700113
114 thresholdVector.emplace_back(level, direction, val);
115 }
116 return true;
117}
118
James Feistd8705872019-02-08 13:26:09 -0800119void persistThreshold(const std::string& path, const std::string& baseInterface,
120 const thresholds::Threshold& threshold,
James Feista222ba72019-03-01 15:57:51 -0800121 std::shared_ptr<sdbusplus::asio::connection>& conn,
Cheng C Yang6b1247a2020-03-09 23:48:39 +0800122 size_t thresholdCount, const std::string& labelMatch)
James Feist6714a252018-09-10 15:26:18 -0700123{
Brad Bishopfbb44ad2019-11-08 09:42:37 -0500124 for (size_t ii = 0; ii < thresholdCount; ii++)
James Feist6714a252018-09-10 15:26:18 -0700125 {
126 std::string thresholdInterface =
127 baseInterface + ".Thresholds" + std::to_string(ii);
128 conn->async_method_call(
Cheng C Yang6b1247a2020-03-09 23:48:39 +0800129 [&, path, threshold, thresholdInterface, labelMatch](
James Feistd8705872019-02-08 13:26:09 -0800130 const boost::system::error_code& ec,
131 const boost::container::flat_map<std::string, BasicVariantType>&
132 result) {
James Feist6714a252018-09-10 15:26:18 -0700133 if (ec)
134 {
135 return; // threshold not supported
136 }
137
Cheng C Yang6b1247a2020-03-09 23:48:39 +0800138 if (!labelMatch.empty())
139 {
140 auto labelFind = result.find("Label");
141 if (labelFind == result.end())
142 {
143 std::cerr << "No label in threshold configuration\n";
144 return;
145 }
146 std::string label =
147 std::visit(VariantToStringVisitor(), labelFind->second);
148 if (label != labelMatch)
149 {
150 return;
151 }
152 }
153
James Feist6714a252018-09-10 15:26:18 -0700154 auto directionFind = result.find("Direction");
155 auto severityFind = result.find("Severity");
156 auto valueFind = result.find("Value");
157 if (valueFind == result.end() || severityFind == result.end() ||
158 directionFind == result.end())
159 {
160 std::cerr << "Malformed threshold in configuration\n";
161 return;
162 }
James Feist3eb82622019-02-08 13:10:22 -0800163 unsigned int level = std::visit(VariantToUnsignedIntVisitor(),
164 severityFind->second);
James Feist6714a252018-09-10 15:26:18 -0700165
James Feist3eb82622019-02-08 13:10:22 -0800166 std::string dir =
167 std::visit(VariantToStringVisitor(), directionFind->second);
James Feist6714a252018-09-10 15:26:18 -0700168 if ((toBusValue(threshold.level) != level) ||
169 (toBusValue(threshold.direction) != dir))
170 {
171 return; // not the droid we're looking for
172 }
173
James Feist3eb82622019-02-08 13:10:22 -0800174 std::variant<double> value(threshold.value);
James Feist6714a252018-09-10 15:26:18 -0700175 conn->async_method_call(
James Feistd8705872019-02-08 13:26:09 -0800176 [](const boost::system::error_code& ec) {
James Feist6714a252018-09-10 15:26:18 -0700177 if (ec)
178 {
179 std::cerr << "Error setting threshold " << ec
180 << "\n";
181 }
182 },
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700183 entityManagerName, path, "org.freedesktop.DBus.Properties",
184 "Set", thresholdInterface, "Value", value);
James Feist6714a252018-09-10 15:26:18 -0700185 },
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700186 entityManagerName, path, "org.freedesktop.DBus.Properties",
James Feist6714a252018-09-10 15:26:18 -0700187 "GetAll", thresholdInterface);
188 }
189}
190
Jae Hyun Yoo95b8a2d2019-02-25 20:15:09 -0800191void updateThresholds(Sensor* sensor)
192{
193 if (sensor->thresholds.empty())
194 {
195 return;
196 }
197
198 for (const auto& threshold : sensor->thresholds)
199 {
200 std::shared_ptr<sdbusplus::asio::dbus_interface> interface;
201 std::string property;
202 if (threshold.level == thresholds::Level::CRITICAL)
203 {
204 interface = sensor->thresholdInterfaceCritical;
205 if (threshold.direction == thresholds::Direction::HIGH)
206 {
207 property = "CriticalHigh";
208 }
209 else
210 {
211 property = "CriticalLow";
212 }
213 }
214 else if (threshold.level == thresholds::Level::WARNING)
215 {
216 interface = sensor->thresholdInterfaceWarning;
217 if (threshold.direction == thresholds::Direction::HIGH)
218 {
219 property = "WarningHigh";
220 }
221 else
222 {
223 property = "WarningLow";
224 }
225 }
226 else
227 {
228 continue;
229 }
230 if (!interface)
231 {
232 continue;
233 }
234 interface->set_property(property, threshold.value);
235 }
236}
237
Josh Lehan883fb3a2020-02-27 14:41:39 -0800238// Debugging counters
239static int cHiTrue = 0;
240static int cHiFalse = 0;
241static int cHiMidstate = 0;
242static int cLoTrue = 0;
243static int cLoFalse = 0;
244static int cLoMidstate = 0;
245static int cDebugThrottle = 0;
246
James Feist46342ec2019-03-06 14:03:41 -0800247static std::vector<std::pair<Threshold, bool>> checkThresholds(Sensor* sensor,
248 double value)
James Feist251c7822018-09-12 12:54:15 -0700249{
James Feist46342ec2019-03-06 14:03:41 -0800250 std::vector<std::pair<Threshold, bool>> thresholdChanges;
James Feist251c7822018-09-12 12:54:15 -0700251 if (sensor->thresholds.empty())
252 {
James Feist46342ec2019-03-06 14:03:41 -0800253 return thresholdChanges;
James Feist251c7822018-09-12 12:54:15 -0700254 }
James Feist46342ec2019-03-06 14:03:41 -0800255
James Feistd8705872019-02-08 13:26:09 -0800256 for (auto& threshold : sensor->thresholds)
James Feist251c7822018-09-12 12:54:15 -0700257 {
Josh Lehan883fb3a2020-02-27 14:41:39 -0800258 // Use "Schmitt trigger" logic to avoid threshold trigger spam,
259 // if value is noisy while hovering very close to a threshold.
260 // When a threshold is crossed, indicate true immediately,
261 // but require more distance to be crossed the other direction,
262 // before resetting the indicator back to false.
James Feist46342ec2019-03-06 14:03:41 -0800263 if (threshold.direction == thresholds::Direction::HIGH)
James Feistdc6c55f2018-10-31 12:53:20 -0700264 {
James Feist551087a2019-12-09 11:17:12 -0800265 if (value >= threshold.value)
James Feist251c7822018-09-12 12:54:15 -0700266 {
James Feist46342ec2019-03-06 14:03:41 -0800267 thresholdChanges.push_back(std::make_pair(threshold, true));
Josh Lehan883fb3a2020-02-27 14:41:39 -0800268 ++cHiTrue;
269 }
270 else if (value < (threshold.value - sensor->hysteresisTrigger))
271 {
272 thresholdChanges.push_back(std::make_pair(threshold, false));
273 ++cHiFalse;
James Feist251c7822018-09-12 12:54:15 -0700274 }
Patrick Venture66235d42019-10-11 08:31:27 -0700275 else
James Feist251c7822018-09-12 12:54:15 -0700276 {
Josh Lehan883fb3a2020-02-27 14:41:39 -0800277 ++cHiMidstate;
James Feist251c7822018-09-12 12:54:15 -0700278 }
279 }
Josh Lehan883fb3a2020-02-27 14:41:39 -0800280 else if (threshold.direction == thresholds::Direction::LOW)
James Feist251c7822018-09-12 12:54:15 -0700281 {
James Feist551087a2019-12-09 11:17:12 -0800282 if (value <= threshold.value)
James Feist251c7822018-09-12 12:54:15 -0700283 {
James Feist46342ec2019-03-06 14:03:41 -0800284 thresholdChanges.push_back(std::make_pair(threshold, true));
Josh Lehan883fb3a2020-02-27 14:41:39 -0800285 ++cLoTrue;
286 }
287 else if (value > (threshold.value + sensor->hysteresisTrigger))
288 {
289 thresholdChanges.push_back(std::make_pair(threshold, false));
290 ++cLoFalse;
James Feist251c7822018-09-12 12:54:15 -0700291 }
Patrick Venture66235d42019-10-11 08:31:27 -0700292 else
James Feist251c7822018-09-12 12:54:15 -0700293 {
Josh Lehan883fb3a2020-02-27 14:41:39 -0800294 ++cLoMidstate;
James Feist251c7822018-09-12 12:54:15 -0700295 }
296 }
Josh Lehan883fb3a2020-02-27 14:41:39 -0800297 else
298 {
299 std::cerr << "Error determining threshold direction\n";
300 }
James Feist251c7822018-09-12 12:54:15 -0700301 }
Josh Lehan883fb3a2020-02-27 14:41:39 -0800302
303 if constexpr (DEBUG)
304 {
305 // Throttle debug output, so that it does not continuously spam
306 ++cDebugThrottle;
307 if (cDebugThrottle >= 1000)
308 {
309 cDebugThrottle = 0;
310 std::cerr << "checkThresholds: High T=" << cHiTrue
311 << " F=" << cHiFalse << " M=" << cHiMidstate
312 << ", Low T=" << cLoTrue << " F=" << cLoFalse
313 << " M=" << cLoMidstate << "\n";
314 }
315 }
316
James Feist46342ec2019-03-06 14:03:41 -0800317 return thresholdChanges;
318}
319
320bool checkThresholds(Sensor* sensor)
321{
James Feist7b18b1e2019-05-14 13:42:09 -0700322 bool status = true;
James Feist46342ec2019-03-06 14:03:41 -0800323 std::vector<std::pair<Threshold, bool>> changes =
324 checkThresholds(sensor, sensor->value);
325 for (const auto& [threshold, asserted] : changes)
326 {
327 assertThresholds(sensor, threshold.level, threshold.direction,
328 asserted);
329 if (threshold.level == thresholds::Level::CRITICAL && asserted)
330 {
James Feist7b18b1e2019-05-14 13:42:09 -0700331 status = false;
James Feist46342ec2019-03-06 14:03:41 -0800332 }
333 }
334
James Feistdc6c55f2018-10-31 12:53:20 -0700335 return status;
James Feist251c7822018-09-12 12:54:15 -0700336}
337
James Feist46342ec2019-03-06 14:03:41 -0800338void checkThresholdsPowerDelay(Sensor* sensor, ThresholdTimer& thresholdTimer)
339{
340
341 std::vector<std::pair<Threshold, bool>> changes =
342 checkThresholds(sensor, sensor->value);
343 for (const auto& [threshold, asserted] : changes)
344 {
345 if (asserted)
346 {
347 thresholdTimer.startTimer(threshold);
348 }
349 else
350 {
351 assertThresholds(sensor, threshold.level, threshold.direction,
352 false);
353 }
354 }
355}
356
James Feistd8705872019-02-08 13:26:09 -0800357void assertThresholds(Sensor* sensor, thresholds::Level level,
James Feist251c7822018-09-12 12:54:15 -0700358 thresholds::Direction direction, bool assert)
359{
360 std::string property;
361 std::shared_ptr<sdbusplus::asio::dbus_interface> interface;
362 if (level == thresholds::Level::WARNING &&
363 direction == thresholds::Direction::HIGH)
364 {
365 property = "WarningAlarmHigh";
366 interface = sensor->thresholdInterfaceWarning;
367 }
368 else if (level == thresholds::Level::WARNING &&
369 direction == thresholds::Direction::LOW)
370 {
371 property = "WarningAlarmLow";
372 interface = sensor->thresholdInterfaceWarning;
373 }
374 else if (level == thresholds::Level::CRITICAL &&
375 direction == thresholds::Direction::HIGH)
376 {
377 property = "CriticalAlarmHigh";
378 interface = sensor->thresholdInterfaceCritical;
379 }
380 else if (level == thresholds::Level::CRITICAL &&
381 direction == thresholds::Direction::LOW)
382 {
383 property = "CriticalAlarmLow";
384 interface = sensor->thresholdInterfaceCritical;
385 }
386 else
387 {
388 std::cerr << "Unknown threshold, level " << level << "direction "
389 << direction << "\n";
390 return;
391 }
392 if (!interface)
393 {
394 std::cout << "trying to set uninitialized interface\n";
395 return;
396 }
397 interface->set_property(property, assert);
398}
399
Zbigniew Kurzynski0a4c4802020-04-01 11:22:27 +0200400std::optional<double> readFile(const std::string& thresholdFile,
401 const double& scaleFactor)
402{
403 std::string line;
404 std::ifstream labelFile(thresholdFile);
405 if (labelFile.good())
406 {
407 std::getline(labelFile, line);
408 labelFile.close();
409
410 try
411 {
412 return std::stod(line) / scaleFactor;
413 }
414 catch (const std::invalid_argument&)
415 {
416 return std::nullopt;
417 }
418 }
419 return std::nullopt;
420}
421
422std::optional<std::tuple<std::string, std::string, std::string>>
423 splitFileName(const std::filesystem::path& filePath)
424{
425 if (filePath.has_filename())
426 {
427 const auto fileName = filePath.filename().string();
428 const std::regex rx(R"((\w+)(\d+)_(.*))");
429 std::smatch mr;
430
431 if (std::regex_search(fileName, mr, rx))
432 {
433 if (mr.size() == 4)
434 {
435 return std::make_optional(std::make_tuple(mr[1], mr[2], mr[3]));
436 }
437 }
438 }
439 return std::nullopt;
440}
James Feist6714a252018-09-10 15:26:18 -0700441
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700442bool parseThresholdsFromAttr(
James Feistd8705872019-02-08 13:26:09 -0800443 std::vector<thresholds::Threshold>& thresholdVector,
Vijay Khemka86dea2b2019-06-06 11:14:37 -0700444 const std::string& inputPath, const double& scaleFactor,
445 const double& offset)
James Feist6714a252018-09-10 15:26:18 -0700446{
Zbigniew Kurzynski0a4c4802020-04-01 11:22:27 +0200447 const boost::container::flat_map<
448 std::string, std::vector<std::tuple<const char*, thresholds::Level,
449 thresholds::Direction, double>>>
450 map = {
451 {"average",
452 {
453 std::make_tuple("average_min", Level::WARNING, Direction::LOW,
454 0.0),
455 std::make_tuple("average_max", Level::WARNING, Direction::HIGH,
456 0.0),
457 }},
458 {"input",
459 {
460 std::make_tuple("min", Level::WARNING, Direction::LOW, 0.0),
461 std::make_tuple("max", Level::WARNING, Direction::HIGH, 0.0),
462 std::make_tuple("lcrit", Level::CRITICAL, Direction::LOW, 0.0),
463 std::make_tuple("crit", Level::CRITICAL, Direction::HIGH,
464 offset),
465 }},
466 };
467
468 if (auto fileParts = splitFileName(inputPath))
James Feist6714a252018-09-10 15:26:18 -0700469 {
Zbigniew Kurzynski0a4c4802020-04-01 11:22:27 +0200470 auto [type, nr, item] = *fileParts;
471 if (map.count(item) != 0)
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700472 {
Zbigniew Kurzynski0a4c4802020-04-01 11:22:27 +0200473 for (const auto& t : map.at(item))
474 {
475 auto [suffix, level, direction, offset] = t;
476 auto attrPath =
477 boost::replace_all_copy(inputPath, item, suffix);
478 if (auto val = readFile(attrPath, scaleFactor))
479 {
480 *val += offset;
481 if (DEBUG)
482 {
483 std::cout << "Threshold: " << attrPath << ": " << *val
484 << "\n";
485 }
486 thresholdVector.emplace_back(level, direction, *val);
487 }
488 }
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700489 }
James Feist6714a252018-09-10 15:26:18 -0700490 }
James Feist6714a252018-09-10 15:26:18 -0700491 return true;
492}
493
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700494bool hasCriticalInterface(
James Feistd8705872019-02-08 13:26:09 -0800495 const std::vector<thresholds::Threshold>& thresholdVector)
James Feist6714a252018-09-10 15:26:18 -0700496{
James Feistd8705872019-02-08 13:26:09 -0800497 for (auto& threshold : thresholdVector)
James Feist6714a252018-09-10 15:26:18 -0700498 {
499 if (threshold.level == Level::CRITICAL)
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700500 {
James Feist6714a252018-09-10 15:26:18 -0700501 return true;
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700502 }
James Feist6714a252018-09-10 15:26:18 -0700503 }
504 return false;
505}
506
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700507bool hasWarningInterface(
James Feistd8705872019-02-08 13:26:09 -0800508 const std::vector<thresholds::Threshold>& thresholdVector)
James Feist6714a252018-09-10 15:26:18 -0700509{
James Feistd8705872019-02-08 13:26:09 -0800510 for (auto& threshold : thresholdVector)
James Feist6714a252018-09-10 15:26:18 -0700511 {
512 if (threshold.level == Level::WARNING)
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700513 {
James Feist6714a252018-09-10 15:26:18 -0700514 return true;
Jae Hyun Yoo9ced0a32018-10-25 10:42:39 -0700515 }
James Feist6714a252018-09-10 15:26:18 -0700516 }
517 return false;
518}
519} // namespace thresholds