mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-01 16:44:23 +08:00
220 lines
7.1 KiB
Bash
Executable File
220 lines
7.1 KiB
Bash
Executable File
#!/bin/sh
|
|
# shellcheck shell=dash
|
|
# Show traffic shaping statistics for all shaped devices.
|
|
# Output: JSON array with extended stats per IP.
|
|
# Fields: ip, rate_kbit, bytes, packets, backlog, drops, overlimits,
|
|
# requeues, lended, borrowed, ecn_mark, new_flows, old_flows,
|
|
# target_us, memory_used
|
|
|
|
. /usr/local/bin/trafficctl-fw.sh
|
|
|
|
LAN_DEV=$(tctl_get_lan_device)
|
|
|
|
if ! command -v tc >/dev/null 2>&1; then
|
|
echo '[]'; exit 0
|
|
fi
|
|
|
|
if ! tc qdisc show dev "$LAN_DEV" 2>/dev/null | grep -q "htb 1:"; then
|
|
echo '[]'; exit 0
|
|
fi
|
|
|
|
# Map classid → real IP from persisted shapes. Minors are allocated, not derived
|
|
# from the address, so this map is the only way back to an IP; classes missing
|
|
# from it belong to another tc user and are skipped rather than mislabelled.
|
|
CLASSMAP=$(grep -oE '\{"ip":"[^"]+","rate_kbit":[0-9]+,"classid":"1:[0-9a-f]+"\}' \
|
|
/etc/trafficctl/shapes.json 2>/dev/null | \
|
|
sed -n 's/{"ip":"\([^"]*\)","rate_kbit":[0-9]*,"classid":"\(1:[0-9a-f]*\)"}/\2 \1/p')
|
|
|
|
# Collect class stats into a temp file so we can merge with qdisc stats
|
|
CLASS_DATA=$(tc -s class show dev "$LAN_DEV" 2>/dev/null)
|
|
QDISC_DATA=$(tc -s qdisc show dev "$LAN_DEV" 2>/dev/null)
|
|
|
|
# Parse class stats: emit lines "classid ip rate bytes pkts backlog drops overlimits requeues lended borrowed"
|
|
CLASS_PARSED=$(echo "$CLASS_DATA" | awk -v classmap="$CLASSMAP" '
|
|
BEGIN {
|
|
n = split(classmap, cml, "\n")
|
|
for (ci = 1; ci <= n; ci++) {
|
|
split(cml[ci], cmf, " ")
|
|
if (cmf[1] != "" && cmf[2] != "") ipmap[cmf[1]] = cmf[2]
|
|
}
|
|
}
|
|
|
|
/class fq_codel/ { skip = 1; next }
|
|
/^class htb 1:/ {
|
|
# emit previous record if valid
|
|
if (have_record && current_rate > 0 && current_rate < 1000000 && (classid in ipmap)) {
|
|
printf "%s %s %d %d %d %d %d %d %d %d %d\n", \
|
|
classid, ipmap[classid], current_rate, \
|
|
bytes, pkts, backlog, drops, overlimits, requeues, lended, borrowed
|
|
}
|
|
minor = $3
|
|
sub(/^1:/, "", minor)
|
|
if (minor == "1" || minor == "fffe") { skip = 1; next }
|
|
skip = 0
|
|
classid = "1:" minor
|
|
current_rate = 0
|
|
for (i = 1; i <= NF; i++) {
|
|
if ($i == "rate") {
|
|
v = $(i+1)
|
|
if (v ~ /Gbit/) { sub(/Gbit/, "", v); current_rate = (v+0) * 1000000 }
|
|
else if (v ~ /Mbit/) { sub(/Mbit/, "", v); current_rate = (v+0) * 1000 }
|
|
else if (v ~ /[Kk]bit/) { sub(/[Kk]bit/, "", v); current_rate = v+0 }
|
|
break
|
|
}
|
|
}
|
|
bytes = 0; pkts = 0; backlog = 0; drops = 0; overlimits = 0; requeues = 0
|
|
lended = 0; borrowed = 0
|
|
have_record = 1
|
|
}
|
|
/Sent [0-9]+ bytes/ && !skip {
|
|
for (i = 1; i <= NF; i++) {
|
|
if ($i == "Sent") bytes = $(i+1)
|
|
if ($i ~ /^[0-9]+$/ && $(i+1) == "pkt") pkts = $i
|
|
}
|
|
# parse "(dropped N, overlimits N requeues N)" from same line
|
|
for (i = 1; i <= NF; i++) {
|
|
if ($i == "(dropped") { v = $(i+1); sub(/,/, "", v); drops = v + 0 }
|
|
if ($i == "overlimits") { v = $(i+1); sub(/[^0-9]/, "", v); overlimits = v + 0 }
|
|
}
|
|
# requeues at end of Sent line
|
|
for (i = 1; i <= NF; i++) {
|
|
if ($i == "requeues") {
|
|
v = $(i+1); sub(/[^0-9]/, "", v)
|
|
if (v != "") requeues = v + 0
|
|
}
|
|
}
|
|
}
|
|
/backlog/ && !skip {
|
|
for (i = 1; i <= NF; i++) {
|
|
if ($i == "backlog") {
|
|
v = $(i+1); sub(/b$/, "", v)
|
|
backlog = v + 0
|
|
}
|
|
}
|
|
}
|
|
/lended:/ && !skip {
|
|
for (i = 1; i <= NF; i++) {
|
|
if ($i == "lended:") lended = $(i+1) + 0
|
|
if ($i == "borrowed:") borrowed = $(i+1) + 0
|
|
}
|
|
}
|
|
END {
|
|
if (have_record && current_rate > 0 && current_rate < 1000000 && (classid in ipmap)) {
|
|
printf "%s %s %d %d %d %d %d %d %d %d %d\n", \
|
|
classid, ipmap[classid], current_rate, \
|
|
bytes, pkts, backlog, drops, overlimits, requeues, lended, borrowed
|
|
}
|
|
}
|
|
')
|
|
|
|
# Parse fq_codel qdisc stats: emit lines "parent_classid ecn_mark new_flows old_flows target_us memory_used"
|
|
QDISC_PARSED=$(echo "$QDISC_DATA" | awk '
|
|
/^qdisc fq_codel/ {
|
|
# emit previous fq_codel record if any
|
|
if (have_qdisc && parent_id != "") {
|
|
printf "%s %d %d %d %d %d\n", parent_id, ecn_mark, new_flows, old_flows, target_us, memory_used
|
|
}
|
|
# extract parent classid
|
|
parent_id = ""
|
|
target_us = 0
|
|
for (i = 1; i <= NF; i++) {
|
|
if ($i == "parent") parent_id = $(i+1)
|
|
if ($i == "target") {
|
|
v = $(i+1)
|
|
if (v ~ /ms$/) { sub(/ms$/, "", v); target_us = (v + 0) * 1000 }
|
|
else if (v ~ /us$/) { sub(/us$/, "", v); target_us = v + 0 }
|
|
}
|
|
}
|
|
ecn_mark = 0; new_flows = 0; old_flows = 0; memory_used = 0
|
|
have_qdisc = 1
|
|
next
|
|
}
|
|
/^qdisc/ && !/fq_codel/ {
|
|
# different qdisc type, emit previous if valid
|
|
if (have_qdisc && parent_id != "") {
|
|
printf "%s %d %d %d %d %d\n", parent_id, ecn_mark, new_flows, old_flows, target_us, memory_used
|
|
}
|
|
have_qdisc = 0
|
|
parent_id = ""
|
|
next
|
|
}
|
|
have_qdisc && /ecn_mark/ {
|
|
for (i = 1; i <= NF; i++) {
|
|
if ($i == "ecn_mark") ecn_mark = $(i+1) + 0
|
|
}
|
|
}
|
|
have_qdisc && /new_flows/ && !/new_flow_count/ {
|
|
for (i = 1; i <= NF; i++) {
|
|
if ($i == "new_flows") new_flows = $(i+1) + 0
|
|
if ($i == "old_flows") old_flows = $(i+1) + 0
|
|
}
|
|
}
|
|
have_qdisc && /memory_used/ {
|
|
for (i = 1; i <= NF; i++) {
|
|
if ($i == "memory_used") memory_used = $(i+1) + 0
|
|
}
|
|
}
|
|
END {
|
|
if (have_qdisc && parent_id != "") {
|
|
printf "%s %d %d %d %d %d\n", parent_id, ecn_mark, new_flows, old_flows, target_us, memory_used
|
|
}
|
|
}
|
|
')
|
|
|
|
# If no classes found, output empty array
|
|
if [ -z "$CLASS_PARSED" ]; then
|
|
echo '[]'
|
|
exit 0
|
|
fi
|
|
|
|
# Write qdisc data to temp file for awk to read
|
|
QDISC_TMP=$(mktemp /tmp/tctl_qdisc.XXXXXX)
|
|
# shellcheck disable=SC2064
|
|
trap "rm -f '$QDISC_TMP'" EXIT INT TERM
|
|
echo "$QDISC_PARSED" > "$QDISC_TMP"
|
|
|
|
# Merge class and qdisc data, output JSON
|
|
# First file (QDISC_TMP) is loaded into arrays, second input (stdin) is class data
|
|
echo "$CLASS_PARSED" | awk '
|
|
NR == FNR {
|
|
# Reading qdisc temp file
|
|
if (NF >= 6) {
|
|
qd_ecn[$1] = $2 + 0
|
|
qd_new[$1] = $3 + 0
|
|
qd_old[$1] = $4 + 0
|
|
qd_target[$1] = $5 + 0
|
|
qd_mem[$1] = $6 + 0
|
|
}
|
|
next
|
|
}
|
|
{
|
|
classid = $1
|
|
ip = $2
|
|
rate = $3 + 0
|
|
bytes = $4 + 0
|
|
pkts = $5 + 0
|
|
backlog = $6 + 0
|
|
drops = $7 + 0
|
|
overlimits = $8 + 0
|
|
requeues = $9 + 0
|
|
lended = $10 + 0
|
|
borrowed = $11 + 0
|
|
|
|
ecn = 0; nf = 0; of = 0; tgt = 0; mem = 0
|
|
if (classid in qd_ecn) ecn = qd_ecn[classid]
|
|
if (classid in qd_new) nf = qd_new[classid]
|
|
if (classid in qd_old) of = qd_old[classid]
|
|
if (classid in qd_target) tgt = qd_target[classid]
|
|
if (classid in qd_mem) mem = qd_mem[classid]
|
|
|
|
if (first) printf ","
|
|
printf "{\"ip\":\"%s\",\"rate_kbit\":%d,\"bytes\":%.0f,\"packets\":%.0f,\"backlog\":%d,\"drops\":%.0f,\"overlimits\":%.0f,\"requeues\":%.0f,\"lended\":%.0f,\"borrowed\":%.0f,\"ecn_mark\":%.0f,\"new_flows\":%d,\"old_flows\":%d,\"target_us\":%d,\"memory_used\":%d}", \
|
|
ip, rate, bytes, pkts, backlog, drops, overlimits, requeues, lended, borrowed, ecn, nf, of, tgt, mem
|
|
first = 1
|
|
}
|
|
BEGIN { printf "[" }
|
|
END { printf "]\n" }
|
|
' "$QDISC_TMP" -
|
|
|
|
rm -f "$QDISC_TMP"
|