]> git.ipfire.org Git - telemetry.git/commitdiff
sources: Collect interface statistics
authorMichael Tremer <michael.tremer@ipfire.org>
Fri, 28 Nov 2025 17:26:15 +0000 (17:26 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Fri, 28 Nov 2025 17:26:15 +0000 (17:26 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
src/daemon/sources.c
src/daemon/sources/interfaces.c [new file with mode: 0644]
src/daemon/sources/interfaces.h [new file with mode: 0644]

index 7530b41851a14b5b2a52f3c9b64e353eb2d3e0e3..923405c616bcfd0185e7a553c78825c3036f2eea 100644 (file)
@@ -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 \
index f565787b336d3c7033ef258af9a9b52faef5bf1a..ea4c5d3c1376367c9e83a912f3ecd534db37a6bb 100644 (file)
@@ -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 (file)
index 0000000..f4ea08c
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+#############################################################################*/
+
+#include <errno.h>
+#include <limits.h>
+
+// libnl-3
+#include <netlink/netlink.h>
+#include <netlink/route/link.h>
+#include <netlink/route/route.h>
+#include <netlink/socket.h>
+
+#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 (file)
index 0000000..754679b
--- /dev/null
@@ -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 <http://www.gnu.org/licenses/>.       #
+#                                                                             #
+#############################################################################*/
+
+#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 */