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