blob: 50ccf5e44d2e979d4a8d4a4384b4df4ff5a7fb07 [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 Spinler403d1f52021-02-01 15:35:25 -060085ThresholdAlarmLogger::ThresholdAlarmLogger(sdbusplus::bus::bus& bus,
86 sdeventplus::Event& event) :
87 bus(bus),
Matt Spinler66e75a72021-05-14 10:32:47 -050088 event(event), _powerState(std::make_unique<PGoodState>(
89 bus, std::bind(&ThresholdAlarmLogger::powerStateChanged,
90 this, std::placeholders::_1))),
Matt Spinler403d1f52021-02-01 15:35:25 -060091 warningMatch(bus,
92 "type='signal',member='PropertiesChanged',"
93 "path_namespace='/xyz/openbmc_project/sensors',"
94 "arg0='" +
95 warningInterface + "'",
96 std::bind(&ThresholdAlarmLogger::propertiesChanged, this,
97 std::placeholders::_1)),
98 criticalMatch(bus,
99 "type='signal',member='PropertiesChanged',"
100 "path_namespace='/xyz/openbmc_project/sensors',"
101 "arg0='" +
102 criticalInterface + "'",
103 std::bind(&ThresholdAlarmLogger::propertiesChanged, this,
104 std::placeholders::_1)),
105 perfLossMatch(bus,
106 "type='signal',member='PropertiesChanged',"
107 "path_namespace='/xyz/openbmc_project/sensors',"
108 "arg0='" +
109 perfLossInterface + "'",
110 std::bind(&ThresholdAlarmLogger::propertiesChanged, this,
111 std::placeholders::_1))
Matt Spinler50bf8162021-02-01 16:24:01 -0600112{
113 // check for any currently asserted threshold alarms
114 std::for_each(
115 thresholdData.begin(), thresholdData.end(),
116 [this](const auto& thresholdInterface) {
117 const auto& interface = thresholdInterface.first;
118 auto objects =
119 SDBusPlus::getSubTreeRaw(this->bus, "/", interface, 0);
120 std::for_each(objects.begin(), objects.end(),
121 [interface, this](const auto& object) {
122 const auto& path = object.first;
123 const auto& service =
124 object.second.begin()->first;
125 checkThresholds(interface, path, service);
126 });
127 });
128}
Matt Spinler403d1f52021-02-01 15:35:25 -0600129
130void ThresholdAlarmLogger::propertiesChanged(sdbusplus::message::message& msg)
131{
Matt Spinlerf5d3be42021-02-01 16:38:01 -0600132 std::map<std::string, std::variant<bool>> properties;
133 std::string sensorPath = msg.get_path();
134 std::string interface;
135
136 msg.read(interface, properties);
137
138 auto alarmProperties = thresholdData.find(interface);
139 if (alarmProperties == thresholdData.end())
140 {
141 return;
142 }
143
144 for (const auto& [propertyName, propertyValue] : properties)
145 {
146 if (alarmProperties->second.find(propertyName) !=
147 alarmProperties->second.end())
148 {
149 // If this is the first time we've seen this alarm, then
150 // assume it was off before so it doesn't create an event
151 // log for a value of false.
152
153 InterfaceKey key{sensorPath, interface};
154 if (alarms.find(key) == alarms.end())
155 {
156 alarms[key][propertyName] = false;
157 }
158
159 // Check if the value changed from what was there before.
160 auto alarmValue = std::get<bool>(propertyValue);
161 if (alarmValue != alarms[key][propertyName])
162 {
163 alarms[key][propertyName] = alarmValue;
Matt Spinler66e75a72021-05-14 10:32:47 -0500164
165 if (_powerState->isPowerOn())
166 {
167 createEventLog(sensorPath, interface, propertyName,
168 alarmValue);
169 }
Matt Spinlerf5d3be42021-02-01 16:38:01 -0600170 }
171 }
172 }
Matt Spinler403d1f52021-02-01 15:35:25 -0600173}
174
Matt Spinler50bf8162021-02-01 16:24:01 -0600175void ThresholdAlarmLogger::checkThresholds(const std::string& interface,
176 const std::string& sensorPath,
177 const std::string& service)
178{
179 auto properties = thresholdData.find(interface);
180 if (properties == thresholdData.end())
181 {
182 return;
183 }
184
185 for (const auto& [property, unused] : properties->second)
186 {
187 try
188 {
189 auto alarmValue = SDBusPlus::getProperty<bool>(
190 bus, service, sensorPath, interface, property);
191 alarms[InterfaceKey(sensorPath, interface)][property] = alarmValue;
192
193 // This is just for checking alarms on startup,
194 // so only look for active alarms.
Matt Spinler66e75a72021-05-14 10:32:47 -0500195 if (alarmValue && _powerState->isPowerOn())
Matt Spinler50bf8162021-02-01 16:24:01 -0600196 {
197 createEventLog(sensorPath, interface, property, alarmValue);
198 }
199 }
200 catch (const DBusError& e)
201 {
202 log<level::ERR>(
203 fmt::format("Failed reading sensor threshold properties: {}",
204 e.what())
205 .c_str());
206 continue;
207 }
208 }
209}
210
211void ThresholdAlarmLogger::createEventLog(const std::string& sensorPath,
212 const std::string& interface,
213 const std::string& alarmProperty,
214 bool alarmValue)
215{
Matt Spinler2f182672021-02-01 16:51:38 -0600216 std::map<std::string, std::string> ad;
217
218 auto type = getSensorType(sensorPath);
219 if (skipSensorType(type))
220 {
221 return;
222 }
223
224 auto it = thresholdData.find(interface);
225 if (it == thresholdData.end())
226 {
227 return;
228 }
229
230 auto properties = it->second.find(alarmProperty);
231 if (properties == it->second.end())
232 {
233 log<level::INFO>(
234 fmt::format("Could not find {} in threshold alarms map",
235 alarmProperty)
236 .c_str());
237 return;
238 }
239
240 ad.emplace("SENSOR_NAME", sensorPath);
Matt Spinler3efec612021-05-11 15:26:17 -0500241 ad.emplace("_PID", std::to_string(getpid()));
Matt Spinler2f182672021-02-01 16:51:38 -0600242
243 try
244 {
245 auto sensorValue = SDBusPlus::getProperty<double>(
246 bus, sensorPath, valueInterface, "Value");
247
248 ad.emplace("SENSOR_VALUE", std::to_string(sensorValue));
249
250 log<level::INFO>(
251 fmt::format("Threshold Event {} {} = {} (sensor value {})",
252 sensorPath, alarmProperty, alarmValue, sensorValue)
253 .c_str());
254 }
255 catch (const DBusServiceError& e)
256 {
257 // If the sensor was just added, the Value interface for it may
258 // not be in the mapper yet. This could only happen if the sensor
259 // application was started up after this one and the value exceeded the
260 // threshold immediately.
261 log<level::INFO>(fmt::format("Threshold Event {} {} = {}", sensorPath,
262 alarmProperty, alarmValue)
263 .c_str());
264 }
265
266 auto callout = getCallout(sensorPath);
267 if (!callout.empty())
268 {
269 ad.emplace("CALLOUT_INVENTORY_PATH", callout);
270 }
271
272 auto errorData = properties->second.find(alarmValue);
273
274 // Add the base error name and the sensor type (like Temperature) to the
275 // error name that's in the thresholdData name to get something like
276 // xyz.openbmc_project.Sensor.Threshold.Error.TemperatureWarningHigh
277 const auto& [name, severity] = errorData->second;
278 type.front() = toupper(type.front());
279 std::string errorName = errorNameBase + type + name;
280
281 SDBusPlus::callMethod(loggingService, loggingPath, loggingCreateIface,
282 "Create", errorName, convertForMessage(severity), ad);
283}
284
285std::string ThresholdAlarmLogger::getSensorType(std::string sensorPath)
286{
287 auto pos = sensorPath.find_last_of('/');
288 if ((sensorPath.back() == '/') || (pos == std::string::npos))
289 {
290 log<level::ERR>(
291 fmt::format("Cannot get sensor type from sensor path {}",
292 sensorPath)
293 .c_str());
294 throw std::runtime_error("Invalid sensor path");
295 }
296
297 sensorPath = sensorPath.substr(0, pos);
298 return sensorPath.substr(sensorPath.find_last_of('/') + 1);
299}
300
301bool ThresholdAlarmLogger::skipSensorType(const std::string& type)
302{
303 return (type == "utilization");
304}
305
306std::string ThresholdAlarmLogger::getCallout(const std::string& sensorPath)
307{
308 const std::array<std::string, 2> assocTypes{"inventory", "chassis"};
309
310 // Different implementations handle the association to the FRU
311 // differently:
312 // * phosphor-inventory-manager uses the 'inventory' association
313 // to point to the FRU.
314 // * dbus-sensors/entity-manager uses the 'chassis' association'.
315 // * For virtual sensors, no association.
316
317 for (const auto& assocType : assocTypes)
318 {
319 auto assocPath = sensorPath + "/" + assocType;
320
321 try
322 {
323 auto endpoints = SDBusPlus::getProperty<std::vector<std::string>>(
324 bus, assocPath, assocInterface, "endpoints");
325
326 if (!endpoints.empty())
327 {
328 return endpoints[0];
329 }
330 }
331 catch (const DBusServiceError& e)
332 {
333 // The association doesn't exist
334 continue;
335 }
336 }
337
338 return std::string{};
Matt Spinler50bf8162021-02-01 16:24:01 -0600339}
340
Matt Spinler66e75a72021-05-14 10:32:47 -0500341void ThresholdAlarmLogger::powerStateChanged(bool powerStateOn)
342{
343 if (powerStateOn)
344 {
345 checkThresholds();
346 }
347}
348
349void ThresholdAlarmLogger::checkThresholds()
350{
351 for (const auto& [interfaceKey, alarmMap] : alarms)
352 {
353 for (const auto& [propertyName, alarmValue] : alarmMap)
354 {
355 if (alarmValue)
356 {
357 const auto& sensorPath = std::get<0>(interfaceKey);
358 const auto& interface = std::get<1>(interfaceKey);
359
360 createEventLog(sensorPath, interface, propertyName, alarmValue);
361 }
362 }
363 }
364}
365
Matt Spinler403d1f52021-02-01 15:35:25 -0600366} // namespace sensor::monitor