blob: f167050f372e5be8452cb17519bed27c0df9d89b [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 */
49void addSensorValue(velia::ietf_hardware::DataTree& res, const std::string& componentName, const int64_t& value)
50{
51 writeSensorValue(res, componentName, std::to_string(value), "ok");
52}
53
54/** @brief Write a sensor-data @p value for a component @p componentName and push it into the @p res DataTree */
55void addSensorValue(velia::ietf_hardware::DataTree& res, const std::string& componentName, const std::string& value)
56{
57 // TODO: Perhaps we should check if the string value is conforming to sensor-value type
58 writeSensorValue(res, componentName, value, "ok");
Tomáš Pecka339bc672020-11-11 15:59:03 +010059}
60}
61
62namespace velia::ietf_hardware {
63
Tomáš Pecka0d8d8ee2023-05-10 12:22:58 +020064IETFHardware::IETFHardware()
65 : m_log(spdlog::get("hardware"))
66{
67}
Tomáš Pecka339bc672020-11-11 15:59:03 +010068
69IETFHardware::~IETFHardware() = default;
70
Tomáš Pecka0d8d8ee2023-05-10 12:22:58 +020071/**
72 * Calls individual registered data readers and processes information obtained from them.
73 * The sensor values are then passed to threshold watchers to detect if the new value violated a threshold.
74 * This function does not raise any alarms. It only returns the data tree *and* any changes in the threshold
75 * crossings (as a mapping from sensor XPath to threshold watcher State (@see Watcher)).
76 **/
77HardwareInfo IETFHardware::process()
Tomáš Pecka339bc672020-11-11 15:59:03 +010078{
Tomáš Pecka0d8d8ee2023-05-10 12:22:58 +020079 DataTree dataTree;
80 std::map<std::string, State> alarms;
Tomáš Pecka339bc672020-11-11 15:59:03 +010081
82 for (auto& dataReader : m_callbacks) {
Tomáš Pecka0d8d8ee2023-05-10 12:22:58 +020083 dataTree.merge(dataReader());
Tomáš Pecka339bc672020-11-11 15:59:03 +010084 }
85
Tomáš Pecka0d8d8ee2023-05-10 12:22:58 +020086 for (auto& [sensorXPath, thresholdsWatcher] : m_thresholdsWatchers) {
87 std::optional<int64_t> newValue;
88
89 if (auto it = dataTree.find(sensorXPath); it != dataTree.end()) {
90 newValue = std::stoll(it->second);
91 } else {
92 newValue = std::nullopt;
93 }
94
95 if (auto newState = thresholdsWatcher.update(newValue)) {
96 m_log->debug("threshold: {} {}", sensorXPath, *newState);
97 alarms.emplace(sensorXPath, *newState);
98 }
99 }
100
101 dataTree[ietfHardwareStatePrefix + "/last-change"] = velia::utils::yangTimeFormat(std::chrono::system_clock::now());
102
103 return {dataTree, alarms};
Tomáš Pecka339bc672020-11-11 15:59:03 +0100104}
105
Tomáš Peckad2322ad2023-05-11 16:20:38 +0200106std::vector<std::string> IETFHardware::sensorsXPaths() const
107{
108 std::vector<std::string> res;
109
110 for (const auto& [sensorXPath, thresholds] : m_thresholdsWatchers) {
111 res.emplace_back(sensorXPath);
112 }
113
114 return res;
115}
116
Tomáš Pecka339bc672020-11-11 15:59:03 +0100117/** @brief A namespace containing predefined data readers for IETFHardware class.
118 * @see IETFHardware for more information
119 */
120namespace data_reader {
121
122DataReader::DataReader(std::string componentName, std::optional<std::string> parent)
123 : m_componentName(std::move(componentName))
124 , m_parent(std::move(parent))
125{
126}
127
128/** @brief Constructs a component without any sensor-data and provide only the static data @p dataTree passed via constructor.
129 * @param componentName the name of the component in the resulting tree
130 * @param parent The component in YANG model has a link to parent. Specify who is the parent.
131 * @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.
132 */
133StaticData::StaticData(std::string componentName, std::optional<std::string> parent, DataTree dataTree)
134 : DataReader(std::move(componentName), std::move(parent))
135{
136 addComponent(m_staticData,
137 m_componentName,
138 m_parent,
139 dataTree);
140}
141
142DataTree StaticData::operator()() const { return m_staticData; }
Tomáš Pecka4886db22023-05-10 10:46:15 +0200143ThresholdsBySensorPath StaticData::thresholds() const { return {}; }
Tomáš Pecka339bc672020-11-11 15:59:03 +0100144
Tomáš Pecka4886db22023-05-10 10:46:15 +0200145Fans::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 +0100146 : DataReader(std::move(componentName), std::move(parent))
147 , m_hwmon(std::move(hwmon))
148 , m_fanChannelsCount(fanChannelsCount)
Tomáš Pecka4886db22023-05-10 10:46:15 +0200149 , m_thresholds(std::move(thresholds))
Tomáš Pecka339bc672020-11-11 15:59:03 +0100150{
151 // fans
152 addComponent(m_staticData,
153 m_componentName,
154 m_parent,
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200155 DataTree{
Tomáš Pecka339bc672020-11-11 15:59:03 +0100156 {"class", "iana-hardware:module"}, // FIXME: Read (or pass via constructor) additional properties (mfg, model, ...). They should be in the fans' tray EEPROM.
157 });
158
159 for (unsigned i = 1; i <= m_fanChannelsCount; i++) {
160 // fans -> fan_i
161 addComponent(m_staticData,
162 m_componentName + ":fan" + std::to_string(i),
163 m_componentName,
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200164 DataTree{
Tomáš Pecka339bc672020-11-11 15:59:03 +0100165 {"class", "iana-hardware:fan"},
166 });
167
168 // fans -> fan_i -> sensor-data
169 addComponent(m_staticData,
170 m_componentName + ":fan" + std::to_string(i) + ":rpm",
171 m_componentName + ":fan" + std::to_string(i),
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200172 DataTree{
Tomáš Pecka339bc672020-11-11 15:59:03 +0100173 {"class", "iana-hardware:sensor"},
174 {"sensor-data/value-type", "rpm"},
175 {"sensor-data/value-scale", "units"},
176 {"sensor-data/value-precision", "0"},
Tomáš Pecka339bc672020-11-11 15:59:03 +0100177 });
178 }
179}
180
181DataTree Fans::operator()() const
182{
183 DataTree res(m_staticData);
184
Tomáš Pecka339bc672020-11-11 15:59:03 +0100185 for (unsigned i = 1; i <= m_fanChannelsCount; i++) {
186 const auto sensorComponentName = m_componentName + ":fan" + std::to_string(i) + ":rpm";
187 const auto attribute = "fan"s + std::to_string(i) + "_input";
188
Tomáš Pecka655062c2023-12-11 15:37:58 +0100189 addSensorValue(res, sensorComponentName, m_hwmon->attribute(attribute));
Tomáš Pecka339bc672020-11-11 15:59:03 +0100190 }
191
192 return res;
193}
194
Tomáš Pecka4886db22023-05-10 10:46:15 +0200195ThresholdsBySensorPath Fans::thresholds() const
196{
197 ThresholdsBySensorPath res;
198
199 for (unsigned i = 1; i <= m_fanChannelsCount; i++) {
200 res.emplace(xpathForComponent(m_componentName + ":fan" + std::to_string(i) + ":rpm") + "sensor-data/value", m_thresholds);
201 }
202
203 return res;
204}
205
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200206std::string getSysfsFilename(const SensorType type, int sysfsChannelNr)
207{
208 switch (type) {
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200209 case SensorType::Temperature:
210 return "temp"s + std::to_string(sysfsChannelNr) + "_input";
211 case SensorType::Current:
212 return "curr"s + std::to_string(sysfsChannelNr) + "_input";
213 case SensorType::Power:
214 return "power"s + std::to_string(sysfsChannelNr) + "_input";
215 case SensorType::VoltageAC:
216 case SensorType::VoltageDC:
217 return "in"s + std::to_string(sysfsChannelNr) + "_input";
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200218 }
219
220 __builtin_unreachable();
221}
222
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200223template <SensorType TYPE>
224const DataTree sysfsStaticData;
225template <>
226const DataTree sysfsStaticData<SensorType::Temperature> = {
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200227 {"class", "iana-hardware:sensor"},
228 {"sensor-data/value-type", "celsius"},
229 {"sensor-data/value-scale", "milli"},
230 {"sensor-data/value-precision", "0"},
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200231};
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200232template <>
233const DataTree sysfsStaticData<SensorType::Current> = {
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100234 {"class", "iana-hardware:sensor"},
235 {"sensor-data/value-type", "amperes"},
236 {"sensor-data/value-scale", "milli"},
237 {"sensor-data/value-precision", "0"},
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100238};
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200239template <>
240const DataTree sysfsStaticData<SensorType::Power> = {
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100241 {"class", "iana-hardware:sensor"},
242 {"sensor-data/value-type", "watts"},
243 {"sensor-data/value-scale", "micro"},
244 {"sensor-data/value-precision", "0"},
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100245};
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200246template <>
247const DataTree sysfsStaticData<SensorType::VoltageAC> = {
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100248 {"class", "iana-hardware:sensor"},
249 {"sensor-data/value-type", "volts-AC"},
Tomáš Pecka76323602022-05-11 09:33:59 +0200250 {"sensor-data/value-scale", "milli"},
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100251 {"sensor-data/value-precision", "0"},
Tomáš Pecka5f07eea2023-12-11 15:23:01 +0100252};
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200253template <>
254const DataTree sysfsStaticData<SensorType::VoltageDC> = {
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100255 {"class", "iana-hardware:sensor"},
256 {"sensor-data/value-type", "volts-DC"},
Tomáš Pecka76323602022-05-11 09:33:59 +0200257 {"sensor-data/value-scale", "milli"},
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100258 {"sensor-data/value-precision", "0"},
Tomáš Pecka5f07eea2023-12-11 15:23:01 +0100259};
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200260
261template <SensorType TYPE>
Tomáš Pecka4886db22023-05-10 10:46:15 +0200262SysfsValue<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 +0100263 : DataReader(std::move(componentName), std::move(parent))
264 , m_hwmon(std::move(hwmon))
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200265 , m_sysfsFile(getSysfsFilename(TYPE, sysfsChannelNr))
Tomáš Pecka4886db22023-05-10 10:46:15 +0200266 , m_thresholds(std::move(thresholds))
Tomáš Pecka339bc672020-11-11 15:59:03 +0100267{
268 addComponent(m_staticData,
269 m_componentName,
270 m_parent,
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200271 sysfsStaticData<TYPE>);
Tomáš Pecka339bc672020-11-11 15:59:03 +0100272}
273
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200274template <SensorType TYPE>
275DataTree SysfsValue<TYPE>::operator()() const
Tomáš Pecka339bc672020-11-11 15:59:03 +0100276{
277 DataTree res(m_staticData);
278
Václav Kubernát0dee6b92021-04-13 09:14:04 +0200279 int64_t sensorValue = m_hwmon->attribute(m_sysfsFile);
Tomáš Pecka655062c2023-12-11 15:37:58 +0100280 addSensorValue(res, m_componentName, sensorValue);
Tomáš Pecka339bc672020-11-11 15:59:03 +0100281
282 return res;
283}
284
Tomáš Pecka4886db22023-05-10 10:46:15 +0200285template <SensorType TYPE>
286ThresholdsBySensorPath SysfsValue<TYPE>::thresholds() const
287{
288 return {{xpathForComponent(m_componentName) + "sensor-data/value", m_thresholds}};
289}
290
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100291template struct SysfsValue<SensorType::Current>;
292template struct SysfsValue<SensorType::Power>;
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200293template struct SysfsValue<SensorType::Temperature>;
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100294template struct SysfsValue<SensorType::VoltageAC>;
295template struct SysfsValue<SensorType::VoltageDC>;
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200296
Tomáš Pecka4886db22023-05-10 10:46:15 +0200297EMMC::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 +0100298 : DataReader(std::move(componentName), std::move(parent))
299 , m_emmc(std::move(emmc))
Tomáš Pecka4886db22023-05-10 10:46:15 +0200300 , m_thresholds(std::move(thresholds))
Tomáš Pecka339bc672020-11-11 15:59:03 +0100301{
302 auto emmcAttrs = m_emmc->attributes();
303
304 // date is specified in MM/YYYY format (source: kernel core/mmc.c) and mfg-date is unfortunately of type yang:date-and-time
305 std::string mfgDate = emmcAttrs.at("date");
Tomáš Peckaf1cba742023-05-03 16:26:37 +0200306 std::chrono::year_month_day calendarDate(
307 std::chrono::year(std::stoi(mfgDate.substr(3, 4))),
308 std::chrono::month(std::stoi(mfgDate.substr(0, 2))),
309 std::chrono::day(1));
310 mfgDate = velia::utils::yangTimeFormat(std::chrono::sys_days{calendarDate});
Tomáš Pecka339bc672020-11-11 15:59:03 +0100311
312 addComponent(m_staticData,
313 m_componentName,
314 m_parent,
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200315 DataTree{
Tomáš Pecka339bc672020-11-11 15:59:03 +0100316 {"class", "iana-hardware:module"},
317 {"mfg-date", mfgDate},
318 {"serial-num", emmcAttrs.at("serial")},
319 {"model-name", emmcAttrs.at("name")},
320 });
321
322 addComponent(m_staticData,
323 m_componentName + ":lifetime",
324 m_componentName,
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200325 DataTree{
Tomáš Pecka339bc672020-11-11 15:59:03 +0100326 {"class", "iana-hardware:sensor"},
327 {"sensor-data/value-type", "other"},
328 {"sensor-data/value-scale", "units"},
329 {"sensor-data/value-precision", "0"},
Tomáš Pecka339bc672020-11-11 15:59:03 +0100330 {"sensor-data/units-display", "percent"s},
331 });
332}
333
334DataTree EMMC::operator()() const
335{
336 DataTree res(m_staticData);
337
338 auto emmcAttrs = m_emmc->attributes();
339 addSensorValue(res, m_componentName + ":lifetime", emmcAttrs.at("life_time"));
340
341 return res;
342}
Tomáš Pecka2a4c9f62023-03-26 10:54:57 +0200343
Tomáš Pecka4886db22023-05-10 10:46:15 +0200344ThresholdsBySensorPath EMMC::thresholds() const
Tomáš Pecka2a4c9f62023-03-26 10:54:57 +0200345{
Tomáš Pecka4886db22023-05-10 10:46:15 +0200346 return {{xpathForComponent(m_componentName + ":lifetime") + "sensor-data/value", m_thresholds}};
Tomáš Pecka2a4c9f62023-03-26 10:54:57 +0200347}
348
349DataTree Group::operator()() const
350{
351 DataTree res;
352 for (const auto& reader : m_readers) {
353 res.merge(reader());
354 }
355 return res;
356}
357
Tomáš Pecka4886db22023-05-10 10:46:15 +0200358ThresholdsBySensorPath Group::thresholds() const
359{
360 return m_thresholds;
361}
Tomáš Pecka339bc672020-11-11 15:59:03 +0100362}
363}