blob: d64366bd5ec9236261e17b9e4aff124708a014f2 [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 Spinler50bf8162021-02-01 16:24:01 -060047using ErrorData = std::tuple<ErrorName, Entry::Level>;
48
49/**
50 * Map of threshold interfaces and alarm properties and values to error data.
51 */
52const std::map<InterfaceName, std::map<PropertyName, std::map<bool, ErrorData>>>
53 thresholdData{
54
55 {warningInterface,
56 {{"WarningAlarmHigh",
57 {{true, ErrorData{"WarningHigh", Entry::Level::Warning}},
58 {false,
59 ErrorData{"WarningHighClear", Entry::Level::Informational}}}},
60 {"WarningAlarmLow",
61 {{true, ErrorData{"WarningLow", Entry::Level::Warning}},
62 {false,
63 ErrorData{"WarningLowClear", Entry::Level::Informational}}}}}},
64
65 {criticalInterface,
66 {{"CriticalAlarmHigh",
67 {{true, ErrorData{"CriticalHigh", Entry::Level::Critical}},
68 {false,
69 ErrorData{"CriticalHighClear", Entry::Level::Informational}}}},
70 {"CriticalAlarmLow",
71 {{true, ErrorData{"CriticalLow", Entry::Level::Critical}},
72 {false,
73 ErrorData{"CriticalLowClear", Entry::Level::Informational}}}}}},
74
75 {perfLossInterface,
76 {{"PerfLossAlarmHigh",
77 {{true, ErrorData{"PerfLossHigh", Entry::Level::Warning}},
78 {false,
79 ErrorData{"PerfLossHighClear", Entry::Level::Informational}}}},
80 {"PerfLossAlarmLow",
81 {{true, ErrorData{"PerfLossLow", Entry::Level::Warning}},
82 {false,
83 ErrorData{"PerfLossLowClear", Entry::Level::Informational}}}}}}};
84
Matt Spinler7f6946b2021-05-14 12:43:50 -050085ThresholdAlarmLogger::ThresholdAlarmLogger(
86 sdbusplus::bus::bus& bus, sdeventplus::Event& event,
87 std::shared_ptr<PowerState> powerState) :
Matt Spinler403d1f52021-02-01 15:35:25 -060088 bus(bus),
Matt Spinler7f6946b2021-05-14 12:43:50 -050089 event(event), _powerState(std::move(powerState)),
Matt Spinler403d1f52021-02-01 15:35:25 -060090 warningMatch(bus,
91 "type='signal',member='PropertiesChanged',"
92 "path_namespace='/xyz/openbmc_project/sensors',"
93 "arg0='" +
94 warningInterface + "'",
95 std::bind(&ThresholdAlarmLogger::propertiesChanged, this,
96 std::placeholders::_1)),
97 criticalMatch(bus,
98 "type='signal',member='PropertiesChanged',"
99 "path_namespace='/xyz/openbmc_project/sensors',"
100 "arg0='" +
101 criticalInterface + "'",
102 std::bind(&ThresholdAlarmLogger::propertiesChanged, this,
103 std::placeholders::_1)),
104 perfLossMatch(bus,
105 "type='signal',member='PropertiesChanged',"
106 "path_namespace='/xyz/openbmc_project/sensors',"
107 "arg0='" +
108 perfLossInterface + "'",
109 std::bind(&ThresholdAlarmLogger::propertiesChanged, this,
Matt Spinlerb7a55402021-10-11 13:45:35 -0500110 std::placeholders::_1)),
111 ifacesRemovedMatch(bus,
112 "type='signal',member='InterfacesRemoved',arg0path="
113 "'/xyz/openbmc_project/sensors/'",
114 std::bind(&ThresholdAlarmLogger::interfacesRemoved, this,
115 std::placeholders::_1))
Matt Spinler50bf8162021-02-01 16:24:01 -0600116{
Matt Spinler7f6946b2021-05-14 12:43:50 -0500117 _powerState->addCallback("thresholdMon",
118 std::bind(&ThresholdAlarmLogger::powerStateChanged,
119 this, std::placeholders::_1));
120
Matt Spinler50bf8162021-02-01 16:24:01 -0600121 // check for any currently asserted threshold alarms
122 std::for_each(
123 thresholdData.begin(), thresholdData.end(),
124 [this](const auto& thresholdInterface) {
125 const auto& interface = thresholdInterface.first;
126 auto objects =
127 SDBusPlus::getSubTreeRaw(this->bus, "/", interface, 0);
128 std::for_each(objects.begin(), objects.end(),
129 [interface, this](const auto& object) {
130 const auto& path = object.first;
131 const auto& service =
132 object.second.begin()->first;
133 checkThresholds(interface, path, service);
134 });
135 });
136}
Matt Spinler403d1f52021-02-01 15:35:25 -0600137
138void ThresholdAlarmLogger::propertiesChanged(sdbusplus::message::message& msg)
139{
Matt Spinlerf5d3be42021-02-01 16:38:01 -0600140 std::map<std::string, std::variant<bool>> properties;
141 std::string sensorPath = msg.get_path();
142 std::string interface;
143
144 msg.read(interface, properties);
145
146 auto alarmProperties = thresholdData.find(interface);
147 if (alarmProperties == thresholdData.end())
148 {
149 return;
150 }
151
152 for (const auto& [propertyName, propertyValue] : properties)
153 {
154 if (alarmProperties->second.find(propertyName) !=
155 alarmProperties->second.end())
156 {
157 // If this is the first time we've seen this alarm, then
158 // assume it was off before so it doesn't create an event
159 // log for a value of false.
160
161 InterfaceKey key{sensorPath, interface};
162 if (alarms.find(key) == alarms.end())
163 {
164 alarms[key][propertyName] = false;
165 }
166
167 // Check if the value changed from what was there before.
168 auto alarmValue = std::get<bool>(propertyValue);
169 if (alarmValue != alarms[key][propertyName])
170 {
171 alarms[key][propertyName] = alarmValue;
Matt Spinler66e75a72021-05-14 10:32:47 -0500172
173 if (_powerState->isPowerOn())
174 {
175 createEventLog(sensorPath, interface, propertyName,
176 alarmValue);
177 }
Matt Spinlerf5d3be42021-02-01 16:38:01 -0600178 }
179 }
180 }
Matt Spinler403d1f52021-02-01 15:35:25 -0600181}
182
Matt Spinlerb7a55402021-10-11 13:45:35 -0500183void ThresholdAlarmLogger::interfacesRemoved(sdbusplus::message::message& msg)
184{
185 static const std::vector<std::string> thresholdNames{
186 warningInterface, criticalInterface, perfLossInterface};
187 sdbusplus::message::object_path path;
188 std::vector<std::string> interfaces;
189
190 msg.read(path, interfaces);
191
192 for (const auto& interface : interfaces)
193 {
194 if (std::find(thresholdNames.begin(), thresholdNames.end(),
195 interface) != thresholdNames.end())
196 {
197 alarms.erase(InterfaceKey{path, interface});
198 }
199 }
200}
201
Matt Spinler50bf8162021-02-01 16:24:01 -0600202void ThresholdAlarmLogger::checkThresholds(const std::string& interface,
203 const std::string& sensorPath,
204 const std::string& service)
205{
206 auto properties = thresholdData.find(interface);
207 if (properties == thresholdData.end())
208 {
209 return;
210 }
211
212 for (const auto& [property, unused] : properties->second)
213 {
214 try
215 {
216 auto alarmValue = SDBusPlus::getProperty<bool>(
217 bus, service, sensorPath, interface, property);
218 alarms[InterfaceKey(sensorPath, interface)][property] = alarmValue;
219
220 // This is just for checking alarms on startup,
221 // so only look for active alarms.
Matt Spinler66e75a72021-05-14 10:32:47 -0500222 if (alarmValue && _powerState->isPowerOn())
Matt Spinler50bf8162021-02-01 16:24:01 -0600223 {
224 createEventLog(sensorPath, interface, property, alarmValue);
225 }
226 }
Matt Spinler4b515922021-10-11 14:55:50 -0500227 catch (const sdbusplus::exception::exception& e)
Matt Spinler50bf8162021-02-01 16:24:01 -0600228 {
Matt Spinler4b515922021-10-11 14:55:50 -0500229 // Sensor daemons that get their direction from entity manager
230 // may only be putting either the high alarm or low alarm on
231 // D-Bus, not both.
Matt Spinler50bf8162021-02-01 16:24:01 -0600232 continue;
233 }
234 }
235}
236
237void ThresholdAlarmLogger::createEventLog(const std::string& sensorPath,
238 const std::string& interface,
239 const std::string& alarmProperty,
240 bool alarmValue)
241{
Matt Spinler2f182672021-02-01 16:51:38 -0600242 std::map<std::string, std::string> ad;
243
244 auto type = getSensorType(sensorPath);
245 if (skipSensorType(type))
246 {
247 return;
248 }
249
250 auto it = thresholdData.find(interface);
251 if (it == thresholdData.end())
252 {
253 return;
254 }
255
256 auto properties = it->second.find(alarmProperty);
257 if (properties == it->second.end())
258 {
259 log<level::INFO>(
260 fmt::format("Could not find {} in threshold alarms map",
261 alarmProperty)
262 .c_str());
263 return;
264 }
265
266 ad.emplace("SENSOR_NAME", sensorPath);
Matt Spinler3efec612021-05-11 15:26:17 -0500267 ad.emplace("_PID", std::to_string(getpid()));
Matt Spinler2f182672021-02-01 16:51:38 -0600268
269 try
270 {
271 auto sensorValue = SDBusPlus::getProperty<double>(
272 bus, sensorPath, valueInterface, "Value");
273
274 ad.emplace("SENSOR_VALUE", std::to_string(sensorValue));
275
276 log<level::INFO>(
277 fmt::format("Threshold Event {} {} = {} (sensor value {})",
278 sensorPath, alarmProperty, alarmValue, sensorValue)
279 .c_str());
280 }
281 catch (const DBusServiceError& e)
282 {
283 // If the sensor was just added, the Value interface for it may
284 // not be in the mapper yet. This could only happen if the sensor
285 // application was started up after this one and the value exceeded the
286 // threshold immediately.
287 log<level::INFO>(fmt::format("Threshold Event {} {} = {}", sensorPath,
288 alarmProperty, alarmValue)
289 .c_str());
290 }
291
292 auto callout = getCallout(sensorPath);
293 if (!callout.empty())
294 {
295 ad.emplace("CALLOUT_INVENTORY_PATH", callout);
296 }
297
298 auto errorData = properties->second.find(alarmValue);
299
300 // Add the base error name and the sensor type (like Temperature) to the
301 // error name that's in the thresholdData name to get something like
302 // xyz.openbmc_project.Sensor.Threshold.Error.TemperatureWarningHigh
303 const auto& [name, severity] = errorData->second;
304 type.front() = toupper(type.front());
305 std::string errorName = errorNameBase + type + name;
306
307 SDBusPlus::callMethod(loggingService, loggingPath, loggingCreateIface,
308 "Create", errorName, convertForMessage(severity), ad);
309}
310
311std::string ThresholdAlarmLogger::getSensorType(std::string sensorPath)
312{
313 auto pos = sensorPath.find_last_of('/');
314 if ((sensorPath.back() == '/') || (pos == std::string::npos))
315 {
316 log<level::ERR>(
317 fmt::format("Cannot get sensor type from sensor path {}",
318 sensorPath)
319 .c_str());
320 throw std::runtime_error("Invalid sensor path");
321 }
322
323 sensorPath = sensorPath.substr(0, pos);
324 return sensorPath.substr(sensorPath.find_last_of('/') + 1);
325}
326
327bool ThresholdAlarmLogger::skipSensorType(const std::string& type)
328{
329 return (type == "utilization");
330}
331
332std::string ThresholdAlarmLogger::getCallout(const std::string& sensorPath)
333{
334 const std::array<std::string, 2> assocTypes{"inventory", "chassis"};
335
336 // Different implementations handle the association to the FRU
337 // differently:
338 // * phosphor-inventory-manager uses the 'inventory' association
339 // to point to the FRU.
340 // * dbus-sensors/entity-manager uses the 'chassis' association'.
341 // * For virtual sensors, no association.
342
343 for (const auto& assocType : assocTypes)
344 {
345 auto assocPath = sensorPath + "/" + assocType;
346
347 try
348 {
349 auto endpoints = SDBusPlus::getProperty<std::vector<std::string>>(
350 bus, assocPath, assocInterface, "endpoints");
351
352 if (!endpoints.empty())
353 {
354 return endpoints[0];
355 }
356 }
357 catch (const DBusServiceError& e)
358 {
359 // The association doesn't exist
360 continue;
361 }
362 }
363
364 return std::string{};
Matt Spinler50bf8162021-02-01 16:24:01 -0600365}
366
Matt Spinler66e75a72021-05-14 10:32:47 -0500367void ThresholdAlarmLogger::powerStateChanged(bool powerStateOn)
368{
369 if (powerStateOn)
370 {
371 checkThresholds();
372 }
373}
374
375void ThresholdAlarmLogger::checkThresholds()
376{
377 for (const auto& [interfaceKey, alarmMap] : alarms)
378 {
379 for (const auto& [propertyName, alarmValue] : alarmMap)
380 {
381 if (alarmValue)
382 {
383 const auto& sensorPath = std::get<0>(interfaceKey);
384 const auto& interface = std::get<1>(interfaceKey);
385
386 createEventLog(sensorPath, interface, propertyName, alarmValue);
387 }
388 }
389 }
390}
391
Matt Spinler403d1f52021-02-01 15:35:25 -0600392} // namespace sensor::monitor