| /** |
| * Copyright © 2021 IBM Corporation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include "config.h" |
| |
| #include "threshold_alarm_logger.hpp" |
| |
| #include "sdbusplus.hpp" |
| |
| #include <unistd.h> |
| |
| #include <phosphor-logging/log.hpp> |
| #include <xyz/openbmc_project/Logging/Entry/server.hpp> |
| |
| #include <format> |
| |
| namespace sensor::monitor |
| { |
| |
| using namespace sdbusplus::xyz::openbmc_project::Logging::server; |
| using namespace phosphor::logging; |
| using namespace phosphor::fan; |
| using namespace phosphor::fan::util; |
| |
| const std::string warningInterface = |
| "xyz.openbmc_project.Sensor.Threshold.Warning"; |
| const std::string criticalInterface = |
| "xyz.openbmc_project.Sensor.Threshold.Critical"; |
| const std::string perfLossInterface = |
| "xyz.openbmc_project.Sensor.Threshold.PerformanceLoss"; |
| constexpr auto loggingService = "xyz.openbmc_project.Logging"; |
| constexpr auto loggingPath = "/xyz/openbmc_project/logging"; |
| constexpr auto loggingCreateIface = "xyz.openbmc_project.Logging.Create"; |
| constexpr auto errorNameBase = "xyz.openbmc_project.Sensor.Threshold.Error."; |
| constexpr auto valueInterface = "xyz.openbmc_project.Sensor.Value"; |
| constexpr auto assocInterface = "xyz.openbmc_project.Association"; |
| |
| const std::vector<std::string> thresholdIfaceNames{ |
| warningInterface, criticalInterface, perfLossInterface}; |
| |
| using ErrorData = std::tuple<ErrorName, ErrorStatus, Entry::Level>; |
| |
| /** |
| * Map of threshold interfaces and alarm properties and values to error data. |
| */ |
| const std::map<InterfaceName, std::map<PropertyName, std::map<bool, ErrorData>>> |
| thresholdData{ |
| |
| {warningInterface, |
| {{"WarningAlarmHigh", |
| {{true, ErrorData{"WarningHigh", "", Entry::Level::Warning}}, |
| {false, |
| ErrorData{"WarningHigh", "Clear", Entry::Level::Informational}}}}, |
| {"WarningAlarmLow", |
| {{true, ErrorData{"WarningLow", "", Entry::Level::Warning}}, |
| {false, |
| ErrorData{"WarningLow", "Clear", Entry::Level::Informational}}}}}}, |
| |
| {criticalInterface, |
| {{"CriticalAlarmHigh", |
| {{true, ErrorData{"CriticalHigh", "", Entry::Level::Critical}}, |
| {false, |
| ErrorData{"CriticalHigh", "Clear", Entry::Level::Informational}}}}, |
| {"CriticalAlarmLow", |
| {{true, ErrorData{"CriticalLow", "", Entry::Level::Critical}}, |
| {false, ErrorData{"CriticalLow", "Clear", |
| Entry::Level::Informational}}}}}}, |
| |
| {perfLossInterface, |
| {{"PerfLossAlarmHigh", |
| {{true, ErrorData{"PerformanceLossHigh", "", Entry::Level::Warning}}, |
| {false, ErrorData{"PerformanceLossHigh", "Clear", |
| Entry::Level::Informational}}}}, |
| {"PerfLossAlarmLow", |
| {{true, ErrorData{"PerformanceLossLow", "", Entry::Level::Warning}}, |
| {false, ErrorData{"PerformanceLossLow", "Clear", |
| Entry::Level::Informational}}}}}}}; |
| |
| ThresholdAlarmLogger::ThresholdAlarmLogger( |
| sdbusplus::bus_t& bus, sdeventplus::Event& event, |
| std::shared_ptr<PowerState> powerState) : |
| bus(bus), event(event), _powerState(std::move(powerState)), |
| warningMatch(bus, |
| "type='signal',member='PropertiesChanged'," |
| "path_namespace='/xyz/openbmc_project/sensors'," |
| "arg0='" + |
| warningInterface + "'", |
| std::bind(&ThresholdAlarmLogger::propertiesChanged, this, |
| std::placeholders::_1)), |
| criticalMatch(bus, |
| "type='signal',member='PropertiesChanged'," |
| "path_namespace='/xyz/openbmc_project/sensors'," |
| "arg0='" + |
| criticalInterface + "'", |
| std::bind(&ThresholdAlarmLogger::propertiesChanged, this, |
| std::placeholders::_1)), |
| perfLossMatch(bus, |
| "type='signal',member='PropertiesChanged'," |
| "path_namespace='/xyz/openbmc_project/sensors'," |
| "arg0='" + |
| perfLossInterface + "'", |
| std::bind(&ThresholdAlarmLogger::propertiesChanged, this, |
| std::placeholders::_1)), |
| ifacesRemovedMatch(bus, |
| "type='signal',member='InterfacesRemoved',arg0path=" |
| "'/xyz/openbmc_project/sensors/'", |
| std::bind(&ThresholdAlarmLogger::interfacesRemoved, this, |
| std::placeholders::_1)), |
| ifacesAddedMatch(bus, |
| "type='signal',member='InterfacesAdded',arg0path=" |
| "'/xyz/openbmc_project/sensors/'", |
| std::bind(&ThresholdAlarmLogger::interfacesAdded, this, |
| std::placeholders::_1)) |
| { |
| _powerState->addCallback("thresholdMon", |
| std::bind(&ThresholdAlarmLogger::powerStateChanged, |
| this, std::placeholders::_1)); |
| |
| // check for any currently asserted threshold alarms |
| std::for_each( |
| thresholdData.begin(), thresholdData.end(), |
| [this](const auto& thresholdInterface) { |
| const auto& interface = thresholdInterface.first; |
| auto objects = |
| SDBusPlus::getSubTreeRaw(this->bus, "/", interface, 0); |
| std::for_each(objects.begin(), objects.end(), |
| [interface, this](const auto& object) { |
| const auto& path = object.first; |
| const auto& service = |
| object.second.begin()->first; |
| checkThresholds(interface, path, service); |
| }); |
| }); |
| } |
| |
| void ThresholdAlarmLogger::propertiesChanged(sdbusplus::message_t& msg) |
| { |
| std::map<std::string, std::variant<bool>> properties; |
| std::string sensorPath = msg.get_path(); |
| std::string interface; |
| |
| msg.read(interface, properties); |
| |
| checkProperties(sensorPath, interface, properties); |
| } |
| |
| void ThresholdAlarmLogger::interfacesRemoved(sdbusplus::message_t& msg) |
| { |
| sdbusplus::message::object_path path; |
| std::vector<std::string> interfaces; |
| |
| msg.read(path, interfaces); |
| |
| for (const auto& interface : interfaces) |
| { |
| if (std::find(thresholdIfaceNames.begin(), thresholdIfaceNames.end(), |
| interface) != thresholdIfaceNames.end()) |
| { |
| alarms.erase(InterfaceKey{path, interface}); |
| } |
| } |
| } |
| |
| void ThresholdAlarmLogger::interfacesAdded(sdbusplus::message_t& msg) |
| { |
| sdbusplus::message::object_path path; |
| std::map<std::string, std::map<std::string, std::variant<bool>>> interfaces; |
| |
| msg.read(path, interfaces); |
| |
| for (const auto& [interface, properties] : interfaces) |
| { |
| if (std::find(thresholdIfaceNames.begin(), thresholdIfaceNames.end(), |
| interface) != thresholdIfaceNames.end()) |
| { |
| checkProperties(path, interface, properties); |
| } |
| } |
| } |
| |
| void ThresholdAlarmLogger::checkProperties( |
| const std::string& sensorPath, const std::string& interface, |
| const std::map<std::string, std::variant<bool>>& properties) |
| { |
| auto alarmProperties = thresholdData.find(interface); |
| if (alarmProperties == thresholdData.end()) |
| { |
| return; |
| } |
| |
| for (const auto& [propertyName, propertyValue] : properties) |
| { |
| if (alarmProperties->second.find(propertyName) != |
| alarmProperties->second.end()) |
| { |
| // If this is the first time we've seen this alarm, then |
| // assume it was off before so it doesn't create an event |
| // log for a value of false. |
| |
| InterfaceKey key{sensorPath, interface}; |
| if (alarms.find(key) == alarms.end()) |
| { |
| alarms[key][propertyName] = false; |
| } |
| |
| // Check if the value changed from what was there before. |
| auto alarmValue = std::get<bool>(propertyValue); |
| if (alarmValue != alarms[key][propertyName]) |
| { |
| alarms[key][propertyName] = alarmValue; |
| #ifndef SKIP_POWER_CHECKING |
| if (_powerState->isPowerOn()) |
| #endif |
| { |
| createEventLog(sensorPath, interface, propertyName, |
| alarmValue); |
| } |
| } |
| } |
| } |
| } |
| |
| void ThresholdAlarmLogger::checkThresholds(const std::string& interface, |
| const std::string& sensorPath, |
| const std::string& service) |
| { |
| auto properties = thresholdData.find(interface); |
| if (properties == thresholdData.end()) |
| { |
| return; |
| } |
| |
| for (const auto& [property, unused] : properties->second) |
| { |
| try |
| { |
| auto alarmValue = SDBusPlus::getProperty<bool>( |
| bus, service, sensorPath, interface, property); |
| alarms[InterfaceKey(sensorPath, interface)][property] = alarmValue; |
| |
| // This is just for checking alarms on startup, |
| // so only look for active alarms. |
| #ifdef SKIP_POWER_CHECKING |
| if (alarmValue) |
| #else |
| if (alarmValue && _powerState->isPowerOn()) |
| #endif |
| { |
| createEventLog(sensorPath, interface, property, alarmValue); |
| } |
| } |
| catch (const sdbusplus::exception_t& e) |
| { |
| // Sensor daemons that get their direction from entity manager |
| // may only be putting either the high alarm or low alarm on |
| // D-Bus, not both. |
| continue; |
| } |
| } |
| } |
| |
| void ThresholdAlarmLogger::createEventLog( |
| const std::string& sensorPath, const std::string& interface, |
| const std::string& alarmProperty, bool alarmValue) |
| { |
| std::map<std::string, std::string> ad; |
| |
| auto type = getSensorType(sensorPath); |
| if (skipSensorType(type)) |
| { |
| return; |
| } |
| |
| auto it = thresholdData.find(interface); |
| if (it == thresholdData.end()) |
| { |
| return; |
| } |
| |
| auto properties = it->second.find(alarmProperty); |
| if (properties == it->second.end()) |
| { |
| log<level::INFO>( |
| std::format("Could not find {} in threshold alarms map", |
| alarmProperty) |
| .c_str()); |
| return; |
| } |
| |
| ad.emplace("SENSOR_NAME", sensorPath); |
| ad.emplace("_PID", std::to_string(getpid())); |
| |
| try |
| { |
| auto sensorValue = SDBusPlus::getProperty<double>( |
| bus, sensorPath, valueInterface, "Value"); |
| |
| ad.emplace("SENSOR_VALUE", std::to_string(sensorValue)); |
| |
| log<level::INFO>( |
| std::format("Threshold Event {} {} = {} (sensor value {})", |
| sensorPath, alarmProperty, alarmValue, sensorValue) |
| .c_str()); |
| } |
| catch (const DBusServiceError& e) |
| { |
| // If the sensor was just added, the Value interface for it may |
| // not be in the mapper yet. This could only happen if the sensor |
| // application was started up after this one and the value exceeded the |
| // threshold immediately. |
| log<level::INFO>(std::format("Threshold Event {} {} = {}", sensorPath, |
| alarmProperty, alarmValue) |
| .c_str()); |
| } |
| |
| auto callout = getCallout(sensorPath); |
| if (!callout.empty()) |
| { |
| ad.emplace("CALLOUT_INVENTORY_PATH", callout); |
| } |
| |
| auto errorData = properties->second.find(alarmValue); |
| |
| // Add the base error name and the sensor type (like Temperature) to the |
| // error name that's in the thresholdData name to get something like |
| // xyz.openbmc_project.Sensor.Threshold.Error.TemperatureWarningHigh |
| const auto& [name, status, severity] = errorData->second; |
| |
| try |
| { |
| auto thresholdValue = |
| SDBusPlus::getProperty<double>(bus, sensorPath, interface, name); |
| |
| ad.emplace("THRESHOLD_VALUE", std::to_string(thresholdValue)); |
| |
| log<level::INFO>( |
| std::format("Threshold Event {} {} = {} (threshold value {})", |
| sensorPath, alarmProperty, alarmValue, thresholdValue) |
| .c_str()); |
| } |
| catch (const DBusServiceError& e) |
| { |
| log<level::INFO>(std::format("Threshold Event {} {} = {}", sensorPath, |
| alarmProperty, alarmValue) |
| .c_str()); |
| } |
| |
| type.front() = toupper(type.front()); |
| std::string errorName = errorNameBase + type + name + status; |
| |
| SDBusPlus::callMethod(loggingService, loggingPath, loggingCreateIface, |
| "Create", errorName, convertForMessage(severity), ad); |
| } |
| |
| std::string ThresholdAlarmLogger::getSensorType(std::string sensorPath) |
| { |
| auto pos = sensorPath.find_last_of('/'); |
| if ((sensorPath.back() == '/') || (pos == std::string::npos)) |
| { |
| log<level::ERR>( |
| std::format("Cannot get sensor type from sensor path {}", |
| sensorPath) |
| .c_str()); |
| throw std::runtime_error("Invalid sensor path"); |
| } |
| |
| sensorPath = sensorPath.substr(0, pos); |
| return sensorPath.substr(sensorPath.find_last_of('/') + 1); |
| } |
| |
| bool ThresholdAlarmLogger::skipSensorType(const std::string& type) |
| { |
| return (type == "utilization"); |
| } |
| |
| std::string ThresholdAlarmLogger::getCallout(const std::string& sensorPath) |
| { |
| const std::array<std::string, 2> assocTypes{"inventory", "chassis"}; |
| |
| // Different implementations handle the association to the FRU |
| // differently: |
| // * phosphor-inventory-manager uses the 'inventory' association |
| // to point to the FRU. |
| // * dbus-sensors/entity-manager uses the 'chassis' association'. |
| // * For virtual sensors, no association. |
| |
| for (const auto& assocType : assocTypes) |
| { |
| auto assocPath = sensorPath + "/" + assocType; |
| |
| try |
| { |
| auto endpoints = SDBusPlus::getProperty<std::vector<std::string>>( |
| bus, assocPath, assocInterface, "endpoints"); |
| |
| if (!endpoints.empty()) |
| { |
| return endpoints[0]; |
| } |
| } |
| catch (const DBusServiceError& e) |
| { |
| // The association doesn't exist |
| continue; |
| } |
| } |
| |
| return std::string{}; |
| } |
| |
| void ThresholdAlarmLogger::powerStateChanged(bool powerStateOn) |
| { |
| if (powerStateOn) |
| { |
| checkThresholds(); |
| } |
| } |
| |
| void ThresholdAlarmLogger::checkThresholds() |
| { |
| std::vector<InterfaceKey> toErase; |
| |
| for (const auto& [interfaceKey, alarmMap] : alarms) |
| { |
| for (const auto& [propertyName, alarmValue] : alarmMap) |
| { |
| if (alarmValue) |
| { |
| const auto& sensorPath = std::get<0>(interfaceKey); |
| const auto& interface = std::get<1>(interfaceKey); |
| std::string service; |
| |
| try |
| { |
| // Check that the service that provides the alarm is still |
| // running, because if it died when the alarm was active |
| // there would be no indication of it unless we listened |
| // for NameOwnerChanged and tracked services, and this is |
| // easier. |
| service = SDBusPlus::getService(bus, sensorPath, interface); |
| } |
| catch (const DBusServiceError& e) |
| { |
| // No longer on D-Bus delete the alarm entry |
| toErase.emplace_back(sensorPath, interface); |
| } |
| |
| if (!service.empty()) |
| { |
| createEventLog(sensorPath, interface, propertyName, |
| alarmValue); |
| } |
| } |
| } |
| } |
| |
| for (const auto& e : toErase) |
| { |
| alarms.erase(e); |
| } |
| } |
| |
| } // namespace sensor::monitor |