From 1a42b9da6b03ba989dc7cdb080607ff3cceb1cb3 Mon Sep 17 00:00:00 2001 From: Daniel Edgecumbe Date: Sun, 1 Oct 2017 02:13:01 +0100 Subject: [PATCH] [net] Add network monitor view --- main.py | 11 ++++- net.py | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 net.py diff --git a/main.py b/main.py index d240e73..ff1e0b9 100644 --- a/main.py +++ b/main.py @@ -17,6 +17,7 @@ import monitor import peers import block import transaction +import net # from macros import DEFAULT_MODE @@ -127,14 +128,21 @@ def create_tasks(client, window, nosplash): modehandler.set_mode, ) + netview = net.NetView() + modehandler.add_callback("monitor", monitorview.on_mode_change) modehandler.add_callback("peers", peerview.on_mode_change) modehandler.add_callback("block", blockview.on_mode_change) modehandler.add_callback("transaction", transactionview.on_mode_change) + modehandler.add_callback("net", netview.on_mode_change) modehandler.add_keypress_handler("block", blockview.handle_keypress) modehandler.add_keypress_handler("transaction", transactionview.handle_keypress) + async def on_nettotals(key, obj): + await headerview.on_nettotals(key, obj) + await netview.on_nettotals(key, obj) + async def on_bestblockhash(key, obj): await monitorview.on_bestblockhash(key, obj) await blockview.on_bestblockhash(key, obj) @@ -157,6 +165,7 @@ def create_tasks(client, window, nosplash): await peerview.on_window_resize(y, x) await blockview.on_window_resize(y, x) await transactionview.on_window_resize(y, x) + await netview.on_window_resize(y, x) ty, tx = window.getmaxyx() tasks = [ @@ -167,7 +176,7 @@ def create_tasks(client, window, nosplash): poll_client(client, "getnetworkinfo", headerview.on_networkinfo, 5.0), poll_client(client, "getnettotals", - headerview.on_nettotals, 5.0), + on_nettotals, 5.0), poll_client(client, "getpeerinfo", on_peerinfo, 5.0), poll_client(client, "getmempoolinfo", diff --git a/net.py b/net.py new file mode 100644 index 0000000..3e2b59a --- /dev/null +++ b/net.py @@ -0,0 +1,130 @@ +# Copyright (c) 2014-2017 esotericnonsense (Daniel Edgecumbe) +# Distributed under the MIT software license, see the accompanying +# file COPYING or https://opensource.org/licenses/mit-license.php + +import math +import curses +import asyncio + +from macros import MIN_WINDOW_SIZE + + +class NetView(object): + def __init__(self): + self._pad = None + + self._visible = False + + self._nettotals_history = [] + + self._window_size = MIN_WINDOW_SIZE + + async def _draw(self, deltas): + ph, pw = 20, 100 + + if self._pad is not None: + self._pad.clear() + else: + self._pad = curses.newpad(ph, pw) + + plot_height = (ph-3) // 2 + plot_offset = plot_height + chart_offset = 13 + chart_width = pw - chart_offset + + CGREEN = curses.color_pair(1) + CCYAN = curses.color_pair(2) + CBOLD = curses.A_BOLD + CREVERSE = curses.A_REVERSE + + if deltas: + if len(deltas) > chart_width: + deltas = deltas[-chart_width:] + + up_str = "Up: {: 9.2f}kB/s".format(deltas[-1][1]/1024).rjust(10) + down_str = "Down: {: 9.2f}kB/s".format(deltas[-1][0]/1024).rjust(10) + total_str = "Total: {: 9.2f}kB/s".format((deltas[-1][0] + deltas[-1][1])/1024).rjust(10) + self._pad.addstr(ph-2, pw-62, up_str, CBOLD + CCYAN) + self._pad.addstr(ph-2, pw-42, down_str, CBOLD + CGREEN) + self._pad.addstr(ph-2, pw-20, total_str, CBOLD) + + max_up = max(delta[0] for delta in deltas) + max_down = max(delta[1] for delta in deltas) + max_total = max(max_up, max_down) + + if max_total > 0: + if max_up > 0: + height = int(math.ceil((1.0 * plot_height * max_up) / max_total)) + self._pad.addstr(plot_offset-height, 1, "{: 5.0f}kB/s".format(max_up//1024).rjust(10), CBOLD) + if max_down > 0: + height = int(math.ceil((1.0 * plot_height * max_down) / max_total)) + self._pad.addstr(plot_offset-1+height, 1, "{: 5.0f}kB/s".format(max_down//1024).rjust(10), CBOLD) + + for i, delta in enumerate(deltas): + if i > chart_width: + break + + height = int(math.ceil((1.0 * plot_height * deltas[i][0]) / max_total)) + for y in range(0, height): + self._pad.addstr(plot_offset-1-y, i+12, " ", CCYAN + CREVERSE) + + height = int(math.ceil((1.0 * plot_height * deltas[i][1]) / max_total)) + for y in range(0, height): + self._pad.addstr(plot_offset+y, i+12, " ", CGREEN + CREVERSE) + + self._draw_pad_to_screen() + + def _draw_pad_to_screen(self): + maxy, maxx = self._window_size + if maxy < 8 or maxx < 3: + return # Can't do it + + self._pad.refresh(0, 0, 4, 0, min(maxy-3, maxy-2), min(maxx-1, 100)) + + async def draw(self): + if self._visible: + deltas = [] + if self._nettotals_history: + hist = self._nettotals_history + i = 1 + while i < len(hist): + prev = hist[i-1] + current = hist[i] + seconds = (current["timemillis"] - prev["timemillis"]) / 1000 + if seconds <= 0: + continue + up = current["totalbytessent"] - prev["totalbytessent"] + down = current["totalbytesrecv"] - prev["totalbytesrecv"] + + deltas.append( + (up/seconds, down/seconds), + ) + + i += 1 + + await self._draw(deltas) + + async def on_nettotals(self, key, obj): + try: + self._nettotals_history.append(obj["result"]) + except KeyError: + pass + + # Avoid memory leak. + if len(self._nettotals_history) > 500: + self._nettotals_history = self._nettotals_history[:300] + + await self.draw() + + async def on_mode_change(self, newmode): + if newmode != "net": + self._visible = False + return + + self._visible = True + await self.draw() + + async def on_window_resize(self, y, x): + # At the moment we ignore the x size and limit to 100. + self._window_size = (y, x) + await self.draw()