blob: 03b35baacf12af18411c9a2e4909ebdc438efd34 [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
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}
Matt Spinler403d1f52021-02-01 15:35:25 -0600145
Patrick Williamscb356d42022-07-22 19:26:53 -0500146void ThresholdAlarmLogger::propertiesChanged(sdbusplus::message_t& msg)
Matt Spinler403d1f52021-02-01 15:35:25 -0600147{
Matt Spinlerf5d3be42021-02-01 16:38:01 -0600148 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
Matt Spinler8ce65072022-11-03 15:15:55 -0400154 checkProperties(sensorPath, interface, properties);
155}
156
157void 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
174void 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
191void ThresholdAlarmLogger::checkProperties(
192 const std::string& sensorPath, const std::string& interface,
193 const std::map<std::string, std::variant<bool>>& properties)
194{
Matt Spinlerf5d3be42021-02-01 16:38:01 -0600195 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;
Matt Spinler66e75a72021-05-14 10:32:47 -0500221
222 if (_powerState->isPowerOn())
223 {
224 createEventLog(sensorPath, interface, propertyName,
225 alarmValue);
226 }
Matt Spinlerf5d3be42021-02-01 16:38:01 -0600227 }
228 }
229 }
Matt Spinler403d1f52021-02-01 15:35:25 -0600230}
231
Matt Spinler50bf8162021-02-01 16:24:01 -0600232void 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.
Matt Spinler66e75a72021-05-14 10:32:47 -0500252 if (alarmValue && _powerState->isPowerOn())
Matt Spinler50bf8162021-02-01 16:24:01 -0600253 {
254 createEventLog(sensorPath, interface, property, alarmValue);
255 }
256 }
Patrick Williamscb356d42022-07-22 19:26:53 -0500257 catch (const sdbusplus::exception_t& e)
Matt Spinler50bf8162021-02-01 16:24:01 -0600258 {
Matt Spinler4b515922021-10-11 14:55:50 -0500259 // 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.
Matt Spinler50bf8162021-02-01 16:24:01 -0600262 continue;
263 }
264 }
265}
266
267void ThresholdAlarmLogger::createEventLog(const std::string& sensorPath,
268 const std::string& interface,
269 const std::string& alarmProperty,
270 bool alarmValue)
271{
Matt Spinler2f182672021-02-01 16:51:38 -0600272 std::map<std::string, std::string> ad;
273
274 auto type = getSensorType(sensorPath);
275 if (skipSensorType(type))
276 {
277 return;
278 }
279
280 auto it = thresholdData.find(interface);
281 if (it == thresholdData.end())
282 {
283 return;
284 }
285
286 auto properties = it->second.find(alarmProperty);
287 if (properties == it->second.end())
288 {
289 log<level::INFO>(
290 fmt::format("Could not find {} in threshold alarms map",
291 alarmProperty)
292 .c_str());
293 return;
294 }
295
296 ad.emplace("SENSOR_NAME", sensorPath);
Matt Spinler3efec612021-05-11 15:26:17 -0500297 ad.emplace("_PID", std::to_string(getpid()));
Matt Spinler2f182672021-02-01 16:51:38 -0600298
299 try
300 {
301 auto sensorValue = SDBusPlus::getProperty<double>(
302 bus, sensorPath, valueInterface, "Value");
303
304 ad.emplace("SENSOR_VALUE", std::to_string(sensorValue));
305
306 log<level::INFO>(
307 fmt::format("Threshold Event {} {} = {} (sensor value {})",
308 sensorPath, alarmProperty, alarmValue, sensorValue)
309 .c_str());
310 }
311 catch (const DBusServiceError& e)
312 {
313 // If the sensor was just added, the Value interface for it may
314 // not be in the mapper yet. This could only happen if the sensor
315 // application was started up after this one and the value exceeded the
316 // threshold immediately.
317 log<level::INFO>(fmt::format("Threshold Event {} {} = {}", sensorPath,
318 alarmProperty, alarmValue)
319 .c_str());
320 }
321
322 auto callout = getCallout(sensorPath);
323 if (!callout.empty())
324 {
325 ad.emplace("CALLOUT_INVENTORY_PATH", callout);
326 }
327
328 auto errorData = properties->second.find(alarmValue);
329
330 // Add the base error name and the sensor type (like Temperature) to the
331 // error name that's in the thresholdData name to get something like
332 // xyz.openbmc_project.Sensor.Threshold.Error.TemperatureWarningHigh
333 const auto& [name, severity] = errorData->second;
334 type.front() = toupper(type.front());
335 std::string errorName = errorNameBase + type + name;
336
337 SDBusPlus::callMethod(loggingService, loggingPath, loggingCreateIface,
338 "Create", errorName, convertForMessage(severity), ad);
339}
340
341std::string ThresholdAlarmLogger::getSensorType(std::string sensorPath)
342{
343 auto pos = sensorPath.find_last_of('/');
344 if ((sensorPath.back() == '/') || (pos == std::string::npos))
345 {
346 log<level::ERR>(
347 fmt::format("Cannot get sensor type from sensor path {}",
348 sensorPath)
349 .c_str());
350 throw std::runtime_error("Invalid sensor path");
351 }
352
353 sensorPath = sensorPath.substr(0, pos);
354 return sensorPath.substr(sensorPath.find_last_of('/') + 1);
355}
356
357bool ThresholdAlarmLogger::skipSensorType(const std::string& type)
358{
359 return (type == "utilization");
360}
361
362std::string ThresholdAlarmLogger::getCallout(const std::string& sensorPath)
363{
364 const std::array<std::string, 2> assocTypes{"inventory", "chassis"};
365
366 // Different implementations handle the association to the FRU
367 // differently:
368 // * phosphor-inventory-manager uses the 'inventory' association
369 // to point to the FRU.
370 // * dbus-sensors/entity-manager uses the 'chassis' association'.
371 // * For virtual sensors, no association.
372
373 for (const auto& assocType : assocTypes)
374 {
375 auto assocPath = sensorPath + "/" + assocType;
376
377 try
378 {
379 auto endpoints = SDBusPlus::getProperty<std::vector<std::string>>(
380 bus, assocPath, assocInterface, "endpoints");
381
382 if (!endpoints.empty())
383 {
384 return endpoints[0];
385 }
386 }
387 catch (const DBusServiceError& e)
388 {
389 // The association doesn't exist
390 continue;
391 }
392 }
393
394 return std::string{};
Matt Spinler50bf8162021-02-01 16:24:01 -0600395}
396
Matt Spinler66e75a72021-05-14 10:32:47 -0500397void ThresholdAlarmLogger::powerStateChanged(bool powerStateOn)
398{
399 if (powerStateOn)
400 {
401 checkThresholds();
402 }
403}
404
405void ThresholdAlarmLogger::checkThresholds()
406{
407 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);
415
416 createEventLog(sensorPath, interface, propertyName, alarmValue);
417 }
418 }
419 }
420}
421
Matt Spinler403d1f52021-02-01 15:35:25 -0600422} // namespace sensor::monitor