Files
felhom.eu/documentation/audits/SPIKE-upload-watch-2026-10-23/tools/trprobe.py
T
admin a91d0b2421
gates / gates (push) Successful in 13m1s
Upload watch: spike, design (08 section 9, proposed), live proofs on 9202; rows R-1244..R-1248 (register 189 -> 194); STATUS D41/D42 + the Monday list
Spike: per-app normal levels on three boxes (19-31 days), the off-site push as a line measure (a lower bound),
qBittorrent/Transmission/Plex explainers measured on 9202, a tc ceiling measured (not built). Live on 9202 with
0.342.0-rc.1: limit set; real seed explained; CPU burner and the hidden flood alarmed once each and cleared once;
the household's limit left alone. Teardown: apps removed, 9202 back on 0.341.0, receiver 9471 destroyed.
2026-10-23 22:52:41 +02:00

48 lines
3.1 KiB
Python

# Runs INSIDE guest 9202. Transmission's own RPC numbers vs the container's exact tx bytes. Password read at use, never printed.
import json, subprocess, sys, time, urllib.request, base64
def sh(*a): return subprocess.run(a, capture_output=True, text=True).stdout.strip()
C = 'transmission'
IP = sh('docker', 'inspect', '-f', '{{range .NetworkSettings.Networks}}{{.IPAddress}} {{end}}', C).split()[0]
PID = sh('docker', 'inspect', '-f', '{{.State.Pid}}', C)
PW = sh('docker', 'exec', C, 'printenv', 'PASS')
AUTH = 'Basic ' + base64.b64encode(('admin:' + PW).encode()).decode()
SID = ''
def rpc(method, args=None):
global SID
for _ in range(2):
r = urllib.request.Request(f'http://{IP}:9091/transmission/rpc', data=json.dumps({'method': method, 'arguments': args or {}}).encode(),
headers={'Authorization': AUTH, 'Host': 'transmission:9091', 'X-Transmission-Session-Id': SID, 'Content-Type': 'application/json'}, method='POST')
try:
with urllib.request.urlopen(r, timeout=20) as resp: return json.loads(resp.read())
except urllib.error.HTTPError as e:
if e.code == 409: SID = e.headers.get('X-Transmission-Session-Id'); continue
return {'http': e.code}
def tx():
for line in open(f'/proc/{PID}/net/dev'):
if line.strip().startswith('eth0:'): return int(line.split(':', 1)[1].split()[8])
mode = sys.argv[1]
if mode == 'info':
g = rpc('session-get')['arguments']
print('version', g.get('version'), {k: g.get(k) for k in ('speed-limit-up', 'speed-limit-up-enabled', 'alt-speed-up', 'alt-speed-enabled', 'alt-speed-time-enabled', 'units')})
print('session-stats', rpc('session-stats')['arguments'])
elif mode == 'add':
meta = base64.b64encode(open('/root/uwtest.torrent', 'rb').read()).decode()
print(rpc('torrent-add', {'metainfo': meta, 'download-dir': '/mnt/felhom-drives/scratch_hdd/userdata/media/transmission', 'paused': False}))
time.sleep(5); rpc('torrent-verify', {}); time.sleep(20)
print(rpc('torrent-get', {'fields': ['name', 'status', 'percentDone', 'error', 'errorString']}))
elif mode == 'limit':
print(rpc('session-set', {'speed-limit-up': int(sys.argv[2]), 'speed-limit-up-enabled': True}))
g = rpc('session-get')['arguments']; print({k: g.get(k) for k in ('speed-limit-up', 'speed-limit-up-enabled')})
elif mode == 'reannounce':
print(rpc('torrent-reannounce', {}))
elif mode == 'sample':
dur, step = int(sys.argv[2]), int(sys.argv[3]); t0 = time.time(); prev = None
while time.time() - t0 <= dur:
st = rpc('session-stats')['arguments']; tg = rpc('torrent-get', {'fields': ['uploadedEver', 'rateUpload']})['arguments']['torrents']
row = {'t': round(time.time() - t0, 1), 'tr_upspeed': st['uploadSpeed'], 'tr_cur_up': st['current-stats']['uploadedBytes'], 'tr_tor_up': sum(x['uploadedEver'] for x in tg), 'tx': tx()}
if prev:
dt = row['t'] - prev['t']
row['rate_tr_Mbit'] = round((row['tr_cur_up'] - prev['tr_cur_up']) * 8 / dt / 1e6, 2)
row['rate_container_Mbit'] = round((row['tx'] - prev['tx']) * 8 / dt / 1e6, 2)
print(json.dumps(row), flush=True); prev = row; time.sleep(step)