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