xref: /openbmc/bmcweb/redfish-core/lib/sensors.hpp (revision c9ab1d509ec70f895f852532f6c5b3314cb4fe65)
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 #include <variant>
26 
27 namespace redfish
28 {
29 
30 constexpr const char* dbusSensorPrefix = "/xyz/openbmc_project/sensors/";
31 
32 using GetSubTreeType = std::vector<
33     std::pair<std::string,
34               std::vector<std::pair<std::string, std::vector<std::string>>>>>;
35 
36 using SensorVariant = std::variant<int64_t, double>;
37 
38 using ManagedObjectsVectorType = std::vector<std::pair<
39     sdbusplus::message::object_path,
40     boost::container::flat_map<
41         std::string, boost::container::flat_map<std::string, SensorVariant>>>>;
42 
43 /**
44  * SensorsAsyncResp
45  * Gathers data needed for response processing after async calls are done
46  */
47 class SensorsAsyncResp
48 {
49   public:
50     SensorsAsyncResp(crow::Response& response, const std::string& chassisId,
51                      const std::initializer_list<const char*> types,
52                      const std::string& subNode) :
53         res(response),
54         chassisId(chassisId), types(types), chassisSubNode(subNode)
55     {
56         res.jsonValue["@odata.id"] =
57             "/redfish/v1/Chassis/" + chassisId + "/Thermal";
58     }
59 
60     ~SensorsAsyncResp()
61     {
62         if (res.result() == boost::beast::http::status::internal_server_error)
63         {
64             // Reset the json object to clear out any data that made it in
65             // before the error happened todo(ed) handle error condition with
66             // proper code
67             res.jsonValue = nlohmann::json::object();
68         }
69         res.end();
70     }
71 
72     crow::Response& res;
73     std::string chassisId{};
74     const std::vector<const char*> types;
75     std::string chassisSubNode{};
76 };
77 
78 /**
79  * @brief Creates connections necessary for chassis sensors
80  * @param SensorsAsyncResp Pointer to object holding response data
81  * @param sensorNames Sensors retrieved from chassis
82  * @param callback Callback for processing gathered connections
83  */
84 template <typename Callback>
85 void getConnections(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
86                     const boost::container::flat_set<std::string>& sensorNames,
87                     Callback&& callback)
88 {
89     BMCWEB_LOG_DEBUG << "getConnections enter";
90     const std::string path = "/xyz/openbmc_project/sensors";
91     const std::array<std::string, 1> interfaces = {
92         "xyz.openbmc_project.Sensor.Value"};
93 
94     // Response handler for parsing objects subtree
95     auto respHandler = [callback{std::move(callback)}, SensorsAsyncResp,
96                         sensorNames](const boost::system::error_code ec,
97                                      const GetSubTreeType& subtree) {
98         BMCWEB_LOG_DEBUG << "getConnections resp_handler enter";
99         if (ec)
100         {
101             messages::internalError(SensorsAsyncResp->res);
102             BMCWEB_LOG_ERROR << "getConnections resp_handler: Dbus error "
103                              << ec;
104             return;
105         }
106 
107         BMCWEB_LOG_DEBUG << "Found " << subtree.size() << " subtrees";
108 
109         // Make unique list of connections only for requested sensor types and
110         // found in the chassis
111         boost::container::flat_set<std::string> connections;
112         // Intrinsic to avoid malloc.  Most systems will have < 8 sensor
113         // producers
114         connections.reserve(8);
115 
116         BMCWEB_LOG_DEBUG << "sensorNames list count: " << sensorNames.size();
117         for (const std::string& tsensor : sensorNames)
118         {
119             BMCWEB_LOG_DEBUG << "Sensor to find: " << tsensor;
120         }
121 
122         for (const std::pair<
123                  std::string,
124                  std::vector<std::pair<std::string, std::vector<std::string>>>>&
125                  object : subtree)
126         {
127             for (const char* type : SensorsAsyncResp->types)
128             {
129                 if (boost::starts_with(object.first, type))
130                 {
131                     auto lastPos = object.first.rfind('/');
132                     if (lastPos != std::string::npos)
133                     {
134                         std::string sensorName =
135                             object.first.substr(lastPos + 1);
136 
137                         if (sensorNames.find(sensorName) != sensorNames.end())
138                         {
139                             // For each Connection name
140                             for (const std::pair<std::string,
141                                                  std::vector<std::string>>&
142                                      objData : object.second)
143                             {
144                                 BMCWEB_LOG_DEBUG << "Adding connection: "
145                                                  << objData.first;
146                                 connections.insert(objData.first);
147                             }
148                         }
149                     }
150                     break;
151                 }
152             }
153         }
154         BMCWEB_LOG_DEBUG << "Found " << connections.size() << " connections";
155         callback(std::move(connections));
156         BMCWEB_LOG_DEBUG << "getConnections resp_handler exit";
157     };
158 
159     // Make call to ObjectMapper to find all sensors objects
160     crow::connections::systemBus->async_method_call(
161         std::move(respHandler), "xyz.openbmc_project.ObjectMapper",
162         "/xyz/openbmc_project/object_mapper",
163         "xyz.openbmc_project.ObjectMapper", "GetSubTree", path, 2, interfaces);
164     BMCWEB_LOG_DEBUG << "getConnections exit";
165 }
166 
167 /**
168  * @brief Retrieves requested chassis sensors and redundancy data from DBus .
169  * @param SensorsAsyncResp   Pointer to object holding response data
170  * @param callback  Callback for next step in gathered sensor processing
171  */
172 template <typename Callback>
173 void getChassis(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp,
174                 Callback&& callback)
175 {
176     BMCWEB_LOG_DEBUG << "getChassis enter";
177     // Process response from EntityManager and extract chassis data
178     auto respHandler = [callback{std::move(callback)},
179                         SensorsAsyncResp](const boost::system::error_code ec,
180                                           ManagedObjectsVectorType& resp) {
181         BMCWEB_LOG_DEBUG << "getChassis respHandler enter";
182         if (ec)
183         {
184             BMCWEB_LOG_ERROR << "getChassis respHandler DBUS error: " << ec;
185             messages::internalError(SensorsAsyncResp->res);
186             return;
187         }
188         boost::container::flat_set<std::string> sensorNames;
189 
190         //   SensorsAsyncResp->chassisId
191         bool foundChassis = false;
192         std::vector<std::string> split;
193         // Reserve space for
194         // /xyz/openbmc_project/inventory/<name>/<subname> + 3 subnames
195         split.reserve(8);
196 
197         for (const auto& objDictEntry : resp)
198         {
199             const std::string& objectPath =
200                 static_cast<const std::string&>(objDictEntry.first);
201             boost::algorithm::split(split, objectPath, boost::is_any_of("/"));
202             if (split.size() < 2)
203             {
204                 BMCWEB_LOG_ERROR << "Got path that isn't long enough "
205                                  << objectPath;
206                 split.clear();
207                 continue;
208             }
209             const std::string& sensorName = split.end()[-1];
210             const std::string& chassisName = split.end()[-2];
211 
212             if (chassisName != SensorsAsyncResp->chassisId)
213             {
214                 split.clear();
215                 continue;
216             }
217             BMCWEB_LOG_DEBUG << "New sensor: " << sensorName;
218             foundChassis = true;
219             sensorNames.emplace(sensorName);
220             split.clear();
221         };
222         BMCWEB_LOG_DEBUG << "Found " << sensorNames.size() << " Sensor names";
223 
224         if (!foundChassis)
225         {
226             BMCWEB_LOG_INFO << "Unable to find chassis named "
227                             << SensorsAsyncResp->chassisId;
228             messages::resourceNotFound(SensorsAsyncResp->res, "Chassis",
229                                        SensorsAsyncResp->chassisId);
230         }
231         else
232         {
233             callback(sensorNames);
234         }
235         BMCWEB_LOG_DEBUG << "getChassis respHandler exit";
236     };
237 
238     // Make call to EntityManager to find all chassis objects
239     crow::connections::systemBus->async_method_call(
240         respHandler, "xyz.openbmc_project.EntityManager", "/",
241         "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
242     BMCWEB_LOG_DEBUG << "getChassis exit";
243 }
244 
245 /**
246  * @brief Builds a json sensor representation of a sensor.
247  * @param sensorName  The name of the sensor to be built
248  * @param sensorType  The type (temperature, fan_tach, etc) of the sensor to
249  * build
250  * @param interfacesDict  A dictionary of the interfaces and properties of said
251  * interfaces to be built from
252  * @param sensor_json  The json object to fill
253  */
254 void objectInterfacesToJson(
255     const std::string& sensorName, const std::string& sensorType,
256     const boost::container::flat_map<
257         std::string, boost::container::flat_map<std::string, SensorVariant>>&
258         interfacesDict,
259     nlohmann::json& sensor_json)
260 {
261     // We need a value interface before we can do anything with it
262     auto valueIt = interfacesDict.find("xyz.openbmc_project.Sensor.Value");
263     if (valueIt == interfacesDict.end())
264     {
265         BMCWEB_LOG_ERROR << "Sensor doesn't have a value interface";
266         return;
267     }
268 
269     // Assume values exist as is (10^0 == 1) if no scale exists
270     int64_t scaleMultiplier = 0;
271 
272     auto scaleIt = valueIt->second.find("Scale");
273     // If a scale exists, pull value as int64, and use the scaling.
274     if (scaleIt != valueIt->second.end())
275     {
276         const int64_t* int64Value = std::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 = std::get_if<int64_t>(&valueVariant);
373 
374                 const double* doubleValue = std::get_if<double>(&valueVariant);
375                 double temp = 0.0;
376                 if (int64Value != nullptr)
377                 {
378                     temp = *int64Value;
379                 }
380                 else if (doubleValue != nullptr)
381                 {
382                     temp = *doubleValue;
383                 }
384                 else
385                 {
386                     BMCWEB_LOG_ERROR
387                         << "Got value interface that wasn't int or double";
388                     continue;
389                 }
390                 temp = temp * std::pow(10, scaleMultiplier);
391                 if (forceToInt)
392                 {
393                     valueIt = static_cast<int64_t>(temp);
394                 }
395                 else
396                 {
397                     valueIt = temp;
398                 }
399             }
400         }
401     }
402     BMCWEB_LOG_DEBUG << "Added sensor " << sensorName;
403 }
404 
405 /**
406  * @brief Entry point for retrieving sensors data related to requested
407  *        chassis.
408  * @param SensorsAsyncResp   Pointer to object holding response data
409  */
410 void getChassisData(std::shared_ptr<SensorsAsyncResp> SensorsAsyncResp)
411 {
412     BMCWEB_LOG_DEBUG << "getChassisData enter";
413     auto getChassisCb = [&, SensorsAsyncResp](
414                             boost::container::flat_set<std::string>&
415                                 sensorNames) {
416         BMCWEB_LOG_DEBUG << "getChassisCb enter";
417         auto getConnectionCb =
418             [&, SensorsAsyncResp, sensorNames](
419                 const boost::container::flat_set<std::string>& connections) {
420                 BMCWEB_LOG_DEBUG << "getConnectionCb enter";
421                 // Get managed objects from all services exposing sensors
422                 for (const std::string& connection : connections)
423                 {
424                     // Response handler to process managed objects
425                     auto getManagedObjectsCb =
426                         [&, SensorsAsyncResp,
427                          sensorNames](const boost::system::error_code ec,
428                                       ManagedObjectsVectorType& resp) {
429                             BMCWEB_LOG_DEBUG << "getManagedObjectsCb enter";
430                             if (ec)
431                             {
432                                 BMCWEB_LOG_ERROR
433                                     << "getManagedObjectsCb DBUS error: " << ec;
434                                 messages::internalError(SensorsAsyncResp->res);
435                                 return;
436                             }
437                             // Go through all objects and update response with
438                             // sensor data
439                             for (const auto& objDictEntry : resp)
440                             {
441                                 const std::string& objPath =
442                                     static_cast<const std::string&>(
443                                         objDictEntry.first);
444                                 BMCWEB_LOG_DEBUG
445                                     << "getManagedObjectsCb parsing object "
446                                     << objPath;
447 
448                                 std::vector<std::string> split;
449                                 // Reserve space for
450                                 // /xyz/openbmc_project/sensors/<name>/<subname>
451                                 split.reserve(6);
452                                 boost::algorithm::split(split, objPath,
453                                                         boost::is_any_of("/"));
454                                 if (split.size() < 6)
455                                 {
456                                     BMCWEB_LOG_ERROR
457                                         << "Got path that isn't long enough "
458                                         << objPath;
459                                     continue;
460                                 }
461                                 // These indexes aren't intuitive, as
462                                 // boost::split puts an empty string at the
463                                 // beggining
464                                 const std::string& sensorType = split[4];
465                                 const std::string& sensorName = split[5];
466                                 BMCWEB_LOG_DEBUG << "sensorName " << sensorName
467                                                  << " sensorType "
468                                                  << sensorType;
469                                 if (sensorNames.find(sensorName) ==
470                                     sensorNames.end())
471                                 {
472                                     BMCWEB_LOG_ERROR << sensorName
473                                                      << " not in sensor list ";
474                                     continue;
475                                 }
476 
477                                 const char* fieldName = nullptr;
478                                 if (sensorType == "temperature")
479                                 {
480                                     fieldName = "Temperatures";
481                                 }
482                                 else if (sensorType == "fan" ||
483                                          sensorType == "fan_tach" ||
484                                          sensorType == "fan_pwm")
485                                 {
486                                     fieldName = "Fans";
487                                 }
488                                 else if (sensorType == "voltage")
489                                 {
490                                     fieldName = "Voltages";
491                                 }
492                                 else if (sensorType == "current")
493                                 {
494                                     fieldName = "PowerSupply";
495                                 }
496                                 else if (sensorType == "power")
497                                 {
498                                     fieldName = "PowerSupply";
499                                 }
500                                 else
501                                 {
502                                     BMCWEB_LOG_ERROR
503                                         << "Unsure how to handle sensorType "
504                                         << sensorType;
505                                     continue;
506                                 }
507 
508                                 nlohmann::json& tempArray =
509                                     SensorsAsyncResp->res.jsonValue[fieldName];
510 
511                                 tempArray.push_back(
512                                     {{"@odata.id",
513                                       "/redfish/v1/Chassis/" +
514                                           SensorsAsyncResp->chassisId + "/" +
515                                           SensorsAsyncResp->chassisSubNode +
516                                           "#/" + fieldName + "/" +
517                                           std::to_string(tempArray.size())}});
518                                 nlohmann::json& sensorJson = tempArray.back();
519 
520                                 objectInterfacesToJson(sensorName, sensorType,
521                                                        objDictEntry.second,
522                                                        sensorJson);
523                             }
524                             BMCWEB_LOG_DEBUG << "getManagedObjectsCb exit";
525                         };
526                     crow::connections::systemBus->async_method_call(
527                         getManagedObjectsCb, connection, "/",
528                         "org.freedesktop.DBus.ObjectManager",
529                         "GetManagedObjects");
530                 };
531                 BMCWEB_LOG_DEBUG << "getConnectionCb exit";
532             };
533         // get connections and then pass it to get sensors
534         getConnections(SensorsAsyncResp, sensorNames,
535                        std::move(getConnectionCb));
536         BMCWEB_LOG_DEBUG << "getChassisCb exit";
537     };
538 
539     // get chassis information related to sensors
540     getChassis(SensorsAsyncResp, std::move(getChassisCb));
541     BMCWEB_LOG_DEBUG << "getChassisData exit";
542 };
543 
544 } // namespace redfish
545