blob: f50fb6a284f5d7c95a7c55724cd935e65b2da173 [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;
31using namespace phosphor::fan::util;
32
Matt Spinler403d1f52021-02-01 15:35:25 -060033const std::string warningInterface =
34 "xyz.openbmc_project.Sensor.Threshold.Warning";
35const std::string criticalInterface =
36 "xyz.openbmc_project.Sensor.Threshold.Critical";
37const std::string perfLossInterface =
38 "xyz.openbmc_project.Sensor.Threshold.PerformanceLoss";
Matt Spinler2f182672021-02-01 16:51:38 -060039constexpr auto loggingService = "xyz.openbmc_project.Logging";
40constexpr auto loggingPath = "/xyz/openbmc_project/logging";
41constexpr auto loggingCreateIface = "xyz.openbmc_project.Logging.Create";
42constexpr auto errorNameBase = "xyz.openbmc_project.Sensor.Threshold.Error.";
43constexpr auto valueInterface = "xyz.openbmc_project.Sensor.Value";
44constexpr auto assocInterface = "xyz.openbmc_project.Association";
Matt Spinler403d1f52021-02-01 15:35:25 -060045
Matt Spinler50bf8162021-02-01 16:24:01 -060046using ErrorData = std::tuple<ErrorName, Entry::Level>;
47
48/**
49 * Map of threshold interfaces and alarm properties and values to error data.
50 */
51const std::map<InterfaceName, std::map<PropertyName, std::map<bool, ErrorData>>>
52 thresholdData{
53
54 {warningInterface,
55 {{"WarningAlarmHigh",
56 {{true, ErrorData{"WarningHigh", Entry::Level::Warning}},
57 {false,
58 ErrorData{"WarningHighClear", Entry::Level::Informational}}}},
59 {"WarningAlarmLow",
60 {{true, ErrorData{"WarningLow", Entry::Level::Warning}},
61 {false,
62 ErrorData{"WarningLowClear", Entry::Level::Informational}}}}}},
63
64 {criticalInterface,
65 {{"CriticalAlarmHigh",
66 {{true, ErrorData{"CriticalHigh", Entry::Level::Critical}},
67 {false,
68 ErrorData{"CriticalHighClear", Entry::Level::Informational}}}},
69 {"CriticalAlarmLow",
70 {{true, ErrorData{"CriticalLow", Entry::Level::Critical}},
71 {false,
72 ErrorData{"CriticalLowClear", Entry::Level::Informational}}}}}},
73
74 {perfLossInterface,
75 {{"PerfLossAlarmHigh",
76 {{true, ErrorData{"PerfLossHigh", Entry::Level::Warning}},
77 {false,
78 ErrorData{"PerfLossHighClear", Entry::Level::Informational}}}},
79 {"PerfLossAlarmLow",
80 {{true, ErrorData{"PerfLossLow", Entry::Level::Warning}},
81 {false,
82 ErrorData{"PerfLossLowClear", Entry::Level::Informational}}}}}}};
83
Matt Spinler403d1f52021-02-01 15:35:25 -060084ThresholdAlarmLogger::ThresholdAlarmLogger(sdbusplus::bus::bus& bus,
85 sdeventplus::Event& event) :
86 bus(bus),
87 event(event),
88 warningMatch(bus,
89 "type='signal',member='PropertiesChanged',"
90 "path_namespace='/xyz/openbmc_project/sensors',"
91 "arg0='" +
92 warningInterface + "'",
93 std::bind(&ThresholdAlarmLogger::propertiesChanged, this,
94 std::placeholders::_1)),
95 criticalMatch(bus,
96 "type='signal',member='PropertiesChanged',"
97 "path_namespace='/xyz/openbmc_project/sensors',"
98 "arg0='" +
99 criticalInterface + "'",
100 std::bind(&ThresholdAlarmLogger::propertiesChanged, this,
101 std::placeholders::_1)),
102 perfLossMatch(bus,
103 "type='signal',member='PropertiesChanged',"
104 "path_namespace='/xyz/openbmc_project/sensors',"
105 "arg0='" +
106 perfLossInterface + "'",
107 std::bind(&ThresholdAlarmLogger::propertiesChanged, this,
108 std::placeholders::_1))
Matt Spinler50bf8162021-02-01 16:24:01 -0600109{
110 // check for any currently asserted threshold alarms
111 std::for_each(
112 thresholdData.begin(), thresholdData.end(),
113 [this](const auto& thresholdInterface) {
114 const auto& interface = thresholdInterface.first;
115 auto objects =
116 SDBusPlus::getSubTreeRaw(this->bus, "/", interface, 0);
117 std::for_each(objects.begin(), objects.end(),
118 [interface, this](const auto& object) {
119 const auto& path = object.first;
120 const auto& service =
121 object.second.begin()->first;
122 checkThresholds(interface, path, service);
123 });
124 });
125}
Matt Spinler403d1f52021-02-01 15:35:25 -0600126
127void ThresholdAlarmLogger::propertiesChanged(sdbusplus::message::message& msg)
128{
Matt Spinlerf5d3be42021-02-01 16:38:01 -0600129 std::map<std::string, std::variant<bool>> properties;
130 std::string sensorPath = msg.get_path();
131 std::string interface;
132
133 msg.read(interface, properties);
134
135 auto alarmProperties = thresholdData.find(interface);
136 if (alarmProperties == thresholdData.end())
137 {
138 return;
139 }
140
141 for (const auto& [propertyName, propertyValue] : properties)
142 {
143 if (alarmProperties->second.find(propertyName) !=
144 alarmProperties->second.end())
145 {
146 // If this is the first time we've seen this alarm, then
147 // assume it was off before so it doesn't create an event
148 // log for a value of false.
149
150 InterfaceKey key{sensorPath, interface};
151 if (alarms.find(key) == alarms.end())
152 {
153 alarms[key][propertyName] = false;
154 }
155
156 // Check if the value changed from what was there before.
157 auto alarmValue = std::get<bool>(propertyValue);
158 if (alarmValue != alarms[key][propertyName])
159 {
160 alarms[key][propertyName] = alarmValue;
161 createEventLog(sensorPath, interface, propertyName, alarmValue);
162 }
163 }
164 }
Matt Spinler403d1f52021-02-01 15:35:25 -0600165}
166
Matt Spinler50bf8162021-02-01 16:24:01 -0600167void ThresholdAlarmLogger::checkThresholds(const std::string& interface,
168 const std::string& sensorPath,
169 const std::string& service)
170{
171 auto properties = thresholdData.find(interface);
172 if (properties == thresholdData.end())
173 {
174 return;
175 }
176
177 for (const auto& [property, unused] : properties->second)
178 {
179 try
180 {
181 auto alarmValue = SDBusPlus::getProperty<bool>(
182 bus, service, sensorPath, interface, property);
183 alarms[InterfaceKey(sensorPath, interface)][property] = alarmValue;
184
185 // This is just for checking alarms on startup,
186 // so only look for active alarms.
187 if (alarmValue)
188 {
189 createEventLog(sensorPath, interface, property, alarmValue);
190 }
191 }
192 catch (const DBusError& e)
193 {
194 log<level::ERR>(
195 fmt::format("Failed reading sensor threshold properties: {}",
196 e.what())
197 .c_str());
198 continue;
199 }
200 }
201}
202
203void ThresholdAlarmLogger::createEventLog(const std::string& sensorPath,
204 const std::string& interface,
205 const std::string& alarmProperty,
206 bool alarmValue)
207{
Matt Spinler2f182672021-02-01 16:51:38 -0600208 std::map<std::string, std::string> ad;
209
210 auto type = getSensorType(sensorPath);
211 if (skipSensorType(type))
212 {
213 return;
214 }
215
216 auto it = thresholdData.find(interface);
217 if (it == thresholdData.end())
218 {
219 return;
220 }
221
222 auto properties = it->second.find(alarmProperty);
223 if (properties == it->second.end())
224 {
225 log<level::INFO>(
226 fmt::format("Could not find {} in threshold alarms map",
227 alarmProperty)
228 .c_str());
229 return;
230 }
231
232 ad.emplace("SENSOR_NAME", sensorPath);
Matt Spinler3efec612021-05-11 15:26:17 -0500233 ad.emplace("_PID", std::to_string(getpid()));
Matt Spinler2f182672021-02-01 16:51:38 -0600234
235 try
236 {
237 auto sensorValue = SDBusPlus::getProperty<double>(
238 bus, sensorPath, valueInterface, "Value");
239
240 ad.emplace("SENSOR_VALUE", std::to_string(sensorValue));
241
242 log<level::INFO>(
243 fmt::format("Threshold Event {} {} = {} (sensor value {})",
244 sensorPath, alarmProperty, alarmValue, sensorValue)
245 .c_str());
246 }
247 catch (const DBusServiceError& e)
248 {
249 // If the sensor was just added, the Value interface for it may
250 // not be in the mapper yet. This could only happen if the sensor
251 // application was started up after this one and the value exceeded the
252 // threshold immediately.
253 log<level::INFO>(fmt::format("Threshold Event {} {} = {}", sensorPath,
254 alarmProperty, alarmValue)
255 .c_str());
256 }
257
258 auto callout = getCallout(sensorPath);
259 if (!callout.empty())
260 {
261 ad.emplace("CALLOUT_INVENTORY_PATH", callout);
262 }
263
264 auto errorData = properties->second.find(alarmValue);
265
266 // Add the base error name and the sensor type (like Temperature) to the
267 // error name that's in the thresholdData name to get something like
268 // xyz.openbmc_project.Sensor.Threshold.Error.TemperatureWarningHigh
269 const auto& [name, severity] = errorData->second;
270 type.front() = toupper(type.front());
271 std::string errorName = errorNameBase + type + name;
272
273 SDBusPlus::callMethod(loggingService, loggingPath, loggingCreateIface,
274 "Create", errorName, convertForMessage(severity), ad);
275}
276
277std::string ThresholdAlarmLogger::getSensorType(std::string sensorPath)
278{
279 auto pos = sensorPath.find_last_of('/');
280 if ((sensorPath.back() == '/') || (pos == std::string::npos))
281 {
282 log<level::ERR>(
283 fmt::format("Cannot get sensor type from sensor path {}",
284 sensorPath)
285 .c_str());
286 throw std::runtime_error("Invalid sensor path");
287 }
288
289 sensorPath = sensorPath.substr(0, pos);
290 return sensorPath.substr(sensorPath.find_last_of('/') + 1);
291}
292
293bool ThresholdAlarmLogger::skipSensorType(const std::string& type)
294{
295 return (type == "utilization");
296}
297
298std::string ThresholdAlarmLogger::getCallout(const std::string& sensorPath)
299{
300 const std::array<std::string, 2> assocTypes{"inventory", "chassis"};
301
302 // Different implementations handle the association to the FRU
303 // differently:
304 // * phosphor-inventory-manager uses the 'inventory' association
305 // to point to the FRU.
306 // * dbus-sensors/entity-manager uses the 'chassis' association'.
307 // * For virtual sensors, no association.
308
309 for (const auto& assocType : assocTypes)
310 {
311 auto assocPath = sensorPath + "/" + assocType;
312
313 try
314 {
315 auto endpoints = SDBusPlus::getProperty<std::vector<std::string>>(
316 bus, assocPath, assocInterface, "endpoints");
317
318 if (!endpoints.empty())
319 {
320 return endpoints[0];
321 }
322 }
323 catch (const DBusServiceError& e)
324 {
325 // The association doesn't exist
326 continue;
327 }
328 }
329
330 return std::string{};
Matt Spinler50bf8162021-02-01 16:24:01 -0600331}
332
Matt Spinler403d1f52021-02-01 15:35:25 -0600333} // namespace sensor::monitor