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 <unistd.h>
21
22 #include <phosphor-logging/log.hpp>
23 #include <xyz/openbmc_project/Logging/Entry/server.hpp>
24
25 #include <format>
26
27 namespace sensor::monitor
28 {
29
30 using namespace sdbusplus::xyz::openbmc_project::Logging::server;
31 using namespace phosphor::logging;
32 using namespace phosphor::fan;
33 using namespace phosphor::fan::util;
34
35 const std::string warningInterface =
36 "xyz.openbmc_project.Sensor.Threshold.Warning";
37 const std::string criticalInterface =
38 "xyz.openbmc_project.Sensor.Threshold.Critical";
39 const std::string perfLossInterface =
40 "xyz.openbmc_project.Sensor.Threshold.PerformanceLoss";
41 constexpr auto loggingService = "xyz.openbmc_project.Logging";
42 constexpr auto loggingPath = "/xyz/openbmc_project/logging";
43 constexpr auto loggingCreateIface = "xyz.openbmc_project.Logging.Create";
44 constexpr auto errorNameBase = "xyz.openbmc_project.Sensor.Threshold.Error.";
45 constexpr auto valueInterface = "xyz.openbmc_project.Sensor.Value";
46 constexpr auto assocInterface = "xyz.openbmc_project.Association";
47
48 const std::vector<std::string> thresholdIfaceNames{
49 warningInterface, criticalInterface, perfLossInterface};
50
51 using ErrorData = std::tuple<ErrorName, ErrorStatus, Entry::Level>;
52
53 /**
54 * Map of threshold interfaces and alarm properties and values to error data.
55 */
56 const 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{"WarningHigh", "Clear", Entry::Level::Informational}}}},
64 {"WarningAlarmLow",
65 {{true, ErrorData{"WarningLow", "", Entry::Level::Warning}},
66 {false,
67 ErrorData{"WarningLow", "Clear", Entry::Level::Informational}}}}}},
68
69 {criticalInterface,
70 {{"CriticalAlarmHigh",
71 {{true, ErrorData{"CriticalHigh", "", Entry::Level::Critical}},
72 {false,
73 ErrorData{"CriticalHigh", "Clear", Entry::Level::Informational}}}},
74 {"CriticalAlarmLow",
75 {{true, ErrorData{"CriticalLow", "", Entry::Level::Critical}},
76 {false, ErrorData{"CriticalLow", "Clear",
77 Entry::Level::Informational}}}}}},
78
79 {perfLossInterface,
80 {{"PerfLossAlarmHigh",
81 {{true, ErrorData{"PerformanceLossHigh", "", Entry::Level::Warning}},
82 {false, ErrorData{"PerformanceLossHigh", "Clear",
83 Entry::Level::Informational}}}},
84 {"PerfLossAlarmLow",
85 {{true, ErrorData{"PerformanceLossLow", "", Entry::Level::Warning}},
86 {false, ErrorData{"PerformanceLossLow", "Clear",
87 Entry::Level::Informational}}}}}}};
88
ThresholdAlarmLogger(sdbusplus::bus_t & bus,sdeventplus::Event & event,std::shared_ptr<PowerState> powerState)89 ThresholdAlarmLogger::ThresholdAlarmLogger(
90 sdbusplus::bus_t& bus, sdeventplus::Event& event,
91 std::shared_ptr<PowerState> powerState) :
92 bus(bus), event(event), _powerState(std::move(powerState)),
93 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,
113 std::placeholders::_1)),
114 ifacesRemovedMatch(bus,
115 "type='signal',member='InterfacesRemoved',arg0path="
116 "'/xyz/openbmc_project/sensors/'",
117 std::bind(&ThresholdAlarmLogger::interfacesRemoved, this,
118 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))
124 {
125 _powerState->addCallback("thresholdMon",
126 std::bind(&ThresholdAlarmLogger::powerStateChanged,
127 this, std::placeholders::_1));
128
129 // check for any currently asserted threshold alarms
130 std::for_each(
131 thresholdData.begin(), thresholdData.end(),
132 [this](const auto& thresholdInterface) {
133 const auto& interface = thresholdInterface.first;
134 auto objects =
135 SDBusPlus::getSubTreeRaw(this->bus, "/", interface, 0);
136 std::for_each(objects.begin(), objects.end(),
137 [interface, this](const auto& object) {
138 const auto& path = object.first;
139 const auto& service =
140 object.second.begin()->first;
141 checkThresholds(interface, path, service);
142 });
143 });
144 }
145
propertiesChanged(sdbusplus::message_t & msg)146 void ThresholdAlarmLogger::propertiesChanged(sdbusplus::message_t& msg)
147 {
148 std::map<std::string, std::variant<bool>> properties;
149 std::string sensorPath = msg.get_path();
150 std::string interface;
151
152 msg.read(interface, properties);
153
154 checkProperties(sensorPath, interface, properties);
155 }
156
interfacesRemoved(sdbusplus::message_t & msg)157 void ThresholdAlarmLogger::interfacesRemoved(sdbusplus::message_t& msg)
158 {
159 sdbusplus::message::object_path path;
160 std::vector<std::string> interfaces;
161
162 msg.read(path, interfaces);
163
164 for (const auto& interface : interfaces)
165 {
166 if (std::find(thresholdIfaceNames.begin(), thresholdIfaceNames.end(),
167 interface) != thresholdIfaceNames.end())
168 {
169 alarms.erase(InterfaceKey{path, interface});
170 }
171 }
172 }
173
interfacesAdded(sdbusplus::message_t & msg)174 void ThresholdAlarmLogger::interfacesAdded(sdbusplus::message_t& msg)
175 {
176 sdbusplus::message::object_path path;
177 std::map<std::string, std::map<std::string, std::variant<bool>>> interfaces;
178
179 msg.read(path, interfaces);
180
181 for (const auto& [interface, properties] : interfaces)
182 {
183 if (std::find(thresholdIfaceNames.begin(), thresholdIfaceNames.end(),
184 interface) != thresholdIfaceNames.end())
185 {
186 checkProperties(path, interface, properties);
187 }
188 }
189 }
190
checkProperties(const std::string & sensorPath,const std::string & interface,const std::map<std::string,std::variant<bool>> & properties)191 void ThresholdAlarmLogger::checkProperties(
192 const std::string& sensorPath, const std::string& interface,
193 const std::map<std::string, std::variant<bool>>& properties)
194 {
195 auto alarmProperties = thresholdData.find(interface);
196 if (alarmProperties == thresholdData.end())
197 {
198 return;
199 }
200
201 for (const auto& [propertyName, propertyValue] : properties)
202 {
203 if (alarmProperties->second.find(propertyName) !=
204 alarmProperties->second.end())
205 {
206 // If this is the first time we've seen this alarm, then
207 // assume it was off before so it doesn't create an event
208 // log for a value of false.
209
210 InterfaceKey key{sensorPath, interface};
211 if (alarms.find(key) == alarms.end())
212 {
213 alarms[key][propertyName] = false;
214 }
215
216 // Check if the value changed from what was there before.
217 auto alarmValue = std::get<bool>(propertyValue);
218 if (alarmValue != alarms[key][propertyName])
219 {
220 alarms[key][propertyName] = alarmValue;
221
222 if (_powerState->isPowerOn())
223 {
224 createEventLog(sensorPath, interface, propertyName,
225 alarmValue);
226 }
227 }
228 }
229 }
230 }
231
checkThresholds(const std::string & interface,const std::string & sensorPath,const std::string & service)232 void ThresholdAlarmLogger::checkThresholds(const std::string& interface,
233 const std::string& sensorPath,
234 const std::string& service)
235 {
236 auto properties = thresholdData.find(interface);
237 if (properties == thresholdData.end())
238 {
239 return;
240 }
241
242 for (const auto& [property, unused] : properties->second)
243 {
244 try
245 {
246 auto alarmValue = SDBusPlus::getProperty<bool>(
247 bus, service, sensorPath, interface, property);
248 alarms[InterfaceKey(sensorPath, interface)][property] = alarmValue;
249
250 // This is just for checking alarms on startup,
251 // so only look for active alarms.
252 if (alarmValue && _powerState->isPowerOn())
253 {
254 createEventLog(sensorPath, interface, property, alarmValue);
255 }
256 }
257 catch (const sdbusplus::exception_t& e)
258 {
259 // Sensor daemons that get their direction from entity manager
260 // may only be putting either the high alarm or low alarm on
261 // D-Bus, not both.
262 continue;
263 }
264 }
265 }
266
createEventLog(const std::string & sensorPath,const std::string & interface,const std::string & alarmProperty,bool alarmValue)267 void ThresholdAlarmLogger::createEventLog(
268 const std::string& sensorPath, const std::string& interface,
269 const std::string& alarmProperty, bool alarmValue)
270 {
271 std::map<std::string, std::string> ad;
272
273 auto type = getSensorType(sensorPath);
274 if (skipSensorType(type))
275 {
276 return;
277 }
278
279 auto it = thresholdData.find(interface);
280 if (it == thresholdData.end())
281 {
282 return;
283 }
284
285 auto properties = it->second.find(alarmProperty);
286 if (properties == it->second.end())
287 {
288 log<level::INFO>(
289 std::format("Could not find {} in threshold alarms map",
290 alarmProperty)
291 .c_str());
292 return;
293 }
294
295 ad.emplace("SENSOR_NAME", sensorPath);
296 ad.emplace("_PID", std::to_string(getpid()));
297
298 try
299 {
300 auto sensorValue = SDBusPlus::getProperty<double>(
301 bus, sensorPath, valueInterface, "Value");
302
303 ad.emplace("SENSOR_VALUE", std::to_string(sensorValue));
304
305 log<level::INFO>(
306 std::format("Threshold Event {} {} = {} (sensor value {})",
307 sensorPath, alarmProperty, alarmValue, sensorValue)
308 .c_str());
309 }
310 catch (const DBusServiceError& e)
311 {
312 // If the sensor was just added, the Value interface for it may
313 // not be in the mapper yet. This could only happen if the sensor
314 // application was started up after this one and the value exceeded the
315 // threshold immediately.
316 log<level::INFO>(std::format("Threshold Event {} {} = {}", sensorPath,
317 alarmProperty, alarmValue)
318 .c_str());
319 }
320
321 auto callout = getCallout(sensorPath);
322 if (!callout.empty())
323 {
324 ad.emplace("CALLOUT_INVENTORY_PATH", callout);
325 }
326
327 auto errorData = properties->second.find(alarmValue);
328
329 // Add the base error name and the sensor type (like Temperature) to the
330 // error name that's in the thresholdData name to get something like
331 // xyz.openbmc_project.Sensor.Threshold.Error.TemperatureWarningHigh
332 const auto& [name, status, severity] = errorData->second;
333
334 try
335 {
336 auto thresholdValue =
337 SDBusPlus::getProperty<double>(bus, sensorPath, interface, name);
338
339 ad.emplace("THRESHOLD_VALUE", std::to_string(thresholdValue));
340
341 log<level::INFO>(
342 std::format("Threshold Event {} {} = {} (threshold value {})",
343 sensorPath, alarmProperty, alarmValue, thresholdValue)
344 .c_str());
345 }
346 catch (const DBusServiceError& e)
347 {
348 log<level::INFO>(std::format("Threshold Event {} {} = {}", sensorPath,
349 alarmProperty, alarmValue)
350 .c_str());
351 }
352
353 type.front() = toupper(type.front());
354 std::string errorName = errorNameBase + type + name + status;
355
356 SDBusPlus::callMethod(loggingService, loggingPath, loggingCreateIface,
357 "Create", errorName, convertForMessage(severity), ad);
358 }
359
getSensorType(std::string sensorPath)360 std::string ThresholdAlarmLogger::getSensorType(std::string sensorPath)
361 {
362 auto pos = sensorPath.find_last_of('/');
363 if ((sensorPath.back() == '/') || (pos == std::string::npos))
364 {
365 log<level::ERR>(
366 std::format("Cannot get sensor type from sensor path {}",
367 sensorPath)
368 .c_str());
369 throw std::runtime_error("Invalid sensor path");
370 }
371
372 sensorPath = sensorPath.substr(0, pos);
373 return sensorPath.substr(sensorPath.find_last_of('/') + 1);
374 }
375
skipSensorType(const std::string & type)376 bool ThresholdAlarmLogger::skipSensorType(const std::string& type)
377 {
378 return (type == "utilization");
379 }
380
getCallout(const std::string & sensorPath)381 std::string ThresholdAlarmLogger::getCallout(const std::string& sensorPath)
382 {
383 const std::array<std::string, 2> assocTypes{"inventory", "chassis"};
384
385 // Different implementations handle the association to the FRU
386 // differently:
387 // * phosphor-inventory-manager uses the 'inventory' association
388 // to point to the FRU.
389 // * dbus-sensors/entity-manager uses the 'chassis' association'.
390 // * For virtual sensors, no association.
391
392 for (const auto& assocType : assocTypes)
393 {
394 auto assocPath = sensorPath + "/" + assocType;
395
396 try
397 {
398 auto endpoints = SDBusPlus::getProperty<std::vector<std::string>>(
399 bus, assocPath, assocInterface, "endpoints");
400
401 if (!endpoints.empty())
402 {
403 return endpoints[0];
404 }
405 }
406 catch (const DBusServiceError& e)
407 {
408 // The association doesn't exist
409 continue;
410 }
411 }
412
413 return std::string{};
414 }
415
powerStateChanged(bool powerStateOn)416 void ThresholdAlarmLogger::powerStateChanged(bool powerStateOn)
417 {
418 if (powerStateOn)
419 {
420 checkThresholds();
421 }
422 }
423
checkThresholds()424 void ThresholdAlarmLogger::checkThresholds()
425 {
426 std::vector<InterfaceKey> toErase;
427
428 for (const auto& [interfaceKey, alarmMap] : alarms)
429 {
430 for (const auto& [propertyName, alarmValue] : alarmMap)
431 {
432 if (alarmValue)
433 {
434 const auto& sensorPath = std::get<0>(interfaceKey);
435 const auto& interface = std::get<1>(interfaceKey);
436 std::string service;
437
438 try
439 {
440 // Check that the service that provides the alarm is still
441 // running, because if it died when the alarm was active
442 // there would be no indication of it unless we listened
443 // for NameOwnerChanged and tracked services, and this is
444 // easier.
445 service = SDBusPlus::getService(bus, sensorPath, interface);
446 }
447 catch (const DBusServiceError& e)
448 {
449 // No longer on D-Bus delete the alarm entry
450 toErase.emplace_back(sensorPath, interface);
451 }
452
453 if (!service.empty())
454 {
455 createEventLog(sensorPath, interface, propertyName,
456 alarmValue);
457 }
458 }
459 }
460 }
461
462 for (const auto& e : toErase)
463 {
464 alarms.erase(e);
465 }
466 }
467
468 } // namespace sensor::monitor
469