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 unsigned int 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 severity = std::visit( 185 VariantToUnsignedIntVisitor(), severityFind->second); 186 187 std::string dir = 188 std::visit(VariantToStringVisitor(), directionFind->second); 189 if (((findThresholdLevel(severity, dir)) != threshold.level) || 190 ((findThresholdDirection(severity, 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