blob: bee10bd3b44f2534414886b28dfe2b43667a891e [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
41/** @brief Write a sensor-data @p value for a component @p componentName and push it into the @p res DataTree */
42void addSensorValue(velia::ietf_hardware::DataTree& res, const std::string& componentName, const std::string& value)
43{
44 const auto componentPrefix = xpathForComponent(componentName);
45 res[componentPrefix + "sensor-data/value"] = value;
46}
47}
48
49namespace velia::ietf_hardware {
50
Tomáš Pecka0d8d8ee2023-05-10 12:22:58 +020051IETFHardware::IETFHardware()
52 : m_log(spdlog::get("hardware"))
53{
54}
Tomáš Pecka339bc672020-11-11 15:59:03 +010055
56IETFHardware::~IETFHardware() = default;
57
Tomáš Pecka0d8d8ee2023-05-10 12:22:58 +020058/**
59 * Calls individual registered data readers and processes information obtained from them.
60 * The sensor values are then passed to threshold watchers to detect if the new value violated a threshold.
61 * This function does not raise any alarms. It only returns the data tree *and* any changes in the threshold
62 * crossings (as a mapping from sensor XPath to threshold watcher State (@see Watcher)).
63 **/
64HardwareInfo IETFHardware::process()
Tomáš Pecka339bc672020-11-11 15:59:03 +010065{
Tomáš Pecka0d8d8ee2023-05-10 12:22:58 +020066 DataTree dataTree;
67 std::map<std::string, State> alarms;
Tomáš Pecka339bc672020-11-11 15:59:03 +010068
69 for (auto& dataReader : m_callbacks) {
Tomáš Pecka0d8d8ee2023-05-10 12:22:58 +020070 dataTree.merge(dataReader());
Tomáš Pecka339bc672020-11-11 15:59:03 +010071 }
72
Tomáš Pecka0d8d8ee2023-05-10 12:22:58 +020073 for (auto& [sensorXPath, thresholdsWatcher] : m_thresholdsWatchers) {
74 std::optional<int64_t> newValue;
75
76 if (auto it = dataTree.find(sensorXPath); it != dataTree.end()) {
77 newValue = std::stoll(it->second);
78 } else {
79 newValue = std::nullopt;
80 }
81
82 if (auto newState = thresholdsWatcher.update(newValue)) {
83 m_log->debug("threshold: {} {}", sensorXPath, *newState);
84 alarms.emplace(sensorXPath, *newState);
85 }
86 }
87
88 dataTree[ietfHardwareStatePrefix + "/last-change"] = velia::utils::yangTimeFormat(std::chrono::system_clock::now());
89
90 return {dataTree, alarms};
Tomáš Pecka339bc672020-11-11 15:59:03 +010091}
92
Tomáš Peckad2322ad2023-05-11 16:20:38 +020093std::vector<std::string> IETFHardware::sensorsXPaths() const
94{
95 std::vector<std::string> res;
96
97 for (const auto& [sensorXPath, thresholds] : m_thresholdsWatchers) {
98 res.emplace_back(sensorXPath);
99 }
100
101 return res;
102}
103
Tomáš Pecka339bc672020-11-11 15:59:03 +0100104/** @brief A namespace containing predefined data readers for IETFHardware class.
105 * @see IETFHardware for more information
106 */
107namespace data_reader {
108
109DataReader::DataReader(std::string componentName, std::optional<std::string> parent)
110 : m_componentName(std::move(componentName))
111 , m_parent(std::move(parent))
112{
113}
114
115/** @brief Constructs a component without any sensor-data and provide only the static data @p dataTree passed via constructor.
116 * @param componentName the name of the component in the resulting tree
117 * @param parent The component in YANG model has a link to parent. Specify who is the parent.
118 * @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.
119 */
120StaticData::StaticData(std::string componentName, std::optional<std::string> parent, DataTree dataTree)
121 : DataReader(std::move(componentName), std::move(parent))
122{
123 addComponent(m_staticData,
124 m_componentName,
125 m_parent,
126 dataTree);
127}
128
129DataTree StaticData::operator()() const { return m_staticData; }
Tomáš Pecka4886db22023-05-10 10:46:15 +0200130ThresholdsBySensorPath StaticData::thresholds() const { return {}; }
Tomáš Pecka339bc672020-11-11 15:59:03 +0100131
Tomáš Pecka4886db22023-05-10 10:46:15 +0200132Fans::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 +0100133 : DataReader(std::move(componentName), std::move(parent))
134 , m_hwmon(std::move(hwmon))
135 , m_fanChannelsCount(fanChannelsCount)
Tomáš Pecka4886db22023-05-10 10:46:15 +0200136 , m_thresholds(std::move(thresholds))
Tomáš Pecka339bc672020-11-11 15:59:03 +0100137{
138 // fans
139 addComponent(m_staticData,
140 m_componentName,
141 m_parent,
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200142 DataTree{
Tomáš Pecka339bc672020-11-11 15:59:03 +0100143 {"class", "iana-hardware:module"}, // FIXME: Read (or pass via constructor) additional properties (mfg, model, ...). They should be in the fans' tray EEPROM.
144 });
145
146 for (unsigned i = 1; i <= m_fanChannelsCount; i++) {
147 // fans -> fan_i
148 addComponent(m_staticData,
149 m_componentName + ":fan" + std::to_string(i),
150 m_componentName,
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200151 DataTree{
Tomáš Pecka339bc672020-11-11 15:59:03 +0100152 {"class", "iana-hardware:fan"},
153 });
154
155 // fans -> fan_i -> sensor-data
156 addComponent(m_staticData,
157 m_componentName + ":fan" + std::to_string(i) + ":rpm",
158 m_componentName + ":fan" + std::to_string(i),
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200159 DataTree{
Tomáš Pecka339bc672020-11-11 15:59:03 +0100160 {"class", "iana-hardware:sensor"},
161 {"sensor-data/value-type", "rpm"},
162 {"sensor-data/value-scale", "units"},
163 {"sensor-data/value-precision", "0"},
164 {"sensor-data/oper-status", "ok"},
165 });
166 }
167}
168
169DataTree Fans::operator()() const
170{
171 DataTree res(m_staticData);
172
Tomáš Pecka339bc672020-11-11 15:59:03 +0100173 for (unsigned i = 1; i <= m_fanChannelsCount; i++) {
174 const auto sensorComponentName = m_componentName + ":fan" + std::to_string(i) + ":rpm";
175 const auto attribute = "fan"s + std::to_string(i) + "_input";
176
Václav Kubernátb0939dd2021-04-28 04:08:48 +0200177 addSensorValue(res, sensorComponentName, std::to_string(m_hwmon->attribute(attribute)));
Tomáš Pecka339bc672020-11-11 15:59:03 +0100178 }
179
180 return res;
181}
182
Tomáš Pecka4886db22023-05-10 10:46:15 +0200183ThresholdsBySensorPath Fans::thresholds() const
184{
185 ThresholdsBySensorPath res;
186
187 for (unsigned i = 1; i <= m_fanChannelsCount; i++) {
188 res.emplace(xpathForComponent(m_componentName + ":fan" + std::to_string(i) + ":rpm") + "sensor-data/value", m_thresholds);
189 }
190
191 return res;
192}
193
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200194std::string getSysfsFilename(const SensorType type, int sysfsChannelNr)
195{
196 switch (type) {
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200197 case SensorType::Temperature:
198 return "temp"s + std::to_string(sysfsChannelNr) + "_input";
199 case SensorType::Current:
200 return "curr"s + std::to_string(sysfsChannelNr) + "_input";
201 case SensorType::Power:
202 return "power"s + std::to_string(sysfsChannelNr) + "_input";
203 case SensorType::VoltageAC:
204 case SensorType::VoltageDC:
205 return "in"s + std::to_string(sysfsChannelNr) + "_input";
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200206 }
207
208 __builtin_unreachable();
209}
210
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200211template <SensorType TYPE>
212const DataTree sysfsStaticData;
213template <>
214const DataTree sysfsStaticData<SensorType::Temperature> = {
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200215 {"class", "iana-hardware:sensor"},
216 {"sensor-data/value-type", "celsius"},
217 {"sensor-data/value-scale", "milli"},
218 {"sensor-data/value-precision", "0"},
219 {"sensor-data/oper-status", "ok"},
220};
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200221template <>
222const DataTree sysfsStaticData<SensorType::Current> = {
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100223 {"class", "iana-hardware:sensor"},
224 {"sensor-data/value-type", "amperes"},
225 {"sensor-data/value-scale", "milli"},
226 {"sensor-data/value-precision", "0"},
227 {"sensor-data/oper-status", "ok"},
228};
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200229template <>
230const DataTree sysfsStaticData<SensorType::Power> = {
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100231 {"class", "iana-hardware:sensor"},
232 {"sensor-data/value-type", "watts"},
233 {"sensor-data/value-scale", "micro"},
234 {"sensor-data/value-precision", "0"},
235 {"sensor-data/oper-status", "ok"},
236};
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200237template <>
238const DataTree sysfsStaticData<SensorType::VoltageAC> = {
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100239 {"class", "iana-hardware:sensor"},
240 {"sensor-data/value-type", "volts-AC"},
Tomáš Pecka76323602022-05-11 09:33:59 +0200241 {"sensor-data/value-scale", "milli"},
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100242 {"sensor-data/value-precision", "0"},
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200243 {"sensor-data/oper-status", "ok"}};
244template <>
245const DataTree sysfsStaticData<SensorType::VoltageDC> = {
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100246 {"class", "iana-hardware:sensor"},
247 {"sensor-data/value-type", "volts-DC"},
Tomáš Pecka76323602022-05-11 09:33:59 +0200248 {"sensor-data/value-scale", "milli"},
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100249 {"sensor-data/value-precision", "0"},
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200250 {"sensor-data/oper-status", "ok"}};
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200251
252template <SensorType TYPE>
Tomáš Pecka4886db22023-05-10 10:46:15 +0200253SysfsValue<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 +0100254 : DataReader(std::move(componentName), std::move(parent))
255 , m_hwmon(std::move(hwmon))
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200256 , m_sysfsFile(getSysfsFilename(TYPE, sysfsChannelNr))
Tomáš Pecka4886db22023-05-10 10:46:15 +0200257 , m_thresholds(std::move(thresholds))
Tomáš Pecka339bc672020-11-11 15:59:03 +0100258{
259 addComponent(m_staticData,
260 m_componentName,
261 m_parent,
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200262 sysfsStaticData<TYPE>);
Tomáš Pecka339bc672020-11-11 15:59:03 +0100263}
264
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200265template <SensorType TYPE>
266DataTree SysfsValue<TYPE>::operator()() const
Tomáš Pecka339bc672020-11-11 15:59:03 +0100267{
268 DataTree res(m_staticData);
269
Václav Kubernát0dee6b92021-04-13 09:14:04 +0200270 int64_t sensorValue = m_hwmon->attribute(m_sysfsFile);
Tomáš Pecka339bc672020-11-11 15:59:03 +0100271 addSensorValue(res, m_componentName, std::to_string(sensorValue));
272
273 return res;
274}
275
Tomáš Pecka4886db22023-05-10 10:46:15 +0200276template <SensorType TYPE>
277ThresholdsBySensorPath SysfsValue<TYPE>::thresholds() const
278{
279 return {{xpathForComponent(m_componentName) + "sensor-data/value", m_thresholds}};
280}
281
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100282template struct SysfsValue<SensorType::Current>;
283template struct SysfsValue<SensorType::Power>;
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200284template struct SysfsValue<SensorType::Temperature>;
Václav Kubernát97e5ea12021-03-24 00:36:57 +0100285template struct SysfsValue<SensorType::VoltageAC>;
286template struct SysfsValue<SensorType::VoltageDC>;
Václav Kubernát6c17d0a2021-03-29 04:55:31 +0200287
Tomáš Pecka4886db22023-05-10 10:46:15 +0200288EMMC::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 +0100289 : DataReader(std::move(componentName), std::move(parent))
290 , m_emmc(std::move(emmc))
Tomáš Pecka4886db22023-05-10 10:46:15 +0200291 , m_thresholds(std::move(thresholds))
Tomáš Pecka339bc672020-11-11 15:59:03 +0100292{
293 auto emmcAttrs = m_emmc->attributes();
294
295 // date is specified in MM/YYYY format (source: kernel core/mmc.c) and mfg-date is unfortunately of type yang:date-and-time
296 std::string mfgDate = emmcAttrs.at("date");
Tomáš Peckaf1cba742023-05-03 16:26:37 +0200297 std::chrono::year_month_day calendarDate(
298 std::chrono::year(std::stoi(mfgDate.substr(3, 4))),
299 std::chrono::month(std::stoi(mfgDate.substr(0, 2))),
300 std::chrono::day(1));
301 mfgDate = velia::utils::yangTimeFormat(std::chrono::sys_days{calendarDate});
Tomáš Pecka339bc672020-11-11 15:59:03 +0100302
303 addComponent(m_staticData,
304 m_componentName,
305 m_parent,
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200306 DataTree{
Tomáš Pecka339bc672020-11-11 15:59:03 +0100307 {"class", "iana-hardware:module"},
308 {"mfg-date", mfgDate},
309 {"serial-num", emmcAttrs.at("serial")},
310 {"model-name", emmcAttrs.at("name")},
311 });
312
313 addComponent(m_staticData,
314 m_componentName + ":lifetime",
315 m_componentName,
Tomáš Pecka77e09c22023-05-04 20:39:57 +0200316 DataTree{
Tomáš Pecka339bc672020-11-11 15:59:03 +0100317 {"class", "iana-hardware:sensor"},
318 {"sensor-data/value-type", "other"},
319 {"sensor-data/value-scale", "units"},
320 {"sensor-data/value-precision", "0"},
321 {"sensor-data/oper-status", "ok"},
322 {"sensor-data/units-display", "percent"s},
323 });
324}
325
326DataTree EMMC::operator()() const
327{
328 DataTree res(m_staticData);
329
330 auto emmcAttrs = m_emmc->attributes();
331 addSensorValue(res, m_componentName + ":lifetime", emmcAttrs.at("life_time"));
332
333 return res;
334}
Tomáš Pecka2a4c9f62023-03-26 10:54:57 +0200335
Tomáš Pecka4886db22023-05-10 10:46:15 +0200336ThresholdsBySensorPath EMMC::thresholds() const
Tomáš Pecka2a4c9f62023-03-26 10:54:57 +0200337{
Tomáš Pecka4886db22023-05-10 10:46:15 +0200338 return {{xpathForComponent(m_componentName + ":lifetime") + "sensor-data/value", m_thresholds}};
Tomáš Pecka2a4c9f62023-03-26 10:54:57 +0200339}
340
341DataTree Group::operator()() const
342{
343 DataTree res;
344 for (const auto& reader : m_readers) {
345 res.merge(reader());
346 }
347 return res;
348}
349
Tomáš Pecka4886db22023-05-10 10:46:15 +0200350ThresholdsBySensorPath Group::thresholds() const
351{
352 return m_thresholds;
353}
Tomáš Pecka339bc672020-11-11 15:59:03 +0100354}
355}