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 { 112 _powerState->addCallback("thresholdMon", 113 std::bind(&ThresholdAlarmLogger::powerStateChanged, 114 this, std::placeholders::_1)); 115 116 // check for any currently asserted threshold alarms 117 std::for_each( 118 thresholdData.begin(), thresholdData.end(), 119 [this](const auto& thresholdInterface) { 120 const auto& interface = thresholdInterface.first; 121 auto objects = 122 SDBusPlus::getSubTreeRaw(this->bus, "/", interface, 0); 123 std::for_each(objects.begin(), objects.end(), 124 [interface, this](const auto& object) { 125 const auto& path = object.first; 126 const auto& service = 127 object.second.begin()->first; 128 checkThresholds(interface, path, service); 129 }); 130 }); 131 } 132 133 void ThresholdAlarmLogger::propertiesChanged(sdbusplus::message::message& msg) 134 { 135 std::map<std::string, std::variant<bool>> properties; 136 std::string sensorPath = msg.get_path(); 137 std::string interface; 138 139 msg.read(interface, properties); 140 141 auto alarmProperties = thresholdData.find(interface); 142 if (alarmProperties == thresholdData.end()) 143 { 144 return; 145 } 146 147 for (const auto& [propertyName, propertyValue] : properties) 148 { 149 if (alarmProperties->second.find(propertyName) != 150 alarmProperties->second.end()) 151 { 152 // If this is the first time we've seen this alarm, then 153 // assume it was off before so it doesn't create an event 154 // log for a value of false. 155 156 InterfaceKey key{sensorPath, interface}; 157 if (alarms.find(key) == alarms.end()) 158 { 159 alarms[key][propertyName] = false; 160 } 161 162 // Check if the value changed from what was there before. 163 auto alarmValue = std::get<bool>(propertyValue); 164 if (alarmValue != alarms[key][propertyName]) 165 { 166 alarms[key][propertyName] = alarmValue; 167 168 if (_powerState->isPowerOn()) 169 { 170 createEventLog(sensorPath, interface, propertyName, 171 alarmValue); 172 } 173 } 174 } 175 } 176 } 177 178 void ThresholdAlarmLogger::checkThresholds(const std::string& interface, 179 const std::string& sensorPath, 180 const std::string& service) 181 { 182 auto properties = thresholdData.find(interface); 183 if (properties == thresholdData.end()) 184 { 185 return; 186 } 187 188 for (const auto& [property, unused] : properties->second) 189 { 190 try 191 { 192 auto alarmValue = SDBusPlus::getProperty<bool>( 193 bus, service, sensorPath, interface, property); 194 alarms[InterfaceKey(sensorPath, interface)][property] = alarmValue; 195 196 // This is just for checking alarms on startup, 197 // so only look for active alarms. 198 if (alarmValue && _powerState->isPowerOn()) 199 { 200 createEventLog(sensorPath, interface, property, alarmValue); 201 } 202 } 203 catch (const DBusError& e) 204 { 205 log<level::ERR>( 206 fmt::format("Failed reading sensor threshold properties: {}", 207 e.what()) 208 .c_str()); 209 continue; 210 } 211 } 212 } 213 214 void ThresholdAlarmLogger::createEventLog(const std::string& sensorPath, 215 const std::string& interface, 216 const std::string& alarmProperty, 217 bool alarmValue) 218 { 219 std::map<std::string, std::string> ad; 220 221 auto type = getSensorType(sensorPath); 222 if (skipSensorType(type)) 223 { 224 return; 225 } 226 227 auto it = thresholdData.find(interface); 228 if (it == thresholdData.end()) 229 { 230 return; 231 } 232 233 auto properties = it->second.find(alarmProperty); 234 if (properties == it->second.end()) 235 { 236 log<level::INFO>( 237 fmt::format("Could not find {} in threshold alarms map", 238 alarmProperty) 239 .c_str()); 240 return; 241 } 242 243 ad.emplace("SENSOR_NAME", sensorPath); 244 ad.emplace("_PID", std::to_string(getpid())); 245 246 try 247 { 248 auto sensorValue = SDBusPlus::getProperty<double>( 249 bus, sensorPath, valueInterface, "Value"); 250 251 ad.emplace("SENSOR_VALUE", std::to_string(sensorValue)); 252 253 log<level::INFO>( 254 fmt::format("Threshold Event {} {} = {} (sensor value {})", 255 sensorPath, alarmProperty, alarmValue, sensorValue) 256 .c_str()); 257 } 258 catch (const DBusServiceError& e) 259 { 260 // If the sensor was just added, the Value interface for it may 261 // not be in the mapper yet. This could only happen if the sensor 262 // application was started up after this one and the value exceeded the 263 // threshold immediately. 264 log<level::INFO>(fmt::format("Threshold Event {} {} = {}", sensorPath, 265 alarmProperty, alarmValue) 266 .c_str()); 267 } 268 269 auto callout = getCallout(sensorPath); 270 if (!callout.empty()) 271 { 272 ad.emplace("CALLOUT_INVENTORY_PATH", callout); 273 } 274 275 auto errorData = properties->second.find(alarmValue); 276 277 // Add the base error name and the sensor type (like Temperature) to the 278 // error name that's in the thresholdData name to get something like 279 // xyz.openbmc_project.Sensor.Threshold.Error.TemperatureWarningHigh 280 const auto& [name, severity] = errorData->second; 281 type.front() = toupper(type.front()); 282 std::string errorName = errorNameBase + type + name; 283 284 SDBusPlus::callMethod(loggingService, loggingPath, loggingCreateIface, 285 "Create", errorName, convertForMessage(severity), ad); 286 } 287 288 std::string ThresholdAlarmLogger::getSensorType(std::string sensorPath) 289 { 290 auto pos = sensorPath.find_last_of('/'); 291 if ((sensorPath.back() == '/') || (pos == std::string::npos)) 292 { 293 log<level::ERR>( 294 fmt::format("Cannot get sensor type from sensor path {}", 295 sensorPath) 296 .c_str()); 297 throw std::runtime_error("Invalid sensor path"); 298 } 299 300 sensorPath = sensorPath.substr(0, pos); 301 return sensorPath.substr(sensorPath.find_last_of('/') + 1); 302 } 303 304 bool ThresholdAlarmLogger::skipSensorType(const std::string& type) 305 { 306 return (type == "utilization"); 307 } 308 309 std::string ThresholdAlarmLogger::getCallout(const std::string& sensorPath) 310 { 311 const std::array<std::string, 2> assocTypes{"inventory", "chassis"}; 312 313 // Different implementations handle the association to the FRU 314 // differently: 315 // * phosphor-inventory-manager uses the 'inventory' association 316 // to point to the FRU. 317 // * dbus-sensors/entity-manager uses the 'chassis' association'. 318 // * For virtual sensors, no association. 319 320 for (const auto& assocType : assocTypes) 321 { 322 auto assocPath = sensorPath + "/" + assocType; 323 324 try 325 { 326 auto endpoints = SDBusPlus::getProperty<std::vector<std::string>>( 327 bus, assocPath, assocInterface, "endpoints"); 328 329 if (!endpoints.empty()) 330 { 331 return endpoints[0]; 332 } 333 } 334 catch (const DBusServiceError& e) 335 { 336 // The association doesn't exist 337 continue; 338 } 339 } 340 341 return std::string{}; 342 } 343 344 void ThresholdAlarmLogger::powerStateChanged(bool powerStateOn) 345 { 346 if (powerStateOn) 347 { 348 checkThresholds(); 349 } 350 } 351 352 void ThresholdAlarmLogger::checkThresholds() 353 { 354 for (const auto& [interfaceKey, alarmMap] : alarms) 355 { 356 for (const auto& [propertyName, alarmValue] : alarmMap) 357 { 358 if (alarmValue) 359 { 360 const auto& sensorPath = std::get<0>(interfaceKey); 361 const auto& interface = std::get<1>(interfaceKey); 362 363 createEventLog(sensorPath, interface, propertyName, alarmValue); 364 } 365 } 366 } 367 } 368 369 } // namespace sensor::monitor 370