blob: 99ed0d62157da7952c556eac583b38b8c9f6ddf6 [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
20#include <fmt/format.h>
Matt Spinler3efec612021-05-11 15:26:17 -050021#include <unistd.h>
Matt Spinler50bf8162021-02-01 16:24:01 -060022
23#include <phosphor-logging/log.hpp>
24#include <xyz/openbmc_project/Logging/Entry/server.hpp>
25
Matt Spinler403d1f52021-02-01 15:35:25 -060026namespace sensor::monitor
27{
28
Matt Spinler50bf8162021-02-01 16:24:01 -060029using namespace sdbusplus::xyz::openbmc_project::Logging::server;
30using namespace phosphor::logging;
Matt Spinler66e75a72021-05-14 10:32:47 -050031using namespace phosphor::fan;
Matt Spinler50bf8162021-02-01 16:24:01 -060032using namespace phosphor::fan::util;
33
Matt Spinler403d1f52021-02-01 15:35:25 -060034const std::string warningInterface =
35 "xyz.openbmc_project.Sensor.Threshold.Warning";
36const std::string criticalInterface =
37 "xyz.openbmc_project.Sensor.Threshold.Critical";
38const std::string perfLossInterface =
39 "xyz.openbmc_project.Sensor.Threshold.PerformanceLoss";
Matt Spinler2f182672021-02-01 16:51:38 -060040constexpr auto loggingService = "xyz.openbmc_project.Logging";
41constexpr auto loggingPath = "/xyz/openbmc_project/logging";
42constexpr auto loggingCreateIface = "xyz.openbmc_project.Logging.Create";
43constexpr auto errorNameBase = "xyz.openbmc_project.Sensor.Threshold.Error.";
44constexpr auto valueInterface = "xyz.openbmc_project.Sensor.Value";
45constexpr auto assocInterface = "xyz.openbmc_project.Association";
Matt Spinler403d1f52021-02-01 15:35:25 -060046
Matt Spinler8ce65072022-11-03 15:15:55 -040047const std::vector<std::string> thresholdIfaceNames{
48 warningInterface, criticalInterface, perfLossInterface};
49
Matt Spinler50bf8162021-02-01 16:24:01 -060050using ErrorData = std::tuple<ErrorName, Entry::Level>;
51
52/**
53 * Map of threshold interfaces and alarm properties and values to error data.
54 */
55const 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
Matt Spinler7f6946b2021-05-14 12:43:50 -050088ThresholdAlarmLogger::ThresholdAlarmLogger(
Patrick Williamscb356d42022-07-22 19:26:53 -050089 sdbusplus::bus_t& bus, sdeventplus::Event& event,
Matt Spinler7f6946b2021-05-14 12:43:50 -050090 std::shared_ptr<PowerState> powerState) :
Matt Spinler403d1f52021-02-01 15:35:25 -060091 bus(bus),
Matt Spinler7f6946b2021-05-14 12:43:50 -050092 event(event), _powerState(std::move(powerState)),
Matt Spinler403d1f52021-02-01 15:35:25 -060093 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,
Matt Spinlerb7a55402021-10-11 13:45:35 -0500113 std::placeholders::_1)),
114 ifacesRemovedMatch(bus,
115 "type='signal',member='InterfacesRemoved',arg0path="
116 "'/xyz/openbmc_project/sensors/'",
117 std::bind(&ThresholdAlarmLogger::interfacesRemoved, this,
Matt Spinler8ce65072022-11-03 15:15:55 -0400118 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))
Matt Spinler50bf8162021-02-01 16:24:01 -0600124{
Matt Spinler7f6946b2021-05-14 12:43:50 -0500125 _powerState->addCallback("thresholdMon",
126 std::bind(&ThresholdAlarmLogger::powerStateChanged,
127 this, std::placeholders::_1));
128
Matt Spinler50bf8162021-02-01 16:24:01 -0600129 // check for any currently asserted threshold alarms
Patrick Williams61b73292023-05-10 07:50:12 -0500130 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);
Matt Spinler50bf8162021-02-01 16:24:01 -0600139 });
Patrick Williams61b73292023-05-10 07:50:12 -0500140 });
Matt Spinler50bf8162021-02-01 16:24:01 -0600141}
Matt Spinler403d1f52021-02-01 15:35:25 -0600142
Patrick Williamscb356d42022-07-22 19:26:53 -0500143void ThresholdAlarmLogger::propertiesChanged(sdbusplus::message_t& msg)
Matt Spinler403d1f52021-02-01 15:35:25 -0600144{
Matt Spinlerf5d3be42021-02-01 16:38:01 -0600145 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
Matt Spinler8ce65072022-11-03 15:15:55 -0400151 checkProperties(sensorPath, interface, properties);
152}
153
154void 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
171void 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
188void ThresholdAlarmLogger::checkProperties(
189 const std::string& sensorPath, const std::string& interface,
190 const std::map<std::string, std::variant<bool>>& properties)
191{
Matt Spinlerf5d3be42021-02-01 16:38:01 -0600192 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;
Matt Spinler66e75a72021-05-14 10:32:47 -0500218
219 if (_powerState->isPowerOn())
220 {
221 createEventLog(sensorPath, interface, propertyName,
222 alarmValue);
223 }
Matt Spinlerf5d3be42021-02-01 16:38:01 -0600224 }
225 }
226 }
Matt Spinler403d1f52021-02-01 15:35:25 -0600227}
228
Matt Spinler50bf8162021-02-01 16:24:01 -0600229void 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.
Matt Spinler66e75a72021-05-14 10:32:47 -0500249 if (alarmValue && _powerState->isPowerOn())
Matt Spinler50bf8162021-02-01 16:24:01 -0600250 {
251 createEventLog(sensorPath, interface, property, alarmValue);
252 }
253 }
Patrick Williamscb356d42022-07-22 19:26:53 -0500254 catch (const sdbusplus::exception_t& e)
Matt Spinler50bf8162021-02-01 16:24:01 -0600255 {
Matt Spinler4b515922021-10-11 14:55:50 -0500256 // 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.
Matt Spinler50bf8162021-02-01 16:24:01 -0600259 continue;
260 }
261 }
262}
263
264void ThresholdAlarmLogger::createEventLog(const std::string& sensorPath,
265 const std::string& interface,
266 const std::string& alarmProperty,
267 bool alarmValue)
268{
Matt Spinler2f182672021-02-01 16:51:38 -0600269 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);
Matt Spinler3efec612021-05-11 15:26:17 -0500294 ad.emplace("_PID", std::to_string(getpid()));
Matt Spinler2f182672021-02-01 16:51:38 -0600295
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
338std::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
354bool ThresholdAlarmLogger::skipSensorType(const std::string& type)
355{
356 return (type == "utilization");
357}
358
359std::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{};
Matt Spinler50bf8162021-02-01 16:24:01 -0600392}
393
Matt Spinler66e75a72021-05-14 10:32:47 -0500394void ThresholdAlarmLogger::powerStateChanged(bool powerStateOn)
395{
396 if (powerStateOn)
397 {
398 checkThresholds();
399 }
400}
401
402void ThresholdAlarmLogger::checkThresholds()
403{
Matt Spinlereee25802022-11-03 15:20:13 -0400404 std::vector<InterfaceKey> toErase;
405
Matt Spinler66e75a72021-05-14 10:32:47 -0500406 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);
Matt Spinlereee25802022-11-03 15:20:13 -0400414 std::string service;
Matt Spinler66e75a72021-05-14 10:32:47 -0500415
Matt Spinlereee25802022-11-03 15:20:13 -0400416 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 }
Matt Spinler66e75a72021-05-14 10:32:47 -0500436 }
437 }
438 }
Matt Spinlereee25802022-11-03 15:20:13 -0400439
440 for (const auto& e : toErase)
441 {
442 alarms.erase(e);
443 }
Matt Spinler66e75a72021-05-14 10:32:47 -0500444}
445
Matt Spinler403d1f52021-02-01 15:35:25 -0600446} // namespace sensor::monitor