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