blob: 000fc2a5e1542145ff442b0be42e392bb6a53754 [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áš Pecka339bc672020-11-11 15:59:03 +01009#include <utility>
10#include "IETFHardware.h"
11#include "utils/log.h"
12#include "utils/time.h"
13
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áš Pecka0d8d8ee2023-05-10 12:22:58 +020077IETFHardware::IETFHardware()
78 : m_log(spdlog::get("hardware"))
79{
80}
Tomáš Pecka339bc672020-11-11 15:59:03 +010081
82IETFHardware::~IETFHardware() = default;
83
Tomáš Pecka0d8d8ee2023-05-10 12:22:58 +020084/**
85 * Calls individual registered data readers and processes information obtained from them.
86 * The sensor values are then passed to threshold watchers to detect if the new value violated a threshold.
87 * This function does not raise any alarms. It only returns the data tree *and* any changes in the threshold
88 * crossings (as a mapping from sensor XPath to threshold watcher State (@see Watcher)).
89 **/
90HardwareInfo IETFHardware::process()
Tomáš Pecka339bc672020-11-11 15:59:03 +010091{
Tomáš Pecka0d8d8ee2023-05-10 12:22:58 +020092 DataTree dataTree;
93 std::map<std::string, State> alarms;
Tomáš Pecka339bc672020-11-11 15:59:03 +010094
95 for (auto& dataReader : m_callbacks) {
Tomáš Pecka0d8d8ee2023-05-10 12:22:58 +020096 dataTree.merge(dataReader());
Tomáš Pecka339bc672020-11-11 15:59:03 +010097 }
98
Tomáš Pecka0d8d8ee2023-05-10 12:22:58 +020099 for (auto& [sensorXPath, thresholdsWatcher] : m_thresholdsWatchers) {
100 std::optional<int64_t> newValue;
101
102 if (auto it = dataTree.find(sensorXPath); it != dataTree.end()) {
103 newValue = std::stoll(it->second);
104 } else {
105 newValue = std::nullopt;
106 }
107
108 if (auto newState = thresholdsWatcher.update(newValue)) {
109 m_log->debug("threshold: {} {}", sensorXPath, *newState);
110 alarms.emplace(sensorXPath, *newState);
111 }
112 }
113
114 dataTree[ietfHardwareStatePrefix + "/last-change"] = velia::utils::yangTimeFormat(std::chrono::system_clock::now());
115
116 return {dataTree, alarms};
Tomáš Pecka339bc672020-11-11 15:59:03 +0100117}
118
Tomáš Peckad2322ad2023-05-11 16:20:38 +0200119std::vector<std::string> IETFHardware::sensorsXPaths() const
120{
121 std::vector<std::string> res;
122
123 for (const auto& [sensorXPath, thresholds] : m_thresholdsWatchers) {
124 res.emplace_back(sensorXPath);
125 }
126
127 return res;
128}
129
Tomáš Pecka339bc672020-11-11 15:59:03 +0100130/** @brief A namespace containing predefined data readers for IETFHardware class.
131 * @see IETFHardware for more information
132 */
133namespace data_reader {
134
135DataReader::DataReader(std::string componentName, std::optional<std::string> parent)
136 : m_componentName(std::move(componentName))
137 , m_parent(std::move(parent))
Tomáš Peckacd7f9cc2023-12-11 15:52:48 +0100138 , m_log(spdlog::get("hardware"))
Tomáš Pecka339bc672020-11-11 15:59:03 +0100139{
140}
141
142/** @brief Constructs a component without any sensor-data and provide only the static data @p dataTree passed via constructor.
143 * @param componentName the name of the component in the resulting tree
144 * @param parent The component in YANG model has a link to parent. Specify who is the parent.
145 * @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.
146 */
147StaticData::StaticData(std::string componentName, std::optional<std::string> parent, DataTree dataTree)
148 : DataReader(std::move(componentName), std::move(parent))
149{
150 addComponent(m_staticData,
151 m_componentName,
152 m_parent,
153 dataTree);
154}
155
156DataTree StaticData::operator()() const { return m_staticData; }
Tomáš Pecka4886db22023-05-10 10:46:15 +0200157ThresholdsBySensorPath StaticData::thresholds() const { return {}; }
Tomáš Pecka339bc672020-11-11 15:59:03 +0100158
Tomáš Pecka4886db22023-05-10 10:46:15 +0200159Fans::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 +0100160 : DataReader(std::move(componentName), std::move(parent))
161 , m_hwmon(std::move(hwmon))
162 , m_fanChannelsCount(fanChannelsCount)
Tomáš Pecka4886db22023-05-10 10:46:15 +0200163 , m_thresholds(std::move(thresholds))
Tomáš Pecka339bc672020-11-11 15:59:03 +0100164{
165 // fans
166 addComponent(m_staticData,
167 m_componentName,
168 m_parent,
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200169 DataTree{
Tomáš Pecka339bc672020-11-11 15:59:03 +0100170 {"class", "iana-hardware:module"}, // FIXME: Read (or pass via constructor) additional properties (mfg, model, ...). They should be in the fans' tray EEPROM.
171 });
172
173 for (unsigned i = 1; i <= m_fanChannelsCount; i++) {
174 // fans -> fan_i
175 addComponent(m_staticData,
176 m_componentName + ":fan" + std::to_string(i),
177 m_componentName,
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200178 DataTree{
Tomáš Pecka339bc672020-11-11 15:59:03 +0100179 {"class", "iana-hardware:fan"},
180 });
181
182 // fans -> fan_i -> sensor-data
183 addComponent(m_staticData,
184 m_componentName + ":fan" + std::to_string(i) + ":rpm",
185 m_componentName + ":fan" + std::to_string(i),
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200186 DataTree{
Tomáš Pecka339bc672020-11-11 15:59:03 +0100187 {"class", "iana-hardware:sensor"},
188 {"sensor-data/value-type", "rpm"},
189 {"sensor-data/value-scale", "units"},
190 {"sensor-data/value-precision", "0"},
Tomáš Pecka339bc672020-11-11 15:59:03 +0100191 });
192 }
193}
194
195DataTree Fans::operator()() const
196{
197 DataTree res(m_staticData);
198
Tomáš Pecka339bc672020-11-11 15:59:03 +0100199 for (unsigned i = 1; i <= m_fanChannelsCount; i++) {
200 const auto sensorComponentName = m_componentName + ":fan" + std::to_string(i) + ":rpm";
201 const auto attribute = "fan"s + std::to_string(i) + "_input";
202
Tomáš Peckacd7f9cc2023-12-11 15:52:48 +0100203 addSensorValue(m_log, res, sensorComponentName, m_hwmon->attribute(attribute));
Tomáš Pecka339bc672020-11-11 15:59:03 +0100204 }
205
206 return res;
207}
208
Tomáš Pecka4886db22023-05-10 10:46:15 +0200209ThresholdsBySensorPath Fans::thresholds() const
210{
211 ThresholdsBySensorPath res;
212
213 for (unsigned i = 1; i <= m_fanChannelsCount; i++) {
214 res.emplace(xpathForComponent(m_componentName + ":fan" + std::to_string(i) + ":rpm") + "sensor-data/value", m_thresholds);
215 }
216
217 return res;
218}
219
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200220std::string getSysfsFilename(const SensorType type, int sysfsChannelNr)
221{
222 switch (type) {
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200223 case SensorType::Temperature:
224 return "temp"s + std::to_string(sysfsChannelNr) + "_input";
225 case SensorType::Current:
226 return "curr"s + std::to_string(sysfsChannelNr) + "_input";
227 case SensorType::Power:
228 return "power"s + std::to_string(sysfsChannelNr) + "_input";
229 case SensorType::VoltageAC:
230 case SensorType::VoltageDC:
231 return "in"s + std::to_string(sysfsChannelNr) + "_input";
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200232 }
233
234 __builtin_unreachable();
235}
236
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200237template <SensorType TYPE>
238const DataTree sysfsStaticData;
239template <>
240const DataTree sysfsStaticData<SensorType::Temperature> = {
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200241 {"class", "iana-hardware:sensor"},
242 {"sensor-data/value-type", "celsius"},
243 {"sensor-data/value-scale", "milli"},
244 {"sensor-data/value-precision", "0"},
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200245};
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200246template <>
247const DataTree sysfsStaticData<SensorType::Current> = {
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100248 {"class", "iana-hardware:sensor"},
249 {"sensor-data/value-type", "amperes"},
250 {"sensor-data/value-scale", "milli"},
251 {"sensor-data/value-precision", "0"},
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100252};
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200253template <>
254const DataTree sysfsStaticData<SensorType::Power> = {
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100255 {"class", "iana-hardware:sensor"},
256 {"sensor-data/value-type", "watts"},
257 {"sensor-data/value-scale", "micro"},
258 {"sensor-data/value-precision", "0"},
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100259};
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200260template <>
261const DataTree sysfsStaticData<SensorType::VoltageAC> = {
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100262 {"class", "iana-hardware:sensor"},
263 {"sensor-data/value-type", "volts-AC"},
Tomáš Pecka76323602022-05-11 09:33:59 +0200264 {"sensor-data/value-scale", "milli"},
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100265 {"sensor-data/value-precision", "0"},
Tomáš Pecka5f07eea2023-12-11 15:23:01 +0100266};
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200267template <>
268const DataTree sysfsStaticData<SensorType::VoltageDC> = {
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100269 {"class", "iana-hardware:sensor"},
270 {"sensor-data/value-type", "volts-DC"},
Tomáš Pecka76323602022-05-11 09:33:59 +0200271 {"sensor-data/value-scale", "milli"},
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100272 {"sensor-data/value-precision", "0"},
Tomáš Pecka5f07eea2023-12-11 15:23:01 +0100273};
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200274
275template <SensorType TYPE>
Tomáš Pecka4886db22023-05-10 10:46:15 +0200276SysfsValue<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 +0100277 : DataReader(std::move(componentName), std::move(parent))
278 , m_hwmon(std::move(hwmon))
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200279 , m_sysfsFile(getSysfsFilename(TYPE, sysfsChannelNr))
Tomáš Pecka4886db22023-05-10 10:46:15 +0200280 , m_thresholds(std::move(thresholds))
Tomáš Pecka339bc672020-11-11 15:59:03 +0100281{
282 addComponent(m_staticData,
283 m_componentName,
284 m_parent,
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200285 sysfsStaticData<TYPE>);
Tomáš Pecka339bc672020-11-11 15:59:03 +0100286}
287
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200288template <SensorType TYPE>
289DataTree SysfsValue<TYPE>::operator()() const
Tomáš Pecka339bc672020-11-11 15:59:03 +0100290{
291 DataTree res(m_staticData);
292
Václav Kubernát0dee6b92021-04-13 09:14:04 +0200293 int64_t sensorValue = m_hwmon->attribute(m_sysfsFile);
Tomáš Peckacd7f9cc2023-12-11 15:52:48 +0100294 addSensorValue(m_log, res, m_componentName, sensorValue);
Tomáš Pecka339bc672020-11-11 15:59:03 +0100295
296 return res;
297}
298
Tomáš Pecka4886db22023-05-10 10:46:15 +0200299template <SensorType TYPE>
300ThresholdsBySensorPath SysfsValue<TYPE>::thresholds() const
301{
302 return {{xpathForComponent(m_componentName) + "sensor-data/value", m_thresholds}};
303}
304
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100305template struct SysfsValue<SensorType::Current>;
306template struct SysfsValue<SensorType::Power>;
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200307template struct SysfsValue<SensorType::Temperature>;
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100308template struct SysfsValue<SensorType::VoltageAC>;
309template struct SysfsValue<SensorType::VoltageDC>;
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200310
Tomáš Pecka4886db22023-05-10 10:46:15 +0200311EMMC::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 +0100312 : DataReader(std::move(componentName), std::move(parent))
313 , m_emmc(std::move(emmc))
Tomáš Pecka4886db22023-05-10 10:46:15 +0200314 , m_thresholds(std::move(thresholds))
Tomáš Pecka339bc672020-11-11 15:59:03 +0100315{
316 auto emmcAttrs = m_emmc->attributes();
317
318 // date is specified in MM/YYYY format (source: kernel core/mmc.c) and mfg-date is unfortunately of type yang:date-and-time
319 std::string mfgDate = emmcAttrs.at("date");
Tomáš Peckaf1cba742023-05-03 16:26:37 +0200320 std::chrono::year_month_day calendarDate(
321 std::chrono::year(std::stoi(mfgDate.substr(3, 4))),
322 std::chrono::month(std::stoi(mfgDate.substr(0, 2))),
323 std::chrono::day(1));
324 mfgDate = velia::utils::yangTimeFormat(std::chrono::sys_days{calendarDate});
Tomáš Pecka339bc672020-11-11 15:59:03 +0100325
326 addComponent(m_staticData,
327 m_componentName,
328 m_parent,
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200329 DataTree{
Tomáš Pecka339bc672020-11-11 15:59:03 +0100330 {"class", "iana-hardware:module"},
331 {"mfg-date", mfgDate},
332 {"serial-num", emmcAttrs.at("serial")},
333 {"model-name", emmcAttrs.at("name")},
334 });
335
336 addComponent(m_staticData,
337 m_componentName + ":lifetime",
338 m_componentName,
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200339 DataTree{
Tomáš Pecka339bc672020-11-11 15:59:03 +0100340 {"class", "iana-hardware:sensor"},
341 {"sensor-data/value-type", "other"},
342 {"sensor-data/value-scale", "units"},
343 {"sensor-data/value-precision", "0"},
Tomáš Pecka339bc672020-11-11 15:59:03 +0100344 {"sensor-data/units-display", "percent"s},
345 });
346}
347
348DataTree EMMC::operator()() const
349{
350 DataTree res(m_staticData);
351
352 auto emmcAttrs = m_emmc->attributes();
Tomáš Peckacd7f9cc2023-12-11 15:52:48 +0100353 addSensorValue(m_log, res, m_componentName + ":lifetime", emmcAttrs.at("life_time"));
Tomáš Pecka339bc672020-11-11 15:59:03 +0100354
355 return res;
356}
Tomáš Pecka2a4c9f62023-03-26 10:54:57 +0200357
Tomáš Pecka4886db22023-05-10 10:46:15 +0200358ThresholdsBySensorPath EMMC::thresholds() const
Tomáš Pecka2a4c9f62023-03-26 10:54:57 +0200359{
Tomáš Pecka4886db22023-05-10 10:46:15 +0200360 return {{xpathForComponent(m_componentName + ":lifetime") + "sensor-data/value", m_thresholds}};
Tomáš Pecka2a4c9f62023-03-26 10:54:57 +0200361}
362
363DataTree Group::operator()() const
364{
365 DataTree res;
366 for (const auto& reader : m_readers) {
367 res.merge(reader());
368 }
369 return res;
370}
371
Tomáš Pecka4886db22023-05-10 10:46:15 +0200372ThresholdsBySensorPath Group::thresholds() const
373{
374 return m_thresholds;
375}
Tomáš Pecka339bc672020-11-11 15:59:03 +0100376}
377}