Skip to content

Commit 37e533e

Browse files
committed
feat: new command top implmented to check RPI hardware status
1 parent 147e51d commit 37e533e

File tree

6 files changed

+167
-14
lines changed

6 files changed

+167
-14
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ For convenience, you can set up an alias named `meteorix`:
168168
# Pure bash completion - no Python involved
169169
function _meteorix_complete {
170170
local cur=${COMP_WORDS[COMP_CWORD]}
171-
local commands="upload delete check head tail info spit plot freq ifconfig monitor eda ml who -h --help"
171+
local commands="upload delete check head tail info spit plot freq ifconfig top monitor eda ml who -h --help"
172172

173173
if [ $COMP_CWORD -eq 1 ]; then
174174
COMPREPLY=($(compgen -W "$commands" -- "$cur"))
@@ -195,7 +195,7 @@ For convenience, you can set up an alias named `meteorix`:
195195

196196
# Zsh completion
197197
function _meteorix {
198-
local commands="upload delete check head tail info spit plot freq ifconfig monitor eda ml who -h --help"
198+
local commands="upload delete check head tail info spit plot freq ifconfig top monitor eda ml who -h --help"
199199
_arguments "1: :($commands)"
200200
}
201201

src/cli.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
create_weather_plot,
1515
toggle_monitor,
1616
get_pi_ip,
17+
get_system_stats,
1718
)
1819

1920
import sys
@@ -30,7 +31,7 @@
3031
def get_parser():
3132
parser = argparse.ArgumentParser(
3233
description="Weather data management CLI",
33-
usage="meteorix [-h] {upload, delete, check, head, tail, info, spit, plot, monitor, eda, ml, who, ifconfig} ...",
34+
usage="meteorix [-h] {upload, delete, check, head, tail, info, spit, plot, monitor, ifconfig, top, eda, ml, who}",
3435
)
3536

3637
subparsers = parser.add_subparsers(dest="command", required=True)
@@ -153,6 +154,16 @@ def get_parser():
153154
),
154155
],
155156
},
157+
"ifconfig": {
158+
"help": "Show Raspberry Pi network information",
159+
"description": "Display network interface information from the weather station Raspberry Pi.",
160+
"args": [],
161+
},
162+
"top": {
163+
"help": "Show Raspberry Pi system status",
164+
"description": "Display system metrics including CPU, memory, disk usage and temperature.",
165+
"args": [],
166+
},
156167
"eda": {
157168
"help": "Run exploratory data analysis",
158169
"description": "Perform exploratory data analysis including correlation analysis and PCA, then upload results to MongoDB.",
@@ -168,11 +179,6 @@ def get_parser():
168179
"description": "Display detailed information about the Meteorix bot and its creators.",
169180
"args": [("--force", {"action": "store_true", "help": argparse.SUPPRESS})],
170181
},
171-
"ifconfig": {
172-
"help": "Show Raspberry Pi IP addresses",
173-
"description": "Display network interface information from the weather station Raspberry Pi.",
174-
"args": [],
175-
},
176182
}
177183

178184
# Create subparsers from command configurations
@@ -213,6 +219,7 @@ def main():
213219
"tail": lambda: show_tail(args.date if hasattr(args, "date") else None),
214220
"monitor": lambda: toggle_monitor(args.action),
215221
"ifconfig": lambda: get_pi_ip(),
222+
"top": lambda: get_system_stats(),
216223
}
217224

218225
# Date-based command handlers

src/cli_components/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .plot import create_weather_plot
1313
from .monitor import toggle_monitor
1414
from .ifconfig import get_pi_ip
15+
from .top import get_system_stats
1516

1617
__all__ = [
1718
"check_analysis_results",
@@ -30,4 +31,5 @@
3031
"create_weather_plot",
3132
"toggle_monitor",
3233
"get_pi_ip",
34+
"get_system_stats",
3335
]

src/cli_components/freq.py

Whitespace-only changes.

