blob: e6d37a09d06713610d53a71ccba017ebc24f7e47 [file] [log] [blame]
Tomáš Pecka339bc672020-11-11 15:59:03 +01001/*
2 * Copyright (C) 2016-2020 CESNET, https://photonics.cesnet.cz/
3 *
4 * Written by Tomáš Pecka <tomas.pecka@fit.cvut.cz>
5 *
6 */
7
Václav Kubernát069d3a92021-11-14 12:37:46 +01008#include <chrono>
Tomáš Pecka72f540e2024-07-08 13:45:22 +02009#include <libyang-cpp/Time.hpp>
Tomáš Pecka339bc672020-11-11 15:59:03 +010010#include <utility>
11#include "IETFHardware.h"
12#include "utils/log.h"
Tomáš Pecka339bc672020-11-11 15:59:03 +010013
14using namespace std::literals;
15
16namespace {
17
Tomáš Pecka83b62e12020-12-16 14:50:49 +010018static const std::string ietfHardwareStatePrefix = "/ietf-hardware:hardware";
Tomáš Pecka339bc672020-11-11 15:59:03 +010019
20/** @brief Constructs a full XPath for a specific component */
21std::string xpathForComponent(const std::string& componentName)
22{
23 return ietfHardwareStatePrefix + "/component[name='" + componentName + "']/";
24}
25
26/** @brief Prefix all properties from values DataTree with a component name (calculated from @p componentName) and push them into the DataTree */
27void addComponent(velia::ietf_hardware::DataTree& res, const std::string& componentName, const std::optional<std::string>& parent, const velia::ietf_hardware::DataTree& values)
28{
29 auto componentPrefix = xpathForComponent(componentName);
30
31 if (parent) {
32 res[componentPrefix + "parent"] = *parent;
33 }
34 for (const auto& [k, v] : values) {
35 res[componentPrefix + k] = v;
36 }
Tomáš Pecka7eb0c422023-04-21 15:36:33 +020037
38 res[componentPrefix + "state/oper-state"] = "enabled";
Tomáš Pecka339bc672020-11-11 15:59:03 +010039}
40
Tomáš Pecka655062c2023-12-11 15:37:58 +010041void writeSensorValue(velia::ietf_hardware::DataTree& res, const std::string& componentName, const std::string& value, const std::string& operStatus)
Tomáš Pecka339bc672020-11-11 15:59:03 +010042{
43 const auto componentPrefix = xpathForComponent(componentName);
44 res[componentPrefix + "sensor-data/value"] = value;
Tomáš Pecka655062c2023-12-11 15:37:58 +010045 res[componentPrefix + "sensor-data/oper-status"] = operStatus;
46}
47
48/** @brief Write a sensor-data @p value for a component @p componentName and push it into the @p res DataTree */
Tomáš Peckacd7f9cc2023-12-11 15:52:48 +010049void addSensorValue(velia::Log log, velia::ietf_hardware::DataTree& res, const std::string& componentName, int64_t value)
Tomáš Pecka655062c2023-12-11 15:37:58 +010050{
Tomáš Peckacd7f9cc2023-12-11 15:52:48 +010051 static constexpr const int64_t YANG_SENSOR_VALUE_MIN = -1'000'000'000;
52 static constexpr const int64_t YANG_SENSOR_VALUE_MAX = 1'000'000'000;
53
54 // FIXME: Valid value range depends on sensor-type as well, see ietf-hardware's sensor-value type description
55
56 if (value <= YANG_SENSOR_VALUE_MIN) {
57 log->error("Sensor's '{}' value '{}' underflow. Setting sensor as nonoperational.", componentName, value);
58 writeSensorValue(res, componentName, std::to_string(YANG_SENSOR_VALUE_MIN), "nonoperational");
59 } else if (value >= YANG_SENSOR_VALUE_MAX) {
60 log->error("Sensor's '{}' value '{}' overflow. Setting sensor as nonoperational.", componentName, value);
61 writeSensorValue(res, componentName, std::to_string(YANG_SENSOR_VALUE_MAX), "nonoperational");
62 } else {
63 writeSensorValue(res, componentName, std::to_string(value), "ok");
64 }
Tomáš Pecka655062c2023-12-11 15:37:58 +010065}
66
67/** @brief Write a sensor-data @p value for a component @p componentName and push it into the @p res DataTree */
Tomáš Peckacd7f9cc2023-12-11 15:52:48 +010068void addSensorValue(velia::Log, velia::ietf_hardware::DataTree& res, const std::string& componentName, const std::string& value)
Tomáš Pecka655062c2023-12-11 15:37:58 +010069{
70 // TODO: Perhaps we should check if the string value is conforming to sensor-value type
71 writeSensorValue(res, componentName, value, "ok");
Tomáš Pecka339bc672020-11-11 15:59:03 +010072}
73}
74
75namespace velia::ietf_hardware {
76
Tomáš Peckac0991ce2023-12-20 15:46:03 +010077void SensorPollData::merge(SensorPollData&& other)
78{
79 data.merge(other.data);
80 thresholds.merge(other.thresholds);
Tomáš Pecka26b38212024-01-16 17:23:31 +010081 sideLoadedAlarms.merge(other.sideLoadedAlarms);
Tomáš Peckac0991ce2023-12-20 15:46:03 +010082}
83
Tomáš Pecka0d8d8ee2023-05-10 12:22:58 +020084IETFHardware::IETFHardware()
85 : m_log(spdlog::get("hardware"))
86{
87}
Tomáš Pecka339bc672020-11-11 15:59:03 +010088
89IETFHardware::~IETFHardware() = default;
90
Tomáš Pecka0d8d8ee2023-05-10 12:22:58 +020091/**
92 * Calls individual registered data readers and processes information obtained from them.
93 * The sensor values are then passed to threshold watchers to detect if the new value violated a threshold.
94 * This function does not raise any alarms. It only returns the data tree *and* any changes in the threshold
95 * crossings (as a mapping from sensor XPath to threshold watcher State (@see Watcher)).
96 **/
97HardwareInfo IETFHardware::process()
Tomáš Pecka339bc672020-11-11 15:59:03 +010098{
Tomáš Peckac0991ce2023-12-20 15:46:03 +010099 SensorPollData pollData;
100 std::set<std::string> activeSensors;
Tomáš Pecka0d8d8ee2023-05-10 12:22:58 +0200101 std::map<std::string, State> alarms;
Tomáš Pecka339bc672020-11-11 15:59:03 +0100102
103 for (auto& dataReader : m_callbacks) {
Tomáš Peckac0991ce2023-12-20 15:46:03 +0100104 pollData.merge(dataReader());
105 }
106
107 /* the thresholds watchers are created dynamically
108 * - when a new sensor occurs then we add a new watcher
109 * - when a sensor disappears we remove the corresponding watcher
110 */
111 for (const auto& [sensorXPath, sensorThresholds] : pollData.thresholds) {
112 if (!m_thresholdsWatchers.contains(sensorXPath)) {
113 m_thresholdsWatchers.emplace(sensorXPath, sensorThresholds);
114 }
115 activeSensors.emplace(sensorXPath);
Tomáš Pecka339bc672020-11-11 15:59:03 +0100116 }
117
Tomáš Pecka0d8d8ee2023-05-10 12:22:58 +0200118 for (auto& [sensorXPath, thresholdsWatcher] : m_thresholdsWatchers) {
119 std::optional<int64_t> newValue;
120
Tomáš Peckac0991ce2023-12-20 15:46:03 +0100121 if (auto it = pollData.data.find(sensorXPath); it != pollData.data.end()) {
Tomáš Pecka0d8d8ee2023-05-10 12:22:58 +0200122 newValue = std::stoll(it->second);
123 } else {
124 newValue = std::nullopt;
125 }
126
127 if (auto newState = thresholdsWatcher.update(newValue)) {
128 m_log->debug("threshold: {} {}", sensorXPath, *newState);
129 alarms.emplace(sensorXPath, *newState);
130 }
131 }
132
Tomáš Pecka72f540e2024-07-08 13:45:22 +0200133 pollData.data[ietfHardwareStatePrefix + "/last-change"] = libyang::yangTimeFormat(std::chrono::system_clock::now(), libyang::TimezoneInterpretation::Unspecified);
Tomáš Pecka0d8d8ee2023-05-10 12:22:58 +0200134
Tomáš Pecka26b38212024-01-16 17:23:31 +0100135 return {pollData.data, alarms, activeSensors, pollData.sideLoadedAlarms};
Tomáš Pecka339bc672020-11-11 15:59:03 +0100136}
137
Tomáš Peckac0991ce2023-12-20 15:46:03 +0100138void IETFHardware::registerDataReader(const IETFHardware::DataReader& callable)
Tomáš Peckad2322ad2023-05-11 16:20:38 +0200139{
Tomáš Peckac0991ce2023-12-20 15:46:03 +0100140 m_callbacks.push_back(callable);
Tomáš Peckad2322ad2023-05-11 16:20:38 +0200141}
142
Tomáš Pecka339bc672020-11-11 15:59:03 +0100143/** @brief A namespace containing predefined data readers for IETFHardware class.
144 * @see IETFHardware for more information
145 */
146namespace data_reader {
147
148DataReader::DataReader(std::string componentName, std::optional<std::string> parent)
149 : m_componentName(std::move(componentName))
150 , m_parent(std::move(parent))
Tomáš Peckacd7f9cc2023-12-11 15:52:48 +0100151 , m_log(spdlog::get("hardware"))
Tomáš Pecka339bc672020-11-11 15:59:03 +0100152{
153}
154
155/** @brief Constructs a component without any sensor-data and provide only the static data @p dataTree passed via constructor.
156 * @param componentName the name of the component in the resulting tree
157 * @param parent The component in YANG model has a link to parent. Specify who is the parent.
158 * @param dataTree static data to insert into the resulting tree. The dataTree keys should only contain the YANG node name, not full XPath. The full XPath is constructed from @componentName and the map key.
159 */
160StaticData::StaticData(std::string componentName, std::optional<std::string> parent, DataTree dataTree)
161 : DataReader(std::move(componentName), std::move(parent))
162{
163 addComponent(m_staticData,
164 m_componentName,
165 m_parent,
166 dataTree);
167}
168
Tomáš Pecka26b38212024-01-16 17:23:31 +0100169SensorPollData StaticData::operator()() const { return {m_staticData, {}, {}}; }
Tomáš Pecka339bc672020-11-11 15:59:03 +0100170
Tomáš Pecka4886db22023-05-10 10:46:15 +0200171Fans::Fans(std::string componentName, std::optional<std::string> parent, std::shared_ptr<sysfs::HWMon> hwmon, unsigned fanChannelsCount, Thresholds<int64_t> thresholds)
Tomáš Pecka339bc672020-11-11 15:59:03 +0100172 : DataReader(std::move(componentName), std::move(parent))
173 , m_hwmon(std::move(hwmon))
174 , m_fanChannelsCount(fanChannelsCount)
Tomáš Pecka4886db22023-05-10 10:46:15 +0200175 , m_thresholds(std::move(thresholds))
Tomáš Pecka339bc672020-11-11 15:59:03 +0100176{
177 // fans
178 addComponent(m_staticData,
179 m_componentName,
180 m_parent,
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200181 DataTree{
Tomáš Pecka339bc672020-11-11 15:59:03 +0100182 {"class", "iana-hardware:module"}, // FIXME: Read (or pass via constructor) additional properties (mfg, model, ...). They should be in the fans' tray EEPROM.
183 });
184
185 for (unsigned i = 1; i <= m_fanChannelsCount; i++) {
186 // fans -> fan_i
187 addComponent(m_staticData,
188 m_componentName + ":fan" + std::to_string(i),
189 m_componentName,
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200190 DataTree{
Tomáš Pecka339bc672020-11-11 15:59:03 +0100191 {"class", "iana-hardware:fan"},
192 });
193
194 // fans -> fan_i -> sensor-data
195 addComponent(m_staticData,
196 m_componentName + ":fan" + std::to_string(i) + ":rpm",
197 m_componentName + ":fan" + std::to_string(i),
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200198 DataTree{
Tomáš Pecka339bc672020-11-11 15:59:03 +0100199 {"class", "iana-hardware:sensor"},
200 {"sensor-data/value-type", "rpm"},
201 {"sensor-data/value-scale", "units"},
202 {"sensor-data/value-precision", "0"},
Tomáš Pecka339bc672020-11-11 15:59:03 +0100203 });
204 }
205}
206
Tomáš Peckac0991ce2023-12-20 15:46:03 +0100207SensorPollData Fans::operator()() const
Tomáš Pecka339bc672020-11-11 15:59:03 +0100208{
Tomáš Peckac0991ce2023-12-20 15:46:03 +0100209 DataTree data(m_staticData);
Tomáš Pecka339bc672020-11-11 15:59:03 +0100210 for (unsigned i = 1; i <= m_fanChannelsCount; i++) {
211 const auto sensorComponentName = m_componentName + ":fan" + std::to_string(i) + ":rpm";
212 const auto attribute = "fan"s + std::to_string(i) + "_input";
213
Tomáš Peckac0991ce2023-12-20 15:46:03 +0100214 addSensorValue(m_log, data, sensorComponentName, m_hwmon->attribute(attribute));
Tomáš Pecka339bc672020-11-11 15:59:03 +0100215 }
216
Tomáš Peckac0991ce2023-12-20 15:46:03 +0100217 ThresholdsBySensorPath thr;
Tomáš Pecka4886db22023-05-10 10:46:15 +0200218 for (unsigned i = 1; i <= m_fanChannelsCount; i++) {
Tomáš Peckac0991ce2023-12-20 15:46:03 +0100219 thr.emplace(xpathForComponent(m_componentName + ":fan" + std::to_string(i) + ":rpm") + "sensor-data/value", m_thresholds);
Tomáš Pecka4886db22023-05-10 10:46:15 +0200220 }
221
Tomáš Pecka26b38212024-01-16 17:23:31 +0100222 return {data, thr, {}};
Tomáš Pecka4886db22023-05-10 10:46:15 +0200223}
224
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200225std::string getSysfsFilename(const SensorType type, int sysfsChannelNr)
226{
227 switch (type) {
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200228 case SensorType::Temperature:
229 return "temp"s + std::to_string(sysfsChannelNr) + "_input";
230 case SensorType::Current:
231 return "curr"s + std::to_string(sysfsChannelNr) + "_input";
232 case SensorType::Power:
233 return "power"s + std::to_string(sysfsChannelNr) + "_input";
234 case SensorType::VoltageAC:
235 case SensorType::VoltageDC:
236 return "in"s + std::to_string(sysfsChannelNr) + "_input";
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200237 }
238
239 __builtin_unreachable();
240}
241
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200242template <SensorType TYPE>
243const DataTree sysfsStaticData;
244template <>
245const DataTree sysfsStaticData<SensorType::Temperature> = {
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200246 {"class", "iana-hardware:sensor"},
247 {"sensor-data/value-type", "celsius"},
248 {"sensor-data/value-scale", "milli"},
249 {"sensor-data/value-precision", "0"},
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200250};
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200251template <>
252const DataTree sysfsStaticData<SensorType::Current> = {
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100253 {"class", "iana-hardware:sensor"},
254 {"sensor-data/value-type", "amperes"},
255 {"sensor-data/value-scale", "milli"},
256 {"sensor-data/value-precision", "0"},
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100257};
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200258template <>
259const DataTree sysfsStaticData<SensorType::Power> = {
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100260 {"class", "iana-hardware:sensor"},
261 {"sensor-data/value-type", "watts"},
262 {"sensor-data/value-scale", "micro"},
263 {"sensor-data/value-precision", "0"},
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100264};
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200265template <>
266const DataTree sysfsStaticData<SensorType::VoltageAC> = {
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100267 {"class", "iana-hardware:sensor"},
268 {"sensor-data/value-type", "volts-AC"},
Tomáš Pecka76323602022-05-11 09:33:59 +0200269 {"sensor-data/value-scale", "milli"},
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100270 {"sensor-data/value-precision", "0"},
Tomáš Pecka5f07eea2023-12-11 15:23:01 +0100271};
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200272template <>
273const DataTree sysfsStaticData<SensorType::VoltageDC> = {
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100274 {"class", "iana-hardware:sensor"},
275 {"sensor-data/value-type", "volts-DC"},
Tomáš Pecka76323602022-05-11 09:33:59 +0200276 {"sensor-data/value-scale", "milli"},
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100277 {"sensor-data/value-precision", "0"},
Tomáš Pecka5f07eea2023-12-11 15:23:01 +0100278};
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200279
280template <SensorType TYPE>
Tomáš Pecka4886db22023-05-10 10:46:15 +0200281SysfsValue<TYPE>::SysfsValue(std::string componentName, std::optional<std::string> parent, std::shared_ptr<sysfs::HWMon> hwmon, int sysfsChannelNr, Thresholds<int64_t> thresholds)
Tomáš Pecka339bc672020-11-11 15:59:03 +0100282 : DataReader(std::move(componentName), std::move(parent))
283 , m_hwmon(std::move(hwmon))
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200284 , m_sysfsFile(getSysfsFilename(TYPE, sysfsChannelNr))
Tomáš Pecka4886db22023-05-10 10:46:15 +0200285 , m_thresholds(std::move(thresholds))
Tomáš Pecka339bc672020-11-11 15:59:03 +0100286{
287 addComponent(m_staticData,
288 m_componentName,
289 m_parent,
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200290 sysfsStaticData<TYPE>);
Tomáš Pecka339bc672020-11-11 15:59:03 +0100291}
292
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200293template <SensorType TYPE>
Tomáš Peckac0991ce2023-12-20 15:46:03 +0100294SensorPollData SysfsValue<TYPE>::operator()() const
Tomáš Pecka339bc672020-11-11 15:59:03 +0100295{
296 DataTree res(m_staticData);
297
Václav Kubernát0dee6b92021-04-13 09:14:04 +0200298 int64_t sensorValue = m_hwmon->attribute(m_sysfsFile);
Tomáš Peckacd7f9cc2023-12-11 15:52:48 +0100299 addSensorValue(m_log, res, m_componentName, sensorValue);
Tomáš Pecka339bc672020-11-11 15:59:03 +0100300
Tomáš Pecka26b38212024-01-16 17:23:31 +0100301 return {res, ThresholdsBySensorPath{{xpathForComponent(m_componentName) + "sensor-data/value", m_thresholds}}, {}};
Tomáš Pecka4886db22023-05-10 10:46:15 +0200302}
303
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100304template struct SysfsValue<SensorType::Current>;
305template struct SysfsValue<SensorType::Power>;
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200306template struct SysfsValue<SensorType::Temperature>;
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100307template struct SysfsValue<SensorType::VoltageAC>;
308template struct SysfsValue<SensorType::VoltageDC>;
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200309
Tomáš Pecka4886db22023-05-10 10:46:15 +0200310EMMC::EMMC(std::string componentName, std::optional<std::string> parent, std::shared_ptr<sysfs::EMMC> emmc, Thresholds<int64_t> thresholds)
Tomáš Pecka339bc672020-11-11 15:59:03 +0100311 : DataReader(std::move(componentName), std::move(parent))
312 , m_emmc(std::move(emmc))
Tomáš Pecka4886db22023-05-10 10:46:15 +0200313 , m_thresholds(std::move(thresholds))
Tomáš Pecka339bc672020-11-11 15:59:03 +0100314{
315 auto emmcAttrs = m_emmc->attributes();
316
317 // date is specified in MM/YYYY format (source: kernel core/mmc.c) and mfg-date is unfortunately of type yang:date-and-time
318 std::string mfgDate = emmcAttrs.at("date");
Tomáš Peckaf1cba742023-05-03 16:26:37 +0200319 std::chrono::year_month_day calendarDate(
320 std::chrono::year(std::stoi(mfgDate.substr(3, 4))),
321 std::chrono::month(std::stoi(mfgDate.substr(0, 2))),
322 std::chrono::day(1));
Tomáš Pecka72f540e2024-07-08 13:45:22 +0200323 mfgDate = libyang::yangTimeFormat(std::chrono::sys_days{calendarDate}, libyang::TimezoneInterpretation::Unspecified);
Tomáš Pecka339bc672020-11-11 15:59:03 +0100324
325 addComponent(m_staticData,
326 m_componentName,
327 m_parent,
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200328 DataTree{
Tomáš Pecka339bc672020-11-11 15:59:03 +0100329 {"class", "iana-hardware:module"},
330 {"mfg-date", mfgDate},
331 {"serial-num", emmcAttrs.at("serial")},
332 {"model-name", emmcAttrs.at("name")},
333 });
334
335 addComponent(m_staticData,
336 m_componentName + ":lifetime",
337 m_componentName,
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200338 DataTree{
Tomáš Pecka339bc672020-11-11 15:59:03 +0100339 {"class", "iana-hardware:sensor"},
340 {"sensor-data/value-type", "other"},
341 {"sensor-data/value-scale", "units"},
342 {"sensor-data/value-precision", "0"},
Tomáš Pecka339bc672020-11-11 15:59:03 +0100343 {"sensor-data/units-display", "percent"s},
344 });
345}
346
Tomáš Peckac0991ce2023-12-20 15:46:03 +0100347SensorPollData EMMC::operator()() const
Tomáš Pecka339bc672020-11-11 15:59:03 +0100348{
Tomáš Peckac0991ce2023-12-20 15:46:03 +0100349 DataTree data(m_staticData);
Tomáš Pecka339bc672020-11-11 15:59:03 +0100350
351 auto emmcAttrs = m_emmc->attributes();
Tomáš Peckac0991ce2023-12-20 15:46:03 +0100352 addSensorValue(m_log, data, m_componentName + ":lifetime", emmcAttrs.at("life_time"));
Tomáš Pecka339bc672020-11-11 15:59:03 +0100353
Tomáš Pecka26b38212024-01-16 17:23:31 +0100354 return {data, ThresholdsBySensorPath{{xpathForComponent(m_componentName + ":lifetime") + "sensor-data/value", m_thresholds}}, {}};
Tomáš Pecka339bc672020-11-11 15:59:03 +0100355}
Tomáš Pecka339bc672020-11-11 15:59:03 +0100356}
357}