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