xref: /openbmc/bmcweb/features/redfish/lib/sensors.hpp (revision 2474adfaf23ea9409891924662ec36e7217b4535)
1 /*
2 // Copyright (c) 2018 Intel 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 #pragma once
17 
18 #include <math.h>
19 
20 #include <boost/algorithm/string/predicate.hpp>
21 #include <boost/algorithm/string/split.hpp>
22 #include <boost/container/flat_map.hpp>
23 #include <boost/range/algorithm/replace_copy_if.hpp>
24 #include <dbus_singleton.hpp>
25 
26 namespace redfish
27 {
28 
29 constexpr const char* dbusSensorPrefix = "/xyz/openbmc_project/sensors/";
30 
31 using GetSubTreeType = std::vector<
32     std::pair<std::string,
33               std::vector<std::pair<std::string, std::vector<std::string>>>>>;
34 
35 using SensorVariant = sdbusplus::message::variant<int64_t, double>;
36 
37 using ManagedObjectsVectorType = std::vector<std::pair<
38     sdbusplus::message::object_path,
39     boost::container::flat_map<
40         std::string, boost::container::flat_map<std::string, SensorVariant>>>>;
41 
42 /**
43  * SensorsAsyncResp
44  * Gathers data needed for response processing after async calls are done
45  */
46 class SensorsAsyncResp
47 {
48   public:
49     SensorsAsyncResp(crow::Response& response, const std::string& chassisId,
50                      const std::initializer_list<const char*> types,
51                      const std::string& subNode) :
52         chassisId(chassisId),
53         res(response), types(types), chassisSubNode(subNode)
54     {
55         res.jsonValue["@odata.id"] =
56             "/redfish/v1/Chassis/" + chassisId + "/Thermal";
57     }
58 
59     ~SensorsAsyncResp()
60     {
61         if (res.result() == boost::beast::http::status::internal_server_error)
62         {
63             // Reset the json object to clear out any data that made it in
64             // before the error happened todo(ed) handle error condition with
65             // proper code
66             res.jsonValue = nlohmann::json::object();
67         }
68         res.end();
69     }
70 
71     void setErrorStatus()
72     {
73         res.result(boost::beast::http::status::internal_server_error);
74     }
75 
76     crow::Response& res;
77     std::string chassisId{};
78     const std::vector<const char*> types;
79     std::string chassisSubNode{};
80 };
81 
82 /**
83  * @brief Creates connections necessary for chassis sensors
84  * @param SensorsAsyncResp Pointer to object holding response data
85  * @param sensorNames Sensors retrieved from chassis
86  * @param callback Callback for processing gathered connections
87  */
88 template <typename Callback>
89 void getConnections(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
90                     const boost::container::flat_set<std::string>& sensorNames,
91                     Callback&& callback)
92 {
93     BMCWEB_LOG_DEBUG << "getConnections enter";
94     const std::string path = "/xyz/openbmc_project/sensors";
95     const std::array<std::string, 1> interfaces = {
96         "xyz.openbmc_project.Sensor.Value"};
97 
98     // Response handler for parsing objects subtree
99     auto respHandler = [callback{std::move(callback)}, SensorsAsyncResp,
100                         sensorNames](const boost::system::error_code ec,
101                                      const GetSubTreeType& subtree) {
102         BMCWEB_LOG_DEBUG << "getConnections resp_handler enter";
103         if (ec)
104         {
105             SensorsAsyncResp->setErrorStatus();
106             BMCWEB_LOG_ERROR << "getConnections resp_handler: Dbus error "
107                              << ec;
108             return;
109         }
110 
111         BMCWEB_LOG_DEBUG << "Found " << subtree.size() << " subtrees";
112 
113         // Make unique list of connections only for requested sensor types and
114         // found in the chassis
115         boost::container::flat_set<std::string> connections;
116         // Intrinsic to avoid malloc.  Most systems will have < 8 sensor
117         // producers
118         connections.reserve(8);
119 
120         BMCWEB_LOG_DEBUG << "sensorNames list count: " << sensorNames.size();
121         for (const std::string& tsensor : sensorNames)
122         {
123             BMCWEB_LOG_DEBUG << "Sensor to find: " << tsensor;
124         }
125 
126         for (const std::pair<
127                  std::string,
128                  std::vector<std::pair<std::string, std::vector<std::string>>>>&
129                  object : subtree)
130         {
131             for (const char* type : SensorsAsyncResp->types)
132             {
133                 if (boost::starts_with(object.first, type))
134                 {
135                     auto lastPos = object.first.rfind('/');
136                     if (lastPos != std::string::npos)
137                     {
138                         std::string sensorName =
139                             object.first.substr(lastPos + 1);
140 
141                         if (sensorNames.find(sensorName) != sensorNames.end())
142                         {
143                             // For each Connection name
144                             for (const std::pair<std::string,
145                                                  std::vector<std::string>>&
146                                      objData : object.second)
147                             {
148                                 BMCWEB_LOG_DEBUG << "Adding connection: "
149                                                  << objData.first;
150                                 connections.insert(objData.first);
151                             }
152                         }
153                     }
154                     break;
155                 }
156             }
157         }
158         BMCWEB_LOG_DEBUG << "Found " << connections.size() << " connections";
159         callback(std::move(connections));
160         BMCWEB_LOG_DEBUG << "getConnections resp_handler exit";
161     };
162 
163     // Make call to ObjectMapper to find all sensors objects
164     crow::connections::systemBus->async_method_call(
165         std::move(respHandler), "xyz.openbmc_project.ObjectMapper",
166         "/xyz/openbmc_project/object_mapper",
167         "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 2, interfaces);
168     BMCWEB_LOG_DEBUG << "getConnections exit";
169 }
170 
171 /**
172  * @brief Retrieves requested chassis sensors and redundancy data from DBus .
173  * @param SensorsAsyncResp   Pointer to object holding response data
174  * @param callback  Callback for next step in gathered sensor processing
175  */
176 template <typename Callback>
177 void getChassis(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
178                 Callback&& callback)
179 {
180     BMCWEB_LOG_DEBUG << "getChassis enter";
181     // Process response from EntityManager and extract chassis data
182     auto respHandler = [callback{std::move(callback)},
183                         SensorsAsyncResp](const boost::system::error_code ec,
184                                           ManagedObjectsVectorType& resp) {
185         BMCWEB_LOG_DEBUG << "getChassis respHandler enter";
186         if (ec)
187         {
188             BMCWEB_LOG_ERROR << "getChassis respHandler DBUS error: " << ec;
189             SensorsAsyncResp->setErrorStatus();
190             return;
191         }
192         boost::container::flat_set<std::string> sensorNames;
193 
194         //   SensorsAsyncResp->chassisId
195         bool foundChassis = false;
196         std::vector<std::string> split;
197         // Reserve space for
198         // /xyz/openbmc_project/inventory/<name>/<subname> + 3 subnames
199         split.reserve(8);
200 
201         for (const auto& objDictEntry : resp)
202         {
203             const std::string& objectPath =
204                 static_cast<const std::string&>(objDictEntry.first);
205             boost::algorithm::split(split, objectPath, boost::is_any_of("/"));
206             if (split.size() < 2)
207             {
208                 BMCWEB_LOG_ERROR << "Got path that isn't long enough "
209                                  << objectPath;
210                 split.clear();
211                 continue;
212             }
213             const std::string& sensorName = split.end()[-1];
214             const std::string& chassisName = split.end()[-2];
215 
216             if (chassisName != SensorsAsyncResp->chassisId)
217             {
218                 split.clear();
219                 continue;
220             }
221             BMCWEB_LOG_DEBUG << "New sensor: " << sensorName;
222             foundChassis = true;
223             sensorNames.emplace(sensorName);
224             split.clear();
225         };
226         BMCWEB_LOG_DEBUG << "Found " << sensorNames.size() << " Sensor names";
227 
228         if (!foundChassis)
229         {
230             BMCWEB_LOG_INFO << "Unable to find chassis named "
231                             << SensorsAsyncResp->chassisId;
232             messages::resourceNotFound(SensorsAsyncResp->res, "Chassis",
233                                        SensorsAsyncResp->chassisId);
234         }
235         else
236         {
237             callback(sensorNames);
238         }
239         BMCWEB_LOG_DEBUG << "getChassis respHandler exit";
240     };
241 
242     // Make call to EntityManager to find all chassis objects
243     crow::connections::systemBus->async_method_call(
244         respHandler, "xyz.openbmc_project.EntityManager", "/",
245         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
246     BMCWEB_LOG_DEBUG << "getChassis exit";
247 }
248 
249 /**
250  * @brief Builds a json sensor representation of a sensor.
251  * @param sensorName  The name of the sensor to be built
252  * @param sensorType  The type (temperature, fan_tach, etc) of the sensor to
253  * build
254  * @param interfacesDict  A dictionary of the interfaces and properties of said
255  * interfaces to be built from
256  * @param sensor_json  The json object to fill
257  */
258 void objectInterfacesToJson(
259     const std::string& sensorName, const std::string& sensorType,
260     const boost::container::flat_map<
261         std::string, boost::container::flat_map<std::string, SensorVariant>>&
262         interfacesDict,
263     nlohmann::json& sensor_json)
264 {
265     // We need a value interface before we can do anything with it
266     auto valueIt = interfacesDict.find("xyz.openbmc_project.Sensor.Value");
267     if (valueIt == interfacesDict.end())
268     {
269         BMCWEB_LOG_ERROR << "Sensor doesn't have a value interface";
270         return;
271     }
272 
273     // Assume values exist as is (10^0 == 1) if no scale exists
274     int64_t scaleMultiplier = 0;
275 
276     auto scaleIt = valueIt->second.find("Scale");
277     // If a scale exists, pull value as int64, and use the scaling.
278     if (scaleIt != valueIt->second.end())
279     {
280         const int64_t* int64Value =
281             mapbox::getPtr<const int64_t>(scaleIt->second);
282         if (int64Value != nullptr)
283         {
284             scaleMultiplier = *int64Value;
285         }
286     }
287 
288     sensor_json["MemberId"] = sensorName;
289     sensor_json["Name"] = sensorName;
290     sensor_json["Status"]["State"] = "Enabled";
291     sensor_json["Status"]["Health"] = "OK";
292 
293     // Parameter to set to override the type we get from dbus, and force it to
294     // int, regardless of what is available.  This is used for schemas like fan,
295     // that require integers, not floats.
296     bool forceToInt = false;
297 
298     const char* unit = "Reading";
299     if (sensorType == "temperature")
300     {
301         unit = "ReadingCelsius";
302         sensor_json["@odata.type"] = "#Thermal.v1_3_0.Temperature";
303         // TODO(ed) Documentation says that path should be type fan_tach,
304         // implementation seems to implement fan
305     }
306     else if (sensorType == "fan" || sensorType == "fan_tach")
307     {
308         unit = "Reading";
309         sensor_json["ReadingUnits"] = "RPM";
310         sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan";
311         forceToInt = true;
312     }
313     else if (sensorType == "fan_pwm")
314     {
315         unit = "Reading";
316         sensor_json["ReadingUnits"] = "Percent";
317         sensor_json["@odata.type"] = "#Thermal.v1_3_0.Fan";
318         forceToInt = true;
319     }
320     else if (sensorType == "voltage")
321     {
322         unit = "ReadingVolts";
323         sensor_json["@odata.type"] = "#Power.v1_0_0.Voltage";
324     }
325     else if (sensorType == "power")
326     {
327         unit = "LastPowerOutputWatts";
328     }
329     else
330     {
331         BMCWEB_LOG_ERROR << "Redfish cannot map object type for " << sensorName;
332         return;
333     }
334     // Map of dbus interface name, dbus property name and redfish property_name
335     std::vector<std::tuple<const char*, const char*, const char*>> properties;
336     properties.reserve(7);
337 
338     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
339     properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
340                             "WarningHigh", "UpperThresholdNonCritical");
341     properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
342                             "WarningLow", "LowerThresholdNonCritical");
343     properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
344                             "CriticalHigh", "UpperThresholdCritical");
345     properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
346                             "CriticalLow", "LowerThresholdCritical");
347 
348     // TODO Need to get UpperThresholdFatal and LowerThresholdFatal
349 
350     if (sensorType == "temperature")
351     {
352         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
353                                 "MinReadingRangeTemp");
354         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
355                                 "MaxReadingRangeTemp");
356     }
357     else
358     {
359         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
360                                 "MinReadingRange");
361         properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
362                                 "MaxReadingRange");
363     }
364 
365     for (const std::tuple<const char*, const char*, const char*>& p :
366          properties)
367     {
368         auto interfaceProperties = interfacesDict.find(std::get<0>(p));
369         if (interfaceProperties != interfacesDict.end())
370         {
371             auto valueIt = interfaceProperties->second.find(std::get<1>(p));
372             if (valueIt != interfaceProperties->second.end())
373             {
374                 const SensorVariant& valueVariant = valueIt->second;
375                 nlohmann::json& valueIt = sensor_json[std::get<2>(p)];
376                 // Attempt to pull the int64 directly
377                 const int64_t* int64Value =
378                     mapbox::getPtr<const int64_t>(valueVariant);
379 
380                 const double* doubleValue =
381                     mapbox::getPtr<const double>(valueVariant);
382                 double temp = 0.0;
383                 if (int64Value != nullptr)
384                 {
385                     temp = *int64Value;
386                 }
387                 else if (doubleValue != nullptr)
388                 {
389                     temp = *doubleValue;
390                 }
391                 else
392                 {
393                     BMCWEB_LOG_ERROR
394                         << "Got value interface that wasn't int or double";
395                     continue;
396                 }
397                 temp = temp * std::pow(10, scaleMultiplier);
398                 if (forceToInt)
399                 {
400                     valueIt = static_cast<int64_t>(temp);
401                 }
402                 else
403                 {
404                     valueIt = temp;
405                 }
406             }
407         }
408     }
409     BMCWEB_LOG_DEBUG << "Added sensor " << sensorName;
410 }
411 
412 /**
413  * @brief Entry point for retrieving sensors data related to requested
414  *        chassis.
415  * @param SensorsAsyncResp   Pointer to object holding response data
416  */
417 void getChassisData(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp)
418 {
419     BMCWEB_LOG_DEBUG << "getChassisData enter";
420     auto getChassisCb = [&, SensorsAsyncResp](
421                             boost::container::flat_set<std::string>&
422                                 sensorNames) {
423         BMCWEB_LOG_DEBUG << "getChassisCb enter";
424         auto getConnectionCb =
425             [&, SensorsAsyncResp, sensorNames](
426                 const boost::container::flat_set<std::string>& connections) {
427                 BMCWEB_LOG_DEBUG << "getConnectionCb enter";
428                 // Get managed objects from all services exposing sensors
429                 for (const std::string& connection : connections)
430                 {
431                     // Response handler to process managed objects
432                     auto getManagedObjectsCb =
433                         [&, SensorsAsyncResp,
434                          sensorNames](const boost::system::error_code ec,
435                                       ManagedObjectsVectorType& resp) {
436                             BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter";
437                             if (ec)
438                             {
439                                 BMCWEB_LOG_ERROR
440                                     << "getManagedObjectsCb DBUS error: " << ec;
441                                 SensorsAsyncResp->setErrorStatus();
442                                 return;
443                             }
444                             // Go through all objects and update response with
445                             // sensor data
446                             for (const auto& objDictEntry : resp)
447                             {
448                                 const std::string& objPath =
449                                     static_cast<const std::string&>(
450                                         objDictEntry.first);
451                                 BMCWEB_LOG_DEBUG
452                                     << "getManagedObjectsCb parsing object "
453                                     << objPath;
454 
455                                 std::vector<std::string> split;
456                                 // Reserve space for
457                                 // /xyz/openbmc_project/sensors/<name>/<subname>
458                                 split.reserve(6);
459                                 boost::algorithm::split(split, objPath,
460                                                         boost::is_any_of("/"));
461                                 if (split.size() < 6)
462                                 {
463                                     BMCWEB_LOG_ERROR
464                                         << "Got path that isn't long enough "
465                                         << objPath;
466                                     continue;
467                                 }
468                                 // These indexes aren't intuitive, as
469                                 // boost::split puts an empty string at the
470                                 // beggining
471                                 const std::string& sensorType = split[4];
472                                 const std::string& sensorName = split[5];
473                                 BMCWEB_LOG_DEBUG << "sensorName " << sensorName
474                                                  << " sensorType "
475                                                  << sensorType;
476                                 if (sensorNames.find(sensorName) ==
477                                     sensorNames.end())
478                                 {
479                                     BMCWEB_LOG_ERROR << sensorName
480                                                      << " not in sensor list ";
481                                     continue;
482                                 }
483 
484                                 const char* fieldName = nullptr;
485                                 if (sensorType == "temperature")
486                                 {
487                                     fieldName = "Temperatures";
488                                 }
489                                 else if (sensorType == "fan" ||
490                                          sensorType == "fan_tach" ||
491                                          sensorType == "fan_pwm")
492                                 {
493                                     fieldName = "Fans";
494                                 }
495                                 else if (sensorType == "voltage")
496                                 {
497                                     fieldName = "Voltages";
498                                 }
499                                 else if (sensorType == "current")
500                                 {
501                                     fieldName = "PowerSupply";
502                                 }
503                                 else if (sensorType == "power")
504                                 {
505                                     fieldName = "PowerSupply";
506                                 }
507                                 else
508                                 {
509                                     BMCWEB_LOG_ERROR
510                                         << "Unsure how to handle sensorType "
511                                         << sensorType;
512                                     continue;
513                                 }
514 
515                                 nlohmann::json& tempArray =
516                                     SensorsAsyncResp->res.jsonValue[fieldName];
517 
518                                 tempArray.push_back(
519                                     {{"@odata.id",
520                                       "/redfish/v1/Chassis/" +
521                                           SensorsAsyncResp->chassisId + "/" +
522                                           SensorsAsyncResp->chassisSubNode +
523                                           "#/" + fieldName + "/" +
524                                           std::to_string(tempArray.size())}});
525                                 nlohmann::json& sensorJson = tempArray.back();
526 
527                                 objectInterfacesToJson(sensorName, sensorType,
528                                                        objDictEntry.second,
529                                                        sensorJson);
530                             }
531                             BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit";
532                         };
533                     crow::connections::systemBus->async_method_call(
534                         getManagedObjectsCb, connection, "/",
535                         "org.freedesktop.DBus.ObjectManager",
536                         "GetManagedObjects");
537                 };
538                 BMCWEB_LOG_DEBUG << "getConnectionCb exit";
539             };
540         // get connections and then pass it to get sensors
541         getConnections(SensorsAsyncResp, sensorNames,
542                        std::move(getConnectionCb));
543         BMCWEB_LOG_DEBUG << "getChassisCb exit";
544     };
545 
546     // get chassis information related to sensors
547     getChassis(SensorsAsyncResp, std::move(getChassisCb));
548     BMCWEB_LOG_DEBUG << "getChassisData exit";
549 };
550 
551 } // namespace redfish
552