xref: /openbmc/bmcweb/features/redfish/lib/sensors.hpp (revision daf36e2e50a6a9c72c94d97e5754a8aa19c9d95b)
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.code != static_cast<int>(HttpRespCode::OK)) {
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.code = static_cast<int>(HttpRespCode::INTERNAL_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(const 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 != 0) {
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(const std::shared_ptr<AsyncResp>& asyncResp,
148                 Callback&& callback) {
149   CROW_LOG_DEBUG << "getChassis Done";
150 
151   // Process response from EntityManager and extract chassis data
152   auto resp_handler = [ callback{std::move(callback)}, asyncResp ](
153       const boost::system::error_code ec, ManagedObjectsVectorType& resp) {
154     CROW_LOG_DEBUG << "getChassis resp_handler called back Done";
155     if (ec) {
156       CROW_LOG_ERROR << "getChassis resp_handler got error " << ec;
157       asyncResp->setErrorStatus();
158       return;
159     }
160     boost::container::flat_set<std::string> sensorNames;
161 
162     //   asyncResp->chassisId
163     bool foundChassis = false;
164     std::vector<std::string> split;
165     // Reserve space for
166     // /xyz/openbmc_project/inventory/<name>/<subname> + 3 subnames
167     split.reserve(8);
168 
169     for (const auto& objDictEntry : resp) {
170       const std::string& objectPath =
171           static_cast<const std::string&>(objDictEntry.first);
172       boost::algorithm::split(split, objectPath, boost::is_any_of("/"));
173       if (split.size() < 2) {
174         CROW_LOG_ERROR << "Got path that isn't long enough " << objectPath;
175         split.clear();
176         continue;
177       }
178       const std::string& sensorName = split.end()[-1];
179       const std::string& chassisName = split.end()[-2];
180 
181       if (chassisName != asyncResp->chassisId) {
182         split.clear();
183         continue;
184       }
185       foundChassis = true;
186       sensorNames.emplace(sensorName);
187       split.clear();
188     };
189     CROW_LOG_DEBUG << "Found " << sensorNames.size() << " Sensor names";
190 
191     if (!foundChassis) {
192       CROW_LOG_INFO << "Unable to find chassis named " << asyncResp->chassisId;
193       asyncResp->res.code = static_cast<int>(HttpRespCode::NOT_FOUND);
194     } else {
195       callback(sensorNames);
196     }
197   };
198 
199   // Make call to EntityManager to find all chassis objects
200   crow::connections::system_bus->async_method_call(
201       resp_handler, "xyz.openbmc_project.EntityManager",
202       "/xyz/openbmc_project/inventory", "org.freedesktop.DBus.ObjectManager",
203       "GetManagedObjects");
204 }
205 
206 /**
207  * @brief Builds a json sensor representation of a sensor.
208  * @param sensorName  The name of the sensor to be built
209  * @param sensorType  The type (temperature, fan_tach, etc) of the sensor to
210  * build
211  * @param interfacesDict  A dictionary of the interfaces and properties of said
212  * interfaces to be built from
213  * @param sensor_json  The json object to fill
214  */
215 void objectInterfacesToJson(
216     const std::string& sensorName, const std::string& sensorType,
217     const boost::container::flat_map<
218         std::string, boost::container::flat_map<std::string, SensorVariant>>&
219         interfacesDict,
220     nlohmann::json& sensor_json) {
221   // We need a value interface before we can do anything with it
222   auto value_it = interfacesDict.find("xyz.openbmc_project.Sensor.Value");
223   if (value_it == interfacesDict.end()) {
224     CROW_LOG_ERROR << "Sensor doesn't have a value interface";
225     return;
226   }
227 
228   // Assume values exist as is (10^0 == 1) if no scale exists
229   int64_t scaleMultiplier = 0;
230 
231   auto scale_it = value_it->second.find("Scale");
232   // If a scale exists, pull value as int64, and use the scaling.
233   if (scale_it != value_it->second.end()) {
234     const int64_t* int64Value =
235         mapbox::get_ptr<const int64_t>(scale_it->second);
236     if (int64Value != nullptr) {
237       scaleMultiplier = *int64Value;
238     }
239   }
240 
241   sensor_json["MemberId"] = sensorName;
242   sensor_json["Name"] = sensorName;
243   sensor_json["Status"]["State"] = "Enabled";
244   sensor_json["Status"]["Health"] = "OK";
245 
246   // Parameter to set to override the type we get from dbus, and force it to
247   // int, regardless of what is available.  This is used for schemas like fan,
248   // that require integers, not floats.
249   bool forceToInt = false;
250 
251   const char* unit = "Reading";
252   if (sensorType == "temperature") {
253     unit = "ReadingCelsius";
254     // TODO(ed) Documentation says that path should be type fan_tach,
255     // implementation seems to implement fan
256   } else if (sensorType == "fan" || sensorType == "fan_type") {
257     unit = "Reading";
258     sensor_json["ReadingUnits"] = "RPM";
259     forceToInt = true;
260   } else if (sensorType == "voltage") {
261     unit = "ReadingVolts";
262   } else {
263     CROW_LOG_ERROR << "Redfish cannot map object type for " << sensorName;
264     return;
265   }
266   // Map of dbus interface name, dbus property name and redfish property_name
267   std::vector<std::tuple<const char*, const char*, const char*>> properties;
268   properties.reserve(7);
269 
270   properties.emplace_back("xyz.openbmc_project.Sensor.Value", "Value", unit);
271   properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
272                           "WarningHigh", "UpperThresholdNonCritical");
273   properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Warning",
274                           "WarningLow", "LowerThresholdNonCritical");
275   properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
276                           "CriticalHigh", "UpperThresholdCritical");
277   properties.emplace_back("xyz.openbmc_project.Sensor.Threshold.Critical",
278                           "CriticalLow", "LowerThresholdCritical");
279 
280   if (sensorType == "temperature") {
281     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
282                             "MinReadingRangeTemp");
283     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
284                             "MaxReadingRangeTemp");
285   } else {
286     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MinValue",
287                             "MinReadingRange");
288     properties.emplace_back("xyz.openbmc_project.Sensor.Value", "MaxValue",
289                             "MaxReadingRange");
290   }
291 
292   for (const std::tuple<const char*, const char*, const char*>& p :
293        properties) {
294     auto interfaceProperties = interfacesDict.find(std::get<0>(p));
295     if (interfaceProperties != interfacesDict.end()) {
296       auto value_it = interfaceProperties->second.find(std::get<1>(p));
297       if (value_it != interfaceProperties->second.end()) {
298         const SensorVariant& valueVariant = value_it->second;
299         nlohmann::json& value_it = sensor_json[std::get<2>(p)];
300 
301         // Attempt to pull the int64 directly
302         const int64_t* int64Value =
303             mapbox::get_ptr<const int64_t>(valueVariant);
304 
305         if (int64Value != nullptr) {
306           if (forceToInt || scaleMultiplier >= 0) {
307             value_it = *int64Value * std::pow(10, scaleMultiplier);
308           } else {
309             value_it = *int64Value *
310                        std::pow(10, static_cast<double>(scaleMultiplier));
311           }
312         }
313         // Attempt to pull the float directly
314         const double* doubleValue = mapbox::get_ptr<const double>(valueVariant);
315 
316         if (doubleValue != nullptr) {
317           if (!forceToInt) {
318             value_it = *doubleValue *
319                        std::pow(10, static_cast<double>(scaleMultiplier));
320           } else {
321             value_it = static_cast<int64_t>(*doubleValue *
322                                             std::pow(10, scaleMultiplier));
323           }
324         }
325       }
326     }
327   }
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(const 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 =
341         [&, asyncResp, sensorNames](
342             const boost::container::flat_set<std::string>& 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 "
356                                << objPath;
357                 if (!boost::starts_with(objPath, DBUS_SENSOR_PREFIX)) {
358                   CROW_LOG_ERROR << "Got path that isn't in sensor namespace: "
359                                  << objPath;
360                   continue;
361                 }
362                 std::vector<std::string> split;
363                 // Reserve space for
364                 // /xyz/openbmc_project/Sensors/<name>/<subname>
365                 split.reserve(6);
366                 boost::algorithm::split(split, objPath, boost::is_any_of("/"));
367                 if (split.size() < 6) {
368                   CROW_LOG_ERROR << "Got path that isn't long enough "
369                                  << objPath;
370                   continue;
371                 }
372                 // These indexes aren't intuitive, as boost::split puts an empty
373                 // string at the beginning
374                 const std::string& sensorType = split[4];
375                 const std::string& sensorName = split[5];
376                 CROW_LOG_DEBUG << "sensorName " << sensorName << " sensorType "
377                                << sensorType;
378                 if (sensorNames.find(sensorName) == sensorNames.end()) {
379                   CROW_LOG_ERROR << sensorName << " not in sensor list ";
380                   continue;
381                 }
382 
383                 const char* fieldName = nullptr;
384                 if (sensorType == "temperature") {
385                   fieldName = "Temperatures";
386                 } else if (sensorType == "fan" || sensorType == "fan_tach") {
387                   fieldName = "Fans";
388                 } else if (sensorType == "voltage") {
389                   fieldName = "Voltages";
390                 } else if (sensorType == "current") {
391                   fieldName = "PowerSupply";
392                 } else if (sensorType == "power") {
393                   fieldName = "PowerSupply";
394                 } else {
395                   CROW_LOG_ERROR << "Unsure how to handle sensorType "
396                                  << sensorType;
397                   continue;
398                 }
399 
400                 nlohmann::json& temp_array =
401                     asyncResp->res.json_value[fieldName];
402 
403                 // Create the array if it doesn't yet exist
404                 if (temp_array.is_array() == false) {
405                   temp_array = nlohmann::json::array();
406                 }
407 
408                 temp_array.push_back(nlohmann::json::object());
409                 nlohmann::json& sensor_json = temp_array.back();
410                 sensor_json["@odata.id"] = "/redfish/v1/Chassis/" +
411                                            asyncResp->chassisId + "/Thermal#/" +
412                                            sensorName;
413                 objectInterfacesToJson(sensorName, sensorType,
414                                        objDictEntry.second, sensor_json);
415               }
416             };
417 
418             crow::connections::system_bus->async_method_call(
419                 getManagedObjectsCb, connection, "/xyz/openbmc_project/Sensors",
420                 "org.freedesktop.DBus.ObjectManager", "GetManagedObjects");
421           };
422         };
423     // Get connections and then pass it to get sensors
424     getConnections(asyncResp, sensorNames, std::move(getConnectionCb));
425   };
426 
427   // Get chassis information related to sensors
428   getChassis(asyncResp, std::move(getChassisCb));
429 };
430 
431 }  // namespace redfish
432