src/cli_components/top.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
from rich import print as rprint
2+
from .ifconfig import SSHClient, find_pi_address
3+
4+
5+
def get_system_stats():
6+
"""Get system statistics from Raspberry Pi"""
7+
try:
8+
# Find the Pi's current address
9+
pi_address = find_pi_address()
10+
if not pi_address:
11+
raise Exception("Could not locate the Raspberry Pi on the network")
12+
13+
# Get SSH client instance
14+
ssh = SSHClient.get_instance().get_client(pi_address)
15+
16+
# Commands to get various system metrics
17+
commands = {
18+
"loadavg": "cat /proc/loadavg", # Load averages
19+
"memory": "free -h", # Memory usage
20+
"cpu_temp": "vcgencmd measure_temp", # CPU temperature
21+
"cpu_usage": "top -bn1 | grep '%Cpu'", # CPU usage
22+
"disk": "df -h /", # Disk usage for root partition
23+
"uptime": "uptime -p", # System uptime
24+
"processes": "ps aux --sort=-%cpu | grep -v 'ps aux' | head -6", # Top 5 CPU-consuming processes, excluding ps command
25+
}
26+
27+
# Collect system information
28+
system_info = {}
29+
for key, cmd in commands.items():
30+
stdin, stdout, stderr = ssh.exec_command(cmd)
31+
system_info[key] = stdout.read().decode().strip()
32+
error = stderr.read().decode()
33+
if error and "command not found" not in error:
34+
rprint(f"[yellow]Warning when running {cmd}: {error}[/yellow]")
35+
36+
# Parse and display results
37+
rprint(f"\n[bold blue]{'='*60}[/bold blue]")
38+
rprint("[bold green]Raspberry Pi System Status[/bold green]")
39+
rprint(f"[bold blue]{'='*60}[/bold blue]\n")
40+
41+
# System Uptime
42+
rprint("[yellow]System Uptime:[/yellow]")
43+
rprint(f" • {system_info['uptime']}\n")
44+
45+
# Load Average
46+
load_1, load_5, load_15, *_ = system_info["loadavg"].split()
47+
rprint("[yellow]Load Average:[/yellow]")
48+
rprint(f" • 1 min: {load_1}")
49+
rprint(f" • 5 min: {load_5}")
50+
rprint(f" • 15 min: {load_15}\n")
51+
52+
# CPU Usage and Temperature
53+
rprint("[yellow]CPU Status:[/yellow]")
54+
cpu_usage = system_info["cpu_usage"].replace(" ", " ").split(",")
55+
user_cpu = cpu_usage[0].split()[1]
56+
system_cpu = cpu_usage[2].split()[0]
57+
idle_cpu = cpu_usage[3].split()[0]
58+
rprint(f" • User: {user_cpu}%")
59+
rprint(f" • System: {system_cpu}%")
60+
rprint(f" • Idle: {idle_cpu}%")
61+
62+
# Temperature
63+
temp = system_info["cpu_temp"].replace("temp=", "").replace("'C", "°C")
64+
rprint(f" • Temperature: {temp}\n")
65+
66+
# Memory Usage
67+
rprint("[yellow]Memory Usage:[/yellow]")
68+
memory_lines = system_info["memory"].split("\n")
69+
for line in memory_lines:
70+
if line.startswith("Mem:"):
71+
total, used, free, shared, buff_cache, available = line.split()[1:]
72+
rprint(f" • Total: {total}")
73+
rprint(f" • Used: {used}")
74+
rprint(f" • Free: {free}")
75+
rprint(f" • Buff/Cache: {buff_cache}")
76+
rprint(f" • Available: {available}\n")
77+
78+
# Disk Usage
79+
rprint("[yellow]Disk Usage:[/yellow]")
80+
disk_lines = system_info["disk"].split("\n")
81+
for line in disk_lines[1:]: # Skip header
82+
fs, size, used, avail, use_percent, mount = line.split()
83+
rprint(f" • Size: {size}")
84+
rprint(f" • Used: {used} ({use_percent})")
85+
rprint(f" • Available: {avail}\n")
86+
87+
# Top Processes
88+
rprint("[yellow]Top Processes (CPU):[/yellow]")
89+
process_lines = system_info["processes"].split("\n")
90+
for line in process_lines[1:]: # Skip header
91+
parts = line.split()
92+
if len(parts) >= 11:
93+
user = parts[0]
94+
pid = parts[1]
95+
cpu = parts[2]
96+
mem = parts[3]
97+
command = " ".join(parts[10:])
98+
rprint(
99+
f" • {user:<8} PID: {pid:<6} {command[:30]:<30} CPU: {cpu:>5}% MEM: {mem:>5}%"
100+
)
101+
102+
rprint(f"\n[bold blue]{'='*60}[/bold blue]")
103+
return True
104+
105+
except Exception as e:
106+
rprint(f"[red]Error: {str(e)}[/red]")
107+
# If there's an error, try to close the connection to allow for a fresh start next time
108+
SSHClient.get_instance().close()
109+
return False

