import multiprocessing
import psutil
import time
import collections
import matplotlib
import matplotlib.animation as animation
import matplotlib.pyplot as plt
INTERFACE = "utun10" # Tailscale's interface name
REFRESH_INTERVAL_S = 0.5
HISTORY_SECONDS_S = 30
HISTORY_LEN = int(HISTORY_SECONDS_S / REFRESH_INTERVAL_S)
TIMES = [i * REFRESH_INTERVAL_S for i in range(-HISTORY_LEN + 1, 0 + 1)]
class Network_Manager:
def __init__(self):
self.process = None
def _update_plot(self, _):
latest_io_counters_time = time.time()
latest_io_counters = psutil.net_io_counters(pernic=True)[INTERFACE]
delta_time = latest_io_counters_time - self.previous_io_counters_time
rx_Mbps = (latest_io_counters.bytes_recv - self.previous_io_counters.bytes_recv) / delta_time / 1024 / 1024 * 8
tx_Mbps = (latest_io_counters.bytes_sent - self.previous_io_counters.bytes_sent) / delta_time / 1024 / 1024 * 8
self.rx_vals.append(rx_Mbps)
self.tx_vals.append(tx_Mbps)
self.previous_io_counters = latest_io_counters
self.previous_io_counters_time = latest_io_counters_time
self.line_tx.set_data(TIMES, self.tx_vals)
self.line_rx.set_data(TIMES, self.rx_vals)
# Remove old fills
for coll in [self.fill_rx, self.fill_tx]:
coll.remove()
self.fill_rx = self.ax.fill_between(TIMES, 0, self.rx_vals, color="#FF0000", alpha=0.4)
self.fill_tx = self.ax.fill_between(TIMES, 0, self.tx_vals, color="#0000FF", alpha=0.4)
max_value = max(max(self.rx_vals), max(self.tx_vals), 0.1)
self.ax.set_ylim(0, max_value * 1.1)
return self.line_tx, self.line_rx, self.fill_tx, self.fill_rx
def _start(self):
matplotlib.use("TkAgg")
self.rx_vals = collections.deque([0] * HISTORY_LEN, maxlen=HISTORY_LEN)
self.tx_vals = collections.deque([0] * HISTORY_LEN, maxlen=HISTORY_LEN)
plt.rc('font', family='serif')
plt.rc('font', size=10)
#plt.rcParams["figure.figsize"] = (, 6)
self.fig, self.ax = plt.subplots()
ax = self.ax
fig = self.fig
self.line_tx, = ax.plot([], [], label="TX", linewidth=1, color="#0000FF")
self.line_rx, = ax.plot([], [], label="RX", linewidth=1, color="#FF0000")
self.fill_tx = ax.fill_between(TIMES, 0, self.tx_vals, color="#0000FF", alpha=0.4)
self.fill_rx = ax.fill_between(TIMES, 0, self.rx_vals, color="#FF0000", alpha=0.4)
fig.subplots_adjust(left=0.14, right=0.96, top=0.96, bottom=0.14)
ax.margins(0)
ax.set_xlim(-HISTORY_SECONDS_S, 0)
plt.legend(loc='upper left', fancybox=False, edgecolor="black")
ax.set_xlabel("Time [s]", labelpad=0)
ax.set_ylabel("Throughput [Mbps]", labelpad=0)
ax.tick_params(direction="in", length=2.5, width=1)
ax.grid(alpha=0.5)
self.previous_io_counters = psutil.net_io_counters(pernic=True)[INTERFACE]
self.previous_io_counters_time = time.time()
# Remove matplotlib's toolbar
manager = plt.get_current_fig_manager()
if hasattr(manager, 'toolbar') and manager.toolbar is not None:
manager.toolbar.pack_forget()
manager.window.overrideredirect(True) # Borderless
#manager.window.geometry("382x300+1112+300") # Set starting position
manager.window.geometry(f"382x300+0+{900-300}") # Set starting position
manager.window.attributes('-topmost', True) # Always on top
# It's required to keep in memory the returned value of FuncAnimation before calling plt.show()
_ = animation.FuncAnimation(self.fig, self._update_plot, interval=REFRESH_INTERVAL_S*1000, blit=False, save_count=HISTORY_LEN)
plt.show()
def start_monitoring(self):
if self.process and self.process.is_alive():
print("NETWORK MANAGER process already launched")
return
self.process = multiprocessing.Process(target=self._start)
self.process.start()
print("NETWORK MANAGER process launched")
def stop_monitoring(self):
if self.process:
self.process.terminate()
self.process = None
print("NETWORK MANAGER process terminated")
else:
print("No NETWORK MANAGER process to terminate")