blob: 9679b3fd4f9d9c0f009ec710e4cf8a655807820b [file] [log] [blame]
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001#include "virtualSensor.hpp"
2
3#include "config.hpp"
4
Patrick Williams82b39c62021-07-28 16:22:27 -05005#include <phosphor-logging/lg2.hpp>
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07006#include <sdeventplus/event.hpp>
7
8#include <fstream>
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07009
10static constexpr bool DEBUG = false;
11static constexpr auto busName = "xyz.openbmc_project.VirtualSensor";
12static constexpr auto sensorDbusPath = "/xyz/openbmc_project/sensors/";
Rashmica Guptae7efe132021-07-27 19:42:11 +100013static constexpr auto entityManagerBusName =
14 "xyz.openbmc_project.EntityManager";
15static constexpr auto vsThresholdsIfaceSuffix = ".Thresholds";
Rashmica Gupta304fd0e2021-08-10 16:50:44 +100016static constexpr std::array<const char*, 1> calculationIfaces = {
17 "xyz.openbmc_project.Configuration.ModifiedMedian"};
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +100018static constexpr auto defaultHysteresis = 0;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070019
Patrick Williams82b39c62021-07-28 16:22:27 -050020PHOSPHOR_LOG2_USING_WITH_FLAGS;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070021
Vijay Khemka51f898e2020-09-09 22:24:18 -070022int handleDbusSignal(sd_bus_message* msg, void* usrData, sd_bus_error*)
23{
24 if (usrData == nullptr)
25 {
26 throw std::runtime_error("Invalid match");
27 }
28
29 auto sdbpMsg = sdbusplus::message::message(msg);
30 std::string msgIfce;
31 std::map<std::string, std::variant<int64_t, double, bool>> msgData;
32
33 sdbpMsg.read(msgIfce, msgData);
34
35 if (msgData.find("Value") != msgData.end())
36 {
37 using namespace phosphor::virtualSensor;
38 VirtualSensor* obj = static_cast<VirtualSensor*>(usrData);
39 // TODO(openbmc/phosphor-virtual-sensor#1): updateVirtualSensor should
40 // be changed to take the information we got from the signal, to avoid
41 // having to do numerous dbus queries.
42 obj->updateVirtualSensor();
43 }
44 return 0;
45}
46
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070047namespace phosphor
48{
49namespace virtualSensor
50{
51
52void printParams(const VirtualSensor::ParamMap& paramMap)
53{
54 for (const auto& p : paramMap)
55 {
56 const auto& p1 = p.first;
57 const auto& p2 = p.second;
58 auto val = p2->getParamValue();
Patrick Williamsfbd71452021-08-30 06:59:46 -050059 debug("Parameter: {PARAM} = {VALUE}", "PARAM", p1, "VALUE", val);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070060 }
61}
62
63double SensorParam::getParamValue()
64{
65 switch (paramType)
66 {
67 case constParam:
68 return value;
69 break;
Vijay Khemka7452a862020-08-11 16:01:23 -070070 case dbusParam:
71 return dbusSensor->getSensorValue();
72 break;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070073 default:
74 throw std::invalid_argument("param type not supported");
75 }
76}
77
Lei YU0fcf0e12021-06-04 11:14:17 +080078using AssociationList =
79 std::vector<std::tuple<std::string, std::string, std::string>>;
80
81AssociationList getAssociationsFromJson(const Json& j)
82{
83 AssociationList assocs{};
84 try
85 {
86 j.get_to(assocs);
87 }
88 catch (const std::exception& ex)
89 {
Patrick Williams82b39c62021-07-28 16:22:27 -050090 error("Failed to parse association: {ERROR}", "ERROR", ex);
Lei YU0fcf0e12021-06-04 11:14:17 +080091 }
92 return assocs;
93}
94
Rashmica Guptae7efe132021-07-27 19:42:11 +100095template <typename U>
96struct VariantToNumber
97{
98 template <typename T>
99 U operator()(const T& t) const
100 {
101 if constexpr (std::is_convertible<T, U>::value)
102 {
103 return static_cast<U>(t);
104 }
105 throw std::invalid_argument("Invalid number type in config\n");
106 }
107};
108
109template <typename U>
110U getNumberFromConfig(const PropertyMap& map, const std::string& name,
111 bool required)
112{
113 if (auto itr = map.find(name); itr != map.end())
114 {
115 return std::visit(VariantToNumber<U>(), itr->second);
116 }
117 else if (required)
118 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500119 error("Required field {NAME} missing in config", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000120 throw std::invalid_argument("Required field missing in config");
121 }
122 return std::numeric_limits<U>::quiet_NaN();
123}
124
125bool isCalculationType(const std::string& interface)
126{
127 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
128 interface);
129 if (itr != calculationIfaces.end())
130 {
131 return true;
132 }
133 return false;
134}
135
136const std::string getThresholdType(const std::string& direction,
137 uint64_t severity)
138{
139 std::string threshold;
140 std::string suffix;
141 static const std::array thresholdTypes{"Warning", "Critical",
142 "PerformanceLoss", "SoftShutdown",
143 "HardShutdown"};
144
145 if (severity >= thresholdTypes.size())
146 {
147 throw std::invalid_argument(
148 "Invalid threshold severity specified in entity manager");
149 }
150 threshold = thresholdTypes[severity];
151
152 if (direction == "less than")
153 {
154 suffix = "Low";
155 }
156 else if (direction == "greater than")
157 {
158 suffix = "High";
159 }
160 else
161 {
162 throw std::invalid_argument(
163 "Invalid threshold direction specified in entity manager");
164 }
165 return threshold + suffix;
166}
167
168void parseThresholds(Json& thresholds, const PropertyMap& propertyMap)
169{
170 std::string direction;
171
172 auto severity =
173 getNumberFromConfig<uint64_t>(propertyMap, "Severity", true);
174 auto value = getNumberFromConfig<double>(propertyMap, "Value", true);
175
176 auto itr = propertyMap.find("Direction");
177 if (itr != propertyMap.end())
178 {
179 direction = std::get<std::string>(itr->second);
180 }
181
182 auto threshold = getThresholdType(direction, severity);
183 thresholds[threshold] = value;
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000184
185 auto hysteresis =
186 getNumberFromConfig<double>(propertyMap, "Hysteresis", false);
187 if (hysteresis != std::numeric_limits<double>::quiet_NaN())
188 {
189 thresholds[threshold + "Hysteresis"] = hysteresis;
190 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000191}
192
193void VirtualSensor::parseConfigInterface(const PropertyMap& propertyMap,
194 const std::string& sensorType,
195 const std::string& interface)
196{
197 /* Parse sensors / DBus params */
198 if (auto itr = propertyMap.find("Sensors"); itr != propertyMap.end())
199 {
200 auto sensors = std::get<std::vector<std::string>>(itr->second);
201 for (auto sensor : sensors)
202 {
203 std::replace(sensor.begin(), sensor.end(), ' ', '_');
204 auto sensorObjPath = sensorDbusPath + sensorType + "/" + sensor;
205
206 auto paramPtr =
207 std::make_unique<SensorParam>(bus, sensorObjPath, this);
208 symbols.create_variable(sensor);
209 paramMap.emplace(std::move(sensor), std::move(paramPtr));
210 }
211 }
212 /* Get expression string */
213 if (!isCalculationType(interface))
214 {
215 throw std::invalid_argument("Invalid expression in interface");
216 }
217 exprStr = interface;
218
219 /* Get optional min and max input and output values */
220 ValueIface::maxValue(
221 getNumberFromConfig<double>(propertyMap, "MaxValue", false));
222 ValueIface::minValue(
223 getNumberFromConfig<double>(propertyMap, "MinValue", false));
224 maxValidInput =
225 getNumberFromConfig<double>(propertyMap, "MaxValidInput", false);
226 minValidInput =
227 getNumberFromConfig<double>(propertyMap, "MinValidInput", false);
228}
229
Matt Spinlerce675222021-01-14 16:38:09 -0600230void VirtualSensor::initVirtualSensor(const Json& sensorConfig,
231 const std::string& objPath)
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700232{
233
234 static const Json empty{};
235
236 /* Get threshold values if defined in config */
237 auto threshold = sensorConfig.value("Threshold", empty);
Matt Spinlerf15189e2021-01-15 10:13:28 -0600238
Rashmica Gupta3e999192021-06-09 16:17:04 +1000239 createThresholds(threshold, objPath);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700240
Harvey Wuf6443742021-04-09 16:47:36 +0800241 /* Get MaxValue, MinValue setting if defined in config */
242 auto confDesc = sensorConfig.value("Desc", empty);
243 if (auto maxConf = confDesc.find("MaxValue");
244 maxConf != confDesc.end() && maxConf->is_number())
245 {
246 ValueIface::maxValue(maxConf->get<double>());
247 }
248 if (auto minConf = confDesc.find("MinValue");
249 minConf != confDesc.end() && minConf->is_number())
250 {
251 ValueIface::minValue(minConf->get<double>());
252 }
253
Lei YU0fcf0e12021-06-04 11:14:17 +0800254 /* Get optional association */
255 auto assocJson = sensorConfig.value("Associations", empty);
256 if (!assocJson.empty())
257 {
258 auto assocs = getAssociationsFromJson(assocJson);
259 if (!assocs.empty())
260 {
261 associationIface =
262 std::make_unique<AssociationObject>(bus, objPath.c_str());
263 associationIface->associations(assocs);
264 }
265 }
266
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700267 /* Get expression string */
268 exprStr = sensorConfig.value("Expression", "");
269
270 /* Get all the parameter listed in configuration */
271 auto params = sensorConfig.value("Params", empty);
272
273 /* Check for constant parameter */
274 const auto& consParams = params.value("ConstParam", empty);
275 if (!consParams.empty())
276 {
277 for (auto& j : consParams)
278 {
279 if (j.find("ParamName") != j.end())
280 {
281 auto paramPtr = std::make_unique<SensorParam>(j["Value"]);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700282 std::string name = j["ParamName"];
283 symbols.create_variable(name);
284 paramMap.emplace(std::move(name), std::move(paramPtr));
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700285 }
286 else
287 {
288 /* Invalid configuration */
289 throw std::invalid_argument(
290 "ParamName not found in configuration");
291 }
292 }
293 }
294
Vijay Khemka7452a862020-08-11 16:01:23 -0700295 /* Check for dbus parameter */
296 auto dbusParams = params.value("DbusParam", empty);
297 if (!dbusParams.empty())
298 {
299 for (auto& j : dbusParams)
300 {
301 /* Get parameter dbus sensor descriptor */
302 auto desc = j.value("Desc", empty);
303 if ((!desc.empty()) && (j.find("ParamName") != j.end()))
304 {
305 std::string sensorType = desc.value("SensorType", "");
306 std::string name = desc.value("Name", "");
307
308 if (!sensorType.empty() && !name.empty())
309 {
Rashmica Gupta862c3d12021-08-06 12:19:31 +1000310 auto objPath = sensorDbusPath + sensorType + "/" + name;
Vijay Khemka7452a862020-08-11 16:01:23 -0700311
Vijay Khemka51f898e2020-09-09 22:24:18 -0700312 auto paramPtr =
313 std::make_unique<SensorParam>(bus, objPath, this);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700314 std::string name = j["ParamName"];
315 symbols.create_variable(name);
316 paramMap.emplace(std::move(name), std::move(paramPtr));
Vijay Khemka7452a862020-08-11 16:01:23 -0700317 }
318 }
319 }
320 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700321
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700322 symbols.add_constants();
Matt Spinler9f1ef4f2020-11-09 15:59:11 -0600323 symbols.add_package(vecopsPackage);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700324 expression.register_symbol_table(symbols);
325
326 /* parser from exprtk */
327 exprtk::parser<double> parser{};
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600328 if (!parser.compile(exprStr, expression))
329 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500330 error("Expression compilation failed");
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600331
332 for (std::size_t i = 0; i < parser.error_count(); ++i)
333 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500334 auto err = parser.get_error(i);
335 error("Error parsing token at {POSITION}: {ERROR}", "POSITION",
336 err.token.position, "TYPE",
337 exprtk::parser_error::to_str(err.mode), "ERROR",
338 err.diagnostic);
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600339 }
340 throw std::runtime_error("Expression compilation failed");
341 }
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700342
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700343 /* Print all parameters for debug purpose only */
344 if (DEBUG)
345 printParams(paramMap);
346}
347
Rashmica Guptae7efe132021-07-27 19:42:11 +1000348void VirtualSensor::initVirtualSensor(const InterfaceMap& interfaceMap,
349 const std::string& objPath,
350 const std::string& sensorType,
351 const std::string& calculationIface)
352{
353 Json thresholds;
354 const std::string vsThresholdsIntf =
355 calculationIface + vsThresholdsIfaceSuffix;
356
357 for (const auto& [interface, propertyMap] : interfaceMap)
358 {
359 /* Each threshold is on it's own interface with a number as a suffix
360 * eg xyz.openbmc_project.Configuration.ModifiedMedian.Thresholds1 */
361 if (interface.find(vsThresholdsIntf) != std::string::npos)
362 {
363 parseThresholds(thresholds, propertyMap);
364 }
365 else if (interface == calculationIface)
366 {
367 parseConfigInterface(propertyMap, sensorType, interface);
368 }
369 }
370
371 createThresholds(thresholds, objPath);
372 symbols.add_constants();
373 symbols.add_package(vecopsPackage);
374 expression.register_symbol_table(symbols);
375
376 /* Print all parameters for debug purpose only */
377 if (DEBUG)
378 {
379 printParams(paramMap);
380 }
381}
382
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700383void VirtualSensor::setSensorValue(double value)
384{
Patrick Williams543bf662021-04-29 09:03:53 -0500385 value = std::clamp(value, ValueIface::minValue(), ValueIface::maxValue());
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700386 ValueIface::value(value);
387}
388
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000389double VirtualSensor::calculateValue(const std::string& calculation,
390 const VirtualSensor::ParamMap& paramMap)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000391{
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000392 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
393 calculation);
394 if (itr == calculationIfaces.end())
395 {
396 return std::numeric_limits<double>::quiet_NaN();
397 }
398 else if (calculation == "xyz.openbmc_project.Configuration.ModifiedMedian")
399 {
400 return calculateModifiedMedianValue(paramMap);
401 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000402 return std::numeric_limits<double>::quiet_NaN();
403}
404
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000405bool VirtualSensor::sensorInRange(double value)
406{
407 if (value <= this->maxValidInput && value >= this->minValidInput)
408 {
409 return true;
410 }
411 return false;
412}
413
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700414void VirtualSensor::updateVirtualSensor()
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700415{
416 for (auto& param : paramMap)
417 {
418 auto& name = param.first;
419 auto& data = param.second;
420 if (auto var = symbols.get_variable(name))
421 {
422 var->ref() = data->getParamValue();
423 }
424 else
425 {
426 /* Invalid parameter */
427 throw std::invalid_argument("ParamName not found in symbols");
428 }
429 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000430 auto itr =
431 std::find(calculationIfaces.begin(), calculationIfaces.end(), exprStr);
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000432 auto val = (itr == calculationIfaces.end())
433 ? expression.value()
434 : calculateValue(exprStr, paramMap);
Vijay Khemka32a71562020-09-10 15:29:18 -0700435
436 /* Set sensor value to dbus interface */
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700437 setSensorValue(val);
Vijay Khemka32a71562020-09-10 15:29:18 -0700438
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700439 if (DEBUG)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000440 {
Patrick Williamsfbd71452021-08-30 06:59:46 -0500441 debug("Sensor {NAME} = {VALUE}", "NAME", this->name, "VALUE", val);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000442 }
Vijay Khemka32a71562020-09-10 15:29:18 -0700443
Matt Spinler8f5e6112021-01-15 10:44:32 -0600444 /* Check sensor thresholds and log required message */
Matt Spinlerb306b032021-02-01 10:05:46 -0600445 checkThresholds(val, perfLossIface);
Patrick Williamsfdb826d2021-01-20 14:37:53 -0600446 checkThresholds(val, warningIface);
447 checkThresholds(val, criticalIface);
448 checkThresholds(val, softShutdownIface);
449 checkThresholds(val, hardShutdownIface);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700450}
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700451
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000452double VirtualSensor::calculateModifiedMedianValue(
453 const VirtualSensor::ParamMap& paramMap)
454{
455 std::vector<double> values;
456
457 for (auto& param : paramMap)
458 {
459 auto& name = param.first;
460 if (auto var = symbols.get_variable(name))
461 {
462 if (!sensorInRange(var->ref()))
463 {
464 continue;
465 }
466 values.push_back(var->ref());
467 }
468 }
469
470 size_t size = values.size();
471 std::sort(values.begin(), values.end());
472 switch (size)
473 {
474 case 2:
475 /* Choose biggest value */
476 return values.at(1);
477 case 0:
478 return std::numeric_limits<double>::quiet_NaN();
479 default:
480 /* Choose median value */
481 if (size % 2 == 0)
482 {
483 // Average of the two middle values
484 return (values.at(size / 2) + values.at(size / 2 - 1)) / 2;
485 }
486 else
487 {
488 return values.at((size - 1) / 2);
489 }
490 }
491}
492
Rashmica Gupta3e999192021-06-09 16:17:04 +1000493void VirtualSensor::createThresholds(const Json& threshold,
494 const std::string& objPath)
495{
496 if (threshold.empty())
497 {
498 return;
499 }
500 // Only create the threshold interfaces if
501 // at least one of their values is present.
502 if (threshold.contains("CriticalHigh") || threshold.contains("CriticalLow"))
503 {
504 criticalIface =
505 std::make_unique<Threshold<CriticalObject>>(bus, objPath.c_str());
506
507 criticalIface->criticalHigh(threshold.value(
508 "CriticalHigh", std::numeric_limits<double>::quiet_NaN()));
509 criticalIface->criticalLow(threshold.value(
510 "CriticalLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000511 criticalIface->setHighHysteresis(
512 threshold.value("CriticalHighHysteresis", defaultHysteresis));
513 criticalIface->setLowHysteresis(
514 threshold.value("CriticalLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000515 }
516
517 if (threshold.contains("WarningHigh") || threshold.contains("WarningLow"))
518 {
519 warningIface =
520 std::make_unique<Threshold<WarningObject>>(bus, objPath.c_str());
521
522 warningIface->warningHigh(threshold.value(
523 "WarningHigh", std::numeric_limits<double>::quiet_NaN()));
524 warningIface->warningLow(threshold.value(
525 "WarningLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000526 warningIface->setHighHysteresis(
527 threshold.value("WarningHighHysteresis", defaultHysteresis));
528 warningIface->setLowHysteresis(
529 threshold.value("WarningLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000530 }
531
532 if (threshold.contains("HardShutdownHigh") ||
533 threshold.contains("HardShutdownLow"))
534 {
535 hardShutdownIface = std::make_unique<Threshold<HardShutdownObject>>(
536 bus, objPath.c_str());
537
538 hardShutdownIface->hardShutdownHigh(threshold.value(
539 "HardShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
540 hardShutdownIface->hardShutdownLow(threshold.value(
541 "HardShutdownLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000542 hardShutdownIface->setHighHysteresis(
543 threshold.value("HardShutdownHighHysteresis", defaultHysteresis));
544 hardShutdownIface->setLowHysteresis(
545 threshold.value("HardShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000546 }
547
548 if (threshold.contains("SoftShutdownHigh") ||
549 threshold.contains("SoftShutdownLow"))
550 {
551 softShutdownIface = std::make_unique<Threshold<SoftShutdownObject>>(
552 bus, objPath.c_str());
553
554 softShutdownIface->softShutdownHigh(threshold.value(
555 "SoftShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
556 softShutdownIface->softShutdownLow(threshold.value(
557 "SoftShutdownLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000558 softShutdownIface->setHighHysteresis(
559 threshold.value("SoftShutdownHighHysteresis", defaultHysteresis));
560 softShutdownIface->setLowHysteresis(
561 threshold.value("SoftShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000562 }
563
564 if (threshold.contains("PerformanceLossHigh") ||
565 threshold.contains("PerformanceLossLow"))
566 {
567 perfLossIface = std::make_unique<Threshold<PerformanceLossObject>>(
568 bus, objPath.c_str());
569
570 perfLossIface->performanceLossHigh(threshold.value(
571 "PerformanceLossHigh", std::numeric_limits<double>::quiet_NaN()));
572 perfLossIface->performanceLossLow(threshold.value(
573 "PerformanceLossLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000574 perfLossIface->setHighHysteresis(threshold.value(
575 "PerformanceLossHighHysteresis", defaultHysteresis));
576 perfLossIface->setLowHysteresis(
577 threshold.value("PerformanceLossLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000578 }
579}
580
Rashmica Guptae7efe132021-07-27 19:42:11 +1000581ManagedObjectType VirtualSensors::getObjectsFromDBus()
582{
583 ManagedObjectType objects;
584
585 try
586 {
587 auto method = bus.new_method_call(entityManagerBusName, "/",
588 "org.freedesktop.DBus.ObjectManager",
589 "GetManagedObjects");
590 auto reply = bus.call(method);
591 reply.read(objects);
592 }
593 catch (const sdbusplus::exception::SdBusError& ex)
594 {
595 // If entity manager isn't running yet, keep going.
596 if (std::string("org.freedesktop.DBus.Error.ServiceUnknown") !=
597 ex.name())
598 {
599 throw ex.name();
600 }
601 }
602
603 return objects;
604}
605
606void VirtualSensors::propertiesChanged(sdbusplus::message::message& msg)
607{
608 std::string path;
609 PropertyMap properties;
610
611 msg.read(path, properties);
612
613 /* We get multiple callbacks for one sensor. 'Type' is a required field and
614 * is a unique label so use to to only proceed once per sensor */
615 if (properties.contains("Type"))
616 {
617 if (isCalculationType(path))
618 {
619 createVirtualSensorsFromDBus(path);
620 }
621 }
622}
623
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700624/** @brief Parsing Virtual Sensor config JSON file */
625Json VirtualSensors::parseConfigFile(const std::string configFile)
626{
627 std::ifstream jsonFile(configFile);
628 if (!jsonFile.is_open())
629 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500630 error("config JSON file {FILENAME} not found", "FILENAME", configFile);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000631 return {};
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700632 }
633
634 auto data = Json::parse(jsonFile, nullptr, false);
635 if (data.is_discarded())
636 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500637 error("config readings JSON parser failure with {FILENAME}", "FILENAME",
638 configFile);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700639 throw std::exception{};
640 }
641
642 return data;
643}
644
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700645std::map<std::string, ValueIface::Unit> unitMap = {
646 {"temperature", ValueIface::Unit::DegreesC},
647 {"fan_tach", ValueIface::Unit::RPMS},
648 {"voltage", ValueIface::Unit::Volts},
649 {"altitude", ValueIface::Unit::Meters},
650 {"current", ValueIface::Unit::Amperes},
651 {"power", ValueIface::Unit::Watts},
652 {"energy", ValueIface::Unit::Joules},
Kumar Thangavel2b56ddb2021-01-13 20:16:11 +0530653 {"utilization", ValueIface::Unit::Percent},
Rashmica Gupta4ac7a7f2021-07-08 12:30:50 +1000654 {"airflow", ValueIface::Unit::CFM},
655 {"pressure", ValueIface::Unit::Pascals}};
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700656
Rashmica Guptae7efe132021-07-27 19:42:11 +1000657const std::string getSensorTypeFromUnit(const std::string& unit)
658{
659 std::string unitPrefix = "xyz.openbmc_project.Sensor.Value.Unit.";
660 for (auto [type, unitObj] : unitMap)
661 {
662 auto unitPath = ValueIface::convertUnitToString(unitObj);
663 if (unitPath == (unitPrefix + unit))
664 {
665 return type;
666 }
667 }
668 return "";
669}
670
671void VirtualSensors::setupMatches()
672{
673 /* Already setup */
674 if (!this->matches.empty())
675 {
676 return;
677 }
678
679 /* Setup matches */
680 auto eventHandler = [this](sdbusplus::message::message& message) {
681 if (message.is_method_error())
682 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500683 error("Callback method error");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000684 return;
685 }
686 this->propertiesChanged(message);
687 };
688
689 for (const char* iface : calculationIfaces)
690 {
691 auto match = std::make_unique<sdbusplus::bus::match::match>(
692 bus,
693 sdbusplus::bus::match::rules::propertiesChangedNamespace(
694 "/xyz/openbmc_project/inventory", iface),
695 eventHandler);
696 this->matches.emplace_back(std::move(match));
697 }
698}
699
700void VirtualSensors::createVirtualSensorsFromDBus(
701 const std::string& calculationIface)
702{
703 if (calculationIface.empty())
704 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500705 error("No calculation type supplied");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000706 return;
707 }
708 auto objects = getObjectsFromDBus();
709
710 /* Get virtual sensors config data */
711 for (const auto& [path, interfaceMap] : objects)
712 {
713 auto objpath = static_cast<std::string>(path);
714 std::string name = path.filename();
715 std::string sensorType, sensorUnit;
716
717 /* Find Virtual Sensor interfaces */
718 if (!interfaceMap.contains(calculationIface))
719 {
720 continue;
721 }
722 if (name.empty())
723 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500724 error("Virtual Sensor name not found in entity manager config");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000725 continue;
726 }
727 if (virtualSensorsMap.contains(name))
728 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500729 error("A virtual sensor named {NAME} already exists", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000730 continue;
731 }
732
733 /* Extract the virtual sensor type as we need this to initialize the
734 * sensor */
735 for (const auto& [interface, propertyMap] : interfaceMap)
736 {
737 if (interface != calculationIface)
738 {
739 continue;
740 }
741 auto itr = propertyMap.find("Units");
742 if (itr != propertyMap.end())
743 {
744 sensorUnit = std::get<std::string>(itr->second);
745 break;
746 }
747 }
748 sensorType = getSensorTypeFromUnit(sensorUnit);
749 if (sensorType.empty())
750 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500751 error("Sensor unit type {TYPE} is not supported", "TYPE",
752 sensorUnit);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000753 continue;
754 }
755
756 try
757 {
758 auto virtObjPath = sensorDbusPath + sensorType + "/" + name;
759
760 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
761 bus, virtObjPath.c_str(), interfaceMap, name, sensorType,
762 calculationIface);
Patrick Williams82b39c62021-07-28 16:22:27 -0500763 info("Added a new virtual sensor: {NAME} {TYPE}", "NAME", name,
764 "TYPE", sensorType);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000765 virtualSensorPtr->updateVirtualSensor();
766
767 /* Initialize unit value for virtual sensor */
768 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
769 virtualSensorPtr->emit_object_added();
770
771 virtualSensorsMap.emplace(name, std::move(virtualSensorPtr));
772
773 /* Setup match for interfaces removed */
774 auto intfRemoved = [this, objpath,
775 name](sdbusplus::message::message& message) {
776 if (!virtualSensorsMap.contains(name))
777 {
778 return;
779 }
780 sdbusplus::message::object_path path;
781 message.read(path);
782 if (static_cast<const std::string&>(path) == objpath)
783 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500784 info("Removed a virtual sensor: {NAME}", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000785 virtualSensorsMap.erase(name);
786 }
787 };
788 auto matchOnRemove = std::make_unique<sdbusplus::bus::match::match>(
789 bus,
790 sdbusplus::bus::match::rules::interfacesRemoved() +
791 sdbusplus::bus::match::rules::argNpath(0, objpath),
792 intfRemoved);
793 /* TODO: slight race condition here. Check that the config still
794 * exists */
795 this->matches.emplace_back(std::move(matchOnRemove));
796 }
797 catch (std::invalid_argument& ia)
798 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500799 error("Failed to set up virtual sensor: {ERROR}", "ERROR", ia);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000800 }
801 }
802}
803
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700804void VirtualSensors::createVirtualSensors()
805{
806 static const Json empty{};
807
808 auto data = parseConfigFile(VIRTUAL_SENSOR_CONFIG_FILE);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000809
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700810 // print values
811 if (DEBUG)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000812 {
Patrick Williamsfbd71452021-08-30 06:59:46 -0500813 debug("JSON: {JSON}", "JSON", data.dump());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000814 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700815
816 /* Get virtual sensors config data */
817 for (const auto& j : data)
818 {
819 auto desc = j.value("Desc", empty);
820 if (!desc.empty())
821 {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000822 if (desc.value("Config", "") == "D-Bus")
823 {
824 /* Look on D-Bus for a virtual sensor config. Set up matches
825 * first because the configs may not be on D-Bus yet and we
826 * don't want to miss them */
827 setupMatches();
828
829 if (desc.contains("Type"))
830 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500831 auto type = desc.value("Type", "");
832 auto path = "xyz.openbmc_project.Configuration." + type;
833
Rashmica Guptae7efe132021-07-27 19:42:11 +1000834 if (!isCalculationType(path))
835 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500836 error("Invalid calculation type {TYPE} supplied.",
837 "TYPE", type);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000838 continue;
839 }
840 createVirtualSensorsFromDBus(path);
841 }
842 continue;
843 }
844
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700845 std::string sensorType = desc.value("SensorType", "");
846 std::string name = desc.value("Name", "");
Rashmica Gupta665a0a22021-06-30 11:35:28 +1000847 std::replace(name.begin(), name.end(), ' ', '_');
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700848
849 if (!name.empty() && !sensorType.empty())
850 {
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700851 if (unitMap.find(sensorType) == unitMap.end())
852 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500853 error("Sensor type {TYPE} is not supported", "TYPE",
854 sensorType);
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700855 }
856 else
857 {
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +1000858 if (virtualSensorsMap.find(name) != virtualSensorsMap.end())
859 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500860 error("A virtual sensor named {NAME} already exists",
861 "NAME", name);
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +1000862 continue;
863 }
Rashmica Gupta862c3d12021-08-06 12:19:31 +1000864 auto objPath = sensorDbusPath + sensorType + "/" + name;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700865
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700866 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
867 bus, objPath.c_str(), j, name);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700868
Patrick Williams82b39c62021-07-28 16:22:27 -0500869 info("Added a new virtual sensor: {NAME}", "NAME", name);
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700870 virtualSensorPtr->updateVirtualSensor();
871
872 /* Initialize unit value for virtual sensor */
873 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
Rashmica Guptaa2fa63a2021-08-06 12:21:13 +1000874 virtualSensorPtr->emit_object_added();
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700875
876 virtualSensorsMap.emplace(std::move(name),
877 std::move(virtualSensorPtr));
878 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700879 }
880 else
881 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500882 error(
883 "Sensor type ({TYPE}) or name ({NAME}) not found in config file",
884 "NAME", name, "TYPE", sensorType);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700885 }
886 }
887 else
888 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500889 error("Descriptor for new virtual sensor not found in config file");
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700890 }
891 }
892}
893
894} // namespace virtualSensor
895} // namespace phosphor
896
897/**
898 * @brief Main
899 */
900int main()
901{
902
903 // Get a default event loop
904 auto event = sdeventplus::Event::get_default();
905
906 // Get a handle to system dbus
907 auto bus = sdbusplus::bus::new_default();
908
Matt Spinler6c19e7d2021-01-12 16:26:45 -0600909 // Add the ObjectManager interface
910 sdbusplus::server::manager::manager objManager(bus, "/");
911
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700912 // Create an virtual sensors object
913 phosphor::virtualSensor::VirtualSensors virtualSensors(bus);
914
915 // Request service bus name
916 bus.request_name(busName);
917
918 // Attach the bus to sd_event to service user requests
919 bus.attach_event(event.get(), SD_EVENT_PRIORITY_NORMAL);
920 event.loop();
921
922 return 0;
923}