@cryptotaxi247 / netdata-1 / commits / db020a1f4

Monitor sensors using libsensors via debugfs.plugin (#19251)

* add libsensors to debugfs.plugin * add string representations of libsensors types * read sensors3.conf during initialization * more work * progress * working set * working set 2 * working sensors * Vendor lm-sensors as a Git submodule * vendored libsensors * search for sensors headers in the vendored library * add flex and bison to required packages * include sensors.h from the vendored directory * remove HAVE_LIBSENSORS variable * do not load sensor subfeatures that are not needed * added device, driver and subsystem labels * add message id to log * copy the default sensors3.conf file to netdata stock configs directory * move sensors to a separate thread; automatically adapt data collection frequency to match actual data collection latency * make debugfs plugin wait while libsensors is running * fix for update every * update chart ctx and id, remove non-important labels * dont set label to feat name if none * just alarm * add sybsystem and driver labels --------- Co-authored-by: ilyam8 <ilya@netdata.cloud>

Costa Tsaousis committed Dec 29, 2024 at 17:37 UTC db020a1f47a63bb71f228ae3f6aa5b7fa21a01d3
14 files changed +1574 -97
.gitmodules
+3
@@ -10,3 +10,6 @@
10 path = src/web/server/h2o/libh2o
11 url = https://github.com/h2o/h2o.git
12 ignore = untracked
13 +[submodule "src/collectors/debugfs.plugin/libsensors/vendored"]
14 + path = src/collectors/debugfs.plugin/libsensors/vendored
15 + url = https://github.com/lm-sensors/lm-sensors.git
CMakeLists.txt
+34 -6
@@ -2186,23 +2186,51 @@ list(APPEND ACLK_FILES ${ACLK_PROTO_BUILT_SRCS}
2186 #
2187
2188 if(ENABLE_PLUGIN_DEBUGFS)
2189 + # Check for libcap (optional)
2190 pkg_check_modules(CAP QUIET libcap)
2191
2191 - set(DEBUGFS_PLUGIN_FILES src/collectors/debugfs.plugin/debugfs_plugin.c
2192 - src/collectors/debugfs.plugin/debugfs_plugin.h
2193 - src/collectors/debugfs.plugin/debugfs_extfrag.c
2194 - src/collectors/debugfs.plugin/debugfs_zswap.c
2195 - src/collectors/debugfs.plugin/sys_devices_virtual_powercap.c)
2192 + # Define debugfs.plugin source files
2193 + set(DEBUGFS_PLUGIN_FILES
2194 + src/collectors/debugfs.plugin/debugfs_plugin.c
2195 + src/collectors/debugfs.plugin/debugfs_plugin.h
2196 + src/collectors/debugfs.plugin/module-numa-extfrag.c
2197 + src/collectors/debugfs.plugin/module-zswap.c
2198 + src/collectors/debugfs.plugin/module-devices-powercap.c
2199 + src/collectors/debugfs.plugin/module-libsensors.c
2200 + )
2201
2202 + # Add executable for debugfs.plugin
2203 add_executable(debugfs.plugin ${DEBUGFS_PLUGIN_FILES})
2198 - target_link_libraries(debugfs.plugin libnetdata ${CAP_LIBRARIES})
2204 +
2205 + # Add vendored libsensors library
2206 + add_subdirectory(src/collectors/debugfs.plugin/libsensors)
2207 +
2208 + # Link debugfs.plugin with vendored libsensors
2209 + target_link_libraries(debugfs.plugin PRIVATE vendored_libsensors)
2210 +
2211 + # Include vendored libsensors headers
2212 + target_include_directories(debugfs.plugin PRIVATE
2213 + src/collectors/debugfs.plugin/libsensors/vendored/lib
2214 + ${CMAKE_CURRENT_BINARY_DIR}/src/collectors/debugfs.plugin/libsensors # For generated headers
2215 + )
2216 +
2217 + # Link against libnetdata and optionally libcap
2218 + target_link_libraries(debugfs.plugin PRIVATE libnetdata ${CAP_LIBRARIES})
2219 target_include_directories(debugfs.plugin PRIVATE ${CAP_INCLUDE_DIRS})
2220 target_compile_options(debugfs.plugin PRIVATE ${CAP_CFLAGS_OTHER})
2221
2222 + # Install the debugfs.plugin binary
2223 install(TARGETS debugfs.plugin
2224 COMPONENT plugin-debugfs
2225 DESTINATION usr/libexec/netdata/plugins.d)
2226
2227 + install(FILES
2228 + src/collectors/debugfs.plugin/libsensors/vendored/etc/sensors.conf.default
2229 + COMPONENT plugin-debugfs
2230 + DESTINATION usr/lib/netdata/conf.d
2231 + RENAME sensors3.conf)
2232 +
2233 + # Install additional packaging files if building for packaging
2234 if(BUILD_FOR_PACKAGING)
2235 install(FILES
2236 ${PKG_FILES_PATH}/copyright
packaging/cmake/Modules/FindLibSensors.cmake new
+23
@@ -0,0 +1,23 @@
1 +# FindLibSensors.cmake
2 +# Locate libsensors library and headers
3 +
4 +# Locate the libsensors header file
5 +find_path(LibSensors_INCLUDE_DIRS
6 + NAMES sensors/sensors.h
7 + PATHS /usr/include /usr/local/include
8 +)
9 +
10 +# Locate the libsensors library
11 +find_library(LibSensors_LIBRARIES
12 + NAMES sensors
13 + PATHS /usr/lib /usr/local/lib
14 +)
15 +
16 +# Check if both the library and header were found
17 +include(FindPackageHandleStandardArgs)
18 +find_package_handle_standard_args(LibSensors
19 + REQUIRED_VARS LibSensors_LIBRARIES LibSensors_INCLUDE_DIRS
20 +)
21 +
22 +# Mark the variables as advanced (not shown in the GUI by default)
23 +mark_as_advanced(LibSensors_LIBRARIES LibSensors_INCLUDE_DIRS)
src/collectors/debugfs.plugin/debugfs_plugin.c
+32 -15
@@ -7,25 +7,38 @@ static char *user_config_dir = CONFIG_DIR;
7 static char *stock_config_dir = LIBCONFIG_DIR;
8
9 static int update_every = 1;
10 +netdata_mutex_t stdout_mutex = NETDATA_MUTEX_INITIALIZER;
11
12 static struct debugfs_module {
13 const char *name;
13 -
14 int enabled;
15 -
15 int (*func)(int update_every, const char *name);
16 } debugfs_modules[] = {
18 - // Memory Fragmentation
19 - { .name = "/sys/kernel/debug/extfrag", .enabled = CONFIG_BOOLEAN_YES,
20 - .func = do_debugfs_extfrag},
21 - { .name = "/sys/kernel/debug/zswap", .enabled = CONFIG_BOOLEAN_YES,
22 - .func = do_debugfs_zswap},
23 - // Linux powercap metrics is here because it needs privilege to read each RAPL zone
24 - { .name = "/sys/devices/virtual/powercap", .enabled = CONFIG_BOOLEAN_YES,
25 - .func = do_sys_devices_virtual_powercap},
17 + {
18 + // Memory Fragmentation
19 + .name = "/sys/kernel/debug/extfrag",
20 + .enabled = CONFIG_BOOLEAN_YES,
21 + .func = do_module_numa_extfrag
22 + },
23 + {
24 + .name = "/sys/kernel/debug/zswap",
25 + .enabled = CONFIG_BOOLEAN_YES,
26 + .func = do_module_zswap
27 + },
28 + {
29 + // Linux powercap metrics is here because it needs privilege to read each RAPL zone
30 + .name = "/sys/devices/virtual/powercap",
31 + .enabled = CONFIG_BOOLEAN_YES,
32 + .func = do_module_devices_powercap
33 + },
34 + {
35 + .name = "libsensors",
36 + .enabled = CONFIG_BOOLEAN_YES,
37 + .func = do_module_libsensors
38 + },
39
40 // The terminator
28 - { .name = NULL, .enabled = CONFIG_BOOLEAN_NO, .func = NULL}
41 + {.name = NULL, .enabled = CONFIG_BOOLEAN_NO, .func = NULL}
42 };
43
44 #ifdef HAVE_SYS_CAPABILITY_H
@@ -207,10 +220,6 @@ int main(int argc, char **argv)
220 exit(1);
221 }
222
210 - // if (!debugfs_check_sys_permission()) {
211 - // exit(2);
212 - // }
213 -
223 debugfs_parse_args(argc, argv);
224
225 size_t iteration;
@@ -230,20 +239,28 @@ int main(int argc, char **argv)
239 if (likely(pm->enabled))
240 enabled++;
241 }
242 +
243 if (!enabled) {
244 netdata_log_info("all modules are disabled, exiting...");
245 return 1;
246 }
247
248 + netdata_mutex_lock(&stdout_mutex);
249 fprintf(stdout, "\n");
250 fflush(stdout);
251 + netdata_mutex_unlock(&stdout_mutex);
252 +
253 if (ferror(stdout) && errno == EPIPE) {
254 netdata_log_error("error writing to stdout: EPIPE. Exiting...");
255 return 1;
256 }
257 }
258
259 + module_libsensors_cleanup();
260 +
261 + netdata_mutex_lock(&stdout_mutex);
262 fprintf(stdout, "EXIT\n");
263 fflush(stdout);
264 + netdata_mutex_unlock(&stdout_mutex);
265 return 0;
266 }
src/collectors/debugfs.plugin/debugfs_plugin.h
+11 -3
@@ -7,9 +7,17 @@
7 #include "collectors/all.h"
8 #include "database/rrd.h"
9
10 -int do_debugfs_extfrag(int update_every, const char *name);
11 -int do_debugfs_zswap(int update_every, const char *name);
12 -int do_sys_devices_virtual_powercap(int update_every, const char *name);
10 +extern netdata_mutex_t stdout_mutex;
11 +
12 +void *libsensors_thread(void *ptr);
13 +
14 +int do_module_numa_extfrag(int update_every, const char *name);
15 +int do_module_zswap(int update_every, const char *name);
16 +int do_module_devices_powercap(int update_every, const char *name);
17 +int do_module_libsensors(int update_every, const char *name);
18 +
19 +void module_libsensors_cleanup(void);
20 +
21 void debugfs2lower(char *name);
22 const char *debugfs_rrdset_type_name(RRDSET_TYPE chart_type);
23 const char *debugfs_rrd_algorithm_name(RRD_ALGORITHM algorithm);
src/collectors/debugfs.plugin/libsensors/CMakeLists.txt new
+65
@@ -0,0 +1,65 @@
1 +# CMake configuration for libsensors
2 +cmake_minimum_required(VERSION 3.5)
3 +
4 +# Add static library for libsensors
5 +add_library(vendored_libsensors STATIC
6 + vendored/lib/access.c
7 + vendored/lib/data.c
8 + vendored/lib/error.c
9 + vendored/lib/general.c
10 + vendored/lib/init.c
11 + vendored/lib/sysfs.c
12 +)
13 +
14 +# Add Flex and Bison dependencies
15 +find_package(FLEX REQUIRED)
16 +find_package(BISON REQUIRED)
17 +
18 +# Generate Bison parser
19 +bison_target(LibsensorsParser vendored/lib/conf-parse.y ${CMAKE_CURRENT_BINARY_DIR}/conf-parse.c
20 + COMPILE_FLAGS "-p sensors_yy --defines=${CMAKE_CURRENT_BINARY_DIR}/conf-parse.h"
21 +)
22 +
23 +# Generate Flex scanner
24 +flex_target(LibsensorsLexer vendored/lib/conf-lex.l ${CMAKE_CURRENT_BINARY_DIR}/conf-lex.c
25 + COMPILE_FLAGS "-Psensors_yy -Cfe -8"
26 +)
27 +
28 +# Add dependency to ensure correct build order
29 +add_flex_bison_dependency(LibsensorsLexer LibsensorsParser)
30 +
31 +# Include generated files
32 +target_sources(vendored_libsensors PRIVATE
33 + ${BISON_LibsensorsParser_OUTPUTS}
34 + ${FLEX_LibsensorsLexer_OUTPUTS}
35 +)
36 +
37 +# Include paths
38 +target_include_directories(vendored_libsensors PUBLIC
39 + vendored/lib
40 + ${CMAKE_CURRENT_BINARY_DIR} # For generated conf-parse.h
41 +)
42 +
43 +# Compile definitions
44 +target_compile_definitions(vendored_libsensors PRIVATE
45 + ETCDIR="/etc" # Define ETCDIR
46 + _GNU_SOURCE # Define GNU extensions
47 +)
48 +
49 +# Suppress warnings for Flex and Bison files
50 +set_source_files_properties(${FLEX_LibsensorsLexer_OUTPUTS} PROPERTIES
51 + COMPILE_FLAGS "-Wno-shadow -Wno-undef -Wno-unused -Wno-missing-prototypes -Wno-sign-compare"
52 +)
53 +set_source_files_properties(${BISON_LibsensorsParser_OUTPUTS} PROPERTIES
54 + COMPILE_FLAGS "-DYYLTYPE_IS_DECLARED=1"
55 +)
56 +
57 +# Link against required libraries
58 +target_link_libraries(vendored_libsensors PRIVATE m)
59 +
60 +# Additional compile options to match Makefile
61 +target_compile_options(vendored_libsensors PRIVATE
62 + -fPIC -D_REENTRANT -Wall -O2 -Wstrict-prototypes -Wshadow
63 + -Wpointer-arith -Wcast-qual -Wcast-align -Wwrite-strings
64 + -Wnested-externs -Winline -Wmissing-prototypes -Wno-format-truncation
65 +)
src/collectors/debugfs.plugin/libsensors/libsensors.version new
+1
@@ -0,0 +1 @@
1 +42f240d2a457834bcbdf4dc8b57237f97b5f5854
src/collectors/debugfs.plugin/libsensors/vendored new
+1
@@ -0,0 +1 @@
1 +Subproject commit 42f240d2a457834bcbdf4dc8b57237f97b5f5854
src/collectors/debugfs.plugin/module-devices-powercap.c renamed
+19 -25
@@ -126,7 +126,7 @@ static struct zone_t *get_main_rapl_zones(void) {
126 return base;
127 }
128
129 -int do_sys_devices_virtual_powercap(int update_every, const char *name __maybe_unused) {
129 +int do_module_devices_powercap(int update_every, const char *name __maybe_unused) {
130
131 if (unlikely(!rapl_zones)) {
132 rapl_zones = get_main_rapl_zones();
@@ -136,82 +136,76 @@ int do_sys_devices_virtual_powercap(int update_every, const char *name __maybe_u
136 }
137 }
138
139 + netdata_mutex_lock(&stdout_mutex);
140 +
141 for(struct zone_t *zone = rapl_zones; zone ; zone = zone->next) {
142 if(!zone->zone_chart_id) {
143 char id[1000 + 1];
144 snprintf(id, 1000, "cpu.powercap_intel_rapl_zone_%s", zone->name);
145 zone->zone_chart_id = strdupz(id);
146
145 - fprintf(stdout,
146 - "CHART '%s' '' 'Intel RAPL Zone Power Consumption' 'Watts' 'powercap' '%s' '%s' %d %d '' 'debugfs.plugin' 'intel_rapl'\n",
147 + printf(PLUGINSD_KEYWORD_CHART " '%s' '' 'Intel RAPL Zone Power Consumption' 'Watts' 'powercap' '%s' '%s' %d %d '' 'debugfs.plugin' 'intel_rapl'\n",
148 zone->zone_chart_id,
149 "cpu.powercap_intel_rapl_zone",
150 debugfs_rrdset_type_name(RRDSET_TYPE_LINE),
151 NETDATA_CHART_PRIO_POWERCAP,
152 update_every);
153
153 - fprintf(stdout,
154 - "CLABEL 'zone' '%s' 1\n"
155 - "CLABEL_COMMIT\n",
154 + printf(PLUGINSD_KEYWORD_CLABEL " 'zone' '%s' 1\n"
155 + PLUGINSD_KEYWORD_CLABEL_COMMIT "\n",
156 zone->name);
157
158 - fprintf(stdout,
159 - "DIMENSION 'power' '' %s 1 1000000 ''\n",
158 + printf(PLUGINSD_KEYWORD_DIMENSION " 'power' '' %s 1 1000000 ''\n",
159 debugfs_rrd_algorithm_name(RRD_ALGORITHM_INCREMENTAL));
160
162 - // for the sub-zones
161 + // the sub-zones
162 snprintf(id, 1000, "cpu.powercap_intel_rapl_subzones_%s", zone->name);
163 zone->subzone_chart_id = strdupz(id);
165 - fprintf(stdout,
166 - "CHART '%s' '' 'Intel RAPL Subzones Power Consumption' 'Watts' 'powercap' '%s' '%s' %d %d '' 'debugfs.plugin' 'intel_rapl'\n",
164 + printf(PLUGINSD_KEYWORD_CHART " '%s' '' 'Intel RAPL Subzones Power Consumption' 'Watts' 'powercap' '%s' '%s' %d %d '' 'debugfs.plugin' 'intel_rapl'\n",
165 zone->subzone_chart_id,
166 "cpu.powercap_intel_rapl_subzones",
167 debugfs_rrdset_type_name(RRDSET_TYPE_LINE),
168 NETDATA_CHART_PRIO_POWERCAP + 1,
169 update_every);
170
173 - fprintf(stdout,
174 - "CLABEL 'zone' '%s' 1\n"
175 - "CLABEL_COMMIT\n",
171 + printf(PLUGINSD_KEYWORD_CLABEL " 'zone' '%s' 1\n"
172 + PLUGINSD_KEYWORD_CLABEL_COMMIT "\n",
173 zone->name);
174
175 for(struct zone_t *subzone = zone->subzones; subzone ; subzone = subzone->next) {
179 - fprintf(stdout,
180 - "DIMENSION '%s' '' %s 1 1000000 ''\n",
176 + printf(PLUGINSD_KEYWORD_DIMENSION " '%s' '' %s 1 1000000 ''\n",
177 subzone->name,
178 debugfs_rrd_algorithm_name(RRD_ALGORITHM_INCREMENTAL));
179 }
180 }
181
182 if(get_measurement(zone->path, &zone->energy_uj)) {
187 - fprintf(stdout,
188 - "BEGIN '%s'\n"
189 - "SET power = %llu\n"
190 - "END\n"
183 + printf(PLUGINSD_KEYWORD_BEGIN " '%s'\n"
184 + PLUGINSD_KEYWORD_SET " power = %llu\n"
185 + PLUGINSD_KEYWORD_END "\n"
186 , zone->zone_chart_id
187 , zone->energy_uj);
188 }
189
190 if(zone->subzones) {
196 - fprintf(stdout,
197 - "BEGIN '%s'\n",
191 + printf(PLUGINSD_KEYWORD_BEGIN " '%s'\n",
192 zone->subzone_chart_id);
193
194 for (struct zone_t *subzone = zone->subzones; subzone; subzone = subzone->next) {
195 if(get_measurement(subzone->path, &subzone->energy_uj)) {
202 - fprintf(stdout,
203 - "SET '%s' = %llu\n",
196 + printf(PLUGINSD_KEYWORD_SET " '%s' = %llu\n",
197 subzone->name,
198 subzone->energy_uj);
199 }
200 }
201
209 - fprintf(stdout, "END\n");
202 + printf(PLUGINSD_KEYWORD_END "\n");
203 }
204
205 }
206
207 fflush(stdout);
208 + netdata_mutex_unlock(&stdout_mutex);
209
210 return 0;
211 }
src/collectors/debugfs.plugin/module-libsensors.c new
+1320
@@ -0,0 +1,1320 @@
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 +}
src/collectors/debugfs.plugin/module-numa-extfrag.c renamed
+22 -18
@@ -51,15 +51,15 @@ static struct netdata_extrafrag *find_or_create_extrafrag(const char *name)
51 static void extfrag_send_chart(char *chart_id, collected_number *values)
52 {
53 int i;
54 - fprintf(stdout, "BEGIN mem.fragmentation_index_%s\n", chart_id);
55 - for (i = 0; i < NETDATA_ORDER_FRAGMENTATION; i++) {
56 - fprintf(stdout, "SET %s = %lld\n", orders[i], values[i]);
57 - }
58 - fprintf(stdout, "END\n");
59 - fflush(stdout);
54 + printf(PLUGINSD_KEYWORD_BEGIN " mem.fragmentation_index_%s\n", chart_id);
55 +
56 + for (i = 0; i < NETDATA_ORDER_FRAGMENTATION; i++)
57 + printf(PLUGINSD_KEYWORD_SET " %s = %lld\n", orders[i], values[i]);
58 +
59 + printf(PLUGINSD_KEYWORD_END "\n");
60 }
61
62 -int do_debugfs_extfrag(int update_every, const char *name) {
62 +int do_module_numa_extfrag(int update_every, const char *name) {
63 static procfile *ff = NULL;;
64
65 if (unlikely(!ff)) {
@@ -95,27 +95,31 @@ int do_debugfs_extfrag(int update_every, const char *name) {
95 line_orders[j] = (collected_number) (value * 1000.0);
96 }
97
98 + netdata_mutex_lock(&stdout_mutex);
99 +
100 if (unlikely(!extrafrag->id)) {
101 extrafrag->id = extrafrag->node_zone;
100 - fprintf(
101 - stdout,
102 - "CHART mem.fragmentation_index_%s '' 'Memory fragmentation index for each order' 'index' 'fragmentation' 'mem.numa_node_zone_fragmentation_index' 'line' %d %d '' 'debugfs.plugin' '%s'\n",
102 + printf(
103 + PLUGINSD_KEYWORD_CHART " mem.fragmentation_index_%s '' 'Memory fragmentation index for each order' 'index' 'fragmentation' 'mem.numa_node_zone_fragmentation_index' 'line' %d %d '' 'debugfs.plugin' '%s'\n",
104 extrafrag->node_zone,
105 NETDATA_CHART_PRIO_MEM_FRAGMENTATION,
106 update_every,
107 name);
107 - for (i = 0; i < NETDATA_ORDER_FRAGMENTATION; i++) {
108 - fprintf(stdout, "DIMENSION '%s' '%s' absolute 1 1000 ''\n", orders[i], orders[i]);
109 - }
110 - fprintf(
111 - stdout,
112 - "CLABEL 'numa_node' 'node%s' 1\n"
113 - "CLABEL 'zone' '%s' 1\n"
114 - "CLABEL_COMMIT\n",
108 +
109 + for (i = 0; i < NETDATA_ORDER_FRAGMENTATION; i++)
110 + printf(PLUGINSD_KEYWORD_DIMENSION " '%s' '%s' absolute 1 1000 ''\n", orders[i], orders[i]);
111 +
112 + printf(
113 + PLUGINSD_KEYWORD_CLABEL " 'numa_node' 'node%s' 1\n"
114 + PLUGINSD_KEYWORD_CLABEL " 'zone' '%s' 1\n"
115 + PLUGINSD_KEYWORD_CLABEL_COMMIT "\n",
116 id,
117 zone);
118 }
119 extfrag_send_chart(chart_id, line_orders);
120 +
121 + fflush(stdout);
122 + netdata_mutex_unlock(&stdout_mutex);
123 }
124
125 return 0;
src/collectors/debugfs.plugin/module-zswap.c renamed
+41 -29
@@ -262,11 +262,10 @@ int zswap_collect_data(struct netdata_zswap_metric *metric)
262 }
263
264 static void
265 -zswap_send_chart(struct netdata_zswap_metric *metric, int update_every, const char *name, const char *option)
265 +zswap_send_chart_unsafe(struct netdata_zswap_metric *metric, int update_every, const char *name, const char *option)
266 {
267 - fprintf(
268 - stdout,
269 - "CHART mem.zswap_%s '' '%s' '%s' 'zswap' '' '%s' %d %d '%s' 'debugfs.plugin' '%s'\n",
267 + printf(
268 + PLUGINSD_KEYWORD_CHART " mem.zswap_%s '' '%s' '%s' 'zswap' '' '%s' %d %d '%s' 'debugfs.plugin' '%s'\n",
269 metric->chart_id,
270 metric->title,
271 metric->units,
@@ -277,90 +276,103 @@ zswap_send_chart(struct netdata_zswap_metric *metric, int update_every, const ch
276 name);
277 }
278
280 -static void zswap_send_dimension(struct netdata_zswap_metric *metric)
279 +static void zswap_send_dimension_unsafe(struct netdata_zswap_metric *metric)
280 {
281 int div = metric->divisor > 0 ? metric->divisor : 1;
283 - fprintf(
284 - stdout,
285 - "DIMENSION '%s' '%s' %s 1 %d ''\n",
282 + printf(
283 + PLUGINSD_KEYWORD_DIMENSION " '%s' '%s' %s 1 %d ''\n",
284 metric->dimension,
285 metric->dimension,
286 debugfs_rrd_algorithm_name(metric->algorithm),
287 div);
288 }
289
292 -static void zswap_send_begin(struct netdata_zswap_metric *metric)
290 +static void zswap_send_begin_unsafe(struct netdata_zswap_metric *metric)
291 {
294 - fprintf(stdout, "BEGIN mem.zswap_%s\n", metric->chart_id);
292 + printf(PLUGINSD_KEYWORD_BEGIN " mem.zswap_%s\n", metric->chart_id);
293 }
294
297 -static void zswap_send_set(struct netdata_zswap_metric *metric)
295 +static void zswap_send_set_unsafe(struct netdata_zswap_metric *metric)
296 {
299 - fprintf(stdout, "SET %s = %lld\n", metric->dimension, metric->value);
297 + printf(PLUGINSD_KEYWORD_SET " %s = %lld\n", metric->dimension, metric->value);
298 }
299
302 -static void zswap_send_end_and_flush()
300 +static void zswap_send_end_unsafe()
301 {
304 - fprintf(stdout, "END\n");
305 - fflush(stdout);
302 + printf(PLUGINSD_KEYWORD_END "\n");
303 }
304
305 static void zswap_independent_chart(struct netdata_zswap_metric *metric, int update_every, const char *name)
306 {
307 + netdata_mutex_lock(&stdout_mutex);
308 +
309 if (unlikely(!metric->chart_created)) {
310 metric->chart_created = CONFIG_BOOLEAN_YES;
311
313 - zswap_send_chart(metric, update_every, name, NULL);
314 - zswap_send_dimension(metric);
312 + zswap_send_chart_unsafe(metric, update_every, name, NULL);
313 + zswap_send_dimension_unsafe(metric);
314 }
315
317 - zswap_send_begin(metric);
318 - zswap_send_set(metric);
319 - zswap_send_end_and_flush();
316 + zswap_send_begin_unsafe(metric);
317 + zswap_send_set_unsafe(metric);
318 + zswap_send_end_unsafe();
319 +
320 + fflush(stdout);
321 + netdata_mutex_unlock(&stdout_mutex);
322 }
323
324 void zswap_reject_chart(int update_every, const char *name)
325 {
326 + netdata_mutex_lock(&stdout_mutex);
327 +
328 struct netdata_zswap_metric *metric = &zswap_rejected_metrics[NETDATA_ZSWAP_REJECTED_CHART];
329
330 if (unlikely(!metric->chart_created)) {
331 metric->chart_created = CONFIG_BOOLEAN_YES;
332
329 - zswap_send_chart(metric, update_every, name, NULL);
333 + zswap_send_chart_unsafe(metric, update_every, name, NULL);
334 for (int i = NETDATA_ZSWAP_REJECTED_COMPRESS_POOR; zswap_rejected_metrics[i].filename; i++) {
335 metric = &zswap_rejected_metrics[i];
336 if (likely(metric->enabled))
333 - zswap_send_dimension(metric);
337 + zswap_send_dimension_unsafe(metric);
338 }
339 }
340
341 metric = &zswap_rejected_metrics[NETDATA_ZSWAP_REJECTED_CHART];
338 - zswap_send_begin(metric);
342 + zswap_send_begin_unsafe(metric);
343 for (int i = NETDATA_ZSWAP_REJECTED_COMPRESS_POOR; zswap_rejected_metrics[i].filename; i++) {
344 metric = &zswap_rejected_metrics[i];
345 if (likely(metric->enabled))
342 - zswap_send_set(metric);
346 + zswap_send_set_unsafe(metric);
347 }
344 - zswap_send_end_and_flush();
348 + zswap_send_end_unsafe();
349 +
350 + fflush(stdout);
351 + netdata_mutex_unlock(&stdout_mutex);
352 }
353
354 static void zswap_obsolete_charts(int update_every, const char *name)
355 {
356 + netdata_mutex_lock(&stdout_mutex);
357 +
358 struct netdata_zswap_metric *metric = NULL;
359
360 for (int i = 0; zswap_independent_metrics[i].filename; i++) {
361 metric = &zswap_independent_metrics[i];
362 if (likely(metric->chart_created))
354 - zswap_send_chart(metric, update_every, name, "obsolete");
363 + zswap_send_chart_unsafe(metric, update_every, name, "obsolete");
364 }
365
366 metric = &zswap_rejected_metrics[NETDATA_ZSWAP_REJECTED_CHART];
367 if (likely(metric->chart_created))
359 - zswap_send_chart(metric, update_every, name, "obsolete");
368 + zswap_send_chart_unsafe(metric, update_every, name, "obsolete");
369
370 metric = &zswap_calculated_metrics[NETDATA_ZSWAP_COMPRESSION_RATIO_CHART];
371 if (likely(metric->chart_created))
363 - zswap_send_chart(metric, update_every, name, "obsolete");
372 + zswap_send_chart_unsafe(metric, update_every, name, "obsolete");
373 +
374 + fflush(stdout);
375 + netdata_mutex_unlock(&stdout_mutex);
376 }
377
378 #define ZSWAP_STATE_SIZE 1 // Y or N
@@ -378,7 +390,7 @@ static int debugfs_is_zswap_enabled()
390 return 1;
391 }
392
381 -int do_debugfs_zswap(int update_every, const char *name)
393 +int do_module_zswap(int update_every, const char *name)
394 {
395 static int check_if_enabled = 1;
396
src/collectors/systemd-journal.plugin/systemd-journal-annotations.c
+1
@@ -610,6 +610,7 @@ static void netdata_systemd_journal_message_ids_init(void) {
610 msgid_into_dict("6e2e3839067648968b646045dbf28d66", "Netdata connection to parent");
611 msgid_into_dict("9ce0cb58ab8b44df82c4bf1ad9ee22de", "Netdata alert transition");
612 msgid_into_dict("6db0018e83e34320ae2a659d78019fb7", "Netdata alert notification");
613 + msgid_into_dict("8ddaf5ba33a74078b609250db1e951f3", "Sensor state transition");
614 }
615
616 void netdata_systemd_journal_transform_message_id(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
src/libnetdata/uuid/uuid.h
+1 -1
@@ -29,9 +29,9 @@ static const ND_UUID UUID_ZERO = (ND_UUID){ { .parts = { .hig64 = 0, .low64 = 0
29 ND_UUID_DEFINE(streaming_from_child_msgid, 0xed,0x4c,0xdb, 0x8f, 0x1b, 0xeb, 0x4a, 0xd3, 0xb5, 0x7c, 0xb3, 0xca, 0xe2, 0xd1, 0x62, 0xfa);
30 ND_UUID_DEFINE(streaming_to_parent_msgid, 0x6e, 0x2e, 0x38, 0x39, 0x06, 0x76, 0x48, 0x96, 0x8b, 0x64, 0x60, 0x45, 0xdb, 0xf2, 0x8d, 0x66);
31 ND_UUID_DEFINE(health_alert_transition_msgid, 0x9c, 0xe0, 0xcb, 0x58, 0xab, 0x8b, 0x44, 0xdf, 0x82, 0xc4, 0xbf, 0x1a, 0xd9, 0xee, 0x22, 0xde);
32 -
32 // this is also defined in alarm-notify.sh.in
33 ND_UUID_DEFINE(health_alert_notification_msgid, 0x6d, 0xb0, 0x01, 0x8e, 0x83, 0xe3, 0x43, 0x20, 0xae, 0x2a, 0x65, 0x9d, 0x78, 0x01, 0x9f, 0xb7);
34 +ND_UUID_DEFINE(sensors_state_transition_msgid, 0x8d, 0xda, 0xf5, 0xba, 0x33, 0xa7, 0x40, 0x78, 0xb6, 0x09, 0x25, 0x0d, 0xb1, 0xe9, 0x51, 0xf3);
35
36 ND_UUID UUID_generate_from_hash(const void *payload, size_t payload_len);
37