frobarng: NetworkProvider done

This commit is contained in:
Geoffrey Frogeye 2024-09-26 23:04:11 +02:00
parent 36df032ecd
commit 1ae7d6b447
Signed by: geoffrey
GPG key ID: C72403E7F82E6AD8

View file

@ -3,9 +3,11 @@
import asyncio import asyncio
import datetime import datetime
import enum import enum
import ipaddress
import logging import logging
import random import random
import signal import signal
import socket
import typing import typing
import coloredlogs import coloredlogs
@ -22,6 +24,21 @@ C = typing.TypeVar("C", bound="ComposableText")
Sortable = str | int Sortable = str | int
def humanSize(numi: int) -> str:
"""
Returns a string of width 3+3
"""
num = float(numi)
for unit in ("B ", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB"):
if abs(num) < 1000:
if num >= 10:
return "{:3d}{}".format(int(num), unit)
else:
return "{:.1f}{}".format(num, unit)
num /= 1024
return "{:d}YiB".format(numi)
class ComposableText(typing.Generic[P, C]): class ComposableText(typing.Generic[P, C]):
def __init__( def __init__(
@ -267,6 +284,7 @@ class Bar(ComposableText):
super().__init__() super().__init__()
self.parent: None self.parent: None
self.children: typing.MutableSequence[Screen] self.children: typing.MutableSequence[Screen]
self.longRunningTasks: list[asyncio.Task] = list()
self.refresh = asyncio.Event() self.refresh = asyncio.Event()
self.taskGroup = asyncio.TaskGroup() self.taskGroup = asyncio.TaskGroup()
@ -280,6 +298,10 @@ class Bar(ComposableText):
continue continue
Screen(parent=self, output=output.name) Screen(parent=self, output=output.name)
def addLongRunningTask(self, coro: typing.Coroutine) -> None:
task = self.taskGroup.create_task(coro)
self.longRunningTasks.append(task)
async def run(self) -> None: async def run(self) -> None:
cmd = [ cmd = [
"lemonbar", "lemonbar",
@ -299,7 +321,6 @@ class Bar(ComposableText):
await self.refresh.wait() await self.refresh.wait()
self.refresh.clear() self.refresh.clear()
markup = self.getMarkup() markup = self.getMarkup()
# log.debug(markup)
proc.stdin.write(markup.encode()) proc.stdin.write(markup.encode())
async def actionHandler() -> None: async def actionHandler() -> None:
@ -310,21 +331,15 @@ class Bar(ComposableText):
callback = self.actions[command] callback = self.actions[command]
callback() callback()
longRunningTasks = list()
def addLongRunningTask(coro: typing.Coroutine) -> None:
task = self.taskGroup.create_task(coro)
longRunningTasks.append(task)
async with self.taskGroup: async with self.taskGroup:
addLongRunningTask(refresher()) self.addLongRunningTask(refresher())
addLongRunningTask(actionHandler()) self.addLongRunningTask(actionHandler())
for provider in self.providers: for provider in self.providers:
addLongRunningTask(provider.run()) self.addLongRunningTask(provider.run())
def exit() -> None: def exit() -> None:
log.info("Terminating") log.info("Terminating")
for task in longRunningTasks: for task in self.longRunningTasks:
task.cancel() task.cancel()
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
@ -563,11 +578,50 @@ class NetworkProviderSection(StatefulSection):
self.numberStates = 5 if self.wifi else 4 self.numberStates = 5 if self.wifi else 4
self.state = 1 if self.wifi else 0 self.state = 1 if self.wifi else 0
async def updateOnStateChange(self) -> None:
while True:
await self.stateChanged.wait()
await self.provider.updateIface(self.iface)
async def getText(self) -> str | None: async def getText(self) -> str | None:
if self.ignore or not self.provider.if_stats[self.iface].isup: if self.ignore or not self.provider.if_stats[self.iface].isup:
return None return None
text = self.icon text = self.icon
state = self.state + (0 if self.wifi else 1) # SSID
if self.wifi and state >= 1:
cmd = ["iwgetid", self.iface, "--raw"]
proc = await asyncio.create_subprocess_exec(
*cmd, stdout=asyncio.subprocess.PIPE
)
stdout, stderr = await proc.communicate()
text += f" {stdout.decode().strip()}"
if state >= 2: # Address
for address in self.provider.if_addrs[self.iface]:
if address.family == socket.AF_INET:
net = ipaddress.IPv4Network(
(address.address, address.netmask), strict=False
)
text += f" {net.with_prefixlen}"
break
if state >= 3: # Speed
prevRecv = self.provider.prev_io_counters[self.iface].bytes_recv
recv = self.provider.io_counters[self.iface].bytes_recv
prevSent = self.provider.prev_io_counters[self.iface].bytes_sent
sent = self.provider.io_counters[self.iface].bytes_sent
recvDiff = recv - prevRecv
sentDiff = sent - prevSent
dt = self.provider.time - self.provider.prev_time
recvDiff /= dt
sentDiff /= dt
text += f"{humanSize(recvDiff)}{humanSize(sentDiff)}"
if state >= 4: # Counter
text += f"{humanSize(recv)}{humanSize(sent)}"
return text return text
@ -582,12 +636,25 @@ class NetworkProvider(MirrorProvider):
async def run(self) -> None: async def run(self) -> None:
await super().run() await super().run()
while True: loop = asyncio.get_running_loop()
# if_addrs: dict[str, list[psutil._common.snicaddr]] = psutil.net_if_addrs() self.time = loop.time()
# io_counters: dict[str, psutil._common.snetio] = psutil.net_io_counters(pernic=True) self.io_counters = psutil.net_io_counters(pernic=True)
while True:
# Separate TaskGroup in case it takes longer than one second,
# it doesn't fill the main TaskGroup
async with asyncio.TaskGroup() as tg: async with asyncio.TaskGroup() as tg:
self.prev_io_counters = self.io_counters
self.prev_time = self.time
# On-demand would only benefit if_addrs:
# stats are used to determine display,
# and we want to keep previous io_counters
# so displaying stats is ~instant.
self.time = loop.time()
self.if_stats = psutil.net_if_stats() self.if_stats = psutil.net_if_stats()
self.if_addrs = psutil.net_if_addrs()
self.io_counters = psutil.net_io_counters(pernic=True)
for iface in self.if_stats: for iface in self.if_stats:
if iface not in self.sections: if iface not in self.sections:
section = NetworkProviderSection( section = NetworkProviderSection(
@ -595,6 +662,7 @@ class NetworkProvider(MirrorProvider):
) )
self.sections[iface] = section self.sections[iface] = section
self.module.bar.addLongRunningTask(section.updateOnStateChange())
tg.create_task(self.updateIface(iface)) tg.create_task(self.updateIface(iface))
for iface, section in self.sections.items(): for iface, section in self.sections.items():
if iface not in self.if_stats: if iface not in self.if_stats: