From: Michael Tremer Date: Fri, 28 Nov 2025 17:26:15 +0000 (+0000) Subject: sources: Collect interface statistics X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=0e8f0b4e0e0e0b96e72e1fd547521b2f1dfc5cdb;p=telemetry.git sources: Collect interface statistics Signed-off-by: Michael Tremer --- diff --git a/Makefile.am b/Makefile.am index 7530b41..923405c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -172,6 +172,8 @@ dist_telemetryd_SOURCES = \ src/daemon/sources/disk.h \ src/daemon/sources/hostapd.c \ src/daemon/sources/hostapd.h \ + src/daemon/sources/interfaces.c \ + src/daemon/sources/interfaces.h \ src/daemon/sources/ipfrag4.c \ src/daemon/sources/ipfrag4.h \ src/daemon/sources/iptables.c \ diff --git a/src/daemon/sources.c b/src/daemon/sources.c index f565787..ea4c5d3 100644 --- a/src/daemon/sources.c +++ b/src/daemon/sources.c @@ -35,6 +35,7 @@ #include "sources/df.h" #include "sources/disk.h" #include "sources/hostapd.h" +#include "sources/interfaces.h" #include "sources/ipfrag4.h" #include "sources/loadavg.h" #include "sources/memory.h" @@ -86,6 +87,7 @@ static const td_source_impl* source_impls[] = { &df_source, &disk_source, &hostapd_source, + &interfaces_source, &ipfrag4_source, &loadavg_source, &memory_source, diff --git a/src/daemon/sources/interfaces.c b/src/daemon/sources/interfaces.c new file mode 100644 index 0000000..f4ea08c --- /dev/null +++ b/src/daemon/sources/interfaces.c @@ -0,0 +1,278 @@ +/*############################################################################# +# # +# telemetryd - The IPFire Telemetry Collection Service # +# Copyright (C) 2025 IPFire Development Team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +#############################################################################*/ + +#include +#include + +// libnl-3 +#include +#include +#include +#include + +#include "../command.h" +#include "../ctx.h" +#include "../source.h" +#include "../time.h" +#include "interfaces.h" + +// Netlink Socket +static struct nl_sock* sock = NULL; + +typedef struct interfaces_stats { + const char* field; + rtnl_link_stat_id_t id; +} interfaces_stats; + +static int interfaces_init(td_ctx* ctx) { + int r; + + // Don't open the socket again + if (sock) + return 0; + + // Create a new netlink socket + sock = nl_socket_alloc(); + if (!sock) { + ERROR(ctx, "Failed to create a netlink socket: %m\n"); + r = -errno; + goto ERROR; + } + + // Select routing + r = nl_connect(sock, NETLINK_ROUTE); + if (r < 0) { + ERROR(ctx, "Failed to select routing: %s\n", nl_geterror(r)); + r = -ENOTSUP; + goto ERROR; + } + + // Success + return 0; + +ERROR: + if (sock) { + nl_socket_free(sock); + sock = NULL; + } + + return r; +} + +static int interfaces_free(td_ctx* ctx) { + if (sock) { + nl_socket_free(sock); + sock = NULL; + } + + return 0; +} + +static const interfaces_stats stats[] = { + // Packets Received/Sent + { "rx_packets", RTNL_LINK_RX_PACKETS }, + { "tx_packets", RTNL_LINK_TX_PACKETS }, + + // Bytes Received/Sent + { "rx_bytes", RTNL_LINK_RX_BYTES }, + { "tx_bytes", RTNL_LINK_TX_BYTES }, + + // RX/TX Errors + { "rx_errors", RTNL_LINK_RX_ERRORS }, + { "tx_errors", RTNL_LINK_TX_ERRORS }, + + // Received Packets Dropped/Packets Dropped During Transmit + { "rx_dropped", RTNL_LINK_RX_DROPPED }, + { "tx_dropped", RTNL_LINK_TX_DROPPED }, + + // Compressed Packets Received/Sent + { "rx_compressed", RTNL_LINK_RX_COMPRESSED }, + { "tx_compressed", RTNL_LINK_TX_COMPRESSED }, + + // FIFO Errors + { "rx_fifo_errors", RTNL_LINK_RX_FIFO_ERR }, + { "tx_fifo_errors", RTNL_LINK_TX_FIFO_ERR }, + + // RX Length Errors + { "rx_length_errors", RTNL_LINK_RX_LEN_ERR }, + + // RX Over Errors + { "rx_over_errors", RTNL_LINK_RX_OVER_ERR }, + + // RX CRC Errors + { "rx_crc_errors", RTNL_LINK_RX_CRC_ERR }, + + // RX Frame Errors + { "rx_frame_errors", RTNL_LINK_RX_FRAME_ERR }, + + // RX Missed Errors + { "rx_missed_errors", RTNL_LINK_RX_MISSED_ERR }, + + // TX Aborted Errors + { "tx_aborted_errors", RTNL_LINK_TX_ABORT_ERR }, + + // TX Carrier Errors + { "tx_carrier_errors", RTNL_LINK_TX_CARRIER_ERR }, + + // TX Heartbeat Errors + { "tx_heartbeat_errors", RTNL_LINK_TX_HBEAT_ERR }, + + // TX Window Errors + { "tx_window_errors", RTNL_LINK_TX_WIN_ERR }, + + // Collisions + { "collisions", RTNL_LINK_COLLISIONS }, + + // Multicast + { "multicast", RTNL_LINK_MULTICAST }, + + { NULL }, +}; + +static void interfaces_link_callback(struct nl_object* object, void* data) { + struct rtnl_link* link = (struct rtnl_link*)object; + td_metrics* metrics = NULL; + td_source* source = data; + uint64_t value = 0; + int r; + + // Fetch the link name + const char *name = rtnl_link_get_name(link); + if (!name) + goto ERROR; + + // Create a new metrics object + r = td_source_create_metrics(source, &metrics, name); + if (r < 0) + goto ERROR; + + // Collect all stats + for (const interfaces_stats* stat = stats; stat->field; stat++) { + // Fetch the value + value = rtnl_link_get_stat(link, stat->id); + + // Submit the value + r = td_metrics_push_uint64(metrics, stat->field, value); + if (r < 0) + goto ERROR; + } + + // Submit metrics + r = td_source_submit_metrics(source, metrics); + if (r < 0) + goto ERROR; + +ERROR: + if (metrics) + td_metrics_unref(metrics); +} + +static int interfaces_heartbeat(td_ctx* ctx, td_source* source) { + struct nl_cache* links = NULL; + int r; + + // Fetch the links + r = rtnl_link_alloc_cache(sock, AF_UNSPEC, &links); + if (r < 0) { + ERROR(ctx, "Failed to fetch the links: %s\n", nl_geterror(r)); + r = -ENOTSUP; + goto ERROR; + } + + // Walk through all interfaces + nl_cache_foreach(links, interfaces_link_callback, source); + +ERROR: + if (links) + nl_cache_free(links); + + return r; +} + +const td_source_impl interfaces_source = { + .name = "interfaces", + + // RRD Data Sources + .rrd_dss = { + // Packets Received/Sent + { "rx_packets", "DERIVE", 0, -1, }, + { "tx_packets", "DERIVE", 0, -1, }, + + // Bytes Received/Sent + { "rx_bytes", "DERIVE", 0, -1, }, + { "tx_bytes", "DERIVE", 0, -1, }, + + // RX/TX Errors + { "rx_errors", "DERIVE", 0, -1, }, + { "tx_errors", "DERIVE", 0, -1, }, + + // Received Packets Dropped/Packets Dropped During Transmit + { "rx_dropped", "DERIVE", 0, -1, }, + { "tx_dropped", "DERIVE", 0, -1, }, + + // Compressed Packets Received/Sent + { "rx_compressed", "DERIVE", 0, -1, }, + { "tx_compressed", "DERIVE", 0, -1, }, + + // FIFO Errors + { "rx_fifo_errors", "DERIVE", 0, -1, }, + { "tx_fifo_errors", "DERIVE", 0, -1, }, + + // RX Length Errors + { "rx_length_errors", "DERIVE", 0, -1, }, + + // RX Over Errors + { "rx_over_errors", "DERIVE", 0, -1, }, + + // RX CRC Errors + { "rx_crc_errors", "DERIVE", 0, -1, }, + + // RX Frame Errors + { "rx_frame_errors", "DERIVE", 0, -1, }, + + // RX Missed Errors + { "rx_missed_errors", "DERIVE", 0, -1, }, + + // TX Aborted Errors + { "tx_aborted_errors", "DERIVE", 0, -1, }, + + // TX Carrier Errors + { "tx_carrier_errors", "DERIVE", 0, -1, }, + + // TX Heartbeat Errors + { "tx_heartbeat_errors", "DERIVE", 0, -1, }, + + // TX Window Errors + { "tx_window_errors", "DERIVE", 0, -1, }, + + // Collisions + { "collisions", "DERIVE", 0, -1, }, + + // Multicast + { "multicast", "DERIVE", 0, -1, }, + + { NULL }, + }, + + // Methods + .init = interfaces_init, + .free = interfaces_free, + .heartbeat = interfaces_heartbeat, +}; diff --git a/src/daemon/sources/interfaces.h b/src/daemon/sources/interfaces.h new file mode 100644 index 0000000..754679b --- /dev/null +++ b/src/daemon/sources/interfaces.h @@ -0,0 +1,28 @@ +/*############################################################################# +# # +# telemetryd - The IPFire Telemetry Collection Service # +# Copyright (C) 2025 IPFire Development Team # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +#############################################################################*/ + +#ifndef TELEMETRY_SOURCE_INTERFACES_H +#define TELEMETRY_SOURCE_INTERFACES_H + +#include "../source.h" + +extern const td_source_impl interfaces_source; + +#endif /* TELEMETRY_SOURCE_INTERFACES_H */