src/meteorix.py

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ async def help_command(ctx, command_name=None):
128128
• `ml` - Run machine learning analysis
129129
• `plot <start_date> [end_date]` - Generate weather plots for specified dates
130130
• `monitor` - Monitor data collection status
131+
• `ifconfig` - Show Raspberry Pi network information
132+
• `top` - Show Raspberry Pi system status
131133
• `who` - Show information about the bot
132134
• `help` - Show this help message
133135
• `help <command>` - Show detailed help for a specific command
@@ -146,7 +148,9 @@ async def help_command(ctx, command_name=None):
146148
`@meteorix who` - Show bot information
147149
`@meteorix monitor enable` - Enable data collection monitoring
148150
`@meteorix monitor disable` - Disable data collection monitoring
149-
`@meteorix monitor status` - Check current monitoring status"""
151+
`@meteorix monitor status` - Check current monitoring status
152+
`@meteorix ifconfig` - Show Pi network status
153+
`@meteorix top` - Show Pi system status"""
150154
await ctx.send(help_text)
151155
return
152156

@@ -165,6 +169,8 @@ async def help_command(ctx, command_name=None):
165169
• `ml` - Run machine learning analysis
166170
• `plot <start_date> [end_date]` - Generate weather plots for specified dates
167171
• `monitor` - Monitor data collection status
172+
• `ifconfig` - Show Raspberry Pi network information
173+
• `top` - Show Raspberry Pi system status
168174
• `who` - Show information about the bot
169175
• `help` - Show this help message
170176
• `help <command>` - Show detailed help for a specific command
@@ -203,6 +209,8 @@ async def help_command(ctx, command_name=None):
203209
• `ml` - Run machine learning analysis
204210
• `plot <start_date> [end_date]` - Generate weather plots for specified dates
205211
• `monitor` - Monitor data collection status
212+
• `ifconfig` - Show Raspberry Pi network information
213+
• `top` - Show Raspberry Pi system status
206214
• `who` - Show information about the bot
207215
• `help` - Show this help message
208216
• `help <command>` - Show detailed help for a specific command
@@ -221,7 +229,9 @@ async def help_command(ctx, command_name=None):
221229
`@meteorix who` - Show bot information
222230
`@meteorix monitor enable` - Enable data collection monitoring
223231
`@meteorix monitor disable` - Disable data collection monitoring
224-
`@meteorix monitor status` - Check current monitoring status"""
232+
`@meteorix monitor status` - Check current monitoring status
233+
`@meteorix ifconfig` - Show Pi network status
234+
`@meteorix top` - Show Pi system status"""
225235
await ctx.send(help_text)
226236

227237

@@ -359,6 +369,12 @@ async def ifconfig(ctx):
359369
await run_cli_command(ctx, ["ifconfig"])
360370

361371

372+
@bot.command(name="top")
373+
@check_channel()
374+
async def top(ctx):
375+
await run_cli_command(ctx, ["top"])
376+
377+
362378
# Slash commands
363379
@bot.tree.command(name="info", description="Show available date range")
364380
@app_commands.describe(month="Optional: Month to show statistics for (YYYY_MM)")
@@ -506,12 +522,18 @@ async def monitor_slash(interaction: discord.Interaction, action: str):
506522
await interaction.response.send_message(f"```\n{output}\n```")
507523

508524

509-
@bot.tree.command(name="ifconfig", description="Show Raspberry Pi IP addresses")
525+
@bot.tree.command(name="ifconfig", description="Show Raspberry Pi network information")
510526
@app_commands.check(check_channel_slash)
511527
async def ifconfig_slash(interaction: discord.Interaction):
512528
await run_cli_command_slash(interaction, ["ifconfig"])
513529

