blob: e8fc218e6de29667fbac719512a403462d948d7a [file] [log] [blame]
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001#include "virtualSensor.hpp"
2
Patrick Williams82b39c62021-07-28 16:22:27 -05003#include <phosphor-logging/lg2.hpp>
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07004
5#include <fstream>
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07006
7static constexpr bool DEBUG = false;
8static constexpr auto busName = "xyz.openbmc_project.VirtualSensor";
9static constexpr auto sensorDbusPath = "/xyz/openbmc_project/sensors/";
Rashmica Guptae7efe132021-07-27 19:42:11 +100010static constexpr auto vsThresholdsIfaceSuffix = ".Thresholds";
Tao Linf6b7e0a2022-10-09 09:35:44 +080011static constexpr std::array<const char*, 2> calculationIfaces = {
12 "xyz.openbmc_project.Configuration.ModifiedMedian",
13 "xyz.openbmc_project.Configuration.Maximum"};
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +100014static constexpr auto defaultHysteresis = 0;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070015
Patrick Williams82b39c62021-07-28 16:22:27 -050016PHOSPHOR_LOG2_USING_WITH_FLAGS;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070017
Vijay Khemka51f898e2020-09-09 22:24:18 -070018int handleDbusSignal(sd_bus_message* msg, void* usrData, sd_bus_error*)
19{
20 if (usrData == nullptr)
21 {
22 throw std::runtime_error("Invalid match");
23 }
24
Patrick Williams8e11ccc2022-07-22 19:26:57 -050025 auto sdbpMsg = sdbusplus::message_t(msg);
Vijay Khemka51f898e2020-09-09 22:24:18 -070026 std::string msgIfce;
27 std::map<std::string, std::variant<int64_t, double, bool>> msgData;
28
29 sdbpMsg.read(msgIfce, msgData);
30
31 if (msgData.find("Value") != msgData.end())
32 {
33 using namespace phosphor::virtualSensor;
34 VirtualSensor* obj = static_cast<VirtualSensor*>(usrData);
35 // TODO(openbmc/phosphor-virtual-sensor#1): updateVirtualSensor should
36 // be changed to take the information we got from the signal, to avoid
37 // having to do numerous dbus queries.
38 obj->updateVirtualSensor();
39 }
40 return 0;
41}
42
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070043namespace phosphor
44{
45namespace virtualSensor
46{
47
48void printParams(const VirtualSensor::ParamMap& paramMap)
49{
50 for (const auto& p : paramMap)
51 {
52 const auto& p1 = p.first;
53 const auto& p2 = p.second;
54 auto val = p2->getParamValue();
Patrick Williamsfbd71452021-08-30 06:59:46 -050055 debug("Parameter: {PARAM} = {VALUE}", "PARAM", p1, "VALUE", val);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070056 }
57}
58
59double SensorParam::getParamValue()
60{
61 switch (paramType)
62 {
63 case constParam:
64 return value;
65 break;
Vijay Khemka7452a862020-08-11 16:01:23 -070066 case dbusParam:
67 return dbusSensor->getSensorValue();
68 break;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -070069 default:
70 throw std::invalid_argument("param type not supported");
71 }
72}
73
Lei YU0fcf0e12021-06-04 11:14:17 +080074using AssociationList =
75 std::vector<std::tuple<std::string, std::string, std::string>>;
76
77AssociationList getAssociationsFromJson(const Json& j)
78{
79 AssociationList assocs{};
80 try
81 {
82 j.get_to(assocs);
83 }
84 catch (const std::exception& ex)
85 {
Patrick Williams82b39c62021-07-28 16:22:27 -050086 error("Failed to parse association: {ERROR}", "ERROR", ex);
Lei YU0fcf0e12021-06-04 11:14:17 +080087 }
88 return assocs;
89}
90
Rashmica Guptae7efe132021-07-27 19:42:11 +100091template <typename U>
92struct VariantToNumber
93{
94 template <typename T>
95 U operator()(const T& t) const
96 {
97 if constexpr (std::is_convertible<T, U>::value)
98 {
99 return static_cast<U>(t);
100 }
101 throw std::invalid_argument("Invalid number type in config\n");
102 }
103};
104
105template <typename U>
106U getNumberFromConfig(const PropertyMap& map, const std::string& name,
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800107 bool required,
108 U defaultValue = std::numeric_limits<U>::quiet_NaN())
Rashmica Guptae7efe132021-07-27 19:42:11 +1000109{
110 if (auto itr = map.find(name); itr != map.end())
111 {
112 return std::visit(VariantToNumber<U>(), itr->second);
113 }
114 else if (required)
115 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500116 error("Required field {NAME} missing in config", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000117 throw std::invalid_argument("Required field missing in config");
118 }
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800119 return defaultValue;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000120}
121
122bool isCalculationType(const std::string& interface)
123{
124 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
125 interface);
126 if (itr != calculationIfaces.end())
127 {
128 return true;
129 }
130 return false;
131}
132
133const std::string getThresholdType(const std::string& direction,
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100134 const std::string& severity)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000135{
Rashmica Guptae7efe132021-07-27 19:42:11 +1000136 std::string suffix;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000137
138 if (direction == "less than")
139 {
140 suffix = "Low";
141 }
142 else if (direction == "greater than")
143 {
144 suffix = "High";
145 }
146 else
147 {
148 throw std::invalid_argument(
149 "Invalid threshold direction specified in entity manager");
150 }
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100151 return severity + suffix;
152}
153
154std::string getSeverityField(const PropertyMap& propertyMap)
155{
156 static const std::array thresholdTypes{"Warning", "Critical",
157 "PerformanceLoss", "SoftShutdown",
158 "HardShutdown"};
159
160 std::string severity;
161 if (auto itr = propertyMap.find("Severity"); itr != propertyMap.end())
162 {
163 /* Severity should be a string, but can be an unsigned int */
164 if (std::holds_alternative<std::string>(itr->second))
165 {
166 severity = std::get<std::string>(itr->second);
167 if (0 == std::ranges::count(thresholdTypes, severity))
168 {
169 throw std::invalid_argument(
170 "Invalid threshold severity specified in entity manager");
171 }
172 }
173 else
174 {
175 auto sev =
176 getNumberFromConfig<uint64_t>(propertyMap, "Severity", true);
177 /* Checking bounds ourselves so we throw invalid argument on
178 * invalid user input */
179 if (sev >= thresholdTypes.size())
180 {
181 throw std::invalid_argument(
182 "Invalid threshold severity specified in entity manager");
183 }
184 severity = thresholdTypes.at(sev);
185 }
186 }
187 return severity;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000188}
189
Tao Lin91799db2022-07-27 21:02:20 +0800190void parseThresholds(Json& thresholds, const PropertyMap& propertyMap,
191 const std::string& entityInterface = "")
Rashmica Guptae7efe132021-07-27 19:42:11 +1000192{
193 std::string direction;
194
Rashmica Guptae7efe132021-07-27 19:42:11 +1000195 auto value = getNumberFromConfig<double>(propertyMap, "Value", true);
196
Rashmica Gupta05b1d412021-11-03 12:01:36 +1100197 auto severity = getSeverityField(propertyMap);
198
199 if (auto itr = propertyMap.find("Direction"); itr != propertyMap.end())
Rashmica Guptae7efe132021-07-27 19:42:11 +1000200 {
201 direction = std::get<std::string>(itr->second);
202 }
203
204 auto threshold = getThresholdType(direction, severity);
205 thresholds[threshold] = value;
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000206
207 auto hysteresis =
208 getNumberFromConfig<double>(propertyMap, "Hysteresis", false);
209 if (hysteresis != std::numeric_limits<double>::quiet_NaN())
210 {
211 thresholds[threshold + "Hysteresis"] = hysteresis;
212 }
Tao Lin91799db2022-07-27 21:02:20 +0800213
214 if (!entityInterface.empty())
215 {
216 thresholds[threshold + "Direction"] = entityInterface;
217 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000218}
219
220void VirtualSensor::parseConfigInterface(const PropertyMap& propertyMap,
221 const std::string& sensorType,
222 const std::string& interface)
223{
224 /* Parse sensors / DBus params */
225 if (auto itr = propertyMap.find("Sensors"); itr != propertyMap.end())
226 {
227 auto sensors = std::get<std::vector<std::string>>(itr->second);
228 for (auto sensor : sensors)
229 {
230 std::replace(sensor.begin(), sensor.end(), ' ', '_');
231 auto sensorObjPath = sensorDbusPath + sensorType + "/" + sensor;
232
233 auto paramPtr =
234 std::make_unique<SensorParam>(bus, sensorObjPath, this);
235 symbols.create_variable(sensor);
236 paramMap.emplace(std::move(sensor), std::move(paramPtr));
237 }
238 }
239 /* Get expression string */
240 if (!isCalculationType(interface))
241 {
242 throw std::invalid_argument("Invalid expression in interface");
243 }
244 exprStr = interface;
245
246 /* Get optional min and max input and output values */
247 ValueIface::maxValue(
248 getNumberFromConfig<double>(propertyMap, "MaxValue", false));
249 ValueIface::minValue(
250 getNumberFromConfig<double>(propertyMap, "MinValue", false));
251 maxValidInput =
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800252 getNumberFromConfig<double>(propertyMap, "MaxValidInput", false,
253 std::numeric_limits<double>::infinity());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000254 minValidInput =
Jiaqing Zhao190f6d02022-05-21 23:26:15 +0800255 getNumberFromConfig<double>(propertyMap, "MinValidInput", false,
256 -std::numeric_limits<double>::infinity());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000257}
258
Matt Spinlerce675222021-01-14 16:38:09 -0600259void VirtualSensor::initVirtualSensor(const Json& sensorConfig,
260 const std::string& objPath)
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700261{
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700262 static const Json empty{};
263
264 /* Get threshold values if defined in config */
265 auto threshold = sensorConfig.value("Threshold", empty);
Matt Spinlerf15189e2021-01-15 10:13:28 -0600266
Rashmica Gupta3e999192021-06-09 16:17:04 +1000267 createThresholds(threshold, objPath);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700268
Harvey Wuf6443742021-04-09 16:47:36 +0800269 /* Get MaxValue, MinValue setting if defined in config */
270 auto confDesc = sensorConfig.value("Desc", empty);
271 if (auto maxConf = confDesc.find("MaxValue");
272 maxConf != confDesc.end() && maxConf->is_number())
273 {
274 ValueIface::maxValue(maxConf->get<double>());
275 }
276 if (auto minConf = confDesc.find("MinValue");
277 minConf != confDesc.end() && minConf->is_number())
278 {
279 ValueIface::minValue(minConf->get<double>());
280 }
281
Lei YU0fcf0e12021-06-04 11:14:17 +0800282 /* Get optional association */
283 auto assocJson = sensorConfig.value("Associations", empty);
284 if (!assocJson.empty())
285 {
286 auto assocs = getAssociationsFromJson(assocJson);
287 if (!assocs.empty())
288 {
289 associationIface =
290 std::make_unique<AssociationObject>(bus, objPath.c_str());
291 associationIface->associations(assocs);
292 }
293 }
294
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700295 /* Get expression string */
Patrick Williams03c4c8e2022-04-14 22:19:44 -0500296 static constexpr auto exprKey = "Expression";
297 if (sensorConfig.contains(exprKey))
298 {
Patrick Williamsa9596782022-04-15 10:20:07 -0500299 auto& ref = sensorConfig.at(exprKey);
Patrick Williams03c4c8e2022-04-14 22:19:44 -0500300 if (ref.is_array())
301 {
302 exprStr = std::string{};
303 for (auto& s : ref)
304 {
305 exprStr += s;
306 }
307 }
308 else if (ref.is_string())
309 {
310 exprStr = std::string{ref};
311 }
312 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700313
314 /* Get all the parameter listed in configuration */
315 auto params = sensorConfig.value("Params", empty);
316
317 /* Check for constant parameter */
318 const auto& consParams = params.value("ConstParam", empty);
319 if (!consParams.empty())
320 {
321 for (auto& j : consParams)
322 {
323 if (j.find("ParamName") != j.end())
324 {
325 auto paramPtr = std::make_unique<SensorParam>(j["Value"]);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700326 std::string name = j["ParamName"];
327 symbols.create_variable(name);
328 paramMap.emplace(std::move(name), std::move(paramPtr));
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700329 }
330 else
331 {
332 /* Invalid configuration */
333 throw std::invalid_argument(
334 "ParamName not found in configuration");
335 }
336 }
337 }
338
Vijay Khemka7452a862020-08-11 16:01:23 -0700339 /* Check for dbus parameter */
340 auto dbusParams = params.value("DbusParam", empty);
341 if (!dbusParams.empty())
342 {
343 for (auto& j : dbusParams)
344 {
345 /* Get parameter dbus sensor descriptor */
346 auto desc = j.value("Desc", empty);
347 if ((!desc.empty()) && (j.find("ParamName") != j.end()))
348 {
349 std::string sensorType = desc.value("SensorType", "");
350 std::string name = desc.value("Name", "");
351
352 if (!sensorType.empty() && !name.empty())
353 {
George Liu1204b432021-12-29 17:24:48 +0800354 auto path = sensorDbusPath + sensorType + "/" + name;
Vijay Khemka7452a862020-08-11 16:01:23 -0700355
Vijay Khemka51f898e2020-09-09 22:24:18 -0700356 auto paramPtr =
George Liu1204b432021-12-29 17:24:48 +0800357 std::make_unique<SensorParam>(bus, path, this);
358 std::string paramName = j["ParamName"];
359 symbols.create_variable(paramName);
360 paramMap.emplace(std::move(paramName), std::move(paramPtr));
Vijay Khemka7452a862020-08-11 16:01:23 -0700361 }
362 }
363 }
364 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700365
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700366 symbols.add_constants();
Matt Spinler9f1ef4f2020-11-09 15:59:11 -0600367 symbols.add_package(vecopsPackage);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700368 expression.register_symbol_table(symbols);
369
370 /* parser from exprtk */
371 exprtk::parser<double> parser{};
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600372 if (!parser.compile(exprStr, expression))
373 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500374 error("Expression compilation failed");
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600375
376 for (std::size_t i = 0; i < parser.error_count(); ++i)
377 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500378 auto err = parser.get_error(i);
379 error("Error parsing token at {POSITION}: {ERROR}", "POSITION",
380 err.token.position, "TYPE",
381 exprtk::parser_error::to_str(err.mode), "ERROR",
382 err.diagnostic);
Matt Spinlerddc6dcd2020-11-09 11:16:31 -0600383 }
384 throw std::runtime_error("Expression compilation failed");
385 }
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700386
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700387 /* Print all parameters for debug purpose only */
388 if (DEBUG)
389 printParams(paramMap);
390}
391
Tao Lindc777012022-07-27 20:41:46 +0800392void VirtualSensor::createAssociation(const std::string& objPath,
393 const std::string& entityPath)
394{
395 if (objPath.empty() || entityPath.empty())
396 {
397 return;
398 }
399
400 std::filesystem::path p(entityPath);
401 auto assocsDbus =
402 AssociationList{{"chassis", "all_sensors", p.parent_path().string()}};
403 associationIface =
404 std::make_unique<AssociationObject>(bus, objPath.c_str());
405 associationIface->associations(assocsDbus);
406}
407
Rashmica Guptae7efe132021-07-27 19:42:11 +1000408void VirtualSensor::initVirtualSensor(const InterfaceMap& interfaceMap,
409 const std::string& objPath,
410 const std::string& sensorType,
411 const std::string& calculationIface)
412{
413 Json thresholds;
414 const std::string vsThresholdsIntf =
415 calculationIface + vsThresholdsIfaceSuffix;
416
417 for (const auto& [interface, propertyMap] : interfaceMap)
418 {
419 /* Each threshold is on it's own interface with a number as a suffix
420 * eg xyz.openbmc_project.Configuration.ModifiedMedian.Thresholds1 */
421 if (interface.find(vsThresholdsIntf) != std::string::npos)
422 {
Tao Lin91799db2022-07-27 21:02:20 +0800423 parseThresholds(thresholds, propertyMap, interface);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000424 }
425 else if (interface == calculationIface)
426 {
427 parseConfigInterface(propertyMap, sensorType, interface);
428 }
429 }
430
431 createThresholds(thresholds, objPath);
432 symbols.add_constants();
433 symbols.add_package(vecopsPackage);
434 expression.register_symbol_table(symbols);
435
Tao Lindc777012022-07-27 20:41:46 +0800436 createAssociation(objPath, entityPath);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000437 /* Print all parameters for debug purpose only */
438 if (DEBUG)
439 {
440 printParams(paramMap);
441 }
442}
443
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700444void VirtualSensor::setSensorValue(double value)
445{
Patrick Williams543bf662021-04-29 09:03:53 -0500446 value = std::clamp(value, ValueIface::minValue(), ValueIface::maxValue());
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700447 ValueIface::value(value);
448}
449
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000450double VirtualSensor::calculateValue(const std::string& calculation,
451 const VirtualSensor::ParamMap& paramMap)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000452{
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000453 auto itr = std::find(calculationIfaces.begin(), calculationIfaces.end(),
454 calculation);
455 if (itr == calculationIfaces.end())
456 {
457 return std::numeric_limits<double>::quiet_NaN();
458 }
459 else if (calculation == "xyz.openbmc_project.Configuration.ModifiedMedian")
460 {
461 return calculateModifiedMedianValue(paramMap);
462 }
Tao Linf6b7e0a2022-10-09 09:35:44 +0800463 else if (calculation == "xyz.openbmc_project.Configuration.Maximum")
464 {
465 return calculateMaximumValue(paramMap);
466 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000467 return std::numeric_limits<double>::quiet_NaN();
468}
469
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000470bool VirtualSensor::sensorInRange(double value)
471{
472 if (value <= this->maxValidInput && value >= this->minValidInput)
473 {
474 return true;
475 }
476 return false;
477}
478
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700479void VirtualSensor::updateVirtualSensor()
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700480{
481 for (auto& param : paramMap)
482 {
483 auto& name = param.first;
484 auto& data = param.second;
485 if (auto var = symbols.get_variable(name))
486 {
487 var->ref() = data->getParamValue();
488 }
489 else
490 {
491 /* Invalid parameter */
492 throw std::invalid_argument("ParamName not found in symbols");
493 }
494 }
Rashmica Guptae7efe132021-07-27 19:42:11 +1000495 auto itr =
496 std::find(calculationIfaces.begin(), calculationIfaces.end(), exprStr);
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000497 auto val = (itr == calculationIfaces.end())
498 ? expression.value()
499 : calculateValue(exprStr, paramMap);
Vijay Khemka32a71562020-09-10 15:29:18 -0700500
501 /* Set sensor value to dbus interface */
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700502 setSensorValue(val);
Vijay Khemka32a71562020-09-10 15:29:18 -0700503
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700504 if (DEBUG)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000505 {
Patrick Williamsfbd71452021-08-30 06:59:46 -0500506 debug("Sensor {NAME} = {VALUE}", "NAME", this->name, "VALUE", val);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000507 }
Vijay Khemka32a71562020-09-10 15:29:18 -0700508
Matt Spinler8f5e6112021-01-15 10:44:32 -0600509 /* Check sensor thresholds and log required message */
Matt Spinlerb306b032021-02-01 10:05:46 -0600510 checkThresholds(val, perfLossIface);
Patrick Williamsfdb826d2021-01-20 14:37:53 -0600511 checkThresholds(val, warningIface);
512 checkThresholds(val, criticalIface);
513 checkThresholds(val, softShutdownIface);
514 checkThresholds(val, hardShutdownIface);
Vijay Khemka3ed9a512020-08-21 16:13:05 -0700515}
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700516
Rashmica Gupta304fd0e2021-08-10 16:50:44 +1000517double VirtualSensor::calculateModifiedMedianValue(
518 const VirtualSensor::ParamMap& paramMap)
519{
520 std::vector<double> values;
521
522 for (auto& param : paramMap)
523 {
524 auto& name = param.first;
525 if (auto var = symbols.get_variable(name))
526 {
527 if (!sensorInRange(var->ref()))
528 {
529 continue;
530 }
531 values.push_back(var->ref());
532 }
533 }
534
535 size_t size = values.size();
536 std::sort(values.begin(), values.end());
537 switch (size)
538 {
539 case 2:
540 /* Choose biggest value */
541 return values.at(1);
542 case 0:
543 return std::numeric_limits<double>::quiet_NaN();
544 default:
545 /* Choose median value */
546 if (size % 2 == 0)
547 {
548 // Average of the two middle values
549 return (values.at(size / 2) + values.at(size / 2 - 1)) / 2;
550 }
551 else
552 {
553 return values.at((size - 1) / 2);
554 }
555 }
556}
557
Tao Linf6b7e0a2022-10-09 09:35:44 +0800558double VirtualSensor::calculateMaximumValue(
559 const VirtualSensor::ParamMap& paramMap)
560{
561 std::vector<double> values;
562
563 for (auto& param : paramMap)
564 {
565 auto& name = param.first;
566 if (auto var = symbols.get_variable(name))
567 {
568 if (!sensorInRange(var->ref()))
569 {
570 continue;
571 }
572 values.push_back(var->ref());
573 }
574 }
575 auto maxIt = std::max_element(values.begin(), values.end());
576 if (maxIt == values.end())
577 {
578 return std::numeric_limits<double>::quiet_NaN();
579 }
580 return *maxIt;
581}
582
Rashmica Gupta3e999192021-06-09 16:17:04 +1000583void VirtualSensor::createThresholds(const Json& threshold,
584 const std::string& objPath)
585{
586 if (threshold.empty())
587 {
588 return;
589 }
590 // Only create the threshold interfaces if
591 // at least one of their values is present.
592 if (threshold.contains("CriticalHigh") || threshold.contains("CriticalLow"))
593 {
594 criticalIface =
595 std::make_unique<Threshold<CriticalObject>>(bus, objPath.c_str());
596
Tao Lin91799db2022-07-27 21:02:20 +0800597 if (threshold.contains("CriticalHigh"))
598 {
599 criticalIface->setEntityInterfaceHigh(
600 threshold.value("CriticalHighDirection", ""));
601 if (DEBUG)
602 {
603 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
604 "INTF", threshold.value("CriticalHighDirection", ""));
605 }
606 }
607 if (threshold.contains("CriticalLow"))
608 {
609 criticalIface->setEntityInterfaceLow(
610 threshold.value("CriticalLowDirection", ""));
611 if (DEBUG)
612 {
613 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
614 "INTF", threshold.value("CriticalLowDirection", ""));
615 }
616 }
617
618 criticalIface->setEntityPath(entityPath);
619 if (DEBUG)
620 {
621 debug("Sensor Threshold:{NAME} = path:{PATH}", "NAME", objPath,
622 "PATH", entityPath);
623 }
Matt Spinlera291ce12023-02-06 15:12:44 -0600624
625 criticalIface->criticalHigh(threshold.value(
626 "CriticalHigh", std::numeric_limits<double>::quiet_NaN()));
627 criticalIface->criticalLow(threshold.value(
628 "CriticalLow", std::numeric_limits<double>::quiet_NaN()));
629 criticalIface->setHighHysteresis(
630 threshold.value("CriticalHighHysteresis", defaultHysteresis));
631 criticalIface->setLowHysteresis(
632 threshold.value("CriticalLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000633 }
634
635 if (threshold.contains("WarningHigh") || threshold.contains("WarningLow"))
636 {
637 warningIface =
638 std::make_unique<Threshold<WarningObject>>(bus, objPath.c_str());
639
Tao Lin91799db2022-07-27 21:02:20 +0800640 if (threshold.contains("WarningHigh"))
641 {
642 warningIface->setEntityInterfaceHigh(
643 threshold.value("WarningHighDirection", ""));
644 if (DEBUG)
645 {
646 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
647 "INTF", threshold.value("WarningHighDirection", ""));
648 }
649 }
650 if (threshold.contains("WarningLow"))
651 {
652 warningIface->setEntityInterfaceLow(
653 threshold.value("WarningLowDirection", ""));
654 if (DEBUG)
655 {
656 debug("Sensor Threshold:{NAME} = intf:{INTF}", "NAME", objPath,
657 "INTF", threshold.value("WarningLowDirection", ""));
658 }
659 }
660
661 warningIface->setEntityPath(entityPath);
662 if (DEBUG)
663 {
664 debug("Sensor Threshold:{NAME} = path:{PATH}", "NAME", objPath,
665 "PATH", entityPath);
666 }
Matt Spinlera291ce12023-02-06 15:12:44 -0600667
668 warningIface->warningHigh(threshold.value(
669 "WarningHigh", std::numeric_limits<double>::quiet_NaN()));
670 warningIface->warningLow(threshold.value(
671 "WarningLow", std::numeric_limits<double>::quiet_NaN()));
672 warningIface->setHighHysteresis(
673 threshold.value("WarningHighHysteresis", defaultHysteresis));
674 warningIface->setLowHysteresis(
675 threshold.value("WarningLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000676 }
677
678 if (threshold.contains("HardShutdownHigh") ||
679 threshold.contains("HardShutdownLow"))
680 {
681 hardShutdownIface = std::make_unique<Threshold<HardShutdownObject>>(
682 bus, objPath.c_str());
683
684 hardShutdownIface->hardShutdownHigh(threshold.value(
685 "HardShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
686 hardShutdownIface->hardShutdownLow(threshold.value(
687 "HardShutdownLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000688 hardShutdownIface->setHighHysteresis(
689 threshold.value("HardShutdownHighHysteresis", defaultHysteresis));
690 hardShutdownIface->setLowHysteresis(
691 threshold.value("HardShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000692 }
693
694 if (threshold.contains("SoftShutdownHigh") ||
695 threshold.contains("SoftShutdownLow"))
696 {
697 softShutdownIface = std::make_unique<Threshold<SoftShutdownObject>>(
698 bus, objPath.c_str());
699
700 softShutdownIface->softShutdownHigh(threshold.value(
701 "SoftShutdownHigh", std::numeric_limits<double>::quiet_NaN()));
702 softShutdownIface->softShutdownLow(threshold.value(
703 "SoftShutdownLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000704 softShutdownIface->setHighHysteresis(
705 threshold.value("SoftShutdownHighHysteresis", defaultHysteresis));
706 softShutdownIface->setLowHysteresis(
707 threshold.value("SoftShutdownLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000708 }
709
710 if (threshold.contains("PerformanceLossHigh") ||
711 threshold.contains("PerformanceLossLow"))
712 {
713 perfLossIface = std::make_unique<Threshold<PerformanceLossObject>>(
714 bus, objPath.c_str());
715
716 perfLossIface->performanceLossHigh(threshold.value(
717 "PerformanceLossHigh", std::numeric_limits<double>::quiet_NaN()));
718 perfLossIface->performanceLossLow(threshold.value(
719 "PerformanceLossLow", std::numeric_limits<double>::quiet_NaN()));
Rashmica Gupta1dff7dc2021-07-27 19:43:31 +1000720 perfLossIface->setHighHysteresis(threshold.value(
721 "PerformanceLossHighHysteresis", defaultHysteresis));
722 perfLossIface->setLowHysteresis(
723 threshold.value("PerformanceLossLowHysteresis", defaultHysteresis));
Rashmica Gupta3e999192021-06-09 16:17:04 +1000724 }
725}
726
Rashmica Guptae7efe132021-07-27 19:42:11 +1000727ManagedObjectType VirtualSensors::getObjectsFromDBus()
728{
729 ManagedObjectType objects;
730
731 try
732 {
Nan Zhouf6825b92022-09-20 20:52:43 +0000733 auto method = bus.new_method_call("xyz.openbmc_project.EntityManager",
734 "/xyz/openbmc_project/inventory",
Rashmica Guptae7efe132021-07-27 19:42:11 +1000735 "org.freedesktop.DBus.ObjectManager",
736 "GetManagedObjects");
737 auto reply = bus.call(method);
738 reply.read(objects);
739 }
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500740 catch (const sdbusplus::exception_t& ex)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000741 {
742 // If entity manager isn't running yet, keep going.
743 if (std::string("org.freedesktop.DBus.Error.ServiceUnknown") !=
744 ex.name())
745 {
Matt Spinler71b9c112022-10-18 09:14:45 -0500746 error("Could not reach entity-manager: {ERROR}", "ERROR", ex);
747 throw;
Rashmica Guptae7efe132021-07-27 19:42:11 +1000748 }
749 }
750
751 return objects;
752}
753
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500754void VirtualSensors::propertiesChanged(sdbusplus::message_t& msg)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000755{
756 std::string path;
757 PropertyMap properties;
758
759 msg.read(path, properties);
760
761 /* We get multiple callbacks for one sensor. 'Type' is a required field and
762 * is a unique label so use to to only proceed once per sensor */
763 if (properties.contains("Type"))
764 {
765 if (isCalculationType(path))
766 {
767 createVirtualSensorsFromDBus(path);
768 }
769 }
770}
771
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700772/** @brief Parsing Virtual Sensor config JSON file */
Patrick Williams32dff212023-02-09 13:54:18 -0600773Json VirtualSensors::parseConfigFile()
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700774{
Patrick Williams32dff212023-02-09 13:54:18 -0600775 using path = std::filesystem::path;
776 auto configFile = []() -> path {
777 static constexpr auto name = "virtual_sensor_config.json";
778
779 for (auto pathSeg : {std::filesystem::current_path(),
780 path{"/var/lib/phosphor-virtual-sensor"},
781 path{"/usr/share/phosphor-virtual-sensor"}})
782 {
783 auto file = pathSeg / name;
784 if (std::filesystem::exists(file))
785 {
786 return file;
787 }
788 }
789 return name;
790 }();
791
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700792 std::ifstream jsonFile(configFile);
793 if (!jsonFile.is_open())
794 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500795 error("config JSON file {FILENAME} not found", "FILENAME", configFile);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000796 return {};
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700797 }
798
799 auto data = Json::parse(jsonFile, nullptr, false);
800 if (data.is_discarded())
801 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500802 error("config readings JSON parser failure with {FILENAME}", "FILENAME",
803 configFile);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700804 throw std::exception{};
805 }
806
807 return data;
808}
809
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700810std::map<std::string, ValueIface::Unit> unitMap = {
811 {"temperature", ValueIface::Unit::DegreesC},
812 {"fan_tach", ValueIface::Unit::RPMS},
813 {"voltage", ValueIface::Unit::Volts},
814 {"altitude", ValueIface::Unit::Meters},
815 {"current", ValueIface::Unit::Amperes},
816 {"power", ValueIface::Unit::Watts},
817 {"energy", ValueIface::Unit::Joules},
Kumar Thangavel2b56ddb2021-01-13 20:16:11 +0530818 {"utilization", ValueIface::Unit::Percent},
Rashmica Gupta4ac7a7f2021-07-08 12:30:50 +1000819 {"airflow", ValueIface::Unit::CFM},
820 {"pressure", ValueIface::Unit::Pascals}};
Vijay Khemkae0d371e2020-09-21 18:35:52 -0700821
Rashmica Guptae7efe132021-07-27 19:42:11 +1000822const std::string getSensorTypeFromUnit(const std::string& unit)
823{
824 std::string unitPrefix = "xyz.openbmc_project.Sensor.Value.Unit.";
825 for (auto [type, unitObj] : unitMap)
826 {
827 auto unitPath = ValueIface::convertUnitToString(unitObj);
828 if (unitPath == (unitPrefix + unit))
829 {
830 return type;
831 }
832 }
833 return "";
834}
835
836void VirtualSensors::setupMatches()
837{
838 /* Already setup */
839 if (!this->matches.empty())
840 {
841 return;
842 }
843
844 /* Setup matches */
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500845 auto eventHandler = [this](sdbusplus::message_t& message) {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000846 if (message.is_method_error())
847 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500848 error("Callback method error");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000849 return;
850 }
851 this->propertiesChanged(message);
852 };
853
854 for (const char* iface : calculationIfaces)
855 {
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500856 auto match = std::make_unique<sdbusplus::bus::match_t>(
Rashmica Guptae7efe132021-07-27 19:42:11 +1000857 bus,
858 sdbusplus::bus::match::rules::propertiesChangedNamespace(
859 "/xyz/openbmc_project/inventory", iface),
860 eventHandler);
861 this->matches.emplace_back(std::move(match));
862 }
863}
864
865void VirtualSensors::createVirtualSensorsFromDBus(
866 const std::string& calculationIface)
867{
868 if (calculationIface.empty())
869 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500870 error("No calculation type supplied");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000871 return;
872 }
873 auto objects = getObjectsFromDBus();
874
875 /* Get virtual sensors config data */
876 for (const auto& [path, interfaceMap] : objects)
877 {
878 auto objpath = static_cast<std::string>(path);
879 std::string name = path.filename();
880 std::string sensorType, sensorUnit;
881
882 /* Find Virtual Sensor interfaces */
883 if (!interfaceMap.contains(calculationIface))
884 {
885 continue;
886 }
887 if (name.empty())
888 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500889 error("Virtual Sensor name not found in entity manager config");
Rashmica Guptae7efe132021-07-27 19:42:11 +1000890 continue;
891 }
892 if (virtualSensorsMap.contains(name))
893 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500894 error("A virtual sensor named {NAME} already exists", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000895 continue;
896 }
897
898 /* Extract the virtual sensor type as we need this to initialize the
899 * sensor */
900 for (const auto& [interface, propertyMap] : interfaceMap)
901 {
902 if (interface != calculationIface)
903 {
904 continue;
905 }
906 auto itr = propertyMap.find("Units");
907 if (itr != propertyMap.end())
908 {
909 sensorUnit = std::get<std::string>(itr->second);
910 break;
911 }
912 }
913 sensorType = getSensorTypeFromUnit(sensorUnit);
914 if (sensorType.empty())
915 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500916 error("Sensor unit type {TYPE} is not supported", "TYPE",
917 sensorUnit);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000918 continue;
919 }
920
921 try
922 {
923 auto virtObjPath = sensorDbusPath + sensorType + "/" + name;
924
925 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
926 bus, virtObjPath.c_str(), interfaceMap, name, sensorType,
Tao Lindc777012022-07-27 20:41:46 +0800927 calculationIface, objpath);
Patrick Williams82b39c62021-07-28 16:22:27 -0500928 info("Added a new virtual sensor: {NAME} {TYPE}", "NAME", name,
929 "TYPE", sensorType);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000930 virtualSensorPtr->updateVirtualSensor();
931
932 /* Initialize unit value for virtual sensor */
933 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
934 virtualSensorPtr->emit_object_added();
935
936 virtualSensorsMap.emplace(name, std::move(virtualSensorPtr));
937
938 /* Setup match for interfaces removed */
939 auto intfRemoved = [this, objpath,
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500940 name](sdbusplus::message_t& message) {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000941 if (!virtualSensorsMap.contains(name))
942 {
943 return;
944 }
945 sdbusplus::message::object_path path;
946 message.read(path);
947 if (static_cast<const std::string&>(path) == objpath)
948 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500949 info("Removed a virtual sensor: {NAME}", "NAME", name);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000950 virtualSensorsMap.erase(name);
951 }
952 };
Patrick Williams8e11ccc2022-07-22 19:26:57 -0500953 auto matchOnRemove = std::make_unique<sdbusplus::bus::match_t>(
Rashmica Guptae7efe132021-07-27 19:42:11 +1000954 bus,
955 sdbusplus::bus::match::rules::interfacesRemoved() +
956 sdbusplus::bus::match::rules::argNpath(0, objpath),
957 intfRemoved);
958 /* TODO: slight race condition here. Check that the config still
959 * exists */
960 this->matches.emplace_back(std::move(matchOnRemove));
961 }
Patrick Williamsdac26632021-10-06 14:38:47 -0500962 catch (const std::invalid_argument& ia)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000963 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500964 error("Failed to set up virtual sensor: {ERROR}", "ERROR", ia);
Rashmica Guptae7efe132021-07-27 19:42:11 +1000965 }
966 }
967}
968
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700969void VirtualSensors::createVirtualSensors()
970{
971 static const Json empty{};
972
Patrick Williams32dff212023-02-09 13:54:18 -0600973 auto data = parseConfigFile();
Rashmica Guptae7efe132021-07-27 19:42:11 +1000974
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700975 // print values
976 if (DEBUG)
Rashmica Guptae7efe132021-07-27 19:42:11 +1000977 {
Patrick Williamsfbd71452021-08-30 06:59:46 -0500978 debug("JSON: {JSON}", "JSON", data.dump());
Rashmica Guptae7efe132021-07-27 19:42:11 +1000979 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -0700980
981 /* Get virtual sensors config data */
982 for (const auto& j : data)
983 {
984 auto desc = j.value("Desc", empty);
985 if (!desc.empty())
986 {
Rashmica Guptae7efe132021-07-27 19:42:11 +1000987 if (desc.value("Config", "") == "D-Bus")
988 {
989 /* Look on D-Bus for a virtual sensor config. Set up matches
990 * first because the configs may not be on D-Bus yet and we
991 * don't want to miss them */
992 setupMatches();
993
994 if (desc.contains("Type"))
995 {
Patrick Williams82b39c62021-07-28 16:22:27 -0500996 auto type = desc.value("Type", "");
997 auto path = "xyz.openbmc_project.Configuration." + type;
998
Rashmica Guptae7efe132021-07-27 19:42:11 +1000999 if (!isCalculationType(path))
1000 {
Patrick Williams82b39c62021-07-28 16:22:27 -05001001 error("Invalid calculation type {TYPE} supplied.",
1002 "TYPE", type);
Rashmica Guptae7efe132021-07-27 19:42:11 +10001003 continue;
1004 }
1005 createVirtualSensorsFromDBus(path);
1006 }
1007 continue;
1008 }
1009
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001010 std::string sensorType = desc.value("SensorType", "");
1011 std::string name = desc.value("Name", "");
Rashmica Gupta665a0a22021-06-30 11:35:28 +10001012 std::replace(name.begin(), name.end(), ' ', '_');
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001013
1014 if (!name.empty() && !sensorType.empty())
1015 {
Vijay Khemkae0d371e2020-09-21 18:35:52 -07001016 if (unitMap.find(sensorType) == unitMap.end())
1017 {
Patrick Williams82b39c62021-07-28 16:22:27 -05001018 error("Sensor type {TYPE} is not supported", "TYPE",
1019 sensorType);
Vijay Khemkae0d371e2020-09-21 18:35:52 -07001020 }
1021 else
1022 {
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +10001023 if (virtualSensorsMap.find(name) != virtualSensorsMap.end())
1024 {
Patrick Williams82b39c62021-07-28 16:22:27 -05001025 error("A virtual sensor named {NAME} already exists",
1026 "NAME", name);
Rashmica Gupta67d8b9d2021-06-30 11:41:14 +10001027 continue;
1028 }
Rashmica Gupta862c3d12021-08-06 12:19:31 +10001029 auto objPath = sensorDbusPath + sensorType + "/" + name;
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001030
Vijay Khemkae0d371e2020-09-21 18:35:52 -07001031 auto virtualSensorPtr = std::make_unique<VirtualSensor>(
1032 bus, objPath.c_str(), j, name);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001033
Patrick Williams82b39c62021-07-28 16:22:27 -05001034 info("Added a new virtual sensor: {NAME}", "NAME", name);
Vijay Khemkae0d371e2020-09-21 18:35:52 -07001035 virtualSensorPtr->updateVirtualSensor();
1036
1037 /* Initialize unit value for virtual sensor */
1038 virtualSensorPtr->ValueIface::unit(unitMap[sensorType]);
Rashmica Guptaa2fa63a2021-08-06 12:21:13 +10001039 virtualSensorPtr->emit_object_added();
Vijay Khemkae0d371e2020-09-21 18:35:52 -07001040
1041 virtualSensorsMap.emplace(std::move(name),
1042 std::move(virtualSensorPtr));
1043 }
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001044 }
1045 else
1046 {
Patrick Williams82b39c62021-07-28 16:22:27 -05001047 error(
1048 "Sensor type ({TYPE}) or name ({NAME}) not found in config file",
1049 "NAME", name, "TYPE", sensorType);
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001050 }
1051 }
1052 else
1053 {
Patrick Williams82b39c62021-07-28 16:22:27 -05001054 error("Descriptor for new virtual sensor not found in config file");
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001055 }
1056 }
1057}
1058
1059} // namespace virtualSensor
1060} // namespace phosphor
1061
1062/**
1063 * @brief Main
1064 */
1065int main()
1066{
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001067 // Get a handle to system dbus
1068 auto bus = sdbusplus::bus::new_default();
1069
Matt Spinler6c19e7d2021-01-12 16:26:45 -06001070 // Add the ObjectManager interface
Ed Tanousf7ec40a2022-10-04 17:39:40 -07001071 sdbusplus::server::manager_t objManager(bus,
1072 "/xyz/openbmc_project/sensors");
Matt Spinler6c19e7d2021-01-12 16:26:45 -06001073
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001074 // Create an virtual sensors object
1075 phosphor::virtualSensor::VirtualSensors virtualSensors(bus);
1076
1077 // Request service bus name
1078 bus.request_name(busName);
1079
Patrick Williamse6672392022-09-02 09:03:24 -05001080 // Run the dbus loop.
1081 bus.process_loop();
Vijay Khemkaabcc94f2020-08-11 15:27:44 -07001082
1083 return 0;
1084}