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