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(thresholdData.begin(), thresholdData.end(), 131 [this](const auto& thresholdInterface) { 132 const auto& interface = thresholdInterface.first; 133 auto objects = SDBusPlus::getSubTreeRaw(this->bus, "/", interface, 0); 134 std::for_each(objects.begin(), objects.end(), 135 [interface, this](const auto& object) { 136 const auto& path = object.first; 137 const auto& service = object.second.begin()->first; 138 checkThresholds(interface, path, service); 139 }); 140 }); 141 } 142 143 void ThresholdAlarmLogger::propertiesChanged(sdbusplus::message_t& msg) 144 { 145 std::map<std::string, std::variant<bool>> properties; 146 std::string sensorPath = msg.get_path(); 147 std::string interface; 148 149 msg.read(interface, properties); 150 151 checkProperties(sensorPath, interface, properties); 152 } 153 154 void ThresholdAlarmLogger::interfacesRemoved(sdbusplus::message_t& msg) 155 { 156 sdbusplus::message::object_path path; 157 std::vector<std::string> interfaces; 158 159 msg.read(path, interfaces); 160 161 for (const auto& interface : interfaces) 162 { 163 if (std::find(thresholdIfaceNames.begin(), thresholdIfaceNames.end(), 164 interface) != thresholdIfaceNames.end()) 165 { 166 alarms.erase(InterfaceKey{path, interface}); 167 } 168 } 169 } 170 171 void ThresholdAlarmLogger::interfacesAdded(sdbusplus::message_t& msg) 172 { 173 sdbusplus::message::object_path path; 174 std::map<std::string, std::map<std::string, std::variant<bool>>> interfaces; 175 176 msg.read(path, interfaces); 177 178 for (const auto& [interface, properties] : interfaces) 179 { 180 if (std::find(thresholdIfaceNames.begin(), thresholdIfaceNames.end(), 181 interface) != thresholdIfaceNames.end()) 182 { 183 checkProperties(path, interface, properties); 184 } 185 } 186 } 187 188 void ThresholdAlarmLogger::checkProperties( 189 const std::string& sensorPath, const std::string& interface, 190 const std::map<std::string, std::variant<bool>>& properties) 191 { 192 auto alarmProperties = thresholdData.find(interface); 193 if (alarmProperties == thresholdData.end()) 194 { 195 return; 196 } 197 198 for (const auto& [propertyName, propertyValue] : properties) 199 { 200 if (alarmProperties->second.find(propertyName) != 201 alarmProperties->second.end()) 202 { 203 // If this is the first time we've seen this alarm, then 204 // assume it was off before so it doesn't create an event 205 // log for a value of false. 206 207 InterfaceKey key{sensorPath, interface}; 208 if (alarms.find(key) == alarms.end()) 209 { 210 alarms[key][propertyName] = false; 211 } 212 213 // Check if the value changed from what was there before. 214 auto alarmValue = std::get<bool>(propertyValue); 215 if (alarmValue != alarms[key][propertyName]) 216 { 217 alarms[key][propertyName] = alarmValue; 218 219 if (_powerState->isPowerOn()) 220 { 221 createEventLog(sensorPath, interface, propertyName, 222 alarmValue); 223 } 224 } 225 } 226 } 227 } 228 229 void ThresholdAlarmLogger::checkThresholds(const std::string& interface, 230 const std::string& sensorPath, 231 const std::string& service) 232 { 233 auto properties = thresholdData.find(interface); 234 if (properties == thresholdData.end()) 235 { 236 return; 237 } 238 239 for (const auto& [property, unused] : properties->second) 240 { 241 try 242 { 243 auto alarmValue = SDBusPlus::getProperty<bool>( 244 bus, service, sensorPath, interface, property); 245 alarms[InterfaceKey(sensorPath, interface)][property] = alarmValue; 246 247 // This is just for checking alarms on startup, 248 // so only look for active alarms. 249 if (alarmValue && _powerState->isPowerOn()) 250 { 251 createEventLog(sensorPath, interface, property, alarmValue); 252 } 253 } 254 catch (const sdbusplus::exception_t& e) 255 { 256 // Sensor daemons that get their direction from entity manager 257 // may only be putting either the high alarm or low alarm on 258 // D-Bus, not both. 259 continue; 260 } 261 } 262 } 263 264 void ThresholdAlarmLogger::createEventLog(const std::string& sensorPath, 265 const std::string& interface, 266 const std::string& alarmProperty, 267 bool alarmValue) 268 { 269 std::map<std::string, std::string> ad; 270 271 auto type = getSensorType(sensorPath); 272 if (skipSensorType(type)) 273 { 274 return; 275 } 276 277 auto it = thresholdData.find(interface); 278 if (it == thresholdData.end()) 279 { 280 return; 281 } 282 283 auto properties = it->second.find(alarmProperty); 284 if (properties == it->second.end()) 285 { 286 log<level::INFO>( 287 fmt::format("Could not find {} in threshold alarms map", 288 alarmProperty) 289 .c_str()); 290 return; 291 } 292 293 ad.emplace("SENSOR_NAME", sensorPath); 294 ad.emplace("_PID", std::to_string(getpid())); 295 296 try 297 { 298 auto sensorValue = SDBusPlus::getProperty<double>( 299 bus, sensorPath, valueInterface, "Value"); 300 301 ad.emplace("SENSOR_VALUE", std::to_string(sensorValue)); 302 303 log<level::INFO>( 304 fmt::format("Threshold Event {} {} = {} (sensor value {})", 305 sensorPath, alarmProperty, alarmValue, sensorValue) 306 .c_str()); 307 } 308 catch (const DBusServiceError& e) 309 { 310 // If the sensor was just added, the Value interface for it may 311 // not be in the mapper yet. This could only happen if the sensor 312 // application was started up after this one and the value exceeded the 313 // threshold immediately. 314 log<level::INFO>(fmt::format("Threshold Event {} {} = {}", sensorPath, 315 alarmProperty, alarmValue) 316 .c_str()); 317 } 318 319 auto callout = getCallout(sensorPath); 320 if (!callout.empty()) 321 { 322 ad.emplace("CALLOUT_INVENTORY_PATH", callout); 323 } 324 325 auto errorData = properties->second.find(alarmValue); 326 327 // Add the base error name and the sensor type (like Temperature) to the 328 // error name that's in the thresholdData name to get something like 329 // xyz.openbmc_project.Sensor.Threshold.Error.TemperatureWarningHigh 330 const auto& [name, severity] = errorData->second; 331 type.front() = toupper(type.front()); 332 std::string errorName = errorNameBase + type + name; 333 334 SDBusPlus::callMethod(loggingService, loggingPath, loggingCreateIface, 335 "Create", errorName, convertForMessage(severity), ad); 336 } 337 338 std::string ThresholdAlarmLogger::getSensorType(std::string sensorPath) 339 { 340 auto pos = sensorPath.find_last_of('/'); 341 if ((sensorPath.back() == '/') || (pos == std::string::npos)) 342 { 343 log<level::ERR>( 344 fmt::format("Cannot get sensor type from sensor path {}", 345 sensorPath) 346 .c_str()); 347 throw std::runtime_error("Invalid sensor path"); 348 } 349 350 sensorPath = sensorPath.substr(0, pos); 351 return sensorPath.substr(sensorPath.find_last_of('/') + 1); 352 } 353 354 bool ThresholdAlarmLogger::skipSensorType(const std::string& type) 355 { 356 return (type == "utilization"); 357 } 358 359 std::string ThresholdAlarmLogger::getCallout(const std::string& sensorPath) 360 { 361 const std::array<std::string, 2> assocTypes{"inventory", "chassis"}; 362 363 // Different implementations handle the association to the FRU 364 // differently: 365 // * phosphor-inventory-manager uses the 'inventory' association 366 // to point to the FRU. 367 // * dbus-sensors/entity-manager uses the 'chassis' association'. 368 // * For virtual sensors, no association. 369 370 for (const auto& assocType : assocTypes) 371 { 372 auto assocPath = sensorPath + "/" + assocType; 373 374 try 375 { 376 auto endpoints = SDBusPlus::getProperty<std::vector<std::string>>( 377 bus, assocPath, assocInterface, "endpoints"); 378 379 if (!endpoints.empty()) 380 { 381 return endpoints[0]; 382 } 383 } 384 catch (const DBusServiceError& e) 385 { 386 // The association doesn't exist 387 continue; 388 } 389 } 390 391 return std::string{}; 392 } 393 394 void ThresholdAlarmLogger::powerStateChanged(bool powerStateOn) 395 { 396 if (powerStateOn) 397 { 398 checkThresholds(); 399 } 400 } 401 402 void ThresholdAlarmLogger::checkThresholds() 403 { 404 std::vector<InterfaceKey> toErase; 405 406 for (const auto& [interfaceKey, alarmMap] : alarms) 407 { 408 for (const auto& [propertyName, alarmValue] : alarmMap) 409 { 410 if (alarmValue) 411 { 412 const auto& sensorPath = std::get<0>(interfaceKey); 413 const auto& interface = std::get<1>(interfaceKey); 414 std::string service; 415 416 try 417 { 418 // Check that the service that provides the alarm is still 419 // running, because if it died when the alarm was active 420 // there would be no indication of it unless we listened 421 // for NameOwnerChanged and tracked services, and this is 422 // easier. 423 service = SDBusPlus::getService(bus, sensorPath, interface); 424 } 425 catch (const DBusServiceError& e) 426 { 427 // No longer on D-Bus delete the alarm entry 428 toErase.emplace_back(sensorPath, interface); 429 } 430 431 if (!service.empty()) 432 { 433 createEventLog(sensorPath, interface, propertyName, 434 alarmValue); 435 } 436 } 437 } 438 } 439 440 for (const auto& e : toErase) 441 { 442 alarms.erase(e); 443 } 444 } 445 446 } // namespace sensor::monitor 447