You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lokinet/contrib/py/admin/lokinetmon

338 lines
11 KiB
Python

#!/usr/bin/env python3
import requests
import json
import time
import curses
import math
import traceback
class Monitor:
_speedSamples = 8
_globalspeed = []
def __init__(self, url):
self.data = dict()
self.win = curses.initscr()
curses.start_color()
curses.init_pair(1, curses.COLOR_RED, curses.COLOR_BLACK)
self._url = url
while len(self._globalspeed) < self._speedSamples:
self._globalspeed.append((0, 0, 0, 0))
def __del__(self):
curses.endwin()
def on_timer(self, event):
"""called on timer event"""
self.update_data()
def jsonrpc(self, meth, params):
r = requests.post(
self._url,
headers={"Content-Type": "application/json", "Host": "localhost"},
json={
"jsonrpc": "2.0",
"id": "0",
"method": "{}".format(meth),
"params": params,
},
)
return r.json()
def update_data(self):
"""update data from lokinet"""
try:
j = self.jsonrpc("llarp.admin.dumpstate", {})
self.data = j["result"]
except Exception as ex:
self.data = None
def _render_path(self, y, path, name):
"""render a path at current position"""
self.win.move(y, 1)
self.win.addstr("({}) ".format(name))
y += 1
self.win.move(y, 1)
y += 1
self.win.addstr("[tx:\t{}]\t[rx:\t{}]".format(self.speedOf(path['txRateCurrent']), self.speedOf(path['rxRateCurrent'])))
self.win.move(y, 1)
y += 1
self.win.addstr("me -> ")
for hop in path["hops"]:
self.win.addstr(" {} ->".format(hop["router"][:4]))
self.win.addstr(" [{} ms latency]".format(path["intro"]["latency"]))
self.win.addstr(" [{} until expire]".format(self.timeTo(path["expiresAt"])))
if path["expiresSoon"]:
self.win.addstr("(expiring)")
elif path["expired"]:
self.win.addstr("(expired)")
return y
def timeTo(self, ts):
""" return time until timestamp in seconds formatted"""
now = time.time() * 1000
return "{} seconds".format(int((ts - now) / 1000))
def speedOf(self, rate):
"""turn int speed into string formatted"""
units = ["B", "KB", "MB", "GB"]
idx = 0
while rate > 1000 and idx < len(units):
rate /= 1000.0
idx += 1
return "{} {}ps".format("%.2f" % rate, units[idx])
def get_all_paths(self):
for k in self.data['services']:
status = self.data['services'][k]
for path in (status['paths'] or []):
yield path
for s in (status['remoteSessions'] or []):
for path in s['paths']:
yield path
for s in (status['snodeSessions'] or []):
for path in s['paths']:
yield path
def display_service(self, y, name, status):
"""display a service at current position"""
self.win.move(y, 1)
self.win.addstr("service [{}]".format(name))
build = status["buildStats"]
ratio = build["success"] / (build["attempts"] or 1)
y += 1
self.win.move(y, 1)
self.win.addstr("build success: {} %".format(int(100 * ratio)))
y += 1
self.win.move(y, 1)
paths = status["paths"]
self.win.addstr("paths: {}".format(len(paths)))
for path in paths:
y = self._render_path(y, path, "inbound")
for session in (status["remoteSessions"] or []):
for path in session["paths"]:
y = self._render_path(
y, path, "[active] {}".format(session["currentConvoTag"])
)
for session in (status["snodeSessions"] or []):
for path in session["paths"]:
y = self._render_path(y, path, "[snode]")
return y
# for k in status:
# self.win.move(y + 1, 1)
# y += 1
# self.win.addstr('{}: {}'.format(k, json.dumps(status[k])))
def display_links(self, y, data):
self.txrate = 0
self.rxrate = 0
for link in data["outbound"]:
y += 1
self.win.move(y, 1)
self.win.addstr("outbound sessions:")
y = self.display_link(y, link)
for link in data["inbound"]:
y += 1
self.win.move(y, 1)
self.win.addstr("inbound sessions:")
y = self.display_link(y, link)
y += 2
self.win.move(y, 1)
self.win.addstr(
"throughput:\t\t[{}\ttx]\t[{}\trx]".format(
self.speedOf(self.txrate), self.speedOf(self.rxrate)
)
)
bloat_tx, bloat_rx = self.calculate_bloat(self.data['links']['outbound'], self.get_all_paths())
y += 1
self.win.move(y, 1)
self.win.addstr("goodput:\t\t[{}\ttx]\t[{}\trx]".format(self.speedOf(self.txrate-bloat_tx), self.speedOf(self.rxrate-bloat_rx)))
y += 1
self.win.move(y, 1)
self.win.addstr("overhead:\t\t[{}\ttx]\t[{}\trx]".format(self.speedOf(bloat_tx), self.speedOf(bloat_rx)))
self._globalspeed.append((self.txrate, self.rxrate, bloat_tx, bloat_rx))
while len(self._globalspeed) > self._speedSamples:
self._globalspeed.pop(0)
return self.display_speedgraph(y + 2, self._globalspeed)
def display_speedgraph(self, y, samps, maxsz=40):
""" display global speed graph """
def scale(x, n):
while n > 0:
x /= 2
n -= 1
return int(x)
txmax, rxmax = 1024, 1024
for tx, rx, _tx, _rx in samps:
if tx > txmax:
txmax = tx
if rx > rxmax:
rxmax = rx
rxscale = 0
while rxmax > maxsz:
rxscale += 1
rxmax /= 2
txscale = 0
while txmax > maxsz:
txscale += 1
txmax /= 2
def makebar(samp, badsamp, max):
bar = "#" * (samp - badsamp)
pad = " " * (max - samp)
return pad, bar, '#' * badsamp
txlabelpad = int(txmax / 2)# - 1
rxlabelpad = int(rxmax / 2)# - 1
if txlabelpad <= 0:
txlabelpad = 1
if rxlabelpad <= 0:
rxlabelpad = 1
txlabelpad = " " * txlabelpad
rxlabelpad = " " * rxlabelpad
y += 1
self.win.move(y, 1)
self.win.addstr(
"{}tx{}{}rx{}".format(txlabelpad, txlabelpad, rxlabelpad, rxlabelpad)
)
for tx, rx, btx, brx in samps:
y += 1
self.win.move(y, 1)
txpad, txbar, btxbar = makebar(scale(tx,txscale),scale(btx,txscale), int(txmax))
rxpad, rxbar, brxbar = makebar(scale(rx,rxscale),scale(brx,rxscale), int(rxmax))
self.win.addstr(txpad)
self.win.addstr(btxbar, curses.color_pair(1))
self.win.addstr(txbar)
self.win.addstr('|')
self.win.addstr(rxbar)
self.win.addstr(brxbar, curses.color_pair(1))
self.win.addstr(rxpad)
return y + 2
def calculate_bloat(self, links, paths):
"""
calculate bandwith overhead
"""
lltx = 0
llrx = 0
tx = 0
rx = 0
for link in links:
sessions = link["sessions"]["established"]
for s in sessions:
lltx += s['tx']
llrx += s['rx']
for path in paths:
tx += path['txRateCurrent']
rx += path['rxRateCurrent']
if lltx > tx:
lltx -= tx
if llrx > rx:
llrx -= rx
return lltx, llrx
def display_link(self, y, link):
y += 1
self.win.move(y, 1)
sessions = link["sessions"]["established"]
for s in sessions:
y = self.display_link_session(y, s)
return y
def display_link_session(self, y, s):
y += 1
self.win.move(y, 1)
self.txrate += s["txRateCurrent"]
self.rxrate += s["rxRateCurrent"]
self.win.addstr(
"{}\t[{}\ttx]\t[{}\trx]".format(
s["remoteAddr"], self.speedOf(s["txRateCurrent"]), self.speedOf(s["rxRateCurrent"])
)
)
if (s['txMsgQueueSize'] or 0) > 1:
self.win.addstr(" [out window: {}]".format(s['txMsgQueueSize']))
if (s['rxMsgQueueSize'] or 0) > 1:
self.win.addstr(" [in window: {}]".format(s['rxMsgQueueSize']))
def display(acks, label, num='acks', dem='packets'):
if acks[dem] > 0:
self.win.addstr(" [{}: {}]".format(label, round(float(acks[num]) / float(acks[dem]), 2)))
if ('recvMACKs' in s) and ('sendMACKs' in s):
display(s['sendMACKs'], 'out MACK density')
display(s['recvMACKs'], 'in MACK density')
d = {'recvAcks': 'in acks', 'sendAcks': 'out acks', 'recvRTX': 'in RTX', 'sendRTX': 'out RTX'}
for k in d:
v = d[k]
if (k in s) and (s[k] > 0):
self.win.addstr(" [{}: {}]".format(v, s[k]))
return y
def display_dht(self, y, data):
y += 2
self.win.move(y, 1)
self.win.addstr("DHT:")
y += 1
self.win.move(y, 1)
self.win.addstr("introset lookups")
y = self.display_bucket(y, data["pendingIntrosetLookups"])
y += 1
self.win.move(y, 1)
self.win.addstr("router lookups")
return self.display_bucket(y, data["pendingRouterLookups"])
def display_bucket(self, y, data):
txs = data["tx"]
self.win.addstr(" ({} lookups)".format(len(txs)))
for tx in txs:
y += 1
self.win.move(y, 1)
self.win.addstr("search for {}".format(tx["tx"]["target"]))
return y
def display_data(self):
"""draw main window"""
if self.data is not None:
self.win.addstr(1, 1, "lokinet online")
# print(self.data)
services = self.data["services"] or {}
y = 3
try:
y = self.display_links(y, self.data["links"])
for k in services:
y = self.display_service(y, k, services[k])
y = self.display_dht(y, self.data["dht"])
except Exception as exc:
pass
else:
self.win.move(1, 1)
self.win.addstr("lokinet offline")
def run(self):
while True:
self.win.clear()
self.win.box()
self.update_data()
self.display_data()
self.win.refresh()
time.sleep(1)
if __name__ == "__main__":
import sys
mon = Monitor(
"http://{}/jsonrpc".format(
len(sys.argv) > 1 and sys.argv[1] or "127.0.0.1:1190"
)
)
mon.run()