Compare commits

...

7 Commits

Author SHA1 Message Date
Mark Qvist
c5248d53d6 Fixed frequency pretty print function 2023-09-30 19:22:39 +02:00
Mark Qvist
2d2f0947ac Fixed frequency pretty print function 2023-09-30 19:18:30 +02:00
Mark Qvist
4fa616a326 Added interface sorting and announce rate display to rnstatus 2023-09-30 19:14:39 +02:00
Mark Qvist
136713eec1 Added announce frequency stats 2023-09-30 19:13:58 +02:00
Mark Qvist
0fd75cb819 Added announce frequency sampling to interfaces 2023-09-30 19:11:10 +02:00
Mark Qvist
ea52153969 Added convenience function for printing frequencies 2023-09-30 19:09:26 +02:00
Mark Qvist
3854781028 Updated manual 2023-09-30 19:08:57 +02:00
18 changed files with 192 additions and 54 deletions

View File

@ -217,7 +217,7 @@ class Identity:
return Identity.truncated_hash(os.urandom(Identity.TRUNCATED_HASHLENGTH//8)) return Identity.truncated_hash(os.urandom(Identity.TRUNCATED_HASHLENGTH//8))
@staticmethod @staticmethod
def validate_announce(packet): def validate_announce(packet, only_validate_signature=False):
try: try:
if packet.packet_type == RNS.Packet.ANNOUNCE: if packet.packet_type == RNS.Packet.ANNOUNCE:
destination_hash = packet.destination_hash destination_hash = packet.destination_hash
@ -238,6 +238,10 @@ class Identity:
announced_identity.load_public_key(public_key) announced_identity.load_public_key(public_key)
if announced_identity.pub != None and announced_identity.validate(signature, signed_data): if announced_identity.pub != None and announced_identity.validate(signature, signed_data):
if only_validate_signature:
del announced_identity
return True
hash_material = name_hash+announced_identity.hash hash_material = name_hash+announced_identity.hash
expected_hash = RNS.Identity.full_hash(hash_material)[:RNS.Reticulum.TRUNCATED_HASHLENGTH//8] expected_hash = RNS.Identity.full_hash(hash_material)[:RNS.Reticulum.TRUNCATED_HASHLENGTH//8]

View File

@ -77,8 +77,7 @@ class AX25KISSInterface(Interface):
RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL) RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL)
RNS.panic() RNS.panic()
self.rxb = 0 super().__init__()
self.txb = 0
self.HW_MTU = 564 self.HW_MTU = 564

View File

@ -76,11 +76,9 @@ class AutoInterface(Interface):
def __init__(self, owner, name, group_id=None, discovery_scope=None, discovery_port=None, data_port=None, allowed_interfaces=None, ignored_interfaces=None, configured_bitrate=None): def __init__(self, owner, name, group_id=None, discovery_scope=None, discovery_port=None, data_port=None, allowed_interfaces=None, ignored_interfaces=None, configured_bitrate=None):
from RNS.vendor.ifaddr import niwrapper from RNS.vendor.ifaddr import niwrapper
super().__init__()
self.netinfo = niwrapper self.netinfo = niwrapper
self.rxb = 0
self.txb = 0
self.HW_MTU = 1064 self.HW_MTU = 1064
self.IN = True self.IN = True

View File

@ -390,8 +390,7 @@ class I2PInterfacePeer(Interface):
TUNNEL_STATE_STALE = 0x02 TUNNEL_STATE_STALE = 0x02
def __init__(self, parent_interface, owner, name, target_i2p_dest=None, connected_socket=None, max_reconnect_tries=None): def __init__(self, parent_interface, owner, name, target_i2p_dest=None, connected_socket=None, max_reconnect_tries=None):
self.rxb = 0 super().__init__()
self.txb = 0
self.HW_MTU = 1064 self.HW_MTU = 1064
@ -832,8 +831,7 @@ class I2PInterface(Interface):
BITRATE_GUESS = 256*1000 BITRATE_GUESS = 256*1000
def __init__(self, owner, name, rns_storagepath, peers, connectable = False, ifac_size = 16, ifac_netname = None, ifac_netkey = None): def __init__(self, owner, name, rns_storagepath, peers, connectable = False, ifac_size = 16, ifac_netname = None, ifac_netkey = None):
self.rxb = 0 super().__init__()
self.txb = 0
self.HW_MTU = 1064 self.HW_MTU = 1064
@ -964,6 +962,12 @@ class I2PInterface(Interface):
def processOutgoing(self, data): def processOutgoing(self, data):
pass pass
def received_announce(self, from_spawned=False):
if from_spawned: self.ia_freq_deque.append(time.time())
def sent_announce(self, from_spawned=False):
if from_spawned: self.oa_freq_deque.append(time.time())
def detach(self): def detach(self):
RNS.log("Detaching "+str(self), RNS.LOG_DEBUG) RNS.log("Detaching "+str(self), RNS.LOG_DEBUG)
self.i2p.stop() self.i2p.stop()

View File

@ -23,6 +23,7 @@
import RNS import RNS
import time import time
import threading import threading
from collections import deque
class Interface: class Interface:
IN = False IN = False
@ -43,14 +44,65 @@ class Interface:
# should actively discover paths for. # should actively discover paths for.
DISCOVER_PATHS_FOR = [MODE_ACCESS_POINT, MODE_GATEWAY] DISCOVER_PATHS_FOR = [MODE_ACCESS_POINT, MODE_GATEWAY]
# How many samples to use for incoming
# announce frequency calculation
IA_FREQ_SAMPLES = 6
OA_FREQ_SAMPLES = 6
def __init__(self): def __init__(self):
self.rxb = 0 self.rxb = 0
self.txb = 0 self.txb = 0
self.online = False self.online = False
self.ia_freq_deque = deque(maxlen=Interface.IA_FREQ_SAMPLES)
self.oa_freq_deque = deque(maxlen=Interface.OA_FREQ_SAMPLES)
def get_hash(self): def get_hash(self):
return RNS.Identity.full_hash(str(self).encode("utf-8")) return RNS.Identity.full_hash(str(self).encode("utf-8"))
def received_announce(self):
self.ia_freq_deque.append(time.time())
if hasattr(self, "parent_interface") and self.parent_interface != None:
self.parent_interface.received_announce(from_spawned=True)
def sent_announce(self):
self.oa_freq_deque.append(time.time())
if hasattr(self, "parent_interface") and self.parent_interface != None:
self.parent_interface.sent_announce(from_spawned=True)
def incoming_announce_frequency(self):
if not len(self.ia_freq_deque) > 1:
return 0
else:
dq_len = len(self.ia_freq_deque)
delta_sum = 0
for i in range(1,dq_len):
delta_sum += self.ia_freq_deque[i]-self.ia_freq_deque[i-1]
delta_sum += time.time() - self.ia_freq_deque[dq_len-1]
if delta_sum == 0:
avg = 0
else:
avg = 1/(delta_sum/(dq_len))
return avg
def outgoing_announce_frequency(self):
if not len(self.oa_freq_deque) > 1:
return 0
else:
dq_len = len(self.oa_freq_deque)
delta_sum = 0
for i in range(1,dq_len):
delta_sum += self.oa_freq_deque[i]-self.oa_freq_deque[i-1]
delta_sum += time.time() - self.oa_freq_deque[dq_len-1]
if delta_sum == 0:
avg = 0
else:
avg = 1/(delta_sum/(dq_len))
return avg
def process_announce_queue(self): def process_announce_queue(self):
if not hasattr(self, "announce_cap"): if not hasattr(self, "announce_cap"):
self.announce_cap = RNS.Reticulum.ANNOUNCE_CAP self.announce_cap = RNS.Reticulum.ANNOUNCE_CAP
@ -79,6 +131,7 @@ class Interface:
self.announce_allowed_at = now + wait_time self.announce_allowed_at = now + wait_time
self.processOutgoing(selected["raw"]) self.processOutgoing(selected["raw"])
self.sent_announce()
if selected in self.announce_queue: if selected in self.announce_queue:
self.announce_queue.remove(selected) self.announce_queue.remove(selected)

View File

@ -70,8 +70,7 @@ class KISSInterface(Interface):
RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL) RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL)
RNS.panic() RNS.panic()
self.rxb = 0 super().__init__()
self.txb = 0
self.HW_MTU = 564 self.HW_MTU = 564

