blob: 13058e71702531bda282181c602f040f98a28976 [file] [log] [blame]
Matt Spinler403d1f52021-02-01 15:35:25 -06001/**
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
Matt Spinler50bf8162021-02-01 16:24:01 -060018#include "sdbusplus.hpp"
19
Matt Spinler3efec612021-05-11 15:26:17 -050020#include <unistd.h>
Matt Spinler50bf8162021-02-01 16:24:01 -060021
22#include <phosphor-logging/log.hpp>
23#include <xyz/openbmc_project/Logging/Entry/server.hpp>
24
Patrick Williamsfbf47032023-07-17 12:27:34 -050025#include <format>
26
Matt Spinler403d1f52021-02-01 15:35:25 -060027namespace sensor::monitor
28{
29
Matt Spinler50bf8162021-02-01 16:24:01 -060030using namespace sdbusplus::xyz::openbmc_project::Logging::server;
31using namespace phosphor::logging;
Matt Spinler66e75a72021-05-14 10:32:47 -050032using namespace phosphor::fan;
Matt Spinler50bf8162021-02-01 16:24:01 -060033using namespace phosphor::fan::util;
34
Matt Spinler403d1f52021-02-01 15:35:25 -060035const std::string warningInterface =
36 "xyz.openbmc_project.Sensor.Threshold.Warning";
37const std::string criticalInterface =
38 "xyz.openbmc_project.Sensor.Threshold.Critical";
39const std::string perfLossInterface =
40 "xyz.openbmc_project.Sensor.Threshold.PerformanceLoss";
Matt Spinler2f182672021-02-01 16:51:38 -060041constexpr auto loggingService = "xyz.openbmc_project.Logging";
42constexpr auto loggingPath = "/xyz/openbmc_project/logging";
43constexpr auto loggingCreateIface = "xyz.openbmc_project.Logging.Create";
44constexpr auto errorNameBase = "xyz.openbmc_project.Sensor.Threshold.Error.";
45constexpr auto valueInterface = "xyz.openbmc_project.Sensor.Value";
46constexpr auto assocInterface = "xyz.openbmc_project.Association";
Matt Spinler403d1f52021-02-01 15:35:25 -060047
Matt Spinler8ce65072022-11-03 15:15:55 -040048const std::vector<std::string> thresholdIfaceNames{
49 warningInterface, criticalInterface, perfLossInterface};
50
Matt Spinler50bf8162021-02-01 16:24:01 -060051using ErrorData = std::tuple<ErrorName, Entry::Level>;
52
53/**
54 * Map of threshold interfaces and alarm properties and values to error data.
55 */
56const std::map<InterfaceName, std::map<PropertyName, std::map<bool, ErrorData>>>
57 thresholdData{
58
59 {warningInterface,
60 {{"WarningAlarmHigh",
61 {{true, ErrorData{"WarningHigh", Entry::Level::Warning}},
62 {false,
63 ErrorData{"WarningHighClear", Entry::Level::Informational}}}},
64 {"WarningAlarmLow",
65 {{true, ErrorData{"WarningLow", Entry::Level::Warning}},
66 {false,
67 ErrorData{"WarningLowClear", Entry::Level::Informational}}}}}},
68
69 {criticalInterface,
70 {{"CriticalAlarmHigh",
71 {{true, ErrorData{"CriticalHigh", Entry::Level::Critical}},
72 {false,
73 ErrorData{"CriticalHighClear", Entry::Level::Informational}}}},
74 {"CriticalAlarmLow",
75 {{true, ErrorData{"CriticalLow", Entry::Level::Critical}},
76 {false,
77 ErrorData{"CriticalLowClear", Entry::Level::Informational}}}}}},
78
79 {perfLossInterface,
80 {{"PerfLossAlarmHigh",
81 {{true, ErrorData{"PerfLossHigh", Entry::Level::Warning}},
82 {false,
83 ErrorData{"PerfLossHighClear", Entry::Level::Informational}}}},
84 {"PerfLossAlarmLow",
85 {{true, ErrorData{"PerfLossLow", Entry::Level::Warning}},
86 {false,
87 ErrorData{"PerfLossLowClear", Entry::Level::Informational}}}}}}};
88
Matt Spinler7f6946b2021-05-14 12:43:50 -050089ThresholdAlarmLogger::ThresholdAlarmLogger(
Patrick Williamscb356d42022-07-22 19:26:53 -050090 sdbusplus::bus_t& bus, sdeventplus::Event& event,
Matt Spinler7f6946b2021-05-14 12:43:50 -050091 std::shared_ptr<PowerState> powerState) :
Matt Spinler403d1f52021-02-01 15:35:25 -060092 bus(bus),
Matt Spinler7f6946b2021-05-14 12:43:50 -050093 event(event), _powerState(std::move(powerState)),
Matt Spinler403d1f52021-02-01 15:35:25 -060094 warningMatch(bus,
95 "type='signal',member='PropertiesChanged',"
96 "path_namespace='/xyz/openbmc_project/sensors',"
97 "arg0='" +
98 warningInterface + "'",
99 std::bind(&ThresholdAlarmLogger::propertiesChanged, this,
100 std::placeholders::_1)),
101 criticalMatch(bus,
102 "type='signal',member='PropertiesChanged',"
103 "path_namespace='/xyz/openbmc_project/sensors',"
104 "arg0='" +
105 criticalInterface + "'",
106 std::bind(&ThresholdAlarmLogger::propertiesChanged, this,
107 std::placeholders::_1)),
108 perfLossMatch(bus,
109 "type='signal',member='PropertiesChanged',"
110 "path_namespace='/xyz/openbmc_project/sensors',"
111 "arg0='" +
112 perfLossInterface + "'",
113 std::bind(&ThresholdAlarmLogger::propertiesChanged, this,
Matt Spinlerb7a55402021-10-11 13:45:35 -0500114 std::placeholders::_1)),
115 ifacesRemovedMatch(bus,
116 "type='signal',member='InterfacesRemoved',arg0path="
117 "'/xyz/openbmc_project/sensors/'",
118 std::bind(&ThresholdAlarmLogger::interfacesRemoved, this,
Matt Spinler8ce65072022-11-03 15:15:55 -0400119 std::placeholders::_1)),
120 ifacesAddedMatch(bus,
121 "type='signal',member='InterfacesAdded',arg0path="
122 "'/xyz/openbmc_project/sensors/'",
123 std::bind(&ThresholdAlarmLogger::interfacesAdded, this,
124 std::placeholders::_1))
Matt Spinler50bf8162021-02-01 16:24:01 -0600125{
Matt Spinler7f6946b2021-05-14 12:43:50 -0500126 _powerState->addCallback("thresholdMon",
127 std::bind(&ThresholdAlarmLogger::powerStateChanged,
128 this, std::placeholders::_1));
129
Matt Spinler50bf8162021-02-01 16:24:01 -0600130 // check for any currently asserted threshold alarms
Patrick Williams61b73292023-05-10 07:50:12 -0500131 std::for_each(thresholdData.begin(), thresholdData.end(),
132 [this](const auto& thresholdInterface) {
133 const auto& interface = thresholdInterface.first;
134 auto objects = SDBusPlus::getSubTreeRaw(this->bus, "/", interface, 0);
135 std::for_each(objects.begin(), objects.end(),
136 [interface, this](const auto& object) {
137 const auto& path = object.first;
138 const auto& service = object.second.begin()->first;
139 checkThresholds(interface, path, service);
Matt Spinler50bf8162021-02-01 16:24:01 -0600140 });
Patrick Williams61b73292023-05-10 07:50:12 -0500141 });
Matt Spinler50bf8162021-02-01 16:24:01 -0600142}
Matt Spinler403d1f52021-02-01 15:35:25 -0600143
Patrick Williamscb356d42022-07-22 19:26:53 -0500144void ThresholdAlarmLogger::propertiesChanged(sdbusplus::message_t& msg)
Matt Spinler403d1f52021-02-01 15:35:25 -0600145{
Matt Spinlerf5d3be42021-02-01 16:38:01 -0600146 std::map<std::string, std::variant<bool>> properties;
147 std::string sensorPath = msg.get_path();
148 std::string interface;
149
150 msg.read(interface, properties);
151
Matt Spinler8ce65072022-11-03 15:15:55 -0400152 checkProperties(sensorPath, interface, properties);
153}
154
155void ThresholdAlarmLogger::interfacesRemoved(sdbusplus::message_t& msg)
156{
157 sdbusplus::message::object_path path;
158 std::vector<std::string> interfaces;
159
160 msg.read(path, interfaces);
161
162 for (const auto& interface : interfaces)
163 {
164 if (std::find(thresholdIfaceNames.begin(), thresholdIfaceNames.end(),
165 interface) != thresholdIfaceNames.end())
166 {
167 alarms.erase(InterfaceKey{path, interface});
168 }
169 }
170}
171
172void ThresholdAlarmLogger::interfacesAdded(sdbusplus::message_t& msg)
173{
174 sdbusplus::message::object_path path;
175 std::map<std::string, std::map<std::string, std::variant<bool>>> interfaces;
176
177 msg.read(path, interfaces);
178
179 for (const auto& [interface, properties] : interfaces)
180 {
181 if (std::find(thresholdIfaceNames.begin(), thresholdIfaceNames.end(),
182 interface) != thresholdIfaceNames.end())
183 {
184 checkProperties(path, interface, properties);
185 }
186 }
187}
188
189void ThresholdAlarmLogger::checkProperties(
190 const std::string& sensorPath, const std::string& interface,
191 const std::map<std::string, std::variant<bool>>& properties)
192{
Matt Spinlerf5d3be42021-02-01 16:38:01 -0600193 auto alarmProperties = thresholdData.find(interface);
194 if (alarmProperties == thresholdData.end())
195 {
196 return;
197 }
198
199 for (const auto& [propertyName, propertyValue] : properties)
200 {
201 if (alarmProperties->second.find(propertyName) !=
202 alarmProperties->second.end())
203 {
204 // If this is the first time we've seen this alarm, then
205 // assume it was off before so it doesn't create an event
206 // log for a value of false.
207
208 InterfaceKey key{sensorPath, interface};
209 if (alarms.find(key) == alarms.end())
210 {
211 alarms[key][propertyName] = false;
212 }
213
214 // Check if the value changed from what was there before.
215 auto alarmValue = std::get<bool>(propertyValue);
216 if (alarmValue != alarms[key][propertyName])
217 {
218 alarms[key][propertyName] = alarmValue;
Matt Spinler66e75a72021-05-14 10:32:47 -0500219
220 if (_powerState->isPowerOn())
221 {
222 createEventLog(sensorPath, interface, propertyName,
223 alarmValue);
224 }
Matt Spinlerf5d3be42021-02-01 16:38:01 -0600225 }
226 }
227 }
Matt Spinler403d1f52021-02-01 15:35:25 -0600228}
229
Matt Spinler50bf8162021-02-01 16:24:01 -0600230void ThresholdAlarmLogger::checkThresholds(const std::string& interface,
231 const std::string& sensorPath,
232 const std::string& service)
233{
234 auto properties = thresholdData.find(interface);
235 if (properties == thresholdData.end())
236 {
237 return;
238 }
239
240 for (const auto& [property, unused] : properties->second)
241 {
242 try
243 {
244 auto alarmValue = SDBusPlus::getProperty<bool>(
245 bus, service, sensorPath, interface, property);
246 alarms[InterfaceKey(sensorPath, interface)][property] = alarmValue;
247
248 // This is just for checking alarms on startup,
249 // so only look for active alarms.
Matt Spinler66e75a72021-05-14 10:32:47 -0500250 if (alarmValue && _powerState->isPowerOn())
Matt Spinler50bf8162021-02-01 16:24:01 -0600251 {
252 createEventLog(sensorPath, interface, property, alarmValue);
253 }
254 }
Patrick Williamscb356d42022-07-22 19:26:53 -0500255 catch (const sdbusplus::exception_t& e)
Matt Spinler50bf8162021-02-01 16:24:01 -0600256 {
Matt Spinler4b515922021-10-11 14:55:50 -0500257 // Sensor daemons that get their direction from entity manager
258 // may only be putting either the high alarm or low alarm on
259 // D-Bus, not both.
Matt Spinler50bf8162021-02-01 16:24:01 -0600260 continue;
261 }
262 }
263}
264
265void ThresholdAlarmLogger::createEventLog(const std::string& sensorPath,
266 const std::string& interface,
267 const std::string& alarmProperty,
268 bool alarmValue)
269{
Matt Spinler2f182672021-02-01 16:51:38 -0600270 std::map<std::string, std::string> ad;
271
272 auto type = getSensorType(sensorPath);
273 if (skipSensorType(type))
274 {
275 return;
276 }
277
278 auto it = thresholdData.find(interface);
279 if (it == thresholdData.end())
280 {
281 return;
282 }
283
284 auto properties = it->second.find(alarmProperty);
285 if (properties == it->second.end())
286 {
287 log<level::INFO>(
Patrick Williamsfbf47032023-07-17 12:27:34 -0500288 std::format("Could not find {} in threshold alarms map",
Matt Spinler2f182672021-02-01 16:51:38 -0600289 alarmProperty)
290 .c_str());
291 return;
292 }
293
294 ad.emplace("SENSOR_NAME", sensorPath);
Matt Spinler3efec612021-05-11 15:26:17 -0500295 ad.emplace("_PID", std::to_string(getpid()));
Matt Spinler2f182672021-02-01 16:51:38 -0600296
297 try
298 {
299 auto sensorValue = SDBusPlus::getProperty<double>(
300 bus, sensorPath, valueInterface, "Value");
301
302 ad.emplace("SENSOR_VALUE", std::to_string(sensorValue));
303
304 log<level::INFO>(
Patrick Williamsfbf47032023-07-17 12:27:34 -0500305 std::format("Threshold Event {} {} = {} (sensor value {})",
Matt Spinler2f182672021-02-01 16:51:38 -0600306 sensorPath, alarmProperty, alarmValue, sensorValue)
307 .c_str());
308 }
309 catch (const DBusServiceError& e)
310 {
311 // If the sensor was just added, the Value interface for it may
312 // not be in the mapper yet. This could only happen if the sensor
313 // application was started up after this one and the value exceeded the
314 // threshold immediately.
Patrick Williamsfbf47032023-07-17 12:27:34 -0500315 log<level::INFO>(std::format("Threshold Event {} {} = {}", sensorPath,
Matt Spinler2f182672021-02-01 16:51:38 -0600316 alarmProperty, alarmValue)
317 .c_str());
318 }
319
320 auto callout = getCallout(sensorPath);
321 if (!callout.empty())
322 {
323 ad.emplace("CALLOUT_INVENTORY_PATH", callout);
324 }
325
326 auto errorData = properties->second.find(alarmValue);
327
328 // Add the base error name and the sensor type (like Temperature) to the
329 // error name that's in the thresholdData name to get something like
330 // xyz.openbmc_project.Sensor.Threshold.Error.TemperatureWarningHigh
331 const auto& [name, severity] = errorData->second;
332 type.front() = toupper(type.front());
333 std::string errorName = errorNameBase + type + name;
334
335 SDBusPlus::callMethod(loggingService, loggingPath, loggingCreateIface,
336 "Create", errorName, convertForMessage(severity), ad);
337}
338
339std::string ThresholdAlarmLogger::getSensorType(std::string sensorPath)
340{
341 auto pos = sensorPath.find_last_of('/');
342 if ((sensorPath.back() == '/') || (pos == std::string::npos))
343 {
344 log<level::ERR>(
Patrick Williamsfbf47032023-07-17 12:27:34 -0500345 std::format("Cannot get sensor type from sensor path {}",
Matt Spinler2f182672021-02-01 16:51:38 -0600346 sensorPath)
347 .c_str());
348 throw std::runtime_error("Invalid sensor path");
349 }
350
351 sensorPath = sensorPath.substr(0, pos);
352 return sensorPath.substr(sensorPath.find_last_of('/') + 1);
353}
354
355bool ThresholdAlarmLogger::skipSensorType(const std::string& type)
356{
357 return (type == "utilization");
358}
359
360std::string ThresholdAlarmLogger::getCallout(const std::string& sensorPath)
361{
362 const std::array<std::string, 2> assocTypes{"inventory", "chassis"};
363
364 // Different implementations handle the association to the FRU
365 // differently:
366 // * phosphor-inventory-manager uses the 'inventory' association
367 // to point to the FRU.
368 // * dbus-sensors/entity-manager uses the 'chassis' association'.
369 // * For virtual sensors, no association.
370
371 for (const auto& assocType : assocTypes)
372 {
373 auto assocPath = sensorPath + "/" + assocType;
374
375 try
376 {
377 auto endpoints = SDBusPlus::getProperty<std::vector<std::string>>(
378 bus, assocPath, assocInterface, "endpoints");
379
380 if (!endpoints.empty())
381 {
382 return endpoints[0];
383 }
384 }
385 catch (const DBusServiceError& e)
386 {
387 // The association doesn't exist
388 continue;
389 }
390 }
391
392 return std::string{};
Matt Spinler50bf8162021-02-01 16:24:01 -0600393}
394
Matt Spinler66e75a72021-05-14 10:32:47 -0500395void ThresholdAlarmLogger::powerStateChanged(bool powerStateOn)
396{
397 if (powerStateOn)
398 {
399 checkThresholds();
400 }
401}
402
403void ThresholdAlarmLogger::checkThresholds()
404{
Matt Spinlereee25802022-11-03 15:20:13 -0400405 std::vector<InterfaceKey> toErase;
406
Matt Spinler66e75a72021-05-14 10:32:47 -0500407 for (const auto& [interfaceKey, alarmMap] : alarms)
408 {
409 for (const auto& [propertyName, alarmValue] : alarmMap)
410 {
411 if (alarmValue)
412 {
413 const auto& sensorPath = std::get<0>(interfaceKey);
414 const auto& interface = std::get<1>(interfaceKey);
Matt Spinlereee25802022-11-03 15:20:13 -0400415 std::string service;
Matt Spinler66e75a72021-05-14 10:32:47 -0500416
Matt Spinlereee25802022-11-03 15:20:13 -0400417 try
418 {
419 // Check that the service that provides the alarm is still
420 // running, because if it died when the alarm was active
421 // there would be no indication of it unless we listened
422 // for NameOwnerChanged and tracked services, and this is
423 // easier.
424 service = SDBusPlus::getService(bus, sensorPath, interface);
425 }
426 catch (const DBusServiceError& e)
427 {
428 // No longer on D-Bus delete the alarm entry
429 toErase.emplace_back(sensorPath, interface);
430 }
431
432 if (!service.empty())
433 {
434 createEventLog(sensorPath, interface, propertyName,
435 alarmValue);
436 }
Matt Spinler66e75a72021-05-14 10:32:47 -0500437 }
438 }
439 }
Matt Spinlereee25802022-11-03 15:20:13 -0400440
441 for (const auto& e : toErase)
442 {
443 alarms.erase(e);
444 }
Matt Spinler66e75a72021-05-14 10:32:47 -0500445}
446
Matt Spinler403d1f52021-02-01 15:35:25 -0600447} // namespace sensor::monitor