Add system resource tracking and download speed estimation to ReticulumMeshChat

- Integrated psutil for memory and network statistics.
- Enhanced app info API to include memory usage, network stats, and download statistics.
- Implemented download speed tracking for files in NomadNetworkPage.
- Added utility functions for formatting numbers and bytes per second.
- Updated frontend components to display new statistics in real-time.
This commit is contained in:
2025-11-11 08:04:07 -06:00
parent 8b044f6dab
commit 3566c6b2da
5 changed files with 313 additions and 2 deletions

View File

@@ -22,6 +22,7 @@ import webbrowser
from peewee import SqliteDatabase
from serial.tools import list_ports
import psutil
import database
from src.backend.announce_handler import AnnounceHandler
@@ -147,6 +148,12 @@ class ReticulumMeshChat:
# remember websocket clients
self.websocket_clients: List[web.WebSocketResponse] = []
# track announce timestamps for rate calculation
self.announce_timestamps = []
# track download speeds for nomadnetwork files (list of tuples: (file_size_bytes, duration_seconds))
self.download_speeds = []
# register audio call identity
self.audio_call_manager = AudioCallManager(identity=self.identity)
self.audio_call_manager.register_incoming_call_callback(self.on_incoming_audio_call)
@@ -954,6 +961,34 @@ class ReticulumMeshChat:
# get app info
@routes.get("/api/v1/app/info")
async def index(request):
# Get memory usage for current process
process = psutil.Process()
memory_info = process.memory_info()
# Get network I/O statistics
net_io = psutil.net_io_counters()
# Get total paths
path_table = self.reticulum.get_path_table()
total_paths = len(path_table)
# Calculate announce rates
current_time = time.time()
announces_per_second = len([t for t in self.announce_timestamps if current_time - t <= 1.0])
announces_per_minute = len([t for t in self.announce_timestamps if current_time - t <= 60.0])
announces_per_hour = len([t for t in self.announce_timestamps if current_time - t <= 3600.0])
# Clean up old announce timestamps (older than 1 hour)
self.announce_timestamps = [t for t in self.announce_timestamps if current_time - t <= 3600.0]
# Calculate average download speed
avg_download_speed_bps = None
if self.download_speeds:
total_bytes = sum(size for size, _ in self.download_speeds)
total_duration = sum(duration for _, duration in self.download_speeds)
if total_duration > 0:
avg_download_speed_bps = total_bytes / total_duration
return web.json_response({
"app_info": {
"version": self.get_app_version(),
@@ -966,6 +1001,25 @@ class ReticulumMeshChat:
"reticulum_config_path": self.reticulum.configpath,
"is_connected_to_shared_instance": self.reticulum.is_connected_to_shared_instance,
"is_transport_enabled": self.reticulum.transport_enabled(),
"memory_usage": {
"rss": memory_info.rss, # Resident Set Size (bytes)
"vms": memory_info.vms, # Virtual Memory Size (bytes)
},
"network_stats": {
"bytes_sent": net_io.bytes_sent,
"bytes_recv": net_io.bytes_recv,
"packets_sent": net_io.packets_sent,
"packets_recv": net_io.packets_recv,
},
"reticulum_stats": {
"total_paths": total_paths,
"announces_per_second": announces_per_second,
"announces_per_minute": announces_per_minute,
"announces_per_hour": announces_per_hour,
},
"download_stats": {
"avg_download_speed_bps": avg_download_speed_bps,
},
},
})
@@ -2245,6 +2299,16 @@ class ReticulumMeshChat:
# handle successful file download
def on_file_download_success(file_name, file_bytes):
# Track download speed
download_size = len(file_bytes)
if hasattr(downloader, 'start_time') and downloader.start_time:
download_duration = time.time() - downloader.start_time
if download_duration > 0:
self.download_speeds.append((download_size, download_duration))
# Keep only last 100 downloads for average calculation
if len(self.download_speeds) > 100:
self.download_speeds.pop(0)
AsyncUtils.run_async(client.send_str(json.dumps({
"type": "nomadnet.file.download",
"nomadnet_file_download": {
@@ -2282,6 +2346,7 @@ class ReticulumMeshChat:
# download the file
downloader = NomadnetFileDownloader(destination_hash, file_path, on_file_download_success, on_file_download_failure, on_file_download_progress)
downloader.start_time = time.time()
AsyncUtils.run_async(downloader.download())
# handle downloading a page from a nomadnet node
@@ -3054,6 +3119,9 @@ class ReticulumMeshChat:
# log received announce
print("Received an announce from " + RNS.prettyhexrep(destination_hash) + " for [call.audio]")
# track announce timestamp
self.announce_timestamps.append(time.time())
# upsert announce to database
self.db_upsert_announce(announced_identity, destination_hash, aspect, app_data, announce_packet_hash)
@@ -3075,6 +3143,9 @@ class ReticulumMeshChat:
# log received announce
print("Received an announce from " + RNS.prettyhexrep(destination_hash) + " for [lxmf.delivery]")
# track announce timestamp
self.announce_timestamps.append(time.time())
# upsert announce to database
self.db_upsert_announce(announced_identity, destination_hash, aspect, app_data, announce_packet_hash)
@@ -3100,6 +3171,9 @@ class ReticulumMeshChat:
# log received announce
print("Received an announce from " + RNS.prettyhexrep(destination_hash) + " for [lxmf.propagation]")
# track announce timestamp
self.announce_timestamps.append(time.time())
# upsert announce to database
self.db_upsert_announce(announced_identity, destination_hash, aspect, app_data, announce_packet_hash)
@@ -3184,6 +3258,9 @@ class ReticulumMeshChat:
# log received announce
print("Received an announce from " + RNS.prettyhexrep(destination_hash) + " for [nomadnetwork.node]")
# track announce timestamp
self.announce_timestamps.append(time.time())
# upsert announce to database
self.db_upsert_announce(announced_identity, destination_hash, aspect, app_data, announce_packet_hash)