1 /** 2 * Copyright © 2021 IBM Corporation 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 #include "threshold_alarm_logger.hpp" 17 18 #include "sdbusplus.hpp" 19 20 #include <fmt/format.h> 21 #include <unistd.h> 22 23 #include <phosphor-logging/log.hpp> 24 #include <xyz/openbmc_project/Logging/Entry/server.hpp> 25 26 namespace sensor::monitor 27 { 28 29 using namespace sdbusplus::xyz::openbmc_project::Logging::server; 30 using namespace phosphor::logging; 31 using namespace phosphor::fan; 32 using namespace phosphor::fan::util; 33 34 const std::string warningInterface = 35 "xyz.openbmc_project.Sensor.Threshold.Warning"; 36 const std::string criticalInterface = 37 "xyz.openbmc_project.Sensor.Threshold.Critical"; 38 const std::string perfLossInterface = 39 "xyz.openbmc_project.Sensor.Threshold.PerformanceLoss"; 40 constexpr auto loggingService = "xyz.openbmc_project.Logging"; 41 constexpr auto loggingPath = "/xyz/openbmc_project/logging"; 42 constexpr auto loggingCreateIface = "xyz.openbmc_project.Logging.Create"; 43 constexpr auto errorNameBase = "xyz.openbmc_project.Sensor.Threshold.Error."; 44 constexpr auto valueInterface = "xyz.openbmc_project.Sensor.Value"; 45 constexpr auto assocInterface = "xyz.openbmc_project.Association"; 46 47 const std::vector<std::string> thresholdIfaceNames{ 48 warningInterface, criticalInterface, perfLossInterface}; 49 50 using ErrorData = std::tuple<ErrorName, Entry::Level>; 51 52 /** 53 * Map of threshold interfaces and alarm properties and values to error data. 54 */ 55 const std::map<InterfaceName, std::map<PropertyName, std::map<bool, ErrorData>>> 56 thresholdData{ 57 58 {warningInterface, 59 {{"WarningAlarmHigh", 60 {{true, ErrorData{"WarningHigh", Entry::Level::Warning}}, 61 {false, 62 ErrorData{"WarningHighClear", Entry::Level::Informational}}}}, 63 {"WarningAlarmLow", 64 {{true, ErrorData{"WarningLow", Entry::Level::Warning}}, 65 {false, 66 ErrorData{"WarningLowClear", Entry::Level::Informational}}}}}}, 67 68 {criticalInterface, 69 {{"CriticalAlarmHigh", 70 {{true, ErrorData{"CriticalHigh", Entry::Level::Critical}}, 71 {false, 72 ErrorData{"CriticalHighClear", Entry::Level::Informational}}}}, 73 {"CriticalAlarmLow", 74 {{true, ErrorData{"CriticalLow", Entry::Level::Critical}}, 75 {false, 76 ErrorData{"CriticalLowClear", Entry::Level::Informational}}}}}}, 77 78 {perfLossInterface, 79 {{"PerfLossAlarmHigh", 80 {{true, ErrorData{"PerfLossHigh", Entry::Level::Warning}}, 81 {false, 82 ErrorData{"PerfLossHighClear", Entry::Level::Informational}}}}, 83 {"PerfLossAlarmLow", 84 {{true, ErrorData{"PerfLossLow", Entry::Level::Warning}}, 85 {false, 86 ErrorData{"PerfLossLowClear", Entry::Level::Informational}}}}}}}; 87 88 ThresholdAlarmLogger::ThresholdAlarmLogger( 89 sdbusplus::bus_t& bus, sdeventplus::Event& event, 90 std::shared_ptr<PowerState> powerState) : 91 bus(bus), 92 event(event), _powerState(std::move(powerState)), 93 warningMatch(bus, 94 "type='signal',member='PropertiesChanged'," 95 "path_namespace='/xyz/openbmc_project/sensors'," 96 "arg0='" + 97 warningInterface + "'", 98 std::bind(&ThresholdAlarmLogger::propertiesChanged, this, 99 std::placeholders::_1)), 100 criticalMatch(bus, 101 "type='signal',member='PropertiesChanged'," 102 "path_namespace='/xyz/openbmc_project/sensors'," 103 "arg0='" + 104 criticalInterface + "'", 105 std::bind(&ThresholdAlarmLogger::propertiesChanged, this, 106 std::placeholders::_1)), 107 perfLossMatch(bus, 108 "type='signal',member='PropertiesChanged'," 109 "path_namespace='/xyz/openbmc_project/sensors'," 110 "arg0='" + 111 perfLossInterface + "'", 112 std::bind(&ThresholdAlarmLogger::propertiesChanged, this, 113 std::placeholders::_1)), 114 ifacesRemovedMatch(bus, 115 "type='signal',member='InterfacesRemoved',arg0path=" 116 "'/xyz/openbmc_project/sensors/'", 117 std::bind(&ThresholdAlarmLogger::interfacesRemoved, this, 118 std::placeholders::_1)), 119 ifacesAddedMatch(bus, 120 "type='signal',member='InterfacesAdded',arg0path=" 121 "'/xyz/openbmc_project/sensors/'", 122 std::bind(&ThresholdAlarmLogger::interfacesAdded, this, 123 std::placeholders::_1)) 124 { 125 _powerState->addCallback("thresholdMon", 126 std::bind(&ThresholdAlarmLogger::powerStateChanged, 127 this, std::placeholders::_1)); 128 129 // check for any currently asserted threshold alarms 130 std::for_each( 131 thresholdData.begin(), thresholdData.end(), 132 [this](const auto& thresholdInterface) { 133 const auto& interface = thresholdInterface.first; 134 auto objects = 135 SDBusPlus::getSubTreeRaw(this->bus, "/", interface, 0); 136 std::for_each(objects.begin(), objects.end(), 137 [interface, this](const auto& object) { 138 const auto& path = object.first; 139 const auto& service = 140 object.second.begin()->first; 141 checkThresholds(interface, path, service); 142 }); 143 }); 144 } 145 146 void ThresholdAlarmLogger::propertiesChanged(sdbusplus::message_t& msg) 147 { 148 std::map<std::string, std::variant<bool>> properties; 149 std::string sensorPath = msg.get_path(); 150 std::string interface; 151 152 msg.read(interface, properties); 153 154 checkProperties(sensorPath, interface, properties); 155 } 156 157 void ThresholdAlarmLogger::interfacesRemoved(sdbusplus::message_t& msg) 158 { 159 sdbusplus::message::object_path path; 160 std::vector<std::string> interfaces; 161 162 msg.read(path, interfaces); 163 164 for (const auto& interface : interfaces) 165 { 166 if (std::find(thresholdIfaceNames.begin(), thresholdIfaceNames.end(), 167 interface) != thresholdIfaceNames.end()) 168 { 169 alarms.erase(InterfaceKey{path, interface}); 170 } 171 } 172 } 173 174 void ThresholdAlarmLogger::interfacesAdded(sdbusplus::message_t& msg) 175 { 176 sdbusplus::message::object_path path; 177 std::map<std::string, std::map<std::string, std::variant<bool>>> interfaces; 178 179 msg.read(path, interfaces); 180 181 for (const auto& [interface, properties] : interfaces) 182 { 183 if (std::find(thresholdIfaceNames.begin(), thresholdIfaceNames.end(), 184 interface) != thresholdIfaceNames.end()) 185 { 186 checkProperties(path, interface, properties); 187 } 188 } 189 } 190 191 void ThresholdAlarmLogger::checkProperties( 192 const std::string& sensorPath, const std::string& interface, 193 const std::map<std::string, std::variant<bool>>& properties) 194 { 195 auto alarmProperties = thresholdData.find(interface); 196 if (alarmProperties == thresholdData.end()) 197 { 198 return; 199 } 200 201 for (const auto& [propertyName, propertyValue] : properties) 202 { 203 if (alarmProperties->second.find(propertyName) != 204 alarmProperties->second.end()) 205 { 206 // If this is the first time we've seen this alarm, then 207 // assume it was off before so it doesn't create an event 208 // log for a value of false. 209 210 InterfaceKey key{sensorPath, interface}; 211 if (alarms.find(key) == alarms.end()) 212 { 213 alarms[key][propertyName] = false; 214 } 215 216 // Check if the value changed from what was there before. 217 auto alarmValue = std::get<bool>(propertyValue); 218 if (alarmValue != alarms[key][propertyName]) 219 { 220 alarms[key][propertyName] = alarmValue; 221 222 if (_powerState->isPowerOn()) 223 { 224 createEventLog(sensorPath, interface, propertyName, 225 alarmValue); 226 } 227 } 228 } 229 } 230 } 231 232 void ThresholdAlarmLogger::checkThresholds(const std::string& interface, 233 const std::string& sensorPath, 234 const std::string& service) 235 { 236 auto properties = thresholdData.find(interface); 237 if (properties == thresholdData.end()) 238 { 239 return; 240 } 241 242 for (const auto& [property, unused] : properties->second) 243 { 244 try 245 { 246 auto alarmValue = SDBusPlus::getProperty<bool>( 247 bus, service, sensorPath, interface, property); 248 alarms[InterfaceKey(sensorPath, interface)][property] = alarmValue; 249 250 // This is just for checking alarms on startup, 251 // so only look for active alarms. 252 if (alarmValue && _powerState->isPowerOn()) 253 { 254 createEventLog(sensorPath, interface, property, alarmValue); 255 } 256 } 257 catch (const sdbusplus::exception_t& e) 258 { 259 // Sensor daemons that get their direction from entity manager 260 // may only be putting either the high alarm or low alarm on 261 // D-Bus, not both. 262 continue; 263 } 264 } 265 } 266 267 void ThresholdAlarmLogger::createEventLog(const std::string& sensorPath, 268 const std::string& interface, 269 const std::string& alarmProperty, 270 bool alarmValue) 271 { 272 std::map<std::string, std::string> ad; 273 274 auto type = getSensorType(sensorPath); 275 if (skipSensorType(type)) 276 { 277 return; 278 } 279 280 auto it = thresholdData.find(interface); 281 if (it == thresholdData.end()) 282 { 283 return; 284 } 285 286 auto properties = it->second.find(alarmProperty); 287 if (properties == it->second.end()) 288 { 289 log<level::INFO>( 290 fmt::format("Could not find {} in threshold alarms map", 291 alarmProperty) 292 .c_str()); 293 return; 294 } 295 296 ad.emplace("SENSOR_NAME", sensorPath); 297 ad.emplace("_PID", std::to_string(getpid())); 298 299 try 300 { 301 auto sensorValue = SDBusPlus::getProperty<double>( 302 bus, sensorPath, valueInterface, "Value"); 303 304 ad.emplace("SENSOR_VALUE", std::to_string(sensorValue)); 305 306 log<level::INFO>( 307 fmt::format("Threshold Event {} {} = {} (sensor value {})", 308 sensorPath, alarmProperty, alarmValue, sensorValue) 309 .c_str()); 310 } 311 catch (const DBusServiceError& e) 312 { 313 // If the sensor was just added, the Value interface for it may 314 // not be in the mapper yet. This could only happen if the sensor 315 // application was started up after this one and the value exceeded the 316 // threshold immediately. 317 log<level::INFO>(fmt::format("Threshold Event {} {} = {}", sensorPath, 318 alarmProperty, alarmValue) 319 .c_str()); 320 } 321 322 auto callout = getCallout(sensorPath); 323 if (!callout.empty()) 324 { 325 ad.emplace("CALLOUT_INVENTORY_PATH", callout); 326 } 327 328 auto errorData = properties->second.find(alarmValue); 329 330 // Add the base error name and the sensor type (like Temperature) to the 331 // error name that's in the thresholdData name to get something like 332 // xyz.openbmc_project.Sensor.Threshold.Error.TemperatureWarningHigh 333 const auto& [name, severity] = errorData->second; 334 type.front() = toupper(type.front()); 335 std::string errorName = errorNameBase + type + name; 336 337 SDBusPlus::callMethod(loggingService, loggingPath, loggingCreateIface, 338 "Create", errorName, convertForMessage(severity), ad); 339 } 340 341 std::string ThresholdAlarmLogger::getSensorType(std::string sensorPath) 342 { 343 auto pos = sensorPath.find_last_of('/'); 344 if ((sensorPath.back() == '/') || (pos == std::string::npos)) 345 { 346 log<level::ERR>( 347 fmt::format("Cannot get sensor type from sensor path {}", 348 sensorPath) 349 .c_str()); 350 throw std::runtime_error("Invalid sensor path"); 351 } 352 353 sensorPath = sensorPath.substr(0, pos); 354 return sensorPath.substr(sensorPath.find_last_of('/') + 1); 355 } 356 357 bool ThresholdAlarmLogger::skipSensorType(const std::string& type) 358 { 359 return (type == "utilization"); 360 } 361 362 std::string ThresholdAlarmLogger::getCallout(const std::string& sensorPath) 363 { 364 const std::array<std::string, 2> assocTypes{"inventory", "chassis"}; 365 366 // Different implementations handle the association to the FRU 367 // differently: 368 // * phosphor-inventory-manager uses the 'inventory' association 369 // to point to the FRU. 370 // * dbus-sensors/entity-manager uses the 'chassis' association'. 371 // * For virtual sensors, no association. 372 373 for (const auto& assocType : assocTypes) 374 { 375 auto assocPath = sensorPath + "/" + assocType; 376 377 try 378 { 379 auto endpoints = SDBusPlus::getProperty<std::vector<std::string>>( 380 bus, assocPath, assocInterface, "endpoints"); 381 382 if (!endpoints.empty()) 383 { 384 return endpoints[0]; 385 } 386 } 387 catch (const DBusServiceError& e) 388 { 389 // The association doesn't exist 390 continue; 391 } 392 } 393 394 return std::string{}; 395 } 396 397 void ThresholdAlarmLogger::powerStateChanged(bool powerStateOn) 398 { 399 if (powerStateOn) 400 { 401 checkThresholds(); 402 } 403 } 404 405 void ThresholdAlarmLogger::checkThresholds() 406 { 407 std::vector<InterfaceKey> toErase; 408 409 for (const auto& [interfaceKey, alarmMap] : alarms) 410 { 411 for (const auto& [propertyName, alarmValue] : alarmMap) 412 { 413 if (alarmValue) 414 { 415 const auto& sensorPath = std::get<0>(interfaceKey); 416 const auto& interface = std::get<1>(interfaceKey); 417 std::string service; 418 419 try 420 { 421 // Check that the service that provides the alarm is still 422 // running, because if it died when the alarm was active 423 // there would be no indication of it unless we listened 424 // for NameOwnerChanged and tracked services, and this is 425 // easier. 426 service = SDBusPlus::getService(bus, sensorPath, interface); 427 } 428 catch (const DBusServiceError& e) 429 { 430 // No longer on D-Bus delete the alarm entry 431 toErase.emplace_back(sensorPath, interface); 432 } 433 434 if (!service.empty()) 435 { 436 createEventLog(sensorPath, interface, propertyName, 437 alarmValue); 438 } 439 } 440 } 441 } 442 443 for (const auto& e : toErase) 444 { 445 alarms.erase(e); 446 } 447 } 448 449 } // namespace sensor::monitor 450