diff --git a/contrib/py/admin/dumpstats.py b/contrib/py/admin/dumpstats.py deleted file mode 100644 index 386148ef3..000000000 --- a/contrib/py/admin/dumpstats.py +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env python3 -import requests -import json -import time -import curses - -class Canvas(): - - def __init__(self): - self.data = dict() - self.win = curses.initscr() - - def __del__(self): - self.win.clear() - curses.endwin() - - def on_timer(self, event): - """called on timer event""" - self.update_data() - - def _log(self, *args): - print("{}".format(*args)) - - def jsonrpc(self, meth, params, url="http://127.0.0.1:1190/jsonrpc"): - r = requests.post(url, headers={"Content-Type": "application/json"}, 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: - 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("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): - now = time.time() * 1000 - return '{} seconds'.format(int((ts - now) / 1000)) - - 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"]: - for path in session["paths"]: - y = self._render_path(y, path, "[active] {}".format(session["currentConvoTag"])) - for session in status["snodeSessions"]: - 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): - 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) - return y - - def display_link(self, y, link): - y += 1 - self.win.move(y, 1) - sessions = link["sessions"]["established"] - for s in sessions: - y += 1 - self.win.move(y, 1) - self.win.addstr('{}\t[{}\ttx]\t[{}\trx]'.format(s['remoteAddr'], s['tx'], s['rx'])) - if s['sendBacklog'] > 0: - self.win.addstr('[backlog {}]'.format(s['sendBacklog'])) - 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.clear() - self.win.box() - self.win.addstr(1,1, "lokinet online") - #print(self.data) - services = self.data["services"] - y = 3 - for k in services: - y = self.display_service(y, k, services[k]) - y = self.display_links(y, self.data["links"]) - y = self.display_dht(y, self.data["dht"]) - self.win.refresh() - - - - def run(self): - while True: - self.update_data() - self.display_data() - time.sleep(0.5) - - - -if __name__ == '__main__': - c = Canvas() - c.run() \ No newline at end of file diff --git a/contrib/py/admin/lokinetmon b/contrib/py/admin/lokinetmon new file mode 100755 index 000000000..1c4258101 --- /dev/null +++ b/contrib/py/admin/lokinetmon @@ -0,0 +1,266 @@ +#!/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() + self._url = url + while len(self._globalspeed) < self._speedSamples: + self._globalspeed.append((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"}, + 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("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 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"]: + for path in session["paths"]: + y = self._render_path( + y, path, "[active] {}".format(session["currentConvoTag"]) + ) + for session in status["snodeSessions"]: + 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( + "global speed:\t\t[{}\ttx]\t[{}\trx]".format( + self.speedOf(self.txrate), self.speedOf(self.rxrate) + ) + ) + + self._globalspeed.append((self.txrate, self.rxrate)) + 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=20): + """ display global speed graph """ + + def scale(x, n): + while n > 0: + x /= 2 + n -= 1 + return int(x) + + txmax, rxmax = 1000, 1000 + for 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, max): + bar = "#" * samp + pad = " " * (max - samp) + return pad, bar + + 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 in samps: + y += 1 + self.win.move(y, 1) + txpad, txbar = makebar(scale(tx, txscale), int(txmax)) + rxpad, rxbar = makebar(scale(rx, rxscale), int(rxmax)) + self.win.addstr("{}{}|{}{}".format(txpad, txbar, rxbar, rxpad)) + return y + 2 + + def display_link(self, y, link): + y += 1 + self.win.move(y, 1) + sessions = link["sessions"]["established"] + for s in sessions: + y += 1 + self.win.move(y, 1) + self.txrate += s["tx"] + self.rxrate += s["rx"] + self.win.addstr( + "{}\t[{}\ttx]\t[{}\trx]".format( + s["remoteAddr"], self.speedOf(s["tx"]), self.speedOf(s["rx"]) + ) + ) + if s["sendBacklog"] > 0: + self.win.addstr("[backlog {}]".format(s["sendBacklog"])) + 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()