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 using ErrorData = std::tuple<ErrorName, Entry::Level>; 48 49 /** 50 * Map of threshold interfaces and alarm properties and values to error data. 51 */ 52 const std::map<InterfaceName, std::map<PropertyName, std::map<bool, ErrorData>>> 53 thresholdData{ 54 55 {warningInterface, 56 {{"WarningAlarmHigh", 57 {{true, ErrorData{"WarningHigh", Entry::Level::Warning}}, 58 {false, 59 ErrorData{"WarningHighClear", Entry::Level::Informational}}}}, 60 {"WarningAlarmLow", 61 {{true, ErrorData{"WarningLow", Entry::Level::Warning}}, 62 {false, 63 ErrorData{"WarningLowClear", Entry::Level::Informational}}}}}}, 64 65 {criticalInterface, 66 {{"CriticalAlarmHigh", 67 {{true, ErrorData{"CriticalHigh", Entry::Level::Critical}}, 68 {false, 69 ErrorData{"CriticalHighClear", Entry::Level::Informational}}}}, 70 {"CriticalAlarmLow", 71 {{true, ErrorData{"CriticalLow", Entry::Level::Critical}}, 72 {false, 73 ErrorData{"CriticalLowClear", Entry::Level::Informational}}}}}}, 74 75 {perfLossInterface, 76 {{"PerfLossAlarmHigh", 77 {{true, ErrorData{"PerfLossHigh", Entry::Level::Warning}}, 78 {false, 79 ErrorData{"PerfLossHighClear", Entry::Level::Informational}}}}, 80 {"PerfLossAlarmLow", 81 {{true, ErrorData{"PerfLossLow", Entry::Level::Warning}}, 82 {false, 83 ErrorData{"PerfLossLowClear", Entry::Level::Informational}}}}}}}; 84 85 ThresholdAlarmLogger::ThresholdAlarmLogger( 86 sdbusplus::bus::bus& bus, sdeventplus::Event& event, 87 std::shared_ptr<PowerState> powerState) : 88 bus(bus), 89 event(event), _powerState(std::move(powerState)), 90 warningMatch(bus, 91 "type='signal',member='PropertiesChanged'," 92 "path_namespace='/xyz/openbmc_project/sensors'," 93 "arg0='" + 94 warningInterface + "'", 95 std::bind(&ThresholdAlarmLogger::propertiesChanged, this, 96 std::placeholders::_1)), 97 criticalMatch(bus, 98 "type='signal',member='PropertiesChanged'," 99 "path_namespace='/xyz/openbmc_project/sensors'," 100 "arg0='" + 101 criticalInterface + "'", 102 std::bind(&ThresholdAlarmLogger::propertiesChanged, this, 103 std::placeholders::_1)), 104 perfLossMatch(bus, 105 "type='signal',member='PropertiesChanged'," 106 "path_namespace='/xyz/openbmc_project/sensors'," 107 "arg0='" + 108 perfLossInterface + "'", 109 std::bind(&ThresholdAlarmLogger::propertiesChanged, this, 110 std::placeholders::_1)), 111 ifacesRemovedMatch(bus, 112 "type='signal',member='InterfacesRemoved',arg0path=" 113 "'/xyz/openbmc_project/sensors/'", 114 std::bind(&ThresholdAlarmLogger::interfacesRemoved, this, 115 std::placeholders::_1)) 116 { 117 _powerState->addCallback("thresholdMon", 118 std::bind(&ThresholdAlarmLogger::powerStateChanged, 119 this, std::placeholders::_1)); 120 121 // check for any currently asserted threshold alarms 122 std::for_each( 123 thresholdData.begin(), thresholdData.end(), 124 [this](const auto& thresholdInterface) { 125 const auto& interface = thresholdInterface.first; 126 auto objects = 127 SDBusPlus::getSubTreeRaw(this->bus, "/", interface, 0); 128 std::for_each(objects.begin(), objects.end(), 129 [interface, this](const auto& object) { 130 const auto& path = object.first; 131 const auto& service = 132 object.second.begin()->first; 133 checkThresholds(interface, path, service); 134 }); 135 }); 136 } 137 138 void ThresholdAlarmLogger::propertiesChanged(sdbusplus::message::message& msg) 139 { 140 std::map<std::string, std::variant<bool>> properties; 141 std::string sensorPath = msg.get_path(); 142 std::string interface; 143 144 msg.read(interface, properties); 145 146 auto alarmProperties = thresholdData.find(interface); 147 if (alarmProperties == thresholdData.end()) 148 { 149 return; 150 } 151 152 for (const auto& [propertyName, propertyValue] : properties) 153 { 154 if (alarmProperties->second.find(propertyName) != 155 alarmProperties->second.end()) 156 { 157 // If this is the first time we've seen this alarm, then 158 // assume it was off before so it doesn't create an event 159 // log for a value of false. 160 161 InterfaceKey key{sensorPath, interface}; 162 if (alarms.find(key) == alarms.end()) 163 { 164 alarms[key][propertyName] = false; 165 } 166 167 // Check if the value changed from what was there before. 168 auto alarmValue = std::get<bool>(propertyValue); 169 if (alarmValue != alarms[key][propertyName]) 170 { 171 alarms[key][propertyName] = alarmValue; 172 173 if (_powerState->isPowerOn()) 174 { 175 createEventLog(sensorPath, interface, propertyName, 176 alarmValue); 177 } 178 } 179 } 180 } 181 } 182 183 void ThresholdAlarmLogger::interfacesRemoved(sdbusplus::message::message& msg) 184 { 185 static const std::vector<std::string> thresholdNames{ 186 warningInterface, criticalInterface, perfLossInterface}; 187 sdbusplus::message::object_path path; 188 std::vector<std::string> interfaces; 189 190 msg.read(path, interfaces); 191 192 for (const auto& interface : interfaces) 193 { 194 if (std::find(thresholdNames.begin(), thresholdNames.end(), 195 interface) != thresholdNames.end()) 196 { 197 alarms.erase(InterfaceKey{path, interface}); 198 } 199 } 200 } 201 202 void ThresholdAlarmLogger::checkThresholds(const std::string& interface, 203 const std::string& sensorPath, 204 const std::string& service) 205 { 206 auto properties = thresholdData.find(interface); 207 if (properties == thresholdData.end()) 208 { 209 return; 210 } 211 212 for (const auto& [property, unused] : properties->second) 213 { 214 try 215 { 216 auto alarmValue = SDBusPlus::getProperty<bool>( 217 bus, service, sensorPath, interface, property); 218 alarms[InterfaceKey(sensorPath, interface)][property] = alarmValue; 219 220 // This is just for checking alarms on startup, 221 // so only look for active alarms. 222 if (alarmValue && _powerState->isPowerOn()) 223 { 224 createEventLog(sensorPath, interface, property, alarmValue); 225 } 226 } 227 catch (const sdbusplus::exception::exception& e) 228 { 229 // Sensor daemons that get their direction from entity manager 230 // may only be putting either the high alarm or low alarm on 231 // D-Bus, not both. 232 continue; 233 } 234 } 235 } 236 237 void ThresholdAlarmLogger::createEventLog(const std::string& sensorPath, 238 const std::string& interface, 239 const std::string& alarmProperty, 240 bool alarmValue) 241 { 242 std::map<std::string, std::string> ad; 243 244 auto type = getSensorType(sensorPath); 245 if (skipSensorType(type)) 246 { 247 return; 248 } 249 250 auto it = thresholdData.find(interface); 251 if (it == thresholdData.end()) 252 { 253 return; 254 } 255 256 auto properties = it->second.find(alarmProperty); 257 if (properties == it->second.end()) 258 { 259 log<level::INFO>( 260 fmt::format("Could not find {} in threshold alarms map", 261 alarmProperty) 262 .c_str()); 263 return; 264 } 265 266 ad.emplace("SENSOR_NAME", sensorPath); 267 ad.emplace("_PID", std::to_string(getpid())); 268 269 try 270 { 271 auto sensorValue = SDBusPlus::getProperty<double>( 272 bus, sensorPath, valueInterface, "Value"); 273 274 ad.emplace("SENSOR_VALUE", std::to_string(sensorValue)); 275 276 log<level::INFO>( 277 fmt::format("Threshold Event {} {} = {} (sensor value {})", 278 sensorPath, alarmProperty, alarmValue, sensorValue) 279 .c_str()); 280 } 281 catch (const DBusServiceError& e) 282 { 283 // If the sensor was just added, the Value interface for it may 284 // not be in the mapper yet. This could only happen if the sensor 285 // application was started up after this one and the value exceeded the 286 // threshold immediately. 287 log<level::INFO>(fmt::format("Threshold Event {} {} = {}", sensorPath, 288 alarmProperty, alarmValue) 289 .c_str()); 290 } 291 292 auto callout = getCallout(sensorPath); 293 if (!callout.empty()) 294 { 295 ad.emplace("CALLOUT_INVENTORY_PATH", callout); 296 } 297 298 auto errorData = properties->second.find(alarmValue); 299 300 // Add the base error name and the sensor type (like Temperature) to the 301 // error name that's in the thresholdData name to get something like 302 // xyz.openbmc_project.Sensor.Threshold.Error.TemperatureWarningHigh 303 const auto& [name, severity] = errorData->second; 304 type.front() = toupper(type.front()); 305 std::string errorName = errorNameBase + type + name; 306 307 SDBusPlus::callMethod(loggingService, loggingPath, loggingCreateIface, 308 "Create", errorName, convertForMessage(severity), ad); 309 } 310 311 std::string ThresholdAlarmLogger::getSensorType(std::string sensorPath) 312 { 313 auto pos = sensorPath.find_last_of('/'); 314 if ((sensorPath.back() == '/') || (pos == std::string::npos)) 315 { 316 log<level::ERR>( 317 fmt::format("Cannot get sensor type from sensor path {}", 318 sensorPath) 319 .c_str()); 320 throw std::runtime_error("Invalid sensor path"); 321 } 322 323 sensorPath = sensorPath.substr(0, pos); 324 return sensorPath.substr(sensorPath.find_last_of('/') + 1); 325 } 326 327 bool ThresholdAlarmLogger::skipSensorType(const std::string& type) 328 { 329 return (type == "utilization"); 330 } 331 332 std::string ThresholdAlarmLogger::getCallout(const std::string& sensorPath) 333 { 334 const std::array<std::string, 2> assocTypes{"inventory", "chassis"}; 335 336 // Different implementations handle the association to the FRU 337 // differently: 338 // * phosphor-inventory-manager uses the 'inventory' association 339 // to point to the FRU. 340 // * dbus-sensors/entity-manager uses the 'chassis' association'. 341 // * For virtual sensors, no association. 342 343 for (const auto& assocType : assocTypes) 344 { 345 auto assocPath = sensorPath + "/" + assocType; 346 347 try 348 { 349 auto endpoints = SDBusPlus::getProperty<std::vector<std::string>>( 350 bus, assocPath, assocInterface, "endpoints"); 351 352 if (!endpoints.empty()) 353 { 354 return endpoints[0]; 355 } 356 } 357 catch (const DBusServiceError& e) 358 { 359 // The association doesn't exist 360 continue; 361 } 362 } 363 364 return std::string{}; 365 } 366 367 void ThresholdAlarmLogger::powerStateChanged(bool powerStateOn) 368 { 369 if (powerStateOn) 370 { 371 checkThresholds(); 372 } 373 } 374 375 void ThresholdAlarmLogger::checkThresholds() 376 { 377 for (const auto& [interfaceKey, alarmMap] : alarms) 378 { 379 for (const auto& [propertyName, alarmValue] : alarmMap) 380 { 381 if (alarmValue) 382 { 383 const auto& sensorPath = std::get<0>(interfaceKey); 384 const auto& interface = std::get<1>(interfaceKey); 385 386 createEventLog(sensorPath, interface, propertyName, alarmValue); 387 } 388 } 389 } 390 } 391 392 } // namespace sensor::monitor 393