Created
December 7, 2025 13:31
-
-
Save toneoesa/5f0aa73613923a30f777b2c9125c856a to your computer and use it in GitHub Desktop.
A ucode script for collecting ethernet statistics and sending to statsd, running on OpenWrt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/ucode | |
| // require package ucode-mod-fs, ucode-mod-uloop, ucode-mod-socket | |
| import * as fs from 'fs'; | |
| import * as uloop from 'uloop'; | |
| import * as socket from 'socket'; | |
| const CONFIG = { | |
| host: "192.168.1.230", | |
| port: 8125, | |
| interval: 1000, | |
| prefix: "openwrt.ethstat", | |
| interfaces: ["wan", "lan2", "lan3", "lan4"], | |
| metrics: { | |
| "TxBytes": "bytes", | |
| "RxBytes": "bytes", | |
| } | |
| }; | |
| const sock = socket.create(socket.AF_INET, socket.SOCK_DGRAM); | |
| if (!sock) { | |
| print("Error: Unable to create socket\n"); | |
| exit(1); | |
| } | |
| if (!sock.connect(CONFIG.host, CONFIG.port)) { | |
| print(`Error: Unable to connect to ${CONFIG.host}:${CONFIG.port}\n`); | |
| exit(1); | |
| } | |
| let cache = {}; | |
| function get_ethtool_stats(iface) { | |
| let p = fs.popen(`ethtool -S ${iface}`, "r"); | |
| if (!p) return null; | |
| let stats = {}; | |
| let line; | |
| while ((line = p.read("line")) != null) { | |
| let parts = split(line, ":"); | |
| if (length(parts) == 2) { | |
| let key = trim(parts[0]); | |
| if (key in CONFIG.metrics) { | |
| stats[key] = +trim(parts[1]); | |
| } | |
| } | |
| } | |
| p.close(); | |
| return stats; | |
| } | |
| function collect_and_send() { | |
| let data_point = []; | |
| for (let iface in CONFIG.interfaces) { | |
| let current_stats = get_ethtool_stats(iface); | |
| if (!current_stats) continue; | |
| if (!cache[iface]) cache[iface] = {}; | |
| for (let key in current_stats) { | |
| let val = current_stats[key]; | |
| let full_key = `${CONFIG.prefix}.${iface}.${key}`; | |
| let unit = CONFIG.metrics[key]; | |
| push(data_point, `${full_key}:${val}|g|#units:${unit}`); | |
| if (exists(cache[iface], key)) { | |
| let delta = val - cache[iface][key]; | |
| if (unit == "bytes") { | |
| delta *= 8; | |
| unit = "bit"; | |
| } | |
| let rate = delta * 1000 / CONFIG.interval; | |
| if (rate >= 0) { | |
| push(data_point, `${full_key}Rate:${rate}|g|#units:${unit}/s`); | |
| } | |
| } | |
| cache[iface][key] = val; | |
| } | |
| } | |
| if (length(data_point) > 0) { | |
| let buffer = join("\n", data_point); | |
| sock.send(buffer); | |
| // printf(`Length: ${length(buffer)}\n`); | |
| // printf(`Buffer:\n${buffer}\n\n`); | |
| } | |
| } | |
| uloop.init(); | |
| uloop.interval(CONFIG.interval, collect_and_send); | |
| uloop.run(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/sh /etc/rc.common | |
| START=99 | |
| USE_PROCD=1 | |
| start_service() { | |
| procd_open_instance | |
| procd_set_param command /root/ethstat.u | |
| procd_set_param respawn ${respawn_threshold:-3600} ${respawn_timeout:-5} ${respawn_retry:-5} | |
| procd_set_param stdout 1 | |
| procd_set_param stderr 1 | |
| procd_close_instance | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment