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