View File

@ -53,8 +53,7 @@ class LocalClientInterface(Interface):
RECONNECT_WAIT = 3 RECONNECT_WAIT = 3
def __init__(self, owner, name, target_port = None, connected_socket=None): def __init__(self, owner, name, target_port = None, connected_socket=None):
self.rxb = 0 super().__init__()
self.txb = 0
# TODO: Remove at some point # TODO: Remove at some point
# self.rxptime = 0 # self.rxptime = 0
@ -280,8 +279,7 @@ class LocalClientInterface(Interface):
class LocalServerInterface(Interface): class LocalServerInterface(Interface):
def __init__(self, owner, bindport=None): def __init__(self, owner, bindport=None):
self.rxb = 0 super().__init__()
self.txb = 0
self.online = False self.online = False
self.clients = 0 self.clients = 0
@ -338,6 +336,12 @@ class LocalServerInterface(Interface):
def processOutgoing(self, data): def processOutgoing(self, data):
pass pass
def received_announce(self, from_spawned=False):
if from_spawned: self.ia_freq_deque.append(time.time())
def sent_announce(self, from_spawned=False):
if from_spawned: self.oa_freq_deque.append(time.time())
def __str__(self): def __str__(self):
return "Shared Instance["+str(self.bind_port)+"]" return "Shared Instance["+str(self.bind_port)+"]"

