Skip to content

Instantly share code, notes, and snippets.

@toneoesa
Created December 7, 2025 13:31
Show Gist options
  • Select an option

  • Save toneoesa/5f0aa73613923a30f777b2c9125c856a to your computer and use it in GitHub Desktop.

Select an option

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
#!/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();
#!/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