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