View File

@ -54,8 +54,7 @@ class PipeInterface(Interface):
if respawn_delay == None: if respawn_delay == None:
respawn_delay = 5 respawn_delay = 5
self.rxb = 0 super().__init__()
self.txb = 0
self.HW_MTU = 1064 self.HW_MTU = 1064

View File

@ -114,8 +114,7 @@ class RNodeInterface(Interface):
RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL) RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL)
RNS.panic() RNS.panic()
self.rxb = 0 super().__init__()
self.txb = 0
self.HW_MTU = 508 self.HW_MTU = 508

View File

@ -60,8 +60,7 @@ class SerialInterface(Interface):
RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL) RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL)
RNS.panic() RNS.panic()
self.rxb = 0 super().__init__()
self.txb = 0
self.HW_MTU = 564 self.HW_MTU = 564

View File

@ -79,8 +79,7 @@ class TCPClientInterface(Interface):
I2P_PROBES = 5 I2P_PROBES = 5
def __init__(self, owner, name, target_ip=None, target_port=None, connected_socket=None, max_reconnect_tries=None, kiss_framing=False, i2p_tunneled = False, connect_timeout = None): def __init__(self, owner, name, target_ip=None, target_port=None, connected_socket=None, max_reconnect_tries=None, kiss_framing=False, i2p_tunneled = False, connect_timeout = None):
self.rxb = 0 super().__init__()
self.txb = 0
self.HW_MTU = 1064 self.HW_MTU = 1064
@ -419,8 +418,7 @@ class TCPServerInterface(Interface):
return ifaddr[netinfo.AF_INET][0]["broadcast"] return ifaddr[netinfo.AF_INET][0]["broadcast"]
def __init__(self, owner, name, device=None, bindip=None, bindport=None, i2p_tunneled=False): def __init__(self, owner, name, device=None, bindip=None, bindport=None, i2p_tunneled=False):
self.rxb = 0 super().__init__()
self.txb = 0
self.HW_MTU = 1064 self.HW_MTU = 1064
@ -505,6 +503,12 @@ class TCPServerInterface(Interface):
self.clients += 1 self.clients += 1
spawned_interface.read_loop() spawned_interface.read_loop()
def received_announce(self, from_spawned=False):
if from_spawned: self.ia_freq_deque.append(time.time())
def sent_announce(self, from_spawned=False):
if from_spawned: self.oa_freq_deque.append(time.time())
def processOutgoing(self, data): def processOutgoing(self, data):
pass pass

View File

@ -45,8 +45,7 @@ class UDPInterface(Interface):
return ifaddr[netinfo.AF_INET][0]["broadcast"] return ifaddr[netinfo.AF_INET][0]["broadcast"]
def __init__(self, owner, name, device=None, bindip=None, bindport=None, forwardip=None, forwardport=None): def __init__(self, owner, name, device=None, bindip=None, bindport=None, forwardip=None, forwardport=None):
self.rxb = 0 super().__init__()
self.txb = 0
self.HW_MTU = 1064 self.HW_MTU = 1064