514530

531+
@bot.tree.command(name="top", description="Show Raspberry Pi system status")
532+
@app_commands.check(check_channel_slash)
533+
async def top_slash(interaction: discord.Interaction):
534+
await run_cli_command_slash(interaction, ["top"])
535+
536+
515537
VALID_COMMANDS = [
516538
"info",
517539
"upload",
@@ -527,6 +549,7 @@ async def ifconfig_slash(interaction: discord.Interaction):
527549
"plot",
528550
"monitor",
529551
"ifconfig",
552+
"top",
530553
]
531554

532555

@@ -545,7 +568,8 @@ def get_command_description(cmd):
545568
"spit": "Get raw CSV data for specified dates",
546569
"plot": "Create weather data plots",
547570
"monitor": "Monitor data collection status",
548-
"ifconfig": "Show Raspberry Pi IP addresses",
571+
"ifconfig": "Show Raspberry Pi network information",
572+
"top": "Show Raspberry Pi system status",
549573
}
550574
return descriptions.get(cmd, "")
551575

@@ -578,6 +602,8 @@ async def help_slash(interaction: discord.Interaction, command_name: str = None)
578602
• `ml` - Run machine learning analysis
579603
• `plot <start_date> [end_date]` - Generate weather plots for specified dates
580604
• `monitor` - Monitor data collection status
605+
• `ifconfig` - Show Raspberry Pi network information
606+
• `top` - Show Raspberry Pi system status
581607
• `who` - Show information about the bot
582608
• `help` - Show this help message
583609
• `help <command>` - Show detailed help for a specific command
@@ -605,6 +631,8 @@ async def help_slash(interaction: discord.Interaction, command_name: str = None)
605631
• `ml` - Run machine learning analysis
606632
• `plot <start_date> [end_date]` - Generate weather plots for specified dates
607633
• `monitor` - Monitor data collection status
634+
• `ifconfig` - Show Raspberry Pi network information
635+
• `top` - Show Raspberry Pi system status
608636
• `who` - Show information about the bot
609637
• `help` - Show this help message
610638
• `help <command>` - Show detailed help for a specific command
@@ -620,7 +648,9 @@ async def help_slash(interaction: discord.Interaction, command_name: str = None)
620648
`/who` - Show bot information
621649
`/monitor enable` - Enable data collection monitoring
622650
`/monitor disable` - Disable data collection monitoring
623-
`/monitor status` - Check current monitoring status"""
651+
`/monitor status` - Check current monitoring status
652+
`/ifconfig` - Show Pi network status
653+
`/top` - Show Pi system status"""
624654
await interaction.followup.send(help_text)
625655
return
626656

@@ -653,6 +683,8 @@ async def help_slash(interaction: discord.Interaction, command_name: str = None)
653683
• `ml` - Run machine learning analysis
654684
• `plot <start_date> [end_date]` - Generate weather plots for specified dates
655685
• `monitor` - Monitor data collection status
686+
• `ifconfig` - Show Raspberry Pi network information
687+
• `top` - Show Raspberry Pi system status
656688
• `who` - Show information about the bot
657689
• `help` - Show this help message
658690
• `help <command>` - Show detailed help for a specific command
@@ -669,7 +701,8 @@ async def help_slash(interaction: discord.Interaction, command_name: str = None)
669701
`/monitor enable` - Enable data collection monitoring
670702
`/monitor disable` - Disable data collection monitoring
671703
`/monitor status` - Check current monitoring status
672-
"""
704+
`/ifconfig` - Show Pi network status
705+
`/top` - Show Pi system status"""
673706
await interaction.followup.send(help_text)
674707

675708

@@ -841,6 +874,8 @@ async def on_command_error(ctx, error):
841874
• `ml` - Run machine learning analysis
842875
• `plot <start_date> [end_date]` - Generate weather plots for specified dates
843876
• `monitor` - Monitor data collection status
877+
• `ifconfig` - Show Raspberry Pi network information
878+
• `top` - Show Raspberry Pi system status
844879
• `who` - Show information about the bot
845880
• `help` - Show this help message
846881
• `help <command>` - Show detailed help for a specific command

0 commit comments

Comments
 (0)