diff --git a/plugins/in_node_exporter_metrics/CMakeLists.txt b/plugins/in_node_exporter_metrics/CMakeLists.txt index 55e9a860067..e04616d6d3f 100644 --- a/plugins/in_node_exporter_metrics/CMakeLists.txt +++ b/plugins/in_node_exporter_metrics/CMakeLists.txt @@ -20,6 +20,7 @@ set(src ne_config.c ne_systemd.c ne_thermalzone.c + ne_powersupply.c ne.c ) diff --git a/plugins/in_node_exporter_metrics/ne.c b/plugins/in_node_exporter_metrics/ne.c index e3906151074..3d4d7770890 100644 --- a/plugins/in_node_exporter_metrics/ne.c +++ b/plugins/in_node_exporter_metrics/ne.c @@ -47,6 +47,7 @@ #include "ne_nvme.h" #include "ne_thermalzone.h" #include "ne_hwmon.h" +#include "ne_powersupply.h" /* * Update the metrics, this function is invoked every time 'scrape_interval' @@ -204,6 +205,7 @@ static int in_ne_init(struct flb_input_instance *in, mk_list_add(&nvme_collector._head, &ctx->collectors); mk_list_add(&thermalzone_collector._head, &ctx->collectors); mk_list_add(&hwmon_collector._head, &ctx->collectors); + mk_list_add(&powersupply_collector._head, &ctx->collectors); mk_list_foreach(head, &ctx->collectors) { coll = mk_list_entry(head, struct flb_ne_collector, _head); diff --git a/plugins/in_node_exporter_metrics/ne.h b/plugins/in_node_exporter_metrics/ne.h index 9fc090652ce..7ade50906de 100644 --- a/plugins/in_node_exporter_metrics/ne.h +++ b/plugins/in_node_exporter_metrics/ne.h @@ -33,9 +33,9 @@ /* Default enabled metrics */ #ifdef __linux__ -#define NE_DEFAULT_ENABLED_METRICS "cpu,cpufreq,meminfo,diskstats,filesystem,uname,stat,time,loadavg,vmstat,netdev,netstat,sockstat,filefd,systemd,nvme,thermal_zone,hwmon" +#define NE_DEFAULT_ENABLED_METRICS "cpu,cpufreq,meminfo,diskstats,filesystem,uname,stat,time,loadavg,vmstat,netdev,netstat,sockstat,filefd,systemd,nvme,thermal_zone,hwmon,powersupplyclass" #elif __APPLE__ -#define NE_DEFAULT_ENABLED_METRICS "cpu,loadavg,meminfo,diskstats,uname,netdev" +#define NE_DEFAULT_ENABLED_METRICS "cpu,loadavg,meminfo,diskstats,filesystem,uname,netdev,powersupplyclass" #endif /* filesystem: regex for ignoring mount points and filesystem types */ @@ -101,6 +101,22 @@ struct flb_ne { struct cmt_gauge *darwin_swap_used_bytes; struct cmt_gauge *darwin_swap_total_bytes; struct cmt_counter *darwin_total_bytes; + + /* powersupply_darwin */ + struct cmt_gauge *darwin_ps_current_capacity; + struct cmt_gauge *darwin_ps_max_capacity; + struct cmt_gauge *darwin_ps_design_capacity; + struct cmt_gauge *darwin_ps_nominal_capacity; + struct cmt_gauge *darwin_ps_time_to_empty; + struct cmt_gauge *darwin_ps_time_to_full; + struct cmt_gauge *darwin_ps_voltage; + struct cmt_gauge *darwin_ps_current; + struct cmt_gauge *darwin_ps_temperature; + struct cmt_gauge *darwin_ps_present; + struct cmt_gauge *darwin_ps_charging; + struct cmt_gauge *darwin_ps_charged; + struct cmt_gauge *darwin_ps_internal_failure; + struct cmt_gauge *darwin_ps_battery_health; #endif /* diskstats: abbreviation 'dt' */ @@ -141,6 +157,10 @@ struct flb_ne { struct cmt_gauge *darwin_noproto; #endif + /* powersupply_linux */ + struct cmt_gauge *powersupply_info; + struct mk_list powersupply_dynamic_metrics; + /* sockstat_linux */ struct cmt_gauge *sockstat_sockets_used; struct cmt_gauge *sockstat_TCP_alloc; @@ -172,6 +192,7 @@ struct flb_ne { struct cmt_counter *netstat_Udp_InErrors; struct cmt_counter *netstat_Udp_OutDatagrams; struct cmt_counter *netstat_Udp_NoPorts; + struct mk_list netstat_dynamic_metrics; /* time */ struct cmt_gauge *time; diff --git a/plugins/in_node_exporter_metrics/ne_filesystem.c b/plugins/in_node_exporter_metrics/ne_filesystem.c index 54b3b27d456..6e17c4f87ae 100644 --- a/plugins/in_node_exporter_metrics/ne_filesystem.c +++ b/plugins/in_node_exporter_metrics/ne_filesystem.c @@ -19,6 +19,8 @@ #ifdef __linux__ #include "ne_filesystem_linux.c" +#elif __APPLE__ +#include "ne_filesystem_darwin.c" #else #include "ne.h" diff --git a/plugins/in_node_exporter_metrics/ne_filesystem_darwin.c b/plugins/in_node_exporter_metrics/ne_filesystem_darwin.c new file mode 100644 index 00000000000..aae2be44a81 --- /dev/null +++ b/plugins/in_node_exporter_metrics/ne_filesystem_darwin.c @@ -0,0 +1,191 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2026 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#include "ne.h" + +static int filesystem_update(struct flb_ne *ctx) +{ + int i; + int count; + int skip_flag; + uint64_t block_size; + uint64_t blocks; + uint64_t free_size; + uint64_t avail_size; + uint64_t size_bytes; + uint64_t avail_bytes; + uint64_t free_bytes; + uint64_t timestamp; + char *labels[3]; + struct statfs *mounts; + + count = getmntinfo(&mounts, MNT_NOWAIT); + if (count == 0) { + return -1; + } + + timestamp = cfl_time_now(); + + for (i = 0; i < count; i++) { + skip_flag = flb_regex_match(ctx->fs_regex_skip_fs_types, + (unsigned char *) mounts[i].f_fstypename, + strlen(mounts[i].f_fstypename)); + if (skip_flag) { + continue; + } + + skip_flag = flb_regex_match(ctx->fs_regex_skip_mount, + (unsigned char *) mounts[i].f_mntonname, + strlen(mounts[i].f_mntonname)); + if (skip_flag) { + continue; + } + + labels[0] = mounts[i].f_mntfromname; + labels[1] = mounts[i].f_fstypename; + labels[2] = mounts[i].f_mntonname; + + block_size = (uint64_t) mounts[i].f_bsize; + blocks = (uint64_t) mounts[i].f_blocks; + free_size = (uint64_t) mounts[i].f_bfree; + avail_size = (uint64_t) mounts[i].f_bavail; + avail_bytes = block_size * avail_size; + size_bytes = block_size * blocks; + free_bytes = block_size * free_size; + + cmt_gauge_set(ctx->fs_avail_bytes, timestamp, avail_bytes, 3, labels); + cmt_gauge_set(ctx->fs_device_error, timestamp, 0, 3, labels); + cmt_gauge_set(ctx->fs_files, timestamp, (uint64_t) mounts[i].f_files, 3, labels); + cmt_gauge_set(ctx->fs_files_free, timestamp, (uint64_t) mounts[i].f_ffree, 3, labels); + cmt_gauge_set(ctx->fs_free_bytes, timestamp, free_bytes, 3, labels); + cmt_gauge_set(ctx->fs_readonly, timestamp, (mounts[i].f_flags & MNT_RDONLY) ? 1 : 0, 3, labels); + cmt_gauge_set(ctx->fs_size_bytes, timestamp, size_bytes, 3, labels); + } + + return 0; +} + +static void ne_filesystem_destroy_regexes(struct flb_ne *ctx) +{ + if (ctx->fs_regex_skip_mount != NULL) { + flb_regex_destroy(ctx->fs_regex_skip_mount); + ctx->fs_regex_skip_mount = NULL; + } + + if (ctx->fs_regex_skip_fs_types != NULL) { + flb_regex_destroy(ctx->fs_regex_skip_fs_types); + ctx->fs_regex_skip_fs_types = NULL; + } +} + +static int ne_filesystem_init(struct flb_ne *ctx) +{ + ctx->fs_regex_skip_mount = flb_regex_create(ctx->fs_regex_ingore_mount_point_text); + if (ctx->fs_regex_skip_mount == NULL) { + goto error; + } + + ctx->fs_regex_skip_fs_types = flb_regex_create(ctx->fs_regex_ingore_filesystem_type_text); + if (ctx->fs_regex_skip_fs_types == NULL) { + goto error; + } + + ctx->fs_avail_bytes = cmt_gauge_create(ctx->cmt, "node", "filesystem", "avail_bytes", + "Filesystem space available to non-root users in bytes.", + 3, (char *[]) {"device", "fstype", "mountpoint"}); + if (ctx->fs_avail_bytes == NULL) { + goto error; + } + + ctx->fs_device_error = cmt_gauge_create(ctx->cmt, "node", "filesystem", "device_error", + "Whether an error occurred while getting statistics for the given device.", + 3, (char *[]) {"device", "fstype", "mountpoint"}); + if (ctx->fs_device_error == NULL) { + goto error; + } + + ctx->fs_files = cmt_gauge_create(ctx->cmt, "node", "filesystem", "files", + "Filesystem total file nodes.", + 3, (char *[]) {"device", "fstype", "mountpoint"}); + if (ctx->fs_files == NULL) { + goto error; + } + + ctx->fs_files_free = cmt_gauge_create(ctx->cmt, "node", "filesystem", "files_free", + "Filesystem total free file nodes.", + 3, (char *[]) {"device", "fstype", "mountpoint"}); + if (ctx->fs_files_free == NULL) { + goto error; + } + + ctx->fs_free_bytes = cmt_gauge_create(ctx->cmt, "node", "filesystem", "free_bytes", + "Filesystem free space in bytes.", + 3, (char *[]) {"device", "fstype", "mountpoint"}); + if (ctx->fs_free_bytes == NULL) { + goto error; + } + + ctx->fs_readonly = cmt_gauge_create(ctx->cmt, "node", "filesystem", "readonly", + "Filesystem read-only status.", + 3, (char *[]) {"device", "fstype", "mountpoint"}); + if (ctx->fs_readonly == NULL) { + goto error; + } + + ctx->fs_size_bytes = cmt_gauge_create(ctx->cmt, "node", "filesystem", "size_bytes", + "Filesystem size in bytes.", + 3, (char *[]) {"device", "fstype", "mountpoint"}); + if (ctx->fs_size_bytes == NULL) { + goto error; + } + + return 0; + +error: + ne_filesystem_destroy_regexes(ctx); + + return -1; +} + +static int ne_filesystem_update(struct flb_input_instance *ins, + struct flb_config *config, void *in_context) +{ + struct flb_ne *ctx = (struct flb_ne *) in_context; + + return filesystem_update(ctx); +} + +static int ne_filesystem_exit(struct flb_ne *ctx) +{ + ne_filesystem_destroy_regexes(ctx); + + return 0; +} + +struct flb_ne_collector filesystem_collector = { + .name = "filesystem", + .cb_init = ne_filesystem_init, + .cb_update = ne_filesystem_update, + .cb_exit = ne_filesystem_exit +}; diff --git a/plugins/in_node_exporter_metrics/ne_netstat_linux.c b/plugins/in_node_exporter_metrics/ne_netstat_linux.c index 826de260b7e..e977d3480eb 100644 --- a/plugins/in_node_exporter_metrics/ne_netstat_linux.c +++ b/plugins/in_node_exporter_metrics/ne_netstat_linux.c @@ -30,9 +30,101 @@ #define NETSTAT_PROTO_NONE 0 #define NETSTAT_PROTO_TCP 1 #define NETSTAT_PROTO_UDP 2 +#define NETSTAT_PROTO_TCPEXT 3 +#define NETSTAT_PROTO_IPEXT 4 + +struct netstat_dynamic_metric { + char *name; + struct cmt_gauge *gauge; + struct mk_list _head; +}; + +static void netstat_dynamic_metrics_destroy(struct flb_ne *ctx) +{ + struct mk_list *tmp; + struct mk_list *head; + struct netstat_dynamic_metric *entry; + + mk_list_foreach_safe(head, tmp, &ctx->netstat_dynamic_metrics) { + entry = mk_list_entry(head, struct netstat_dynamic_metric, _head); + mk_list_del(&entry->_head); + if (entry->name != NULL) { + flb_free(entry->name); + } + flb_free(entry); + } +} + +static int netstat_is_static_metric(const char *name) +{ + if (strcmp(name, "Tcp_CurrEstab") == 0 || + strcmp(name, "Tcp_ActiveOpens") == 0 || + strcmp(name, "Tcp_PassiveOpens") == 0 || + strcmp(name, "Tcp_RetransSegs") == 0 || + strcmp(name, "Udp_InDatagrams") == 0 || + strcmp(name, "Udp_NoPorts") == 0 || + strcmp(name, "Udp_InErrors") == 0 || + strcmp(name, "Udp_OutDatagrams") == 0) { + return FLB_TRUE; + } + + return FLB_FALSE; +} + +static struct cmt_gauge *netstat_dynamic_metric_get(struct flb_ne *ctx, const char *name) +{ + int ret; + size_t name_len; + char *metric_name; + struct mk_list *head; + struct netstat_dynamic_metric *entry; + + mk_list_foreach(head, &ctx->netstat_dynamic_metrics) { + entry = mk_list_entry(head, struct netstat_dynamic_metric, _head); + if (strcmp(entry->name, name) == 0) { + return entry->gauge; + } + } + + entry = flb_calloc(1, sizeof(struct netstat_dynamic_metric)); + if (entry == NULL) { + flb_errno(); + return NULL; + } + + name_len = strlen(name); + metric_name = flb_malloc(name_len + 1); + if (metric_name == NULL) { + flb_free(entry); + return NULL; + } + + ret = snprintf(metric_name, name_len + 1, "%s", name); + if (ret < 0 || ret >= (int) (name_len + 1)) { + flb_free(metric_name); + flb_free(entry); + return NULL; + } + + entry->gauge = cmt_gauge_create(ctx->cmt, "node", "netstat", metric_name, + "Network statistics from /proc/net/netstat.", + 0, NULL); + if (entry->gauge == NULL) { + flb_free(metric_name); + flb_free(entry); + return NULL; + } + + entry->name = metric_name; + mk_list_add(&entry->_head, &ctx->netstat_dynamic_metrics); + + return entry->gauge; +} static int netstat_configure(struct flb_ne *ctx) { + mk_list_init(&ctx->netstat_dynamic_metrics); + ctx->netstat_Tcp_CurrEstab = cmt_gauge_create(ctx->cmt, "node", "netstat", "Tcp_CurrEstab", "Number of TCP connections in ESTABLISHED or CLOSE-WAIT state.", @@ -184,6 +276,13 @@ static void netstat_process_pair(struct flb_ne *ctx, int values_count; struct mk_list headers; struct mk_list values; + int idx; + double d_val; + struct cmt_gauge *metric; + char metric_name[256]; + struct flb_slist_entry *proto_name; + struct flb_slist_entry *key; + struct flb_slist_entry *val; mk_list_init(&headers); mk_list_init(&values); @@ -198,6 +297,65 @@ static void netstat_process_pair(struct flb_ne *ctx, else if (proto == NETSTAT_PROTO_UDP) { netstat_process_udp(ctx, &headers, headers_count, &values, values_count, ts); } + else if (proto == NETSTAT_PROTO_TCPEXT || proto == NETSTAT_PROTO_IPEXT) { + for (idx = 1; idx < headers_count && idx < values_count; idx++) { + key = flb_slist_entry_get(&headers, idx); + val = flb_slist_entry_get(&values, idx); + + if (key == NULL || val == NULL) { + continue; + } + + if (ne_utils_str_to_double(val->str, &d_val) != 0) { + continue; + } + + if (proto == NETSTAT_PROTO_TCPEXT) { + snprintf(metric_name, sizeof(metric_name) - 1, "TcpExt_%s", key->str); + } + else { + snprintf(metric_name, sizeof(metric_name) - 1, "IpExt_%s", key->str); + } + metric_name[sizeof(metric_name) - 1] = '\0'; + + metric = netstat_dynamic_metric_get(ctx, metric_name); + if (metric != NULL) { + cmt_gauge_set(metric, ts, d_val, 0, NULL); + } + } + } + else { + proto_name = flb_slist_entry_get(&headers, 0); + if (proto_name != NULL && strlen(proto_name->str) > 1) { + proto_name->str[strlen(proto_name->str) - 1] = '\0'; + } + + for (idx = 1; idx < headers_count && idx < values_count; idx++) { + key = flb_slist_entry_get(&headers, idx); + val = flb_slist_entry_get(&values, idx); + + if (key == NULL || val == NULL || proto_name == NULL) { + continue; + } + + if (ne_utils_str_to_double(val->str, &d_val) != 0) { + continue; + } + + snprintf(metric_name, sizeof(metric_name) - 1, "%s_%s", + proto_name->str, key->str); + metric_name[sizeof(metric_name) - 1] = '\0'; + + if (netstat_is_static_metric(metric_name) == FLB_TRUE) { + continue; + } + + metric = netstat_dynamic_metric_get(ctx, metric_name); + if (metric != NULL) { + cmt_gauge_set(metric, ts, d_val, 0, NULL); + } + } + } } flb_slist_destroy(&headers); @@ -222,40 +380,142 @@ static int netstat_update(struct flb_ne *ctx) ts = cfl_time_now(); prev_line = NULL; - prev_proto = NETSTAT_PROTO_NONE; mk_list_foreach(head, &list) { line = mk_list_entry(head, struct flb_slist_entry, _head); - if (prev_proto != NETSTAT_PROTO_NONE) { - if (prev_proto == NETSTAT_PROTO_TCP && strncmp(line->str, "Tcp:", 4) == 0) { + if (prev_line != NULL) { + if (strncmp(prev_line, "Tcp:", 4) == 0 && strncmp(line->str, "Tcp:", 4) == 0) { netstat_process_pair(ctx, prev_line, line->str, NETSTAT_PROTO_TCP, ts); - prev_proto = NETSTAT_PROTO_NONE; + netstat_process_pair(ctx, prev_line, line->str, NETSTAT_PROTO_NONE, ts); prev_line = NULL; continue; } - else if (prev_proto == NETSTAT_PROTO_UDP && strncmp(line->str, "Udp:", 4) == 0) { + else if (strncmp(prev_line, "Udp:", 4) == 0 && strncmp(line->str, "Udp:", 4) == 0) { netstat_process_pair(ctx, prev_line, line->str, NETSTAT_PROTO_UDP, ts); - prev_proto = NETSTAT_PROTO_NONE; + netstat_process_pair(ctx, prev_line, line->str, NETSTAT_PROTO_NONE, ts); + prev_line = NULL; + continue; + } + else if ((strncmp(prev_line, "Ip:", 3) == 0 && strncmp(line->str, "Ip:", 3) == 0) || + (strncmp(prev_line, "Icmp:", 5) == 0 && strncmp(line->str, "Icmp:", 5) == 0) || + (strncmp(prev_line, "IcmpMsg:", 8) == 0 && strncmp(line->str, "IcmpMsg:", 8) == 0) || + (strncmp(prev_line, "UdpLite:", 8) == 0 && strncmp(line->str, "UdpLite:", 8) == 0)) { + netstat_process_pair(ctx, prev_line, line->str, NETSTAT_PROTO_NONE, ts); prev_line = NULL; continue; } - prev_proto = NETSTAT_PROTO_NONE; prev_line = NULL; } - if (strncmp(line->str, "Tcp:", 4) == 0) { + if (strchr(line->str, ':') != NULL) { prev_line = line->str; - prev_proto = NETSTAT_PROTO_TCP; } - else if (strncmp(line->str, "Udp:", 4) == 0) { - prev_line = line->str; - prev_proto = NETSTAT_PROTO_UDP; + } + + flb_slist_destroy(&list); + + mk_list_init(&list); + ret = ne_utils_file_read_lines(ctx->path_procfs, "/net/netstat", &list); + if (ret == 0) { + prev_line = NULL; + prev_proto = NETSTAT_PROTO_NONE; + + mk_list_foreach(head, &list) { + line = mk_list_entry(head, struct flb_slist_entry, _head); + + if (prev_proto != NETSTAT_PROTO_NONE) { + if (prev_proto == NETSTAT_PROTO_TCPEXT && strncmp(line->str, "TcpExt:", 7) == 0) { + netstat_process_pair(ctx, prev_line, line->str, NETSTAT_PROTO_TCPEXT, ts); + prev_proto = NETSTAT_PROTO_NONE; + prev_line = NULL; + continue; + } + else if (prev_proto == NETSTAT_PROTO_IPEXT && strncmp(line->str, "IpExt:", 6) == 0) { + netstat_process_pair(ctx, prev_line, line->str, NETSTAT_PROTO_IPEXT, ts); + prev_proto = NETSTAT_PROTO_NONE; + prev_line = NULL; + continue; + } + + prev_proto = NETSTAT_PROTO_NONE; + prev_line = NULL; + } + + if (strncmp(line->str, "TcpExt:", 7) == 0) { + prev_line = line->str; + prev_proto = NETSTAT_PROTO_TCPEXT; + } + else if (strncmp(line->str, "IpExt:", 6) == 0) { + prev_line = line->str; + prev_proto = NETSTAT_PROTO_IPEXT; + } } } flb_slist_destroy(&list); + + mk_list_init(&list); + ret = ne_utils_file_read_lines(ctx->path_procfs, "/net/snmp6", &list); + if (ret == 0) { + mk_list_foreach(head, &list) { + int i; + int six_index; + int metric_name_len; + double d_val; + char metric_name[256]; + char value[128]; + char raw_name[128]; + char proto_name[64]; + char field_name[64]; + struct cmt_gauge *metric; + + line = mk_list_entry(head, struct flb_slist_entry, _head); + if (sscanf(line->str, "%127s %127s", raw_name, value) != 2) { + continue; + } + + six_index = -1; + i = 0; + while (raw_name[i] != '\0') { + if (raw_name[i] == '6') { + six_index = i; + break; + } + i++; + } + if (six_index == -1 || six_index == 0 || raw_name[six_index + 1] == '\0') { + continue; + } + + snprintf(proto_name, sizeof(proto_name) - 1, "%.*s", + six_index + 1, raw_name); + proto_name[sizeof(proto_name) - 1] = '\0'; + + snprintf(field_name, sizeof(field_name) - 1, "%s", + raw_name + six_index + 1); + field_name[sizeof(field_name) - 1] = '\0'; + + metric_name_len = snprintf(metric_name, sizeof(metric_name) - 1, "%s_%s", + proto_name, field_name); + if (metric_name_len < 0) { + continue; + } + metric_name[sizeof(metric_name) - 1] = '\0'; + + if (ne_utils_str_to_double(value, &d_val) != 0) { + continue; + } + + metric = netstat_dynamic_metric_get(ctx, metric_name); + if (metric != NULL) { + cmt_gauge_set(metric, ts, d_val, 0, NULL); + } + } + flb_slist_destroy(&list); + } + return 0; } @@ -273,10 +533,18 @@ static int ne_netstat_update(struct flb_input_instance *ins, return 0; } +static int ne_netstat_exit(struct flb_ne *ctx) +{ + if (ctx != NULL) { + netstat_dynamic_metrics_destroy(ctx); + } + + return 0; +} + struct flb_ne_collector netstat_collector = { .name = "netstat", .cb_init = ne_netstat_init, .cb_update = ne_netstat_update, - .cb_exit = NULL + .cb_exit = ne_netstat_exit }; - diff --git a/plugins/in_node_exporter_metrics/ne_powersupply.c b/plugins/in_node_exporter_metrics/ne_powersupply.c new file mode 100644 index 00000000000..99f4c70c2fe --- /dev/null +++ b/plugins/in_node_exporter_metrics/ne_powersupply.c @@ -0,0 +1,35 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2026 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifdef __linux__ +#include "ne_powersupply_linux.c" +#elif __APPLE__ +#include "ne_powersupply_darwin.c" +#else + +#include "ne.h" + +struct flb_ne_collector powersupply_collector = { + .name = "powersupplyclass", + .cb_init = NULL, + .cb_update = NULL, + .cb_exit = NULL +}; + +#endif diff --git a/plugins/in_node_exporter_metrics/ne_powersupply.h b/plugins/in_node_exporter_metrics/ne_powersupply.h new file mode 100644 index 00000000000..116b6192d05 --- /dev/null +++ b/plugins/in_node_exporter_metrics/ne_powersupply.h @@ -0,0 +1,27 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2026 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FLB_IN_NE_POWERSUPPLY_H +#define FLB_IN_NE_POWERSUPPLY_H + +#include "ne.h" + +extern struct flb_ne_collector powersupply_collector; + +#endif diff --git a/plugins/in_node_exporter_metrics/ne_powersupply_darwin.c b/plugins/in_node_exporter_metrics/ne_powersupply_darwin.c new file mode 100644 index 00000000000..53a0da6eeb8 --- /dev/null +++ b/plugins/in_node_exporter_metrics/ne_powersupply_darwin.c @@ -0,0 +1,313 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2026 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include "ne.h" + +static int cf_number_to_double(CFTypeRef value, double *out) +{ + if (value == NULL || CFGetTypeID(value) != CFNumberGetTypeID()) { + return -1; + } + + if (!CFNumberGetValue((CFNumberRef) value, kCFNumberDoubleType, out)) { + return -1; + } + + return 0; +} + +static int cf_boolean_to_double(CFTypeRef value, double *out) +{ + if (value == NULL || CFGetTypeID(value) != CFBooleanGetTypeID()) { + return -1; + } + + *out = CFBooleanGetValue((CFBooleanRef) value) ? 1.0 : 0.0; + return 0; +} + +static int set_numeric_metric(struct cmt_gauge *gauge, + CFDictionaryRef description, + CFStringRef key, + uint64_t ts, + const char *name, + double divisor) +{ + CFTypeRef value; + double out; + char *labels[] = {(char *) name}; + + value = CFDictionaryGetValue(description, key); + if (cf_number_to_double(value, &out) == -1) { + return -1; + } + + cmt_gauge_set(gauge, ts, out / divisor, 1, labels); + return 0; +} + +static int set_boolean_metric(struct cmt_gauge *gauge, + CFDictionaryRef description, + CFStringRef key, + uint64_t ts, + const char *name) +{ + CFTypeRef value; + double out; + char *labels[] = {(char *) name}; + + value = CFDictionaryGetValue(description, key); + if (cf_boolean_to_double(value, &out) == -1) { + return -1; + } + + cmt_gauge_set(gauge, ts, out, 1, labels); + return 0; +} + +static int update_power_source(struct flb_ne *ctx, + CFTypeRef source, + CFTypeRef info, + uint64_t ts) +{ + CFDictionaryRef description; + CFTypeRef name_value; + char name[128]; + CFTypeRef battery_health_value; + char battery_health[64]; + char *health_labels_good[] = {name, "Good"}; + char *health_labels_fair[] = {name, "Fair"}; + char *health_labels_poor[] = {name, "Poor"}; + + (void) ctx; + + description = IOPSGetPowerSourceDescription(info, source); + if (description == NULL) { + return -1; + } + + name_value = CFDictionaryGetValue(description, CFSTR(kIOPSNameKey)); + if (name_value == NULL || CFGetTypeID(name_value) != CFStringGetTypeID()) { + return -1; + } + + if (!CFStringGetCString((CFStringRef) name_value, name, + sizeof(name), kCFStringEncodingUTF8)) { + return -1; + } + + set_numeric_metric(ctx->darwin_ps_current_capacity, description, CFSTR(kIOPSCurrentCapacityKey), ts, name, 1.0); + set_numeric_metric(ctx->darwin_ps_max_capacity, description, CFSTR(kIOPSMaxCapacityKey), ts, name, 1.0); + set_numeric_metric(ctx->darwin_ps_design_capacity, description, CFSTR(kIOPSDesignCapacityKey), ts, name, 1.0); + set_numeric_metric(ctx->darwin_ps_nominal_capacity, description, CFSTR(kIOPSNominalCapacityKey), ts, name, 1.0); + set_numeric_metric(ctx->darwin_ps_time_to_empty, description, CFSTR(kIOPSTimeToEmptyKey), ts, name, (1.0 / 60.0)); + set_numeric_metric(ctx->darwin_ps_time_to_full, description, CFSTR(kIOPSTimeToFullChargeKey), ts, name, (1.0 / 60.0)); + set_numeric_metric(ctx->darwin_ps_voltage, description, CFSTR(kIOPSVoltageKey), ts, name, 1000.0); + set_numeric_metric(ctx->darwin_ps_current, description, CFSTR(kIOPSCurrentKey), ts, name, 1000.0); + set_numeric_metric(ctx->darwin_ps_temperature, description, CFSTR(kIOPSTemperatureKey), ts, name, 1.0); + + set_boolean_metric(ctx->darwin_ps_present, description, CFSTR(kIOPSIsPresentKey), ts, name); + set_boolean_metric(ctx->darwin_ps_charging, description, CFSTR(kIOPSIsChargingKey), ts, name); + set_boolean_metric(ctx->darwin_ps_charged, description, CFSTR(kIOPSIsChargedKey), ts, name); + set_boolean_metric(ctx->darwin_ps_internal_failure, description, CFSTR(kIOPSInternalFailureKey), ts, name); + + battery_health_value = CFDictionaryGetValue(description, CFSTR(kIOPSBatteryHealthKey)); + if (battery_health_value != NULL && + CFGetTypeID(battery_health_value) == CFStringGetTypeID() && + CFStringGetCString((CFStringRef) battery_health_value, battery_health, + sizeof(battery_health), kCFStringEncodingUTF8)) { + cmt_gauge_set(ctx->darwin_ps_battery_health, ts, strcmp(battery_health, "Good") == 0 ? 1.0 : 0.0, + 2, health_labels_good); + cmt_gauge_set(ctx->darwin_ps_battery_health, ts, strcmp(battery_health, "Fair") == 0 ? 1.0 : 0.0, + 2, health_labels_fair); + cmt_gauge_set(ctx->darwin_ps_battery_health, ts, strcmp(battery_health, "Poor") == 0 ? 1.0 : 0.0, + 2, health_labels_poor); + } + + return 0; +} + +static int ne_powersupply_init(struct flb_ne *ctx) +{ + char *label[] = {"power_supply"}; + + ctx->darwin_ps_current_capacity = cmt_gauge_create(ctx->cmt, "node", "powersupply", "current_capacity", + "Current battery capacity.", 1, label); + if (ctx->darwin_ps_current_capacity == NULL) { + flb_plg_error(ctx->ins, "failed to create gauge node_powersupply_current_capacity"); + return -1; + } + + ctx->darwin_ps_max_capacity = cmt_gauge_create(ctx->cmt, "node", "powersupply", "max_capacity", + "Maximum battery capacity.", 1, label); + if (ctx->darwin_ps_max_capacity == NULL) { + flb_plg_error(ctx->ins, "failed to create gauge node_powersupply_max_capacity"); + return -1; + } + + ctx->darwin_ps_design_capacity = cmt_gauge_create(ctx->cmt, "node", "powersupply", "design_capacity", + "Design battery capacity.", 1, label); + if (ctx->darwin_ps_design_capacity == NULL) { + flb_plg_error(ctx->ins, "failed to create gauge node_powersupply_design_capacity"); + return -1; + } + + ctx->darwin_ps_nominal_capacity = cmt_gauge_create(ctx->cmt, "node", "powersupply", "nominal_capacity", + "Nominal battery capacity.", 1, label); + if (ctx->darwin_ps_nominal_capacity == NULL) { + flb_plg_error(ctx->ins, "failed to create gauge node_powersupply_nominal_capacity"); + return -1; + } + + ctx->darwin_ps_time_to_empty = cmt_gauge_create(ctx->cmt, "node", "powersupply", "time_to_empty_seconds", + "Estimated time to empty in seconds.", 1, label); + if (ctx->darwin_ps_time_to_empty == NULL) { + flb_plg_error(ctx->ins, "failed to create gauge node_powersupply_time_to_empty_seconds"); + return -1; + } + + ctx->darwin_ps_time_to_full = cmt_gauge_create(ctx->cmt, "node", "powersupply", "time_to_full_seconds", + "Estimated time to full charge in seconds.", 1, label); + if (ctx->darwin_ps_time_to_full == NULL) { + flb_plg_error(ctx->ins, "failed to create gauge node_powersupply_time_to_full_seconds"); + return -1; + } + + ctx->darwin_ps_voltage = cmt_gauge_create(ctx->cmt, "node", "powersupply", "voltage_volt", + "Battery voltage in volts.", 1, label); + if (ctx->darwin_ps_voltage == NULL) { + flb_plg_error(ctx->ins, "failed to create gauge node_powersupply_voltage_volt"); + return -1; + } + + ctx->darwin_ps_current = cmt_gauge_create(ctx->cmt, "node", "powersupply", "current_ampere", + "Battery current in amperes.", 1, label); + if (ctx->darwin_ps_current == NULL) { + flb_plg_error(ctx->ins, "failed to create gauge node_powersupply_current_ampere"); + return -1; + } + + ctx->darwin_ps_temperature = cmt_gauge_create(ctx->cmt, "node", "powersupply", "temp_celsius", + "Battery temperature in celsius.", 1, label); + if (ctx->darwin_ps_temperature == NULL) { + flb_plg_error(ctx->ins, "failed to create gauge node_powersupply_temp_celsius"); + return -1; + } + + ctx->darwin_ps_present = cmt_gauge_create(ctx->cmt, "node", "powersupply", "present", + "Power supply present status.", 1, label); + if (ctx->darwin_ps_present == NULL) { + flb_plg_error(ctx->ins, "failed to create gauge node_powersupply_present"); + return -1; + } + + ctx->darwin_ps_charging = cmt_gauge_create(ctx->cmt, "node", "powersupply", "charging", + "Power supply charging status.", 1, label); + if (ctx->darwin_ps_charging == NULL) { + flb_plg_error(ctx->ins, "failed to create gauge node_powersupply_charging"); + return -1; + } + + ctx->darwin_ps_charged = cmt_gauge_create(ctx->cmt, "node", "powersupply", "charged", + "Power supply charged status.", 1, label); + if (ctx->darwin_ps_charged == NULL) { + flb_plg_error(ctx->ins, "failed to create gauge node_powersupply_charged"); + return -1; + } + + ctx->darwin_ps_internal_failure = cmt_gauge_create(ctx->cmt, "node", "powersupply", "internal_failure", + "Power supply internal failure status.", 1, label); + if (ctx->darwin_ps_internal_failure == NULL) { + flb_plg_error(ctx->ins, "failed to create gauge node_powersupply_internal_failure"); + return -1; + } + + ctx->darwin_ps_battery_health = cmt_gauge_create(ctx->cmt, "node", "powersupply", "battery_health", + "Power supply battery health status.", 2, + (char *[]) {"power_supply", "state"}); + if (ctx->darwin_ps_battery_health == NULL) { + flb_plg_error(ctx->ins, "failed to create gauge node_powersupply_battery_health"); + return -1; + } + + return 0; +} + +static int ne_powersupply_update(struct flb_input_instance *ins, + struct flb_config *config, void *in_context) +{ + struct flb_ne *ctx; + uint64_t ts; + CFTypeRef info; + CFArrayRef list; + CFTypeRef source; + CFIndex i; + int ret; + int has_failures; + + (void) ins; + (void) config; + + ctx = in_context; + ts = cfl_time_now(); + has_failures = FLB_FALSE; + + info = IOPSCopyPowerSourcesInfo(); + if (info == NULL) { + return -1; + } + + list = IOPSCopyPowerSourcesList(info); + if (list == NULL) { + CFRelease(info); + return -1; + } + + for (i = 0; i < CFArrayGetCount(list); i++) { + source = CFArrayGetValueAtIndex(list, i); + ret = update_power_source(ctx, source, info, ts); + if (ret != 0) { + flb_plg_debug(ctx->ins, + "failed to update power source metrics for source index=%ld ptr=%p", + i, source); + has_failures = FLB_TRUE; + continue; + } + } + + CFRelease(list); + CFRelease(info); + + if (has_failures == FLB_TRUE) { + return -1; + } + + return 0; +} + +struct flb_ne_collector powersupply_collector = { + .name = "powersupplyclass", + .cb_init = ne_powersupply_init, + .cb_update = ne_powersupply_update, + .cb_exit = NULL +}; diff --git a/plugins/in_node_exporter_metrics/ne_powersupply_linux.c b/plugins/in_node_exporter_metrics/ne_powersupply_linux.c new file mode 100644 index 00000000000..8d55d0897d8 --- /dev/null +++ b/plugins/in_node_exporter_metrics/ne_powersupply_linux.c @@ -0,0 +1,238 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ + +/* Fluent Bit + * ========== + * Copyright (C) 2015-2026 The Fluent Bit Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include + +#include "ne.h" + +struct ps_metric_spec { + const char *metric_name; + const char *file_name; + double divisor; +}; + +struct ps_metric_entry { + char *name; + struct cmt_gauge *gauge; + struct mk_list _head; +}; + +static void ps_dynamic_metrics_destroy(struct flb_ne *ctx) +{ + struct mk_list *tmp; + struct mk_list *head; + struct ps_metric_entry *entry; + + mk_list_foreach_safe(head, tmp, &ctx->powersupply_dynamic_metrics) { + entry = mk_list_entry(head, struct ps_metric_entry, _head); + mk_list_del(&entry->_head); + if (entry->name != NULL) { + flb_free(entry->name); + } + flb_free(entry); + } +} + +static int ne_powersupply_exit(struct flb_ne *ctx) +{ + ps_dynamic_metrics_destroy(ctx); + return 0; +} + +static struct ps_metric_spec ps_numeric_metrics[] = { + {"authentic", "authentic", 1.0}, + {"calibrate", "calibrate", 1.0}, + {"capacity", "capacity", 1.0}, + {"capacity_alert_max", "capacity_alert_max", 1.0}, + {"capacity_alert_min", "capacity_alert_min", 1.0}, + {"cyclecount", "cycle_count", 1.0}, + {"online", "online", 1.0}, + {"present", "present", 1.0}, + {"time_to_empty_seconds", "time_to_empty_now", 1.0}, + {"time_to_full_seconds", "time_to_full_now", 1.0}, + {"current_boot", "current_boot", 1000000.0}, + {"current_max", "current_max", 1000000.0}, + {"current_ampere", "current_now", 1000000.0}, + {"energy_empty", "energy_empty", 1000000.0}, + {"energy_empty_design", "energy_empty_design", 1000000.0}, + {"energy_full", "energy_full", 1000000.0}, + {"energy_full_design", "energy_full_design", 1000000.0}, + {"energy_watthour", "energy_now", 1000000.0}, + {"voltage_boot", "voltage_boot", 1000000.0}, + {"voltage_max", "voltage_max", 1000000.0}, + {"voltage_max_design", "voltage_max_design", 1000000.0}, + {"voltage_min", "voltage_min", 1000000.0}, + {"voltage_min_design", "voltage_min_design", 1000000.0}, + {"voltage_volt", "voltage_now", 1000000.0}, + {"voltage_ocv", "voltage_ocv", 1000000.0}, + {"charge_control_limit", "charge_control_limit", 1000000.0}, + {"charge_control_limit_max", "charge_control_limit_max", 1000000.0}, + {"charge_counter", "charge_counter", 1000000.0}, + {"charge_empty", "charge_empty", 1000000.0}, + {"charge_empty_design", "charge_empty_design", 1000000.0}, + {"charge_full", "charge_full", 1000000.0}, + {"charge_full_design", "charge_full_design", 1000000.0}, + {"charge_ampere", "charge_now", 1000000.0}, + {"charge_term_current", "charge_term_current", 1000000.0}, + {"constant_charge_current", "constant_charge_current", 1000000.0}, + {"constant_charge_current_max", "constant_charge_current_max", 1000000.0}, + {"constant_charge_voltage", "constant_charge_voltage", 1000000.0}, + {"constant_charge_voltage_max", "constant_charge_voltage_max", 1000000.0}, + {"precharge_current", "precharge_current", 1000000.0}, + {"input_current_limit", "input_current_limit", 1000000.0}, + {"power_watt", "power_now", 1000000.0}, + {"temp_celsius", "temp", 10.0}, + {"temp_alert_max_celsius", "temp_alert_max", 10.0}, + {"temp_alert_min_celsius", "temp_alert_min", 10.0}, + {"temp_ambient_celsius", "temp_ambient", 10.0}, + {"temp_ambient_max_celsius", "temp_ambient_max", 10.0}, + {"temp_ambient_min_celsius", "temp_ambient_min", 10.0}, + {"temp_max_celsius", "temp_max", 10.0}, + {"temp_min_celsius", "temp_min", 10.0}, +}; + +static struct cmt_gauge *ps_metric_get(struct flb_ne *ctx, const char *name) +{ + struct mk_list *head; + struct ps_metric_entry *entry; + + mk_list_foreach(head, &ctx->powersupply_dynamic_metrics) { + entry = mk_list_entry(head, struct ps_metric_entry, _head); + if (strcmp(entry->name, name) == 0) { + return entry->gauge; + } + } + + entry = flb_calloc(1, sizeof(struct ps_metric_entry)); + if (entry == NULL) { + return NULL; + } + entry->name = flb_strdup(name); + if (entry->name == NULL) { + flb_free(entry); + return NULL; + } + + entry->gauge = cmt_gauge_create(ctx->cmt, "node", "powersupply", name, + "Power supply metric from /sys/class/power_supply.", + 1, (char *[]) {"power_supply"}); + if (entry->gauge == NULL) { + flb_free(entry->name); + flb_free(entry); + return NULL; + } + + mk_list_add(&entry->_head, &ctx->powersupply_dynamic_metrics); + return entry->gauge; +} + +static void emit_info_metric(struct flb_ne *ctx, const char *name, uint64_t ts) +{ + char *labels[] = {(char *) name}; + + (void) ctx; + cmt_gauge_set(ctx->powersupply_info, ts, 1.0, 1, labels); +} + +static int update_one(struct flb_ne *ctx, const char *name) +{ + size_t i; + long v; + uint64_t ts; + char path[1024]; + char *buf; + char *end; + size_t buf_size; + char *labels[] = {(char *) name}; + struct cmt_gauge *gauge; + + ts = cfl_time_now(); + + for (i = 0; i < sizeof(ps_numeric_metrics) / sizeof(ps_numeric_metrics[0]); i++) { + snprintf(path, sizeof(path) - 1, "%s/class/power_supply/%s/%s", + ctx->path_sysfs, name, ps_numeric_metrics[i].file_name); + + if (flb_utils_read_file(path, &buf, &buf_size) == 0 && buf != NULL && buf_size > 0) { + v = strtol(buf, &end, 10); + if (end == buf) { + flb_free(buf); + continue; + } + flb_free(buf); + + gauge = ps_metric_get(ctx, ps_numeric_metrics[i].metric_name); + if (gauge != NULL) { + cmt_gauge_set(gauge, ts, ((double) v) / ps_numeric_metrics[i].divisor, + 1, labels); + } + } + } + + emit_info_metric(ctx, name, ts); + return 0; +} + +static int ne_powersupply_init(struct flb_ne *ctx) +{ + mk_list_init(&ctx->powersupply_dynamic_metrics); + ctx->powersupply_info = cmt_gauge_create(ctx->cmt, "node", "powersupply", "info", + "info of /sys/class/power_supply/.", + 1, (char *[]) {"power_supply"}); + return 0; +} + +static int ne_powersupply_update(struct flb_input_instance *ins, + struct flb_config *config, void *in_context) +{ + DIR *dir; + struct dirent *ent; + struct flb_ne *ctx; + char path[1024]; + + (void) ins; + (void) config; + + ctx = in_context; + snprintf(path, sizeof(path) - 1, "%s/class/power_supply", ctx->path_sysfs); + dir = opendir(path); + if (dir == NULL) { + return -1; + } + + while ((ent = readdir(dir)) != NULL) { + if (ent->d_name[0] == '.') { + continue; + } + update_one(ctx, ent->d_name); + } + + closedir(dir); + return 0; +} + +struct flb_ne_collector powersupply_collector = { + .name = "powersupplyclass", + .cb_init = ne_powersupply_init, + .cb_update = ne_powersupply_update, + .cb_exit = ne_powersupply_exit +};