View File

@ -1143,6 +1143,8 @@ class Reticulum:
ifstats["name"] = str(interface) ifstats["name"] = str(interface)
ifstats["rxb"] = interface.rxb ifstats["rxb"] = interface.rxb
ifstats["txb"] = interface.txb ifstats["txb"] = interface.txb
ifstats["incoming_announce_frequency"] = interface.incoming_announce_frequency()
ifstats["outgoing_announce_frequency"] = interface.outgoing_announce_frequency()
ifstats["status"] = interface.online ifstats["status"] = interface.online
ifstats["mode"] = interface.mode ifstats["mode"] = interface.mode

View File

@ -887,6 +887,8 @@ class Transport:
# thread.start() # thread.start()
Transport.transmit(interface, packet.raw) Transport.transmit(interface, packet.raw)
if packet.packet_type == RNS.Packet.ANNOUNCE:
interface.sent_announce()
sent = True sent = True
if sent: if sent:
@ -1227,6 +1229,9 @@ class Transport:
# announces, queueing rebroadcasts of these, and removal # announces, queueing rebroadcasts of these, and removal
# of queued announce rebroadcasts once handed to the next node. # of queued announce rebroadcasts once handed to the next node.
if packet.packet_type == RNS.Packet.ANNOUNCE: if packet.packet_type == RNS.Packet.ANNOUNCE:
if interface != None and RNS.Identity.validate_announce(packet, only_validate_signature=True):
interface.received_announce()
local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None) local_destination = next((d for d in Transport.destinations if d.hash == packet.destination_hash), None)
if local_destination == None and RNS.Identity.validate_announce(packet): if local_destination == None and RNS.Identity.validate_announce(packet):
if packet.transport_id != None: if packet.transport_id != None:

View File

