1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+#include "debugfs_plugin.h"
4
+
5
+#define NETDATA_CALCULATED_STATES 1
6
+
7
+#include "libsensors/vendored/lib/sensors.h"
8
+#include "libsensors/vendored/lib/error.h"
9
+
10
+typedef short SENSOR_BUS_TYPE;
11
+ENUM_STR_MAP_DEFINE(SENSOR_BUS_TYPE) = {
12
+ { .id = SENSORS_BUS_TYPE_ANY, .name = "any", },
13
+ { .id = SENSORS_BUS_TYPE_I2C, .name = "i2c", },
14
+ { .id = SENSORS_BUS_TYPE_ISA, .name = "isa", },
15
+ { .id = SENSORS_BUS_TYPE_PCI, .name = "pci", },
16
+ { .id = SENSORS_BUS_TYPE_SPI, .name = "spi", },
17
+ { .id = SENSORS_BUS_TYPE_VIRTUAL, .name = "virtual", },
18
+ { .id = SENSORS_BUS_TYPE_ACPI, .name = "acpi", },
19
+ { .id = SENSORS_BUS_TYPE_HID, .name = "hid", },
20
+ { .id = SENSORS_BUS_TYPE_MDIO, .name = "mdio", },
21
+ { .id = SENSORS_BUS_TYPE_SCSI, .name = "scsi", },
22
+
23
+ // terminator
24
+ {.id = 0, .name = NULL}
25
+};
26
+ENUM_STR_DEFINE_FUNCTIONS(SENSOR_BUS_TYPE, SENSORS_BUS_TYPE_ANY, "any");
27
+
28
+typedef sensors_feature_type SENSOR_TYPE;
29
+ENUM_STR_MAP_DEFINE(SENSOR_TYPE) = {
30
+ { .id = SENSORS_FEATURE_IN, .name = "voltage", },
31
+ { .id = SENSORS_FEATURE_FAN, .name = "fan", },
32
+ { .id = SENSORS_FEATURE_TEMP, .name = "temperature", },
33
+ { .id = SENSORS_FEATURE_POWER, .name = "power", },
34
+ { .id = SENSORS_FEATURE_ENERGY, .name = "energy", },
35
+ { .id = SENSORS_FEATURE_CURR, .name = "curr", },
36
+ { .id = SENSORS_FEATURE_HUMIDITY, .name = "humidity", },
37
+ { .id = SENSORS_FEATURE_VID, .name = "vid", },
38
+ { .id = SENSORS_FEATURE_INTRUSION, .name = "intrusion", },
39
+ { .id = SENSORS_FEATURE_BEEP_ENABLE, .name = "beep_enable", },
40
+ { .id = SENSORS_FEATURE_UNKNOWN, .name = "unknown", },
41
+
42
+ // terminator
43
+ {.id = 0, .name = NULL}
44
+};
45
+ENUM_STR_DEFINE_FUNCTIONS(SENSOR_TYPE, SENSORS_FEATURE_UNKNOWN, "unknown");
46
+
47
+typedef sensors_subfeature_type SENSOR_SUBFEATURE_TYPE;
48
+ENUM_STR_MAP_DEFINE(SENSOR_SUBFEATURE_TYPE) = {
49
+ // Voltage input subfeatures
50
+ { .id = SENSORS_SUBFEATURE_IN_INPUT, .name = "input", },
51
+ { .id = SENSORS_SUBFEATURE_IN_MIN, .name = "minimum", },
52
+ { .id = SENSORS_SUBFEATURE_IN_MAX, .name = "maximum", },
53
+ { .id = SENSORS_SUBFEATURE_IN_LCRIT, .name = "critical low", },
54
+ { .id = SENSORS_SUBFEATURE_IN_CRIT, .name = "critical high", },
55
+ { .id = SENSORS_SUBFEATURE_IN_AVERAGE, .name = "average", },
56
+ { .id = SENSORS_SUBFEATURE_IN_LOWEST, .name = "lowest", },
57
+ { .id = SENSORS_SUBFEATURE_IN_HIGHEST, .name = "highest", },
58
+ { .id = SENSORS_SUBFEATURE_IN_ALARM, .name = "alarm", },
59
+ { .id = SENSORS_SUBFEATURE_IN_MIN_ALARM, .name = "alarm low", },
60
+ { .id = SENSORS_SUBFEATURE_IN_MAX_ALARM, .name = "alarm high", },
61
+ { .id = SENSORS_SUBFEATURE_IN_BEEP, .name = "beep", },
62
+ { .id = SENSORS_SUBFEATURE_IN_LCRIT_ALARM, .name = "critical alarm low", },
63
+ { .id = SENSORS_SUBFEATURE_IN_CRIT_ALARM, .name = "critical alarm high", },
64
+
65
+ // Fan subfeatures
66
+ { .id = SENSORS_SUBFEATURE_FAN_INPUT, .name = "input", },
67
+ { .id = SENSORS_SUBFEATURE_FAN_MIN, .name = "minimum", },
68
+ { .id = SENSORS_SUBFEATURE_FAN_MAX, .name = "maximum", },
69
+ { .id = SENSORS_SUBFEATURE_FAN_ALARM, .name = "alarm", },
70
+ { .id = SENSORS_SUBFEATURE_FAN_FAULT, .name = "fault", },
71
+ { .id = SENSORS_SUBFEATURE_FAN_DIV, .name = "divisor", },
72
+ { .id = SENSORS_SUBFEATURE_FAN_BEEP, .name = "beep", },
73
+ { .id = SENSORS_SUBFEATURE_FAN_PULSES, .name = "pulses", },
74
+ { .id = SENSORS_SUBFEATURE_FAN_MIN_ALARM, .name = "alarm low", },
75
+ { .id = SENSORS_SUBFEATURE_FAN_MAX_ALARM, .name = "alarm high", },
76
+
77
+ // Temperature subfeatures
78
+ { .id = SENSORS_SUBFEATURE_TEMP_INPUT, .name = "input", },
79
+ { .id = SENSORS_SUBFEATURE_TEMP_MAX, .name = "maximum", },
80
+ { .id = SENSORS_SUBFEATURE_TEMP_MAX_HYST, .name = "maximum hysteresis", },
81
+ { .id = SENSORS_SUBFEATURE_TEMP_MIN, .name = "minimum", },
82
+ { .id = SENSORS_SUBFEATURE_TEMP_CRIT, .name = "critical high", },
83
+ { .id = SENSORS_SUBFEATURE_TEMP_CRIT_HYST, .name = "critical hysteresis", },
84
+ { .id = SENSORS_SUBFEATURE_TEMP_LCRIT, .name = "critical low", },
85
+ { .id = SENSORS_SUBFEATURE_TEMP_EMERGENCY, .name = "emergency", },
86
+ { .id = SENSORS_SUBFEATURE_TEMP_EMERGENCY_HYST, .name = "emergency hysteresis", },
87
+ { .id = SENSORS_SUBFEATURE_TEMP_LOWEST, .name = "lowest", },
88
+ { .id = SENSORS_SUBFEATURE_TEMP_HIGHEST, .name = "highest", },
89
+ { .id = SENSORS_SUBFEATURE_TEMP_MIN_HYST, .name = "minimum hysteresis", },
90
+ { .id = SENSORS_SUBFEATURE_TEMP_LCRIT_HYST, .name = "critical low hysteresis", },
91
+ { .id = SENSORS_SUBFEATURE_TEMP_ALARM, .name = "alarm", },
92
+ { .id = SENSORS_SUBFEATURE_TEMP_MAX_ALARM, .name = "alarm high", },
93
+ { .id = SENSORS_SUBFEATURE_TEMP_MIN_ALARM, .name = "alarm low", },
94
+ { .id = SENSORS_SUBFEATURE_TEMP_CRIT_ALARM, .name = "critical alarm high", },
95
+ { .id = SENSORS_SUBFEATURE_TEMP_FAULT, .name = "fault", },
96
+ { .id = SENSORS_SUBFEATURE_TEMP_TYPE, .name = "type", },
97
+ { .id = SENSORS_SUBFEATURE_TEMP_OFFSET, .name = "offset", },
98
+ { .id = SENSORS_SUBFEATURE_TEMP_BEEP, .name = "beep", },
99
+ { .id = SENSORS_SUBFEATURE_TEMP_EMERGENCY_ALARM, .name = "emergency alarm", },
100
+ { .id = SENSORS_SUBFEATURE_TEMP_LCRIT_ALARM, .name = "critical alarm low", },
101
+
102
+ // Power subfeatures
103
+ { .id = SENSORS_SUBFEATURE_POWER_AVERAGE, .name = "average", },
104
+ { .id = SENSORS_SUBFEATURE_POWER_AVERAGE_HIGHEST, .name = "average highest", },
105
+ { .id = SENSORS_SUBFEATURE_POWER_AVERAGE_LOWEST, .name = "average lowest", },
106
+ { .id = SENSORS_SUBFEATURE_POWER_INPUT, .name = "input", },
107
+ { .id = SENSORS_SUBFEATURE_POWER_INPUT_HIGHEST, .name = "input highest", },
108
+ { .id = SENSORS_SUBFEATURE_POWER_INPUT_LOWEST, .name = "input lowest", },
109
+ { .id = SENSORS_SUBFEATURE_POWER_CAP, .name = "cap", },
110
+ { .id = SENSORS_SUBFEATURE_POWER_CAP_HYST, .name = "cap hysteresis", },
111
+ { .id = SENSORS_SUBFEATURE_POWER_MAX, .name = "maximum", },
112
+ { .id = SENSORS_SUBFEATURE_POWER_CRIT, .name = "critical high", },
113
+ { .id = SENSORS_SUBFEATURE_POWER_MIN, .name = "minimum", },
114
+ { .id = SENSORS_SUBFEATURE_POWER_LCRIT, .name = "critical low", },
115
+ { .id = SENSORS_SUBFEATURE_POWER_AVERAGE_INTERVAL, .name = "average interval", },
116
+ { .id = SENSORS_SUBFEATURE_POWER_ALARM, .name = "alarm", },
117
+ { .id = SENSORS_SUBFEATURE_POWER_CAP_ALARM, .name = "cap alarm", },
118
+ { .id = SENSORS_SUBFEATURE_POWER_MAX_ALARM, .name = "alarm high", },
119
+ { .id = SENSORS_SUBFEATURE_POWER_CRIT_ALARM, .name = "critical alarm high", },
120
+ { .id = SENSORS_SUBFEATURE_POWER_MIN_ALARM, .name = "alarm low", },
121
+ { .id = SENSORS_SUBFEATURE_POWER_LCRIT_ALARM, .name = "critical alarm low", },
122
+
123
+ // Energy subfeatures
124
+ { .id = SENSORS_SUBFEATURE_ENERGY_INPUT, .name = "input", },
125
+
126
+ // Current subfeatures
127
+ { .id = SENSORS_SUBFEATURE_CURR_INPUT, .name = "input", },
128
+ { .id = SENSORS_SUBFEATURE_CURR_MIN, .name = "minimum", },
129
+ { .id = SENSORS_SUBFEATURE_CURR_MAX, .name = "maximum", },
130
+ { .id = SENSORS_SUBFEATURE_CURR_LCRIT, .name = "critical low", },
131
+ { .id = SENSORS_SUBFEATURE_CURR_CRIT, .name = "critical high", },
132
+ { .id = SENSORS_SUBFEATURE_CURR_AVERAGE, .name = "average", },
133
+ { .id = SENSORS_SUBFEATURE_CURR_LOWEST, .name = "lowest", },
134
+ { .id = SENSORS_SUBFEATURE_CURR_HIGHEST, .name = "highest", },
135
+ { .id = SENSORS_SUBFEATURE_CURR_ALARM, .name = "alarm", },
136
+ { .id = SENSORS_SUBFEATURE_CURR_MIN_ALARM, .name = "alarm low", },
137
+ { .id = SENSORS_SUBFEATURE_CURR_MAX_ALARM, .name = "alarm high", },
138
+ { .id = SENSORS_SUBFEATURE_CURR_BEEP, .name = "beep", },
139
+ { .id = SENSORS_SUBFEATURE_CURR_LCRIT_ALARM, .name = "critical alarm low", },
140
+ { .id = SENSORS_SUBFEATURE_CURR_CRIT_ALARM, .name = "critical alarm high", },
141
+
142
+ // Humidity subfeatures
143
+ { .id = SENSORS_SUBFEATURE_HUMIDITY_INPUT, .name = "input", },
144
+
145
+ // VID subfeatures
146
+ { .id = SENSORS_SUBFEATURE_VID, .name = "value", },
147
+
148
+ // Intrusion subfeatures
149
+ { .id = SENSORS_SUBFEATURE_INTRUSION_ALARM, .name = "alarm", },
150
+ { .id = SENSORS_SUBFEATURE_INTRUSION_BEEP, .name = "beep", },
151
+
152
+ // Beep enable subfeatures
153
+ { .id = SENSORS_SUBFEATURE_BEEP_ENABLE, .name = "enable", },
154
+
155
+ // Unknown subfeature
156
+ { .id = SENSORS_SUBFEATURE_UNKNOWN, .name = "unknown", },
157
+
158
+ // terminator
159
+ {.id = 0, .name = NULL}
160
+};
161
+ENUM_STR_DEFINE_FUNCTIONS(SENSOR_SUBFEATURE_TYPE, SENSORS_SUBFEATURE_UNKNOWN, "unknown");
162
+
163
+typedef enum {
164
+ SENSOR_STATE_NONE = 0, // unset
165
+ SENSOR_STATE_CLEAR = (1 << 0), // everything is good
166
+ SENSOR_STATE_WARNING = (1 << 1), // our own calculations indicate an alarm, but not the driver
167
+ SENSOR_STATE_CAP = (1 << 2), // our own calculations or the driver, indicate cap
168
+ SENSOR_STATE_ALARM = (1 << 3), // the kernel driver has raised an alarm
169
+ SENSOR_STATE_CRITICAL = (1 << 4), // our own calculations, or the driver, indicate a critical condition
170
+ SENSOR_STATE_EMERGENCY = (1 << 5), // our own calculations, or the driver, indicate an emergency
171
+ SENSOR_STATE_FAULT = (1 << 6), // our own calculations, or the driver, indicate a fault
172
+} SENSOR_STATE;
173
+
174
+ENUM_STR_MAP_DEFINE(SENSOR_STATE) = {
175
+ { .id = SENSOR_STATE_CLEAR, .name = "clear", },
176
+ { .id = SENSOR_STATE_WARNING, .name = "warning", },
177
+ { .id = SENSOR_STATE_CAP, .name = "cap", },
178
+ { .id = SENSOR_STATE_ALARM, .name = "alarm", },
179
+ { .id = SENSOR_STATE_CRITICAL, .name = "critical", },
180
+ { .id = SENSOR_STATE_EMERGENCY, .name = "emergency", },
181
+ { .id = SENSOR_STATE_FAULT, .name = "fault", },
182
+
183
+ // terminator
184
+ { .id = 0, .name = NULL, },
185
+};
186
+ENUM_STR_DEFINE_FUNCTIONS(SENSOR_STATE, SENSOR_STATE_NONE, "unknown");
187
+
188
+#define NOT_SUPPORTED (SENSORS_SUBFEATURE_UNKNOWN)
189
+
190
+struct sensor_config {
191
+ bool enabled;
192
+
193
+ bool report_state;
194
+ bool report_value;
195
+ const char *title;
196
+ const char *units;
197
+ const char *context;
198
+ const char *family;
199
+ int priority;
200
+
201
+ // sensor readings
202
+ SENSOR_SUBFEATURE_TYPE input;
203
+ SENSOR_SUBFEATURE_TYPE average;
204
+
205
+ // thresholds
206
+ SENSOR_SUBFEATURE_TYPE min;
207
+ SENSOR_SUBFEATURE_TYPE max;
208
+ SENSOR_SUBFEATURE_TYPE lcrit;
209
+ SENSOR_SUBFEATURE_TYPE crit;
210
+ SENSOR_SUBFEATURE_TYPE cap;
211
+ SENSOR_SUBFEATURE_TYPE emergency;
212
+
213
+ // alarms
214
+ SENSOR_SUBFEATURE_TYPE fault;
215
+ SENSOR_SUBFEATURE_TYPE alarm;
216
+ SENSOR_SUBFEATURE_TYPE min_alarm;
217
+ SENSOR_SUBFEATURE_TYPE max_alarm;
218
+ SENSOR_SUBFEATURE_TYPE lcrit_alarm;
219
+ SENSOR_SUBFEATURE_TYPE crit_alarm;
220
+ SENSOR_SUBFEATURE_TYPE cap_alarm;
221
+ SENSOR_SUBFEATURE_TYPE emergency_alarm;
222
+} sensors_configurations[] = {
223
+ [SENSORS_FEATURE_IN] = {
224
+ .enabled = true,
225
+ .title = "Sensor Voltage",
226
+ .units = "Volts",
227
+ .context = "system.hw.sensor.voltage",
228
+ .family = "Voltage",
229
+ .priority = 70002,
230
+ .report_value = true,
231
+ .report_state = true,
232
+
233
+ .input = SENSORS_SUBFEATURE_IN_INPUT,
234
+ .average = SENSORS_SUBFEATURE_IN_AVERAGE,
235
+
236
+ .min = SENSORS_SUBFEATURE_IN_MIN,
237
+ .max = SENSORS_SUBFEATURE_IN_MAX,
238
+ .lcrit = SENSORS_SUBFEATURE_IN_LCRIT,
239
+ .crit = SENSORS_SUBFEATURE_IN_CRIT,
240
+ .cap = NOT_SUPPORTED,
241
+ .emergency = NOT_SUPPORTED,
242
+
243
+ .fault = NOT_SUPPORTED,
244
+ .alarm = SENSORS_SUBFEATURE_IN_ALARM,
245
+ .min_alarm = SENSORS_SUBFEATURE_IN_MIN_ALARM,
246
+ .max_alarm = SENSORS_SUBFEATURE_IN_MAX_ALARM,
247
+ .lcrit_alarm = SENSORS_SUBFEATURE_IN_LCRIT_ALARM,
248
+ .crit_alarm = SENSORS_SUBFEATURE_IN_CRIT_ALARM,
249
+ .cap_alarm = NOT_SUPPORTED,
250
+ .emergency_alarm = NOT_SUPPORTED,
251
+ },
252
+
253
+ [SENSORS_FEATURE_FAN] = {
254
+ .enabled = true,
255
+ .title = "Sensor Fan Speed",
256
+ .units = "rotations per minute",
257
+ .context = "system.hw.sensor.fan",
258
+ .family = "Fan",
259
+ .priority = 70005,
260
+ .report_value = true,
261
+ .report_state = true,
262
+
263
+ .input = SENSORS_SUBFEATURE_FAN_INPUT,
264
+ .average = NOT_SUPPORTED,
265
+
266
+ .min = SENSORS_SUBFEATURE_FAN_MIN,
267
+ .max = SENSORS_SUBFEATURE_FAN_MAX,
268
+ .lcrit = NOT_SUPPORTED,
269
+ .crit = NOT_SUPPORTED,
270
+ .cap = NOT_SUPPORTED,
271
+ .emergency = NOT_SUPPORTED,
272
+
273
+ .fault = SENSORS_SUBFEATURE_FAN_FAULT,
274
+ .alarm = SENSORS_SUBFEATURE_FAN_ALARM,
275
+ .min_alarm = SENSORS_SUBFEATURE_FAN_MIN_ALARM,
276
+ .max_alarm = SENSORS_SUBFEATURE_FAN_MAX_ALARM,
277
+ .lcrit_alarm = NOT_SUPPORTED,
278
+ .crit_alarm = NOT_SUPPORTED,
279
+ .cap_alarm = NOT_SUPPORTED,
280
+ .emergency_alarm = NOT_SUPPORTED,
281
+ },
282
+
283
+ [SENSORS_FEATURE_TEMP] = {
284
+ .enabled = true,
285
+ .title = "Sensor Temperature",
286
+ .units = "degrees Celsius",
287
+ .context = "system.hw.sensor.temperature",
288
+ .family = "Temperature",
289
+ .priority = 70000,
290
+ .report_value = true,
291
+ .report_state = true,
292
+
293
+ .input = SENSORS_SUBFEATURE_TEMP_INPUT,
294
+ .average = NOT_SUPPORTED,
295
+
296
+ .min = SENSORS_SUBFEATURE_TEMP_MIN,
297
+ .max = SENSORS_SUBFEATURE_TEMP_MAX,
298
+ .lcrit = SENSORS_SUBFEATURE_TEMP_LCRIT,
299
+ .crit = SENSORS_SUBFEATURE_TEMP_CRIT,
300
+ .cap = NOT_SUPPORTED,
301
+ .emergency = SENSORS_SUBFEATURE_TEMP_EMERGENCY,
302
+
303
+ .fault = SENSORS_SUBFEATURE_TEMP_FAULT,
304
+ .alarm = SENSORS_SUBFEATURE_TEMP_ALARM,
305
+ .min_alarm = SENSORS_SUBFEATURE_TEMP_MIN_ALARM,
306
+ .max_alarm = SENSORS_SUBFEATURE_TEMP_MAX_ALARM,
307
+ .lcrit_alarm = SENSORS_SUBFEATURE_TEMP_LCRIT_ALARM,
308
+ .crit_alarm = SENSORS_SUBFEATURE_TEMP_CRIT_ALARM,
309
+ .cap_alarm = NOT_SUPPORTED,
310
+ .emergency_alarm = SENSORS_SUBFEATURE_TEMP_EMERGENCY_ALARM,
311
+ },
312
+
313
+ [SENSORS_FEATURE_POWER] = {
314
+ .enabled = true,
315
+ .title = "Sensor Power",
316
+ .units = "Watts",
317
+ .context = "system.hw.sensor.power",
318
+ .family = "Power",
319
+ .priority = 70006,
320
+ .report_value = true,
321
+ .report_state = true,
322
+
323
+ .input = SENSORS_SUBFEATURE_POWER_INPUT,
324
+ .average = SENSORS_SUBFEATURE_POWER_AVERAGE,
325
+
326
+ .min = SENSORS_SUBFEATURE_POWER_MIN,
327
+ .max = SENSORS_SUBFEATURE_POWER_MAX,
328
+ .lcrit = SENSORS_SUBFEATURE_POWER_LCRIT,
329
+ .crit = SENSORS_SUBFEATURE_POWER_CRIT,
330
+ .cap = SENSORS_SUBFEATURE_POWER_CAP,
331
+ .emergency = NOT_SUPPORTED,
332
+
333
+ .fault = NOT_SUPPORTED,
334
+ .alarm = SENSORS_SUBFEATURE_POWER_ALARM,
335
+ .min_alarm = SENSORS_SUBFEATURE_POWER_MIN_ALARM,
336
+ .max_alarm = SENSORS_SUBFEATURE_POWER_MAX_ALARM,
337
+ .lcrit_alarm = SENSORS_SUBFEATURE_POWER_LCRIT_ALARM,
338
+ .crit_alarm = SENSORS_SUBFEATURE_POWER_CRIT_ALARM,
339
+ .cap_alarm = SENSORS_SUBFEATURE_POWER_CAP_ALARM,
340
+ .emergency_alarm = NOT_SUPPORTED,
341
+ },
342
+
343
+ [SENSORS_FEATURE_ENERGY] = {
344
+ .enabled = true,
345
+ .title = "Sensor Energy",
346
+ .units = "Joules",
347
+ .context = "system.hw.sensor.energy",
348
+ .family = "Energy",
349
+ .priority = 70007,
350
+ .report_value = true,
351
+ .report_state = true,
352
+
353
+ .input = SENSORS_SUBFEATURE_ENERGY_INPUT,
354
+ .average = NOT_SUPPORTED,
355
+
356
+ .min = NOT_SUPPORTED,
357
+ .max = NOT_SUPPORTED,
358
+ .lcrit = NOT_SUPPORTED,
359
+ .crit = NOT_SUPPORTED,
360
+ .cap = NOT_SUPPORTED,
361
+ .emergency = NOT_SUPPORTED,
362
+
363
+ .fault = NOT_SUPPORTED,
364
+ .alarm = NOT_SUPPORTED,
365
+ .min_alarm = NOT_SUPPORTED,
366
+ .max_alarm = NOT_SUPPORTED,
367
+ .lcrit_alarm = NOT_SUPPORTED,
368
+ .crit_alarm = NOT_SUPPORTED,
369
+ .cap_alarm = NOT_SUPPORTED,
370
+ .emergency_alarm = NOT_SUPPORTED,
371
+ },
372
+
373
+ [SENSORS_FEATURE_CURR] = {
374
+ .enabled = true,
375
+ .title = "Sensor Current",
376
+ .units = "Amperes",
377
+ .context = "system.hw.sensor.current",
378
+ .family = "Current",
379
+ .priority = 70003,
380
+ .report_value = true,
381
+ .report_state = true,
382
+
383
+ .input = SENSORS_SUBFEATURE_CURR_INPUT,
384
+ .average = SENSORS_SUBFEATURE_CURR_AVERAGE,
385
+
386
+ .min = SENSORS_SUBFEATURE_CURR_MIN,
387
+ .max = SENSORS_SUBFEATURE_CURR_MAX,
388
+ .lcrit = SENSORS_SUBFEATURE_CURR_LCRIT,
389
+ .crit = SENSORS_SUBFEATURE_CURR_CRIT,
390
+ .cap = NOT_SUPPORTED,
391
+ .emergency = NOT_SUPPORTED,
392
+
393
+ .fault = NOT_SUPPORTED,
394
+ .alarm = SENSORS_SUBFEATURE_CURR_ALARM,
395
+ .min_alarm = SENSORS_SUBFEATURE_CURR_MIN_ALARM,
396
+ .max_alarm = SENSORS_SUBFEATURE_CURR_MAX_ALARM,
397
+ .lcrit_alarm = SENSORS_SUBFEATURE_CURR_LCRIT_ALARM,
398
+ .crit_alarm = SENSORS_SUBFEATURE_CURR_CRIT_ALARM,
399
+ .cap_alarm = NOT_SUPPORTED,
400
+ .emergency_alarm = NOT_SUPPORTED,
401
+ },
402
+
403
+ [SENSORS_FEATURE_HUMIDITY] = {
404
+ .enabled = true,
405
+ .title = "Sensor Humidity",
406
+ .units = "percentage",
407
+ .context = "system.hw.sensor.humidity",
408
+ .family = "Humidity",
409
+ .priority = 70004,
410
+ .report_value = true,
411
+ .report_state = true,
412
+
413
+ .input = SENSORS_SUBFEATURE_HUMIDITY_INPUT,
414
+ .average = NOT_SUPPORTED,
415
+
416
+ .min = NOT_SUPPORTED,
417
+ .max = NOT_SUPPORTED,
418
+ .lcrit = NOT_SUPPORTED,
419
+ .crit = NOT_SUPPORTED,
420
+ .cap = NOT_SUPPORTED,
421
+ .emergency = NOT_SUPPORTED,
422
+
423
+ .fault = NOT_SUPPORTED,
424
+ .alarm = NOT_SUPPORTED,
425
+ .min_alarm = NOT_SUPPORTED,
426
+ .max_alarm = NOT_SUPPORTED,
427
+ .lcrit_alarm = NOT_SUPPORTED,
428
+ .crit_alarm = NOT_SUPPORTED,
429
+ .cap_alarm = NOT_SUPPORTED,
430
+ .emergency_alarm = NOT_SUPPORTED,
431
+ },
432
+
433
+ [SENSORS_FEATURE_INTRUSION] = {
434
+ .enabled = true,
435
+ .title = "Sensor Intrusion",
436
+ .units = "", // No specific unit, as this is a binary state
437
+ .context = "system.hw.sensor.intrusion",
438
+ .family = "Intrusion",
439
+ .priority = 70008,
440
+ .report_value = false, // there is not value in intrusion
441
+ .report_state = true,
442
+
443
+ .input = NOT_SUPPORTED,
444
+ .average = NOT_SUPPORTED,
445
+
446
+ .min = NOT_SUPPORTED,
447
+ .max = NOT_SUPPORTED,
448
+ .lcrit = NOT_SUPPORTED,
449
+ .crit = NOT_SUPPORTED,
450
+ .cap = NOT_SUPPORTED,
451
+ .emergency = NOT_SUPPORTED,
452
+
453
+ .fault = NOT_SUPPORTED,
454
+ .alarm = SENSORS_SUBFEATURE_INTRUSION_ALARM,
455
+ .min_alarm = NOT_SUPPORTED,
456
+ .max_alarm = NOT_SUPPORTED,
457
+ .lcrit_alarm = NOT_SUPPORTED,
458
+ .crit_alarm = NOT_SUPPORTED,
459
+ .cap_alarm = NOT_SUPPORTED,
460
+ .emergency_alarm = NOT_SUPPORTED,
461
+ },
462
+};
463
+
464
+typedef struct subfeature {
465
+ STRING *name;
466
+ bool read;
467
+ double value;
468
+} SUBFEATURE;
469
+DEFINE_JUDYL_TYPED(SUBFEATURES, SUBFEATURE *);
470
+
471
+typedef struct sensor {
472
+ bool read;
473
+
474
+ bool exposed_input;
475
+ bool exposed_average;
476
+ SENSOR_STATE exposed_states;
477
+
478
+ // double divisor; // the divisor required to convert to base units
479
+ double input;
480
+ double average;
481
+
482
+ STRING *id;
483
+
484
+ struct {
485
+ STRING *id;
486
+ STRING *driver;
487
+ STRING *adapter;
488
+ STRING *path;
489
+ STRING *device;
490
+ STRING *subsystem;
491
+ short bus;
492
+ int addr;
493
+ } chip;
494
+
495
+ struct {
496
+ SENSOR_TYPE type;
497
+ STRING *name;
498
+ STRING *label;
499
+ } feature;
500
+
501
+ SENSOR_STATE state;
502
+ SENSOR_STATE state_logged;
503
+ SENSOR_STATE supported_states;
504
+ SUBFEATURES_JudyLSet values;
505
+
506
+ struct sensor_config config;
507
+ STRING *log_msg;
508
+} SENSOR;
509
+
510
+static inline msec_t chip_update_interval(const char *path, msec_t default_interval_ms) {
511
+ char filename[FILENAME_MAX];
512
+ snprintfz(filename, sizeof(filename), "%s/update_interval", path);
513
+
514
+ unsigned long long result = 0;
515
+ if(read_single_number_file(filename, &result) != 0)
516
+ result = default_interval_ms;
517
+
518
+ return result;
519
+}
520
+
521
+static STRING *get_device_name(const char *hwmon_path) {
522
+ char device_path[FILENAME_MAX];
523
+ char link_target[FILENAME_MAX];
524
+
525
+ // Construct path to the device symlink
526
+ snprintfz(device_path, sizeof(device_path), "%s/device", hwmon_path);
527
+
528
+ // Read the symlink
529
+ ssize_t len = readlink(device_path, link_target, sizeof(link_target) - 1);
530
+ if (len < 0) return NULL;
531
+ link_target[len] = '\0';
532
+
533
+ // Extract the last component of the path
534
+ char *last_slash = strrchr(link_target, '/');
535
+ if (last_slash)
536
+ return string_strdupz(last_slash + 1);
537
+
538
+ return NULL;
539
+}
540
+
541
+static STRING *get_subsystem_name(const char *hwmon_path) {
542
+ char device_path[FILENAME_MAX];
543
+ char link_target[FILENAME_MAX];
544
+
545
+ // Construct path to the device symlink
546
+ snprintfz(device_path, sizeof(device_path), "%s/device/subsystem", hwmon_path);
547
+
548
+ // Read the symlink
549
+ ssize_t len = readlink(device_path, link_target, sizeof(link_target) - 1);
550
+ if (len < 0) return NULL;
551
+ link_target[len] = '\0';
552
+
553
+ // Extract the last component of the path
554
+ char *last_slash = strrchr(link_target, '/');
555
+ if (last_slash)
556
+ return string_strdupz(last_slash + 1);
557
+
558
+ return NULL;
559
+}
560
+
561
+static inline bool sensor_subfeature_needed(SENSOR *s, SENSOR_SUBFEATURE_TYPE type) {
562
+ return
563
+ type != NOT_SUPPORTED &&
564
+ (
565
+ type == s->config.input ||
566
+ type == s->config.average ||
567
+ type == s->config.min ||
568
+ type == s->config.max ||
569
+ type == s->config.lcrit ||
570
+ type == s->config.crit ||
571
+ type == s->config.cap ||
572
+ type == s->config.emergency ||
573
+ type == s->config.fault ||
574
+ type == s->config.alarm ||
575
+ type == s->config.min_alarm ||
576
+ type == s->config.max_alarm ||
577
+ type == s->config.lcrit_alarm ||
578
+ type == s->config.crit_alarm ||
579
+ type == s->config.cap_alarm ||
580
+ type == s->config.emergency_alarm
581
+ );
582
+}
583
+
584
+static inline double sensor_value(SENSOR *s, SENSOR_SUBFEATURE_TYPE type) {
585
+ double value = NAN;
586
+
587
+ SUBFEATURE *sft = SUBFEATURES_GET(&s->values, type);
588
+ if(sft && sft->read && !isinf(sft->value) && !isnan(sft->value))
589
+ value = sft->value;
590
+
591
+ return value;
592
+}
593
+
594
+static inline void transition_to_state(SENSOR *s) {
595
+ if(s->state_logged == s->state) {
596
+ string_freez(s->log_msg);
597
+ s->log_msg = NULL;
598
+ return;
599
+ }
600
+
601
+ ND_LOG_STACK lgs[] = {
602
+ ND_LOG_FIELD_UUID(NDF_MESSAGE_ID, &sensors_state_transition_msgid),
603
+ ND_LOG_FIELD_END(),
604
+ };
605
+ ND_LOG_STACK_PUSH(lgs);
606
+
607
+ ND_LOG_FIELD_PRIORITY prio;
608
+ switch(s->state) {
609
+ default:
610
+ case SENSOR_STATE_CLEAR:
611
+ prio = NDLP_NOTICE;
612
+ break;
613
+
614
+ case SENSOR_STATE_CAP:
615
+ case SENSOR_STATE_WARNING:
616
+ prio = NDLP_WARNING;
617
+ break;
618
+
619
+ case SENSOR_STATE_FAULT:
620
+ case SENSOR_STATE_ALARM:
621
+ prio = NDLP_ERR;
622
+ break;
623
+
624
+ case SENSOR_STATE_CRITICAL:
625
+ prio = NDLP_CRIT;
626
+ break;
627
+
628
+ case SENSOR_STATE_EMERGENCY:
629
+ prio = NDLP_ALERT;
630
+ break;
631
+ }
632
+
633
+ nd_log(NDLS_COLLECTORS, prio,
634
+ "%s sensor '%s' transitioned from state '%s' to '%s' [device '%s', driver '%s', subsystem '%s', path '%s']%s%s",
635
+ SENSOR_TYPE_2str(s->feature.type),
636
+ string2str(s->id),
637
+ SENSOR_STATE_2str(s->state_logged), SENSOR_STATE_2str(s->state),
638
+ string2str(s->chip.device), string2str(s->chip.driver),
639
+ string2str(s->chip.subsystem), string2str(s->chip.path),
640
+ s->log_msg ? ": " : "", string2str(s->log_msg));
641
+
642
+ string_freez(s->log_msg);
643
+ s->log_msg = NULL;
644
+
645
+ s->state_logged = s->state;
646
+}
647
+
648
+static inline void check_value_greater_than_zero(SENSOR *s, SENSOR_SUBFEATURE_TYPE *config, SENSOR_STATE state) {
649
+ if(*config == NOT_SUPPORTED)
650
+ return;
651
+
652
+ double status = sensor_value(s, *config);
653
+ if(isnan(status)) {
654
+ // we cannot read this
655
+ // exclude it from future iterations for this sensor
656
+ *config = NOT_SUPPORTED;
657
+ }
658
+ else {
659
+ // the sensor supports this state
660
+ s->supported_states |= state;
661
+
662
+ // set it to this state if it is raised
663
+ if(status > 0 && s->state == SENSOR_STATE_CLEAR) {
664
+ s->state = state;
665
+
666
+ string_freez(s->log_msg);
667
+ char buf[100];
668
+ snprintf(buf, sizeof(buf), "%s == %f (kernel driver generated)",
669
+ SENSOR_SUBFEATURE_TYPE_2str(*config), status);
670
+ s->log_msg = string_strdupz(buf);
671
+ }
672
+ }
673
+}
674
+
675
+static void userspace_evaluation_log_msg(SENSOR *s, const char *reading_txt, const char *condition, const char *threshold_txt, double reading, double threshold) {
676
+ string_freez(s->log_msg);
677
+ char buf[200];
678
+ snprintf(buf, sizeof(buf), "%s %f %s %s %f (userspace evaluation using kernel provided thresholds)",
679
+ reading_txt, reading, condition, threshold_txt, threshold);
680
+ s->log_msg = string_strdupz(buf);
681
+}
682
+
683
+static inline void check_smaller_than_threshold(SENSOR *s, SENSOR_SUBFEATURE_TYPE *config, SENSOR_STATE state) {
684
+ if(*config == NOT_SUPPORTED)
685
+ return;
686
+
687
+ double threshold = sensor_value(s, *config);
688
+ if(isnan(threshold)) {
689
+ // we cannot read this
690
+ // exclude it from future iterations for this sensor
691
+ *config = NOT_SUPPORTED;
692
+ }
693
+ else {
694
+ // the sensor supports this state
695
+ s->supported_states |= state;
696
+
697
+ // set it to this state if it is raised
698
+ if(s->input < threshold && s->state == SENSOR_STATE_CLEAR) {
699
+ s->state = state;
700
+ userspace_evaluation_log_msg(
701
+ s, "input", "<", SENSOR_SUBFEATURE_TYPE_2str(*config),
702
+ s->input, threshold);
703
+ }
704
+ else if(s->average < threshold && s->state == SENSOR_STATE_CLEAR) {
705
+ s->state = state;
706
+ userspace_evaluation_log_msg(
707
+ s, "average", "<", SENSOR_SUBFEATURE_TYPE_2str(*config),
708
+ s->average, threshold);
709
+ }
710
+ }
711
+}
712
+
713
+static inline void check_greater_than_threshold(SENSOR *s, SENSOR_SUBFEATURE_TYPE *config, SENSOR_STATE state) {
714
+ if(*config == NOT_SUPPORTED)
715
+ return;
716
+
717
+ double threshold = sensor_value(s, *config);
718
+ if(isnan(threshold)) {
719
+ // we cannot read this
720
+ // exclude it from future iterations for this sensor
721
+ *config = NOT_SUPPORTED;
722
+ }
723
+ else {
724
+ // the sensor supports this state
725
+ s->supported_states |= state;
726
+
727
+ // set it to this state if it is raised
728
+ if(s->input > threshold && s->state == SENSOR_STATE_CLEAR) {
729
+ s->state = state;
730
+ userspace_evaluation_log_msg(
731
+ s, "input", ">", SENSOR_SUBFEATURE_TYPE_2str(*config),
732
+ s->input, threshold);
733
+ }
734
+ else if(s->average > threshold && s->state == SENSOR_STATE_CLEAR) {
735
+ s->state = state;
736
+ userspace_evaluation_log_msg(
737
+ s, "average", ">", SENSOR_SUBFEATURE_TYPE_2str(*config),
738
+ s->average, threshold);
739
+ }
740
+ }
741
+}
742
+
743
+static inline void check_smaller_or_equal_to_threshold(SENSOR *s, SENSOR_SUBFEATURE_TYPE *config, SENSOR_STATE state) {
744
+ if(*config == NOT_SUPPORTED)
745
+ return;
746
+
747
+ double threshold = sensor_value(s, *config);
748
+ if(isnan(threshold)) {
749
+ // we cannot read this
750
+ // exclude it from future iterations for this sensor
751
+ *config = NOT_SUPPORTED;
752
+ }
753
+ else {
754
+ // the sensor supports this state
755
+ s->supported_states |= state;
756
+
757
+ // set it to this state if it is raised
758
+ if(s->input <= threshold && s->state == SENSOR_STATE_CLEAR) {
759
+ s->state = state;
760
+ userspace_evaluation_log_msg(
761
+ s, "input", "<=", SENSOR_SUBFEATURE_TYPE_2str(*config),
762
+ s->input, threshold);
763
+ }
764
+ else if(s->average <= threshold && s->state == SENSOR_STATE_CLEAR) {
765
+ s->state = state;
766
+ userspace_evaluation_log_msg(
767
+ s, "average", "<=", SENSOR_SUBFEATURE_TYPE_2str(*config),
768
+ s->average, threshold);
769
+ }
770
+ }
771
+}
772
+
773
+static inline void check_greater_or_equal_to_threshold(SENSOR *s, SENSOR_SUBFEATURE_TYPE *config, SENSOR_STATE state) {
774
+ if(*config == NOT_SUPPORTED)
775
+ return;
776
+
777
+ double threshold = sensor_value(s, *config);
778
+ if(isnan(threshold)) {
779
+ // we cannot read this
780
+ // exclude it from future iterations for this sensor
781
+ *config = NOT_SUPPORTED;
782
+ }
783
+ else {
784
+ // the sensor supports this state
785
+ s->supported_states |= state;
786
+
787
+ // set it to this state if it is raised
788
+ if(s->input >= threshold && s->state == SENSOR_STATE_CLEAR) {
789
+ s->state = state;
790
+ userspace_evaluation_log_msg(
791
+ s, "input", ">=", SENSOR_SUBFEATURE_TYPE_2str(*config),
792
+ s->input, threshold);
793
+ }
794
+ else if(s->average >= threshold && s->state == SENSOR_STATE_CLEAR) {
795
+ s->state = state;
796
+ userspace_evaluation_log_msg(
797
+ s, "average", ">=", SENSOR_SUBFEATURE_TYPE_2str(*config),
798
+ s->average, threshold);
799
+ }
800
+ }
801
+}
802
+
803
+static void set_sensor_state(SENSOR *s) {
804
+ s->supported_states = SENSOR_STATE_CLEAR;
805
+ s->state = SENSOR_STATE_CLEAR;
806
+
807
+ // ----------------------------------------------------------------------------------------------------------------
808
+ // read the values
809
+
810
+ if(s->config.input != NOT_SUPPORTED) {
811
+ s->input = sensor_value(s, s->config.input);
812
+ if(isnan(s->input) && !s->exposed_input) {
813
+ s->config.input = NOT_SUPPORTED;
814
+ s->input = NAN;
815
+ }
816
+ }
817
+
818
+ if(s->config.average != NOT_SUPPORTED) {
819
+ s->average = sensor_value(s, s->config.average);
820
+ if(isnan(s->average) && !s->exposed_average) {
821
+ s->config.average = NOT_SUPPORTED;
822
+ s->average = NAN;
823
+ }
824
+ }
825
+
826
+ // ----------------------------------------------------------------------------------------------------------------
827
+ // read the sensor alarms as exposed by the kernel driver
828
+
829
+ check_value_greater_than_zero(s, &s->config.fault, SENSOR_STATE_FAULT);
830
+ check_value_greater_than_zero(s, &s->config.emergency_alarm, SENSOR_STATE_EMERGENCY);
831
+ check_value_greater_than_zero(s, &s->config.crit_alarm, SENSOR_STATE_CRITICAL);
832
+ check_value_greater_than_zero(s, &s->config.lcrit_alarm, SENSOR_STATE_CRITICAL);
833
+ check_value_greater_than_zero(s, &s->config.max_alarm, SENSOR_STATE_ALARM);
834
+ check_value_greater_than_zero(s, &s->config.min_alarm, SENSOR_STATE_ALARM);
835
+ check_value_greater_than_zero(s, &s->config.alarm, SENSOR_STATE_ALARM);
836
+ check_value_greater_than_zero(s, &s->config.cap_alarm, SENSOR_STATE_CAP);
837
+
838
+#ifdef NETDATA_CALCULATED_STATES
839
+
840
+ // ----------------------------------------------------------------------------------------------------------------
841
+ // our custom logic for triggering state changes
842
+
843
+ // if the sensor is already exposed to netdata, but now it cannot give values,
844
+ // set it to faulty state
845
+ s->supported_states |= SENSOR_STATE_FAULT;
846
+ if(isnan(s->input) && isnan(s->average) && (s->exposed_input || s->exposed_average) &&
847
+ s->state == SENSOR_STATE_CLEAR) {
848
+ s->state = SENSOR_STATE_FAULT;
849
+ }
850
+
851
+ check_greater_or_equal_to_threshold(s, &s->config.emergency, SENSOR_STATE_EMERGENCY);
852
+ check_greater_or_equal_to_threshold(s, &s->config.crit, SENSOR_STATE_CRITICAL);
853
+ check_smaller_or_equal_to_threshold(s, &s->config.lcrit, SENSOR_STATE_CRITICAL);
854
+ check_greater_than_threshold(s, &s->config.cap, SENSOR_STATE_CAP);
855
+ check_greater_than_threshold(s, &s->config.max, SENSOR_STATE_WARNING);
856
+ check_smaller_than_threshold(s, &s->config.min, SENSOR_STATE_WARNING);
857
+
858
+#endif
859
+
860
+ // ----------------------------------------------------------------------------------------------------------------
861
+ // log any transitions
862
+
863
+ transition_to_state(s);
864
+}
865
+
866
+static SENSOR *sensor_get_or_create(DICTIONARY *dict, const sensors_chip_name *chip, const sensors_feature *feature) {
867
+ static __thread char buf[4096];
868
+
869
+ struct sensor_config *config = NULL;
870
+ if(feature->type < _countof(sensors_configurations))
871
+ config = &sensors_configurations[feature->type];
872
+
873
+ if(!config || !config->enabled)
874
+ return NULL;
875
+
876
+ snprintfz(buf, sizeof(buf),
877
+ "%s|%s-%d-%d-%s",
878
+ chip->path, chip->prefix, chip->bus.type, chip->addr, feature->name);
879
+
880
+ SENSOR *s = dictionary_get(dict, buf);
881
+ if(s) return s;
882
+
883
+ s = dictionary_set(dict, buf, NULL, sizeof(SENSOR));
884
+ s->config = *config;
885
+ s->state_logged = SENSOR_STATE_CLEAR;
886
+ s->input = NAN;
887
+ s->average = NAN;
888
+ s->feature.label = NULL;
889
+
890
+ sensors_snprintf_chip_name(buf, sizeof(buf), chip);
891
+ s->chip.id = string_strdupz(buf);
892
+ s->chip.driver = string_strdupz(chip->prefix);
893
+ s->chip.adapter = string_strdupz(sensors_get_adapter_name(&chip->bus));
894
+ s->chip.path = string_strdupz(chip->path);
895
+ s->chip.device = get_device_name(chip->path);
896
+ s->chip.subsystem = get_subsystem_name(chip->path);
897
+ s->chip.bus = chip->bus.type;
898
+ s->chip.addr = chip->addr;
899
+
900
+ s->feature.name = string_strdupz(feature->name);
901
+ s->feature.type = feature->type;
902
+
903
+ // returns feature->name if no label
904
+ // https://github.com/lm-sensors/lm-sensors/blob/master/lib/access.c#L199-L200
905
+ const char *label = sensors_get_label(chip, feature);
906
+
907
+ if (label && strcmp(label, string2str(s->feature.name)) != 0) {
908
+ s->feature.label = string_strdupz(label);
909
+ snprintfz(
910
+ buf,
911
+ sizeof(buf),
912
+ "%s_%s_%s_%s",
913
+ SENSOR_TYPE_2str(s->feature.type),
914
+ string2str(s->chip.id),
915
+ string2str(s->feature.name),
916
+ string2str(s->feature.label));
917
+ } else {
918
+ snprintfz(
919
+ buf,
920
+ sizeof(buf),
921
+ "%s_%s_%s",
922
+ SENSOR_TYPE_2str(s->feature.type),
923
+ string2str(s->chip.id),
924
+ string2str(s->feature.name));
925
+ }
926
+
927
+ netdata_fix_chart_id(buf);
928
+ s->id = string_strdupz(buf);
929
+
930
+ return s;
931
+}
932
+
933
+static void sensor_labels(SENSOR *ft) {
934
+ printf(PLUGINSD_KEYWORD_CLABEL " feature '%s' 1\n", string2str(ft->feature.name));
935
+ printf(PLUGINSD_KEYWORD_CLABEL " label '%s' 1\n", string2str(ft->feature.label));
936
+ printf(PLUGINSD_KEYWORD_CLABEL " chip_id '%s' 1\n", string2str(ft->chip.id));
937
+ printf(PLUGINSD_KEYWORD_CLABEL " path '%s' 1\n", string2str(ft->chip.path));
938
+ printf(PLUGINSD_KEYWORD_CLABEL " subsystem '%s' 1\n", string2str(ft->chip.subsystem));
939
+ printf(PLUGINSD_KEYWORD_CLABEL " driver '%s' 1\n", string2str(ft->chip.device));
940
+
941
+// printf(
942
+// PLUGINSD_KEYWORD_CLABEL " sensor '%s - %s' 1\n",
943
+// string2str(ft->chip.name),
944
+// string2str(ft->feature.label));
945
+
946
+ printf(PLUGINSD_KEYWORD_CLABEL_COMMIT "\n");
947
+}
948
+
949
+static size_t states_count(SENSOR_STATE state) {
950
+ // the gcc way
951
+ return __builtin_popcount(state);
952
+
953
+// size_t count = 0;
954
+// while (state) {
955
+// state &= (state - 1); // Clear the least significant set bit
956
+// count++;
957
+// }
958
+// return count;
959
+}
960
+
961
+static void sensor_process(SENSOR *s, int update_every, const char *name) {
962
+ // evaluate the state of the feature
963
+ set_sensor_state(s);
964
+ internal_fatal(s->state == 0,
965
+ "SENSORS: state %u is not a valid state", s->state);
966
+
967
+ internal_fatal((s->state & s->supported_states) == 0,
968
+ "SENSORS: state %u is not in the supported list of states %u",
969
+ s->state,
970
+ s->supported_states);
971
+
972
+ bool do_input = s->config.report_value && !isnan(s->input);
973
+ bool do_average = s->config.report_value && !isnan(s->average);
974
+ bool do_state = s->config.report_state && states_count(s->supported_states) > 1;
975
+
976
+ // send the feature data to netdata
977
+ if(do_input && !s->exposed_input) {
978
+ printf(
979
+ PLUGINSD_KEYWORD_CHART " 'sensors.%s_input' '' '%s' '%s' '%s' '%s.input' line %d %d '' debugfs %s\n",
980
+ string2str(s->id),
981
+ s->config.title,
982
+ s->config.units,
983
+ s->config.family,
984
+ s->config.context,
985
+ s->config.priority,
986
+ update_every,
987
+ name);
988
+
989
+ printf(PLUGINSD_KEYWORD_DIMENSION " input '' absolute 1 10000 ''\n");
990
+ sensor_labels(s);
991
+ s->exposed_input = true;
992
+ }
993
+
994
+ if(do_average && !s->exposed_average) {
995
+ printf(
996
+ PLUGINSD_KEYWORD_CHART " 'sensors.%s_average' '' '%s Average' '%s' '%s' '%s.average' line %d %d '' debugfs %s\n",
997
+ string2str(s->id),
998
+ s->config.title,
999
+ s->config.units,
1000
+ s->config.family,
1001
+ s->config.context,
1002
+ s->config.priority + 1,
1003
+ update_every,
1004
+ name);
1005
+
1006
+ printf(PLUGINSD_KEYWORD_DIMENSION " average '' absolute 1 10000 ''\n");
1007
+ sensor_labels(s);
1008
+ s->exposed_average = true;
1009
+ }
1010
+
1011
+ if(do_state && s->exposed_states != s->supported_states) {
1012
+ printf(
1013
+ PLUGINSD_KEYWORD_CHART " 'sensors.%s_alarm' '' '%s Alarm Status' '%s' '%s' '%s.alarm' line %d %d '' debugfs %s\n",
1014
+ string2str(s->id),
1015
+ s->config.title,
1016
+ "status",
1017
+ s->config.family,
1018
+ s->config.context,
1019
+ s->config.priority + 2,
1020
+ update_every,
1021
+ name);
1022
+
1023
+ if(s->supported_states & SENSOR_STATE_CLEAR)
1024
+ printf(PLUGINSD_KEYWORD_DIMENSION " clear '' absolute 1 1 ''\n");
1025
+ if(s->supported_states & SENSOR_STATE_WARNING)
1026
+ printf(PLUGINSD_KEYWORD_DIMENSION " warning '' absolute 1 1 ''\n");
1027
+ if(s->supported_states & SENSOR_STATE_CAP)
1028
+ printf(PLUGINSD_KEYWORD_DIMENSION " cap '' absolute 1 1 ''\n");
1029
+ if(s->supported_states & SENSOR_STATE_ALARM)
1030
+ printf(PLUGINSD_KEYWORD_DIMENSION " alarm '' absolute 1 1 ''\n");
1031
+ if(s->supported_states & SENSOR_STATE_CRITICAL)
1032
+ printf(PLUGINSD_KEYWORD_DIMENSION " critical '' absolute 1 1 ''\n");
1033
+ if(s->supported_states & SENSOR_STATE_EMERGENCY)
1034
+ printf(PLUGINSD_KEYWORD_DIMENSION " emergency '' absolute 1 1 ''\n");
1035
+ if(s->supported_states & SENSOR_STATE_FAULT)
1036
+ printf(PLUGINSD_KEYWORD_DIMENSION " fault '' absolute 1 1 ''\n");
1037
+
1038
+ sensor_labels(s);
1039
+ s->exposed_states = s->supported_states;
1040
+ }
1041
+
1042
+#if 0
1043
+ // ----------------------------------------------------------------------------------------------------------------
1044
+ // debugging
1045
+
1046
+ fprintf(stderr,
1047
+ "SENSORS: "
1048
+ "{ chip id '%s', name '%s', addr %d }, "
1049
+ "{ adapter '%s', bus '%s', path '%s'}, "
1050
+ "{ feature label '%s', name '%s', type '%s' }\n",
1051
+ string2str(s->chip.id),
1052
+ string2str(s->chip.driver),
1053
+ s->chip.addr,
1054
+ string2str(s->chip.adapter),
1055
+ SENSOR_BUS_TYPE_2str(s->chip.bus),
1056
+ string2str(s->chip.path),
1057
+ string2str(s->feature.label),
1058
+ string2str(s->feature.name),
1059
+ SENSOR_TYPE_2str(s->feature.type));
1060
+
1061
+ Word_t idx = 0;
1062
+ for(SUBFEATURE *sft = SUBFEATURES_FIRST(&s->values, &idx);
1063
+ sft;
1064
+ sft = SUBFEATURES_NEXT(&s->values, &idx)) {
1065
+ fprintf(stderr,
1066
+ " ------------ >>> "
1067
+ "{ subfeature '%s', type '%s' } "
1068
+ "value %f, %s\n",
1069
+ string2str(sft->name), SENSOR_SUBFEATURE_TYPE_2str(idx),
1070
+ sft->value, sft->read ? "OK" : "FAILED");
1071
+ }
1072
+
1073
+ if(do_input)
1074
+ fprintf(stderr, " ------------ >>> %f (input)\n", s->input);
1075
+
1076
+ if(do_average)
1077
+ fprintf(stderr, " ------------ >>> %f (average)\n", s->average);
1078
+
1079
+ if(do_state)
1080
+ fprintf(stderr, " ------------ >>> %u (state)\n", s->state);
1081
+#endif
1082
+
1083
+ // ----------------------------------------------------------------------------------------------------------------
1084
+ // send the data
1085
+
1086
+ if(do_input) {
1087
+ printf(PLUGINSD_KEYWORD_BEGIN " 'sensors.%s_input'\n", string2str(s->id));
1088
+
1089
+ printf(PLUGINSD_KEYWORD_SET " input = %lld\n", (long long)(s->input * 10000.0));
1090
+ printf(PLUGINSD_KEYWORD_END "\n");
1091
+ }
1092
+
1093
+ if(do_average) {
1094
+ printf(PLUGINSD_KEYWORD_BEGIN " 'sensors.%s_average'\n", string2str(s->id));
1095
+
1096
+ printf(PLUGINSD_KEYWORD_SET " average = %lld\n", (long long)(s->average * 10000.0));
1097
+ printf(PLUGINSD_KEYWORD_END "\n");
1098
+ }
1099
+
1100
+ if(do_state) {
1101
+ printf(
1102
+ PLUGINSD_KEYWORD_BEGIN " 'sensors.%s_alarm'\n",
1103
+ string2str(s->id));
1104
+
1105
+ if(s->supported_states & SENSOR_STATE_CLEAR)
1106
+ printf(PLUGINSD_KEYWORD_SET " clear = %d\n", s->state == SENSOR_STATE_CLEAR ? 1 : 0);
1107
+ if(s->supported_states & SENSOR_STATE_WARNING)
1108
+ printf(PLUGINSD_KEYWORD_SET " warning = %d\n", s->state == SENSOR_STATE_WARNING ? 1 : 0);
1109
+ if(s->supported_states & SENSOR_STATE_CAP)
1110
+ printf(PLUGINSD_KEYWORD_SET " cap = %d\n", s->state == SENSOR_STATE_CAP ? 1 : 0);
1111
+ if(s->supported_states & SENSOR_STATE_ALARM)
1112
+ printf(PLUGINSD_KEYWORD_SET " alarm = %d\n", s->state == SENSOR_STATE_ALARM ? 1 : 0);
1113
+ if(s->supported_states & SENSOR_STATE_CRITICAL)
1114
+ printf(PLUGINSD_KEYWORD_SET " critical = %d\n", s->state == SENSOR_STATE_CRITICAL ? 1 : 0);
1115
+ if(s->supported_states & SENSOR_STATE_EMERGENCY)
1116
+ printf(PLUGINSD_KEYWORD_SET " emergency = %d\n", s->state == SENSOR_STATE_EMERGENCY ? 1 : 0);
1117
+ if(s->supported_states & SENSOR_STATE_FAULT)
1118
+ printf(PLUGINSD_KEYWORD_SET " fault = %d\n", s->state == SENSOR_STATE_FAULT ? 1 : 0);
1119
+
1120
+ printf(PLUGINSD_KEYWORD_END "\n");
1121
+ }
1122
+}
1123
+
1124
+static FILE *sensors_open_file(const char *env_var, const char *def_dir, const char *file) {
1125
+ const char *dir = getenv(env_var);
1126
+ if(!dir || !*dir)
1127
+ dir = def_dir;
1128
+
1129
+ if (dir && *dir) {
1130
+ char filename[FILENAME_MAX];
1131
+ snprintfz(filename, sizeof(filename), "%s/%s", dir, file);
1132
+ return fopen(filename, "r");
1133
+ }
1134
+
1135
+ return NULL;
1136
+}
1137
+
1138
+static DICTIONARY *sensors_dict = NULL;
1139
+
1140
+static int sensors_collect_data(void) {
1141
+ // ----------------------------------------------------------------------------------------------------------------
1142
+ // reset all sensors to unread
1143
+
1144
+ SENSOR *s;
1145
+ dfe_start_read(sensors_dict, s) {
1146
+ s->read = false;
1147
+ }
1148
+ dfe_done(s);
1149
+
1150
+ // ----------------------------------------------------------------------------------------------------------------
1151
+ // Iterate over all detected chips
1152
+
1153
+ size_t subfeatures_collected = 0;
1154
+
1155
+ const sensors_chip_name *chip;
1156
+ int chip_nr = 0;
1157
+ while ((chip = sensors_get_detected_chips(NULL, &chip_nr)) != NULL) {
1158
+
1159
+ // Iterate over all features of the chip
1160
+ const sensors_feature *feature;
1161
+ int feature_nr = 0;
1162
+ while ((feature = sensors_get_features(chip, &feature_nr)) != NULL) {
1163
+ s = sensor_get_or_create(sensors_dict, chip, feature);
1164
+ if(!s) continue;
1165
+
1166
+ internal_fatal(s->read, "The features key is not unique!");
1167
+ s->read = true;
1168
+
1169
+ // --------------------------------------------------------------------------------------------------------
1170
+ // mark all existing subfeatures as unread
1171
+
1172
+ Word_t idx = 0;
1173
+ for(SUBFEATURE *sf = SUBFEATURES_FIRST(&s->values, &idx); sf; sf = SUBFEATURES_NEXT(&s->values, &idx)) {
1174
+ sf->read = false;
1175
+ sf->value = NAN;
1176
+ }
1177
+
1178
+ // --------------------------------------------------------------------------------------------------------
1179
+ // iterate over all subfeatures of the feature
1180
+
1181
+ const sensors_subfeature *subfeature;
1182
+ int subfeature_nr = 0;
1183
+ while ((subfeature = sensors_get_all_subfeatures(chip, feature, &subfeature_nr)) != NULL) {
1184
+ if(!(subfeature->flags & SENSORS_MODE_R) || // not readable
1185
+ !sensor_subfeature_needed(s, subfeature->type)) // we don't need it
1186
+ continue;
1187
+
1188
+ SUBFEATURE *sft = SUBFEATURES_GET(&s->values, subfeature->type);
1189
+ if(!sft) {
1190
+ sft = callocz(1, sizeof(*sft));
1191
+ sft->name = string_strdupz(subfeature->name);
1192
+ SUBFEATURES_SET(&s->values, subfeature->type, sft);
1193
+ }
1194
+
1195
+ if (sensors_get_value(chip, subfeature->number, &sft->value) == 0) {
1196
+ sft->read = true;
1197
+ subfeatures_collected++;
1198
+ }
1199
+ else {
1200
+ sft->value = NAN;
1201
+ sft->read = false;
1202
+ }
1203
+ }
1204
+ }
1205
+ }
1206
+
1207
+ return subfeatures_collected ? 0 : 1;
1208
+}
1209
+
1210
+static bool libsensors_running = false;
1211
+static int libsensors_update_every = 1;
1212
+
1213
+void *libsensors_thread(void *ptr) {
1214
+ int update_every = libsensors_update_every;
1215
+
1216
+ // first try the default directory for libsensors
1217
+ FILE *fp = fopen("/etc/sensors3.conf", "r");
1218
+ if(!fp) fp = sensors_open_file("NETDATA_CONFIG_DIR", CONFIG_DIR, "../sensors3.conf");
1219
+ if(!fp) fp = sensors_open_file("NETDATA_CONFIG_DIR", CONFIG_DIR, "sensors3.conf");
1220
+ if(!fp) fp = sensors_open_file("NETDATA_STOCK_CONFIG_DIR", LIBCONFIG_DIR, "sensors3.conf");
1221
+
1222
+ if (sensors_init(fp) != 0) {
1223
+ nd_log(NDLS_COLLECTORS, NDLP_ERR, "cannot initialize libsensors - disabling sensors monitoring");
1224
+ if(fp) fclose(fp);
1225
+ goto cleanup;
1226
+ }
1227
+ if(fp) fclose(fp);
1228
+
1229
+ sensors_dict = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_SINGLE_THREADED);
1230
+
1231
+ // preflight to check data collection latency
1232
+ {
1233
+ SENSOR *s;
1234
+
1235
+ sensors_collect_data(); // do the first before starting measurements
1236
+ dfe_start_read(sensors_dict, s) { set_sensor_state(s); } dfe_done(s);
1237
+
1238
+ usec_t max_ut = 0;
1239
+ size_t samples = 0;
1240
+ usec_t started_ut = now_monotonic_usec();
1241
+ for(size_t i = 0; i < 5; i++) {
1242
+ usec_t before_ut = now_monotonic_usec();
1243
+ sensors_collect_data();
1244
+ dfe_start_read(sensors_dict, s) { set_sensor_state(s); } dfe_done(s);
1245
+ usec_t after_ut = now_monotonic_usec();
1246
+ max_ut = MAX(max_ut, after_ut - before_ut);
1247
+ samples++;
1248
+ }
1249
+ usec_t ended_ut = now_monotonic_usec();
1250
+ usec_t average_ut = (ended_ut - started_ut) / samples;
1251
+
1252
+ // List of valid intervals in seconds (divisors and multiples of 60)
1253
+ static const int valid_update_every_intervals[] = {1, 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60, 120, 180, 240, 300, 600, 900, 1200, 1800, 3600};
1254
+
1255
+ // Find the smallest valid interval that satisfies our timing requirement
1256
+ int best_update_every = update_every;
1257
+ for(size_t i = 0; i < _countof(valid_update_every_intervals); i++) {
1258
+ if(valid_update_every_intervals[i] >= update_every &&
1259
+ max_ut <= (valid_update_every_intervals[i] * USEC_PER_SEC / 5)) {
1260
+ best_update_every = valid_update_every_intervals[i];
1261
+ break;
1262
+ }
1263
+ }
1264
+
1265
+ char avg[64], max[64];
1266
+ duration_snprintf(avg, sizeof(avg), (int64_t)average_ut, "us", false);
1267
+ duration_snprintf(max, sizeof(max), (int64_t)max_ut, "us", false);
1268
+
1269
+ nd_log(NDLS_COLLECTORS, NDLP_NOTICE,
1270
+ "SENSORS max data collection latency is %s (average %s), setting update_every to %ds (default is %ds)",
1271
+ max, avg, best_update_every, update_every);
1272
+
1273
+ update_every = best_update_every;
1274
+ }
1275
+
1276
+ heartbeat_t hb;
1277
+ heartbeat_init(&hb, update_every * USEC_PER_SEC);
1278
+
1279
+ while(!nd_thread_signaled_to_cancel()) {
1280
+ heartbeat_next(&hb);
1281
+
1282
+ if(sensors_collect_data())
1283
+ break;
1284
+
1285
+ netdata_mutex_lock(&stdout_mutex);
1286
+
1287
+ SENSOR *s;
1288
+ dfe_start_read(sensors_dict, s) {
1289
+ sensor_process(s, update_every, "sensors");
1290
+ }
1291
+ dfe_done(s);
1292
+
1293
+ fflush(stdout);
1294
+ netdata_mutex_unlock(&stdout_mutex);
1295
+ }
1296
+
1297
+cleanup:
1298
+ libsensors_running = false;
1299
+
1300
+ dictionary_destroy(sensors_dict);
1301
+ sensors_dict = NULL;
1302
+
1303
+ return NULL;
1304
+}
1305
+
1306
+static ND_THREAD *libsensors = NULL;
1307
+int do_module_libsensors(int update_every, const char *name __maybe_unused) {
1308
+ if(!libsensors) {
1309
+ libsensors_update_every = update_every;
1310
+ libsensors_running = true;
1311
+ libsensors = nd_thread_create("LIBSENSORS", NETDATA_THREAD_OPTION_JOINABLE, libsensors_thread, NULL);
1312
+ }
1313
+
1314
+ return libsensors && libsensors_running ? 0 : 1;
1315
+}
1316
+
1317
+void module_libsensors_cleanup(void) {
1318
+ nd_thread_signal_cancel(libsensors);
1319
+ nd_thread_join(libsensors);
1320
+}