@ -46,7 +46,7 @@ def size_str(num, suffix='B'):
return "%.2f%s%s" % (num, last_unit, suffix) return "%.2f%s%s" % (num, last_unit, suffix)
def program_setup(configdir, dispall=False, verbosity=0, name_filter=None,json=False): def program_setup(configdir, dispall=False, verbosity=0, name_filter=None, json=False, astats=False, sorting=None, sort_reverse=False):
reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity) reticulum = RNS.Reticulum(configdir = configdir, loglevel = 3+verbosity)
stats = None stats = None
@ -62,16 +62,36 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None,json=F
if isinstance(stats[s], bytes): if isinstance(stats[s], bytes):
stats[s] = RNS.hexrep(stats[s], delimit=False) stats[s] = RNS.hexrep(stats[s], delimit=False)
for i in stats[s]: if isinstance(stats[s], dict):
if isinstance(i, dict): for i in stats[s]:
for k in i: if isinstance(i, dict):
if isinstance(i[k], bytes): for k in i:
i[k] = RNS.hexrep(i[k], delimit=False) if isinstance(i[k], bytes):
i[k] = RNS.hexrep(i[k], delimit=False)
print(json.dumps(stats)) print(json.dumps(stats))
exit() exit()
for ifstat in stats["interfaces"]: interfaces = stats["interfaces"]
if sorting != None and isinstance(sorting, str):
sorting = sorting.lower()
if sorting == "rate" or sorting == "bitrate":
interfaces.sort(key=lambda i: i["bitrate"], reverse=not sort_reverse)
if sorting == "rx":
interfaces.sort(key=lambda i: i["rxb"], reverse=not sort_reverse)
if sorting == "tx":
interfaces.sort(key=lambda i: i["txb"], reverse=not sort_reverse)
if sorting == "traffic":
interfaces.sort(key=lambda i: i["rxb"]+i["txb"], reverse=not sort_reverse)
if sorting == "announces" or sorting == "announce":
interfaces.sort(key=lambda i: i["incoming_announce_frequency"]+i["outgoing_announce_frequency"], reverse=not sort_reverse)
if sorting == "arx":
interfaces.sort(key=lambda i: i["incoming_announce_frequency"], reverse=not sort_reverse)
if sorting == "atx":
interfaces.sort(key=lambda i: i["outgoing_announce_frequency"], reverse=not sort_reverse)
for ifstat in interfaces:
name = ifstat["name"] name = ifstat["name"]
if dispall or not ( if dispall or not (
@ -113,7 +133,7 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None,json=F
else: else:
spec_str = " programs" spec_str = " programs"
clients_string = "Serving : "+str(cnum)+spec_str clients_string = "Serving : "+str(cnum)+spec_str
elif name.startswith("I2PInterface["): elif name.startswith("I2PInterface["):
if "i2p_connectable" in ifstat and ifstat["i2p_connectable"] == True: if "i2p_connectable" in ifstat and ifstat["i2p_connectable"] == True:
cnum = clients cnum = clients
@ -122,11 +142,11 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None,json=F
else: else:
spec_str = " connected I2P endpoints" spec_str = " connected I2P endpoints"
clients_string = "Peers : "+str(cnum)+spec_str clients_string = "Peers : "+str(cnum)+spec_str
else: else:
clients_string = "" clients_string = ""
else: else:
clients_string = "Clients : "+str(clients) clients_string = "Clients : "+str(clients)
else: else:
clients = None clients = None
@ -134,46 +154,50 @@ def program_setup(configdir, dispall=False, verbosity=0, name_filter=None,json=F
print(" {n}".format(n=ifstat["name"])) print(" {n}".format(n=ifstat["name"]))
if "ifac_netname" in ifstat and ifstat["ifac_netname"] != None: if "ifac_netname" in ifstat and ifstat["ifac_netname"] != None:
print(" Network : {nn}".format(nn=ifstat["ifac_netname"])) print(" Network : {nn}".format(nn=ifstat["ifac_netname"]))
print(" Status : {ss}".format(ss=ss)) print(" Status : {ss}".format(ss=ss))
if clients != None and clients_string != "": if clients != None and clients_string != "":
print(" "+clients_string) print(" "+clients_string)
if not (name.startswith("Shared Instance[") or name.startswith("TCPInterface[Client") or name.startswith("LocalInterface[")): if not (name.startswith("Shared Instance[") or name.startswith("TCPInterface[Client") or name.startswith("LocalInterface[")):
print(" Mode : {mode}".format(mode=modestr)) print(" Mode : {mode}".format(mode=modestr))
if "bitrate" in ifstat and ifstat["bitrate"] != None: if "bitrate" in ifstat and ifstat["bitrate"] != None:
print(" Rate : {ss}".format(ss=speed_str(ifstat["bitrate"]))) print(" Rate : {ss}".format(ss=speed_str(ifstat["bitrate"])))
if "airtime_short" in ifstat and "airtime_long" in ifstat: if "airtime_short" in ifstat and "airtime_long" in ifstat:
print(" Airtime : {atl}% (1h), {ats}% (15s)".format(ats=str(ifstat["airtime_short"]),atl=str(ifstat["airtime_long"]))) print(" Airtime : {atl}% (1h), {ats}% (15s)".format(ats=str(ifstat["airtime_short"]),atl=str(ifstat["airtime_long"])))
if "channel_load_short" in ifstat and "channel_load_long" in ifstat: if "channel_load_short" in ifstat and "channel_load_long" in ifstat:
print(" Ch.Load : {atl}% (1h), {ats}% (15s)".format(ats=str(ifstat["channel_load_short"]),atl=str(ifstat["channel_load_long"]))) print(" Ch.Load : {atl}% (1h), {ats}% (15s)".format(ats=str(ifstat["channel_load_short"]),atl=str(ifstat["channel_load_long"])))
if "peers" in ifstat and ifstat["peers"] != None: if "peers" in ifstat and ifstat["peers"] != None:
print(" Peers : {np} reachable".format(np=ifstat["peers"])) print(" Peers : {np} reachable".format(np=ifstat["peers"]))
if "tunnelstate" in ifstat and ifstat["tunnelstate"] != None: if "tunnelstate" in ifstat and ifstat["tunnelstate"] != None:
print(" I2P : {ts}".format(ts=ifstat["tunnelstate"])) print(" I2P : {ts}".format(ts=ifstat["tunnelstate"]))
if "ifac_signature" in ifstat and ifstat["ifac_signature"] != None: if "ifac_signature" in ifstat and ifstat["ifac_signature"] != None:
sigstr = "<…"+RNS.hexrep(ifstat["ifac_signature"][-5:], delimit=False)+">" sigstr = "<…"+RNS.hexrep(ifstat["ifac_signature"][-5:], delimit=False)+">"
print(" Access : {nb}-bit IFAC by {sig}".format(nb=ifstat["ifac_size"]*8, sig=sigstr)) print(" Access : {nb}-bit IFAC by {sig}".format(nb=ifstat["ifac_size"]*8, sig=sigstr))
if "i2p_b32" in ifstat and ifstat["i2p_b32"] != None: if "i2p_b32" in ifstat and ifstat["i2p_b32"] != None:
print(" I2P B32 : {ep}".format(ep=str(ifstat["i2p_b32"]))) print(" I2P B32 : {ep}".format(ep=str(ifstat["i2p_b32"])))
if "announce_queue" in ifstat and ifstat["announce_queue"] != None and ifstat["announce_queue"] > 0: if astats and "announce_queue" in ifstat and ifstat["announce_queue"] != None and ifstat["announce_queue"] > 0:
aqn = ifstat["announce_queue"] aqn = ifstat["announce_queue"]
if aqn == 1: if aqn == 1:
print(" Queued : {np} announce".format(np=aqn)) print(" Queued : {np} announce".format(np=aqn))
else: else:
print(" Queued : {np} announces".format(np=aqn)) print(" Queued : {np} announces".format(np=aqn))
print(" Traffic : {txb}\n {rxb}".format(rxb=size_str(ifstat["rxb"]), txb=size_str(ifstat["txb"]))) if astats and "incoming_announce_frequency" in ifstat and ifstat["incoming_announce_frequency"] != None:
print(" Announces : {iaf}".format(iaf=RNS.prettyfrequency(ifstat["outgoing_announce_frequency"])))
print(" {iaf}".format(iaf=RNS.prettyfrequency(ifstat["incoming_announce_frequency"])))
print(" Traffic : {txb}\n {rxb}".format(rxb=size_str(ifstat["rxb"]), txb=size_str(ifstat["txb"])))
if "transport_id" in stats and stats["transport_id"] != None: if "transport_id" in stats and stats["transport_id"] != None:
print("\n Transport Instance "+RNS.prettyhexrep(stats["transport_id"])+" running") print("\n Transport Instance "+RNS.prettyhexrep(stats["transport_id"])+" running")
@ -200,6 +224,31 @@ def main():
default=False default=False
) )
parser.add_argument(
"-A",
"--announce-stats",
action="store_true",
help="show announce stats",
default=False
)
parser.add_argument(
"-s",
"--sort",
action="store",
help="sort interfaces by [traffic, rx, tx, announces, arx, atx, rate]",
default=None,
type=str
)
parser.add_argument(
"-r",
"--reverse",
action="store_true",
help="reverse sorting",
default=False,
)
parser.add_argument( parser.add_argument(
"-j", "-j",
"--json", "--json",
@ -219,7 +268,16 @@ def main():
else: else:
configarg = None configarg = None
program_setup(configdir = configarg, dispall = args.all, verbosity=args.verbose, name_filter=args.filter, json=args.json) program_setup(
configdir = configarg,
dispall = args.all,
verbosity=args.verbose,
name_filter=args.filter,
json=args.json,
astats=args.announce_stats,
sorting=args.sort,
sort_reverse=args.reverse,
)
except KeyboardInterrupt: except KeyboardInterrupt:
print("") print("")

View File

@ -180,6 +180,18 @@ def prettysize(num, suffix='B'):
return "%.2f%s%s" % (num, last_unit, suffix) return "%.2f%s%s" % (num, last_unit, suffix)
def prettyfrequency(hz, suffix="Hz"):
num = hz*1e6
units = ["µ", "m", "", "K","M","G","T","P","E","Z"]
last_unit = "Y"
for unit in units:
if abs(num) < 1000.0:
return "%.2f %s%s" % (num, unit, suffix)
num /= 1000.0
return "%.2f%s%s" % (num, last_unit, suffix)
def prettytime(time, verbose=False): def prettytime(time, verbose=False):
days = int(time // (24 * 3600)) days = int(time // (24 * 3600))
time = time % (24 * 3600) time = time % (24 * 3600)

Binary file not shown.

Binary file not shown.