guro 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- guro/__init__.py +2 -0
- guro/cli/__init__.py +4 -0
- guro/cli/main.py +161 -0
- guro/core/__init__.py +3 -0
- guro/core/benchmark.py +317 -0
- guro/core/heatmap.py +349 -0
- guro/core/monitor.py +303 -0
- guro-0.1.0.dist-info/LICENSE +21 -0
- guro-0.1.0.dist-info/METADATA +126 -0
- guro-0.1.0.dist-info/RECORD +13 -0
- guro-0.1.0.dist-info/WHEEL +5 -0
- guro-0.1.0.dist-info/entry_points.txt +2 -0
- guro-0.1.0.dist-info/top_level.txt +1 -0
guro/__init__.py
ADDED
guro/cli/__init__.py
ADDED
guro/cli/main.py
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import click
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
from rich.panel import Panel
|
|
4
|
+
from rich.table import Table
|
|
5
|
+
from rich.prompt import Prompt, Confirm
|
|
6
|
+
from rich import print as rprint
|
|
7
|
+
from typing import Optional
|
|
8
|
+
from ..core.monitor import SystemMonitor
|
|
9
|
+
from ..core.benchmark import SafeSystemBenchmark
|
|
10
|
+
from ..core.heatmap import SystemHeatmap
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
def print_banner():
|
|
15
|
+
"""Display the Guro banner"""
|
|
16
|
+
banner = """
|
|
17
|
+
[bold cyan]
|
|
18
|
+
██████╗ ██╗ ██╗██████╗ ██████╗
|
|
19
|
+
██╔════╝ ██║ ██║██╔══██╗██╔═══██╗
|
|
20
|
+
██║ ███╗██║ ██║██████╔╝██║ ██║
|
|
21
|
+
██║ ██║██║ ██║██╔══██╗██║ ██║
|
|
22
|
+
╚██████╔╝╚██████╔╝██║ ██║╚██████╔╝
|
|
23
|
+
╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝
|
|
24
|
+
[/bold cyan]
|
|
25
|
+
[yellow]A Simple System Monitoring & Benchmarking Toolkit[/yellow]
|
|
26
|
+
"""
|
|
27
|
+
console.print(banner)
|
|
28
|
+
|
|
29
|
+
@click.group()
|
|
30
|
+
@click.version_option(version='1.0.0')
|
|
31
|
+
def cli():
|
|
32
|
+
"""🚀 Guro - A Simple System Monitoring & Benchmarking Toolkit"""
|
|
33
|
+
print_banner()
|
|
34
|
+
|
|
35
|
+
@cli.command()
|
|
36
|
+
@click.option('--interval', '-i', default=1.0, help='Monitoring interval in seconds')
|
|
37
|
+
@click.option('--duration', '-d', default=None, type=int, help='Monitoring duration in seconds')
|
|
38
|
+
@click.option('--export', '-e', is_flag=True, help='Export monitoring data to CSV')
|
|
39
|
+
def monitor(interval: float, duration: Optional[int], export: bool):
|
|
40
|
+
"""📊 Monitor system resources and performance in real-time"""
|
|
41
|
+
try:
|
|
42
|
+
monitor = SystemMonitor()
|
|
43
|
+
if export:
|
|
44
|
+
click.echo("📝 Monitoring data will be exported to 'monitoring_data.csv'")
|
|
45
|
+
|
|
46
|
+
with console.status("[bold green]Initializing system monitor..."):
|
|
47
|
+
monitor.run_performance_test(
|
|
48
|
+
interval=interval,
|
|
49
|
+
duration=duration,
|
|
50
|
+
export_data=export
|
|
51
|
+
)
|
|
52
|
+
except KeyboardInterrupt:
|
|
53
|
+
console.print("\n[yellow]Monitoring stopped by user[/yellow]")
|
|
54
|
+
except Exception as e:
|
|
55
|
+
console.print(f"\n[red]Error during monitoring: {str(e)}[/red]")
|
|
56
|
+
|
|
57
|
+
@cli.command()
|
|
58
|
+
@click.option('--type', '-t', 'test_type',
|
|
59
|
+
type=click.Choice(['mini', 'god'], case_sensitive=False),
|
|
60
|
+
help='Type of benchmark test to run')
|
|
61
|
+
@click.option('--gpu-only', is_flag=True, help='Run only GPU benchmark')
|
|
62
|
+
@click.option('--cpu-only', is_flag=True, help='Run only CPU benchmark')
|
|
63
|
+
def benchmark(test_type: str, gpu_only: bool, cpu_only: bool):
|
|
64
|
+
"""🔥 Run system benchmarks"""
|
|
65
|
+
try:
|
|
66
|
+
benchmark = SafeSystemBenchmark()
|
|
67
|
+
|
|
68
|
+
if not test_type:
|
|
69
|
+
test_type = Prompt.ask(
|
|
70
|
+
"Select benchmark type",
|
|
71
|
+
choices=["mini", "god"],
|
|
72
|
+
default="mini"
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
with console.status("[bold green]Preparing benchmark..."):
|
|
76
|
+
if test_type == "mini":
|
|
77
|
+
benchmark.mini_test(gpu_only=gpu_only, cpu_only=cpu_only)
|
|
78
|
+
else:
|
|
79
|
+
benchmark.god_test(gpu_only=gpu_only, cpu_only=cpu_only)
|
|
80
|
+
|
|
81
|
+
except KeyboardInterrupt:
|
|
82
|
+
console.print("\n[yellow]Benchmark stopped by user[/yellow]")
|
|
83
|
+
except Exception as e:
|
|
84
|
+
console.print(Panel(
|
|
85
|
+
f"[red]Error during benchmark: {str(e)}[/red]",
|
|
86
|
+
title="⚠️ Benchmark Error",
|
|
87
|
+
border_style="red"
|
|
88
|
+
))
|
|
89
|
+
|
|
90
|
+
@cli.command() # Added the cli decorator here
|
|
91
|
+
@click.option('--interval', '-i',
|
|
92
|
+
type=click.FloatRange(min=0.1, min_open=False),
|
|
93
|
+
default=1.0,
|
|
94
|
+
help='Update interval in seconds (must be greater than 0.1)')
|
|
95
|
+
@click.option('--duration', '-d',
|
|
96
|
+
type=click.IntRange(min=1, min_open=False),
|
|
97
|
+
default=10,
|
|
98
|
+
help='Duration to run in seconds (default: 10)')
|
|
99
|
+
def heatmap(interval: float, duration: int):
|
|
100
|
+
"""🌡️ Display unified system temperature heatmap"""
|
|
101
|
+
try:
|
|
102
|
+
heatmap = SystemHeatmap()
|
|
103
|
+
|
|
104
|
+
with console.status("[bold green]Initializing system heatmap..."):
|
|
105
|
+
updates = heatmap.run(
|
|
106
|
+
interval=interval,
|
|
107
|
+
duration=duration
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
console.print(f"\n[green]Heatmap completed after {updates} updates[/green]")
|
|
111
|
+
|
|
112
|
+
except KeyboardInterrupt:
|
|
113
|
+
console.print("\n[yellow]Heatmap visualization stopped by user[/yellow]")
|
|
114
|
+
except Exception as e:
|
|
115
|
+
console.print(f"[red]Error during heatmap visualization: {str(e)}[/red]")
|
|
116
|
+
|
|
117
|
+
@cli.command(name='list')
|
|
118
|
+
def list_features():
|
|
119
|
+
"""📋 List all available features and commands"""
|
|
120
|
+
table = Table(title="Guro Commands and Features")
|
|
121
|
+
table.add_column("Command", style="cyan")
|
|
122
|
+
table.add_column("Description", style="green")
|
|
123
|
+
table.add_column("Options", style="yellow")
|
|
124
|
+
|
|
125
|
+
commands = {
|
|
126
|
+
"monitor": ("📊 Real-time system monitoring", "-i/--interval, -d/--duration, -e/--export"),
|
|
127
|
+
"benchmark": ("🔥 System benchmarking", "-t/--type [mini/god], --gpu-only, --cpu-only"),
|
|
128
|
+
"heatmap": ("🌡️ Hardware Heatmap Analysis", "-i/--interval, -d/--duration"),
|
|
129
|
+
"about": ("ℹ️ About Guro", "None"),
|
|
130
|
+
"list": ("📋 List all commands", "None")
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for cmd, (desc, opts) in commands.items():
|
|
134
|
+
table.add_row(cmd, desc, opts)
|
|
135
|
+
|
|
136
|
+
console.print(table)
|
|
137
|
+
|
|
138
|
+
@cli.command(name='about')
|
|
139
|
+
def about():
|
|
140
|
+
"""ℹ️ Display information about Guro"""
|
|
141
|
+
about_text = """[bold cyan]Guro - A Simple System Monitoring & Benchmarking Toolkit[/bold cyan]
|
|
142
|
+
|
|
143
|
+
[green]Version:[/green] 1.0.0
|
|
144
|
+
[green]Author:[/green] Dhanush Kandhan
|
|
145
|
+
[green]License:[/green] MIT
|
|
146
|
+
|
|
147
|
+
🛠️ A Simple powerful toolkit for system monitoring and benchmarking.
|
|
148
|
+
|
|
149
|
+
[yellow]Key Features:[/yellow]
|
|
150
|
+
• 📊 Real-time system monitoring
|
|
151
|
+
• 💾 Memory management
|
|
152
|
+
• 🔥 Performance benchmarking
|
|
153
|
+
• 🌡️ Hardware Heatmap Analysis
|
|
154
|
+
|
|
155
|
+
[blue]GitHub:[/blue] https://github.com/dhanushk-offl/guro
|
|
156
|
+
[blue]Documentation:[/blue] https://github.com/dhanushk-offl/guro/wiki"""
|
|
157
|
+
|
|
158
|
+
console.print(Panel(about_text, title="About Guro", border_style="blue"))
|
|
159
|
+
|
|
160
|
+
if __name__ == '__main__':
|
|
161
|
+
cli()
|
guro/core/__init__.py
ADDED
guro/core/benchmark.py
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import psutil
|
|
2
|
+
import time
|
|
3
|
+
import numpy as np
|
|
4
|
+
import multiprocessing
|
|
5
|
+
from rich.progress import Progress, SpinnerColumn, TimeElapsedColumn, BarColumn
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
from rich.live import Live
|
|
9
|
+
from rich.table import Table
|
|
10
|
+
import subprocess
|
|
11
|
+
import platform
|
|
12
|
+
from threading import Thread
|
|
13
|
+
import signal
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
import GPUtil
|
|
18
|
+
HAS_GPU_STATS = True
|
|
19
|
+
except ImportError:
|
|
20
|
+
HAS_GPU_STATS = False
|
|
21
|
+
|
|
22
|
+
class SafeSystemBenchmark:
|
|
23
|
+
def __init__(self):
|
|
24
|
+
self.console = Console()
|
|
25
|
+
self.results = {}
|
|
26
|
+
self.running = False
|
|
27
|
+
self.baseline_scores = {
|
|
28
|
+
'cpu_single': 2.5,
|
|
29
|
+
'cpu_multi': 1.0,
|
|
30
|
+
'gpu_compute': 1.5
|
|
31
|
+
}
|
|
32
|
+
# Safety thresholds
|
|
33
|
+
self.MAX_CPU_USAGE = 80 # Maximum CPU usage percentage
|
|
34
|
+
self.MAX_MEMORY_USAGE = 80 # Maximum memory usage percentage
|
|
35
|
+
self.has_gpu = self._check_gpu()
|
|
36
|
+
|
|
37
|
+
def _check_gpu(self):
|
|
38
|
+
"""Check if GPU is available and get GPU information"""
|
|
39
|
+
gpu_info = {'available': False, 'info': None}
|
|
40
|
+
|
|
41
|
+
if HAS_GPU_STATS:
|
|
42
|
+
try:
|
|
43
|
+
gpus = GPUtil.getGPUs()
|
|
44
|
+
if gpus and len(gpus) > 0:
|
|
45
|
+
gpu = gpus[0] # Get first GPU info
|
|
46
|
+
gpu_info['available'] = True
|
|
47
|
+
gpu_info['info'] = {
|
|
48
|
+
'name': gpu.name,
|
|
49
|
+
'count': len(gpus),
|
|
50
|
+
'memory_total': gpu.memoryTotal,
|
|
51
|
+
'driver_version': gpu.driver
|
|
52
|
+
}
|
|
53
|
+
except Exception:
|
|
54
|
+
pass
|
|
55
|
+
return gpu_info
|
|
56
|
+
|
|
57
|
+
def get_system_info(self):
|
|
58
|
+
"""Get basic system information safely"""
|
|
59
|
+
info = {
|
|
60
|
+
'system': platform.system(),
|
|
61
|
+
'processor': platform.processor(),
|
|
62
|
+
'memory_total': psutil.virtual_memory().total,
|
|
63
|
+
'cpu_cores': psutil.cpu_count(logical=False),
|
|
64
|
+
'cpu_threads': psutil.cpu_count(logical=True),
|
|
65
|
+
'gpu': self.has_gpu
|
|
66
|
+
}
|
|
67
|
+
return info
|
|
68
|
+
|
|
69
|
+
def safe_gpu_test(self, duration):
|
|
70
|
+
"""Safe GPU benchmark with controlled load"""
|
|
71
|
+
if not self.has_gpu['available']:
|
|
72
|
+
return {'times': [], 'loads': [], 'error': 'No GPU available'}
|
|
73
|
+
|
|
74
|
+
result = {'times': [], 'loads': [], 'memory_usage': []}
|
|
75
|
+
start_time = time.time()
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
while time.time() - start_time < duration and self.running:
|
|
79
|
+
if HAS_GPU_STATS:
|
|
80
|
+
gpus = GPUtil.getGPUs()
|
|
81
|
+
if gpus and len(gpus) > 0:
|
|
82
|
+
gpu = gpus[0]
|
|
83
|
+
gpu_load = gpu.load * 100
|
|
84
|
+
gpu_memory = gpu.memoryUsed
|
|
85
|
+
|
|
86
|
+
result['times'].append(time.time() - start_time)
|
|
87
|
+
result['loads'].append(gpu_load)
|
|
88
|
+
result['memory_usage'].append(gpu_memory)
|
|
89
|
+
|
|
90
|
+
time.sleep(0.1)
|
|
91
|
+
|
|
92
|
+
except Exception as e:
|
|
93
|
+
result['error'] = str(e)
|
|
94
|
+
|
|
95
|
+
return result
|
|
96
|
+
|
|
97
|
+
def monitor_resources(self):
|
|
98
|
+
"""Monitor system resources in real-time"""
|
|
99
|
+
while self.running:
|
|
100
|
+
cpu_percent = psutil.cpu_percent()
|
|
101
|
+
memory_percent = psutil.virtual_memory().percent
|
|
102
|
+
|
|
103
|
+
# Safety check
|
|
104
|
+
if cpu_percent > self.MAX_CPU_USAGE or memory_percent > self.MAX_MEMORY_USAGE:
|
|
105
|
+
self.running = False
|
|
106
|
+
self.console.print("[red]Warning: System resource usage too high. Stopping benchmark.[/red]")
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
time.sleep(0.5)
|
|
110
|
+
|
|
111
|
+
def safe_cpu_test(self, duration):
|
|
112
|
+
"""Safe CPU benchmark with controlled load"""
|
|
113
|
+
start_time = time.time()
|
|
114
|
+
result = {'times': [], 'loads': []}
|
|
115
|
+
|
|
116
|
+
while time.time() - start_time < duration and self.running:
|
|
117
|
+
# Matrix operations with controlled size
|
|
118
|
+
size = 100
|
|
119
|
+
matrix = np.random.rand(size, size)
|
|
120
|
+
np.dot(matrix, matrix.T)
|
|
121
|
+
|
|
122
|
+
result['times'].append(time.time() - start_time)
|
|
123
|
+
result['loads'].append(psutil.cpu_percent())
|
|
124
|
+
time.sleep(0.1)
|
|
125
|
+
|
|
126
|
+
return result
|
|
127
|
+
|
|
128
|
+
def safe_memory_test(self, duration):
|
|
129
|
+
"""Safe memory benchmark"""
|
|
130
|
+
start_time = time.time()
|
|
131
|
+
result = {'times': [], 'usage': []}
|
|
132
|
+
|
|
133
|
+
while time.time() - start_time < duration and self.running:
|
|
134
|
+
memory = psutil.virtual_memory()
|
|
135
|
+
result['times'].append(time.time() - start_time)
|
|
136
|
+
result['usage'].append(memory.percent)
|
|
137
|
+
time.sleep(0.1)
|
|
138
|
+
|
|
139
|
+
return result
|
|
140
|
+
|
|
141
|
+
def mini_test(self, gpu_only=False, cpu_only=False):
|
|
142
|
+
"""Run 30-second mini benchmark"""
|
|
143
|
+
self.running = True
|
|
144
|
+
duration = 30
|
|
145
|
+
|
|
146
|
+
# Start resource monitoring
|
|
147
|
+
monitor_thread = Thread(target=self.monitor_resources)
|
|
148
|
+
monitor_thread.start()
|
|
149
|
+
|
|
150
|
+
with Live(self.generate_status_table(), refresh_per_second=4) as live:
|
|
151
|
+
self.results = {
|
|
152
|
+
'system_info': self.get_system_info(),
|
|
153
|
+
'duration': duration
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if not gpu_only:
|
|
157
|
+
# CPU Test
|
|
158
|
+
self.results['cpu'] = self.safe_cpu_test(duration/2)
|
|
159
|
+
# Memory Test
|
|
160
|
+
self.results['memory'] = self.safe_memory_test(duration/2)
|
|
161
|
+
|
|
162
|
+
if not cpu_only and self.has_gpu['available']:
|
|
163
|
+
# GPU Test
|
|
164
|
+
self.results['gpu'] = self.safe_gpu_test(duration/2)
|
|
165
|
+
|
|
166
|
+
live.update(self.generate_status_table())
|
|
167
|
+
|
|
168
|
+
self.running = False
|
|
169
|
+
monitor_thread.join()
|
|
170
|
+
self.display_results("Mini-Test")
|
|
171
|
+
|
|
172
|
+
def god_test(self, gpu_only=False, cpu_only=False):
|
|
173
|
+
"""Running GOD - LEVEL comprehensive benchmark"""
|
|
174
|
+
self.running = True
|
|
175
|
+
duration = 60
|
|
176
|
+
|
|
177
|
+
# Start resource monitoring
|
|
178
|
+
monitor_thread = Thread(target=self.monitor_resources)
|
|
179
|
+
monitor_thread.start()
|
|
180
|
+
|
|
181
|
+
with Live(self.generate_status_table(), refresh_per_second=4) as live:
|
|
182
|
+
self.results = {
|
|
183
|
+
'system_info': self.get_system_info(),
|
|
184
|
+
'duration': duration
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if not gpu_only:
|
|
188
|
+
# Extended CPU Test
|
|
189
|
+
self.results['cpu'] = self.safe_cpu_test(duration/2)
|
|
190
|
+
# Extended Memory Test
|
|
191
|
+
self.results['memory'] = self.safe_memory_test(duration/2)
|
|
192
|
+
|
|
193
|
+
if not cpu_only and self.has_gpu['available']:
|
|
194
|
+
# Extended GPU Test
|
|
195
|
+
self.results['gpu'] = self.safe_gpu_test(duration/2)
|
|
196
|
+
|
|
197
|
+
live.update(self.generate_status_table())
|
|
198
|
+
|
|
199
|
+
self.running = False
|
|
200
|
+
monitor_thread.join()
|
|
201
|
+
self.display_results("God-Test")
|
|
202
|
+
|
|
203
|
+
def generate_status_table(self):
|
|
204
|
+
"""Generate real-time status table"""
|
|
205
|
+
table = Table(title="Benchmark Status")
|
|
206
|
+
table.add_column("Metric")
|
|
207
|
+
table.add_column("Value")
|
|
208
|
+
|
|
209
|
+
cpu_percent = psutil.cpu_percent()
|
|
210
|
+
memory_percent = psutil.virtual_memory().percent
|
|
211
|
+
|
|
212
|
+
table.add_row("CPU Usage", f"{cpu_percent}%")
|
|
213
|
+
table.add_row("Memory Usage", f"{memory_percent}%")
|
|
214
|
+
|
|
215
|
+
if self.has_gpu['available'] and HAS_GPU_STATS:
|
|
216
|
+
gpus = GPUtil.getGPUs()
|
|
217
|
+
if gpus and len(gpus) > 0:
|
|
218
|
+
gpu = gpus[0]
|
|
219
|
+
table.add_row("GPU", f"[green]{gpu.name}[/green]")
|
|
220
|
+
table.add_row("GPU Usage", f"{gpu.load * 100}%")
|
|
221
|
+
table.add_row("GPU Memory", f"{gpu.memoryUsed} MB / {gpu.memoryTotal} MB")
|
|
222
|
+
|
|
223
|
+
table.add_row("Status", "[green]Running[/green]" if self.running else "[red]Stopped[/red]")
|
|
224
|
+
|
|
225
|
+
return table
|
|
226
|
+
|
|
227
|
+
def display_results(self, test_type):
|
|
228
|
+
"""Display benchmark results"""
|
|
229
|
+
if not self.results:
|
|
230
|
+
return
|
|
231
|
+
|
|
232
|
+
result_text = f"[bold cyan]{test_type} Results[/bold cyan]\n\n"
|
|
233
|
+
|
|
234
|
+
# System Information
|
|
235
|
+
result_text += "[green]System Information:[/green]\n"
|
|
236
|
+
sys_info = self.results.get('system_info', {})
|
|
237
|
+
result_text += f"• System: {sys_info.get('system', 'N/A')}\n"
|
|
238
|
+
result_text += f"• Processor: {sys_info.get('processor', 'N/A')}\n"
|
|
239
|
+
result_text += f"• CPU Cores: {sys_info.get('cpu_cores', 'N/A')}\n"
|
|
240
|
+
result_text += f"• CPU Threads: {sys_info.get('cpu_threads', 'N/A')}\n"
|
|
241
|
+
|
|
242
|
+
# GPU Information
|
|
243
|
+
gpu_info = sys_info.get('gpu', {})
|
|
244
|
+
if gpu_info.get('available', False):
|
|
245
|
+
gpu_details = gpu_info.get('info', {})
|
|
246
|
+
result_text += f"• GPU: {gpu_details.get('name', 'N/A')}\n"
|
|
247
|
+
result_text += f"• GPU Count: {gpu_details.get('count', 'N/A')}\n"
|
|
248
|
+
result_text += f"• GPU Memory: {gpu_details.get('memory_total', 'N/A')} MB\n"
|
|
249
|
+
result_text += f"• Driver Version: {gpu_details.get('driver_version', 'N/A')}\n"
|
|
250
|
+
else:
|
|
251
|
+
result_text += "• GPU: Not Available\n"
|
|
252
|
+
|
|
253
|
+
result_text += "\n[green]Performance Results:[/green]\n"
|
|
254
|
+
|
|
255
|
+
# CPU Results
|
|
256
|
+
if 'cpu' in self.results:
|
|
257
|
+
cpu_loads = self.results['cpu'].get('loads', [])
|
|
258
|
+
if cpu_loads:
|
|
259
|
+
result_text += f"• Average CPU Load: {np.mean(cpu_loads):.2f}%\n"
|
|
260
|
+
result_text += f"• Peak CPU Load: {max(cpu_loads):.2f}%\n"
|
|
261
|
+
|
|
262
|
+
# Memory Results
|
|
263
|
+
if 'memory' in self.results:
|
|
264
|
+
memory_usage = self.results['memory'].get('usage', [])
|
|
265
|
+
if memory_usage:
|
|
266
|
+
result_text += f"• Average Memory Usage: {np.mean(memory_usage):.2f}%\n"
|
|
267
|
+
result_text += f"• Peak Memory Usage: {max(memory_usage):.2f}%\n"
|
|
268
|
+
|
|
269
|
+
# GPU Results
|
|
270
|
+
if 'gpu' in self.results and 'error' not in self.results['gpu']:
|
|
271
|
+
gpu_loads = self.results['gpu'].get('loads', [])
|
|
272
|
+
if gpu_loads:
|
|
273
|
+
result_text += f"• Average GPU Load: {np.mean(gpu_loads):.2f}%\n"
|
|
274
|
+
result_text += f"• Peak GPU Load: {max(gpu_loads):.2f}%\n"
|
|
275
|
+
if 'memory_usage' in self.results['gpu']:
|
|
276
|
+
gpu_mem_usage = self.results['gpu'].get('memory_usage', [])
|
|
277
|
+
if gpu_mem_usage:
|
|
278
|
+
result_text += f"• Average GPU Memory Usage: {np.mean(gpu_mem_usage):.2f} MB\n"
|
|
279
|
+
result_text += f"• Peak GPU Memory Usage: {max(gpu_mem_usage):.2f} MB\n"
|
|
280
|
+
|
|
281
|
+
result_text += f"• Test Duration: {self.results.get('duration', 'N/A')} seconds\n"
|
|
282
|
+
|
|
283
|
+
self.console.print(Panel(
|
|
284
|
+
result_text,
|
|
285
|
+
title=f"System Benchmark Report - {test_type}",
|
|
286
|
+
border_style="blue"
|
|
287
|
+
))
|
|
288
|
+
|
|
289
|
+
def main():
|
|
290
|
+
benchmark = SafeSystemBenchmark()
|
|
291
|
+
|
|
292
|
+
# Handle Ctrl+C gracefully
|
|
293
|
+
def signal_handler(sig, frame):
|
|
294
|
+
benchmark.running = False
|
|
295
|
+
print("\nBenchmark stopped by user.")
|
|
296
|
+
sys.exit(0)
|
|
297
|
+
|
|
298
|
+
signal.signal(signal.SIGINT, signal_handler)
|
|
299
|
+
|
|
300
|
+
# Test selection
|
|
301
|
+
console = Console()
|
|
302
|
+
console.print("\n[cyan]Select Benchmark Type:[/cyan]")
|
|
303
|
+
console.print("1. Mini-Test (30 seconds)")
|
|
304
|
+
console.print("2. God-Test (120 seconds)")
|
|
305
|
+
|
|
306
|
+
choice = input("\nEnter your choice (1 or 2): ")
|
|
307
|
+
|
|
308
|
+
if choice == "1":
|
|
309
|
+
benchmark.mini_test()
|
|
310
|
+
elif choice == "2":
|
|
311
|
+
benchmark.god_test()
|
|
312
|
+
else:
|
|
313
|
+
console.print("[red]Invalid choice. Exiting.[/red]")
|
|
314
|
+
|
|
315
|
+
if __name__ == "__main__":
|
|
316
|
+
main()
|
|
317
|
+
|
guro/core/heatmap.py
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import psutil
|
|
3
|
+
import platform
|
|
4
|
+
import numpy as np
|
|
5
|
+
from typing import List, Optional, Dict
|
|
6
|
+
from rich.live import Live
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.text import Text
|
|
10
|
+
import subprocess
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
import ctypes
|
|
13
|
+
from ctypes import Structure
|
|
14
|
+
import os
|
|
15
|
+
|
|
16
|
+
# Import platform-specific modules
|
|
17
|
+
if platform.system() == "Windows":
|
|
18
|
+
from ctypes import windll, wintypes, byref, POINTER
|
|
19
|
+
|
|
20
|
+
class SYSTEM_POWER_STATUS(Structure):
|
|
21
|
+
_fields_ = [
|
|
22
|
+
('ACLineStatus', wintypes.BYTE),
|
|
23
|
+
('BatteryFlag', wintypes.BYTE),
|
|
24
|
+
('BatteryLifePercent', wintypes.BYTE),
|
|
25
|
+
('SystemStatusFlag', wintypes.BYTE),
|
|
26
|
+
('BatteryLifeTime', wintypes.DWORD),
|
|
27
|
+
('BatteryFullLifeTime', wintypes.DWORD),
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
class PROCESSOR_POWER_INFORMATION(Structure):
|
|
31
|
+
_fields_ = [
|
|
32
|
+
("CurrentFrequency", wintypes.ULONG),
|
|
33
|
+
("MaxMhz", wintypes.ULONG),
|
|
34
|
+
("CurrentMhz", wintypes.ULONG),
|
|
35
|
+
("MhzLimit", wintypes.ULONG),
|
|
36
|
+
("MaxIdleState", wintypes.ULONG),
|
|
37
|
+
("CurrentIdleState", wintypes.ULONG),
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
class SystemHeatmap:
|
|
41
|
+
def __init__(self):
|
|
42
|
+
self.console = Console()
|
|
43
|
+
self.history_size = 60
|
|
44
|
+
self.system = platform.system()
|
|
45
|
+
self.components = {
|
|
46
|
+
'CPU': {'position': (2, 5), 'size': (8, 15)},
|
|
47
|
+
'GPU': {'position': (12, 5), 'size': (8, 15)},
|
|
48
|
+
'Motherboard': {'position': (0, 0), 'size': (25, 40)},
|
|
49
|
+
'RAM': {'position': (2, 25), 'size': (4, 10)},
|
|
50
|
+
'Storage': {'position': (18, 25), 'size': (4, 10)}
|
|
51
|
+
}
|
|
52
|
+
self.initialize_temp_maps()
|
|
53
|
+
if self.system == "Windows":
|
|
54
|
+
self.setup_windows_api()
|
|
55
|
+
|
|
56
|
+
def setup_windows_api(self):
|
|
57
|
+
if platform.system() == "Windows":
|
|
58
|
+
self.GetSystemPowerStatus = windll.kernel32.GetSystemPowerStatus
|
|
59
|
+
self.GetSystemPowerStatus.argtypes = [POINTER(SYSTEM_POWER_STATUS)]
|
|
60
|
+
self.GetSystemPowerStatus.restype = wintypes.BOOL
|
|
61
|
+
self.NtQuerySystemInformation = windll.ntdll.NtQuerySystemInformation
|
|
62
|
+
self.CallNtPowerInformation = windll.powrprof.CallNtPowerInformation
|
|
63
|
+
|
|
64
|
+
def initialize_temp_maps(self):
|
|
65
|
+
self.temp_maps = {
|
|
66
|
+
component: np.zeros(dims['size'])
|
|
67
|
+
for component, dims in self.components.items()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
def get_windows_temps(self) -> Dict[str, float]:
|
|
71
|
+
if platform.system() != "Windows":
|
|
72
|
+
return self.get_fallback_temps()
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
'CPU': self.get_windows_cpu_temp(),
|
|
76
|
+
'GPU': self.get_windows_gpu_temp(),
|
|
77
|
+
'Motherboard': self.get_windows_motherboard_temp(),
|
|
78
|
+
'Storage': self.get_windows_storage_temp(),
|
|
79
|
+
'RAM': self.get_windows_ram_temp()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
def get_windows_cpu_temp(self) -> float:
|
|
83
|
+
if platform.system() != "Windows":
|
|
84
|
+
return self.get_cpu_load_temp()
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
buffer_size = ctypes.sizeof(PROCESSOR_POWER_INFORMATION)
|
|
88
|
+
buffer = ctypes.create_string_buffer(buffer_size)
|
|
89
|
+
if self.CallNtPowerInformation(11, None, 0, buffer, buffer_size) == 0:
|
|
90
|
+
info = PROCESSOR_POWER_INFORMATION.from_buffer_copy(buffer)
|
|
91
|
+
return float(40 + (info.CurrentMhz / max(info.MaxMhz, 1)) * 40) # Fixed division by zero
|
|
92
|
+
return self.get_cpu_load_temp()
|
|
93
|
+
except:
|
|
94
|
+
return self.get_cpu_load_temp()
|
|
95
|
+
|
|
96
|
+
def get_windows_gpu_temp(self) -> float:
|
|
97
|
+
if platform.system() != "Windows":
|
|
98
|
+
return self.get_gpu_load_temp()
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
if hasattr(windll, 'd3d11'):
|
|
102
|
+
device = ctypes.c_void_p()
|
|
103
|
+
if windll.d3d11.D3D11CreateDevice(None, 0, None, 0, None, 0, 0,
|
|
104
|
+
ctypes.byref(device), None, None) == 0:
|
|
105
|
+
return self.get_gpu_load_temp()
|
|
106
|
+
return self.get_gpu_load_temp()
|
|
107
|
+
except:
|
|
108
|
+
return self.get_gpu_load_temp()
|
|
109
|
+
|
|
110
|
+
def get_windows_motherboard_temp(self) -> float:
|
|
111
|
+
if platform.system() != "Windows":
|
|
112
|
+
return 45.0
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
status = SYSTEM_POWER_STATUS()
|
|
116
|
+
if self.GetSystemPowerStatus(byref(status)):
|
|
117
|
+
return float(35 + (status.BatteryLifePercent / 100) * 15) # Fixed type conversion
|
|
118
|
+
return 45.0
|
|
119
|
+
except:
|
|
120
|
+
return 45.0
|
|
121
|
+
|
|
122
|
+
def get_windows_storage_temp(self) -> float:
|
|
123
|
+
if platform.system() != "Windows":
|
|
124
|
+
return 35.0
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
if hasattr(windll, 'kernel32'):
|
|
128
|
+
hDevice = windll.kernel32.CreateFileW(
|
|
129
|
+
"\\\\.\\PhysicalDrive0",
|
|
130
|
+
0x80000000,
|
|
131
|
+
0x3,
|
|
132
|
+
None,
|
|
133
|
+
3,
|
|
134
|
+
0,
|
|
135
|
+
None
|
|
136
|
+
)
|
|
137
|
+
if hDevice != -1:
|
|
138
|
+
return self.get_disk_load_temp()
|
|
139
|
+
return 35.0
|
|
140
|
+
except:
|
|
141
|
+
return 35.0
|
|
142
|
+
|
|
143
|
+
def get_windows_ram_temp(self) -> float:
|
|
144
|
+
return float(psutil.virtual_memory().percent / 2) # Fixed type conversion
|
|
145
|
+
|
|
146
|
+
def get_linux_temps(self) -> Dict[str, float]:
|
|
147
|
+
temps = {
|
|
148
|
+
'CPU': 0.0,
|
|
149
|
+
'GPU': 0.0,
|
|
150
|
+
'Motherboard': 0.0,
|
|
151
|
+
'Storage': 0.0,
|
|
152
|
+
'RAM': 0.0
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
# CPU Temperature
|
|
157
|
+
cpu_temps = psutil.sensors_temperatures()
|
|
158
|
+
if 'coretemp' in cpu_temps:
|
|
159
|
+
temps['CPU'] = float(max(temp.current for temp in cpu_temps['coretemp']))
|
|
160
|
+
|
|
161
|
+
# GPU Temperature
|
|
162
|
+
try:
|
|
163
|
+
nvidia_output = subprocess.check_output(
|
|
164
|
+
["nvidia-smi", "--query-gpu=temperature.gpu", "--format=csv,noheader"],
|
|
165
|
+
stderr=subprocess.DEVNULL
|
|
166
|
+
).decode()
|
|
167
|
+
temps['GPU'] = float(nvidia_output.strip())
|
|
168
|
+
except:
|
|
169
|
+
try:
|
|
170
|
+
for card in Path('/sys/class/drm').glob('card?'):
|
|
171
|
+
temp_file = next(card.glob('device/hwmon/hwmon*/temp1_input'))
|
|
172
|
+
with open(temp_file) as f:
|
|
173
|
+
temps['GPU'] = float(f.read().strip()) / 1000
|
|
174
|
+
break
|
|
175
|
+
except:
|
|
176
|
+
temps['GPU'] = self.get_gpu_load_temp()
|
|
177
|
+
|
|
178
|
+
# Motherboard Temperature
|
|
179
|
+
if 'acpitz' in cpu_temps:
|
|
180
|
+
temps['Motherboard'] = float(cpu_temps['acpitz'][0].current)
|
|
181
|
+
|
|
182
|
+
# Storage Temperature
|
|
183
|
+
try:
|
|
184
|
+
output = subprocess.check_output(['smartctl', '-A', '/dev/sda']).decode()
|
|
185
|
+
for line in output.split('\n'):
|
|
186
|
+
if 'Temperature' in line:
|
|
187
|
+
temps['Storage'] = float(line.split()[9])
|
|
188
|
+
break
|
|
189
|
+
except:
|
|
190
|
+
temps['Storage'] = self.get_disk_load_temp()
|
|
191
|
+
|
|
192
|
+
temps['RAM'] = float(psutil.virtual_memory().percent / 2)
|
|
193
|
+
|
|
194
|
+
return temps
|
|
195
|
+
except:
|
|
196
|
+
return self.get_fallback_temps()
|
|
197
|
+
|
|
198
|
+
def get_macos_temps(self) -> Dict[str, float]:
|
|
199
|
+
temps = self.get_fallback_temps() # Start with fallback temperatures
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
# SMC temperature readings
|
|
203
|
+
output = subprocess.check_output(
|
|
204
|
+
['sudo', 'powermetrics', '-n', '1'],
|
|
205
|
+
stderr=subprocess.DEVNULL
|
|
206
|
+
).decode()
|
|
207
|
+
|
|
208
|
+
for line in output.split('\n'):
|
|
209
|
+
if 'CPU die temperature' in line:
|
|
210
|
+
temps['CPU'] = float(line.split(':')[1].split()[0])
|
|
211
|
+
elif 'GPU die temperature' in line:
|
|
212
|
+
temps['GPU'] = float(line.split(':')[1].split()[0])
|
|
213
|
+
|
|
214
|
+
temps['Motherboard'] = float(temps['CPU'] * 0.8)
|
|
215
|
+
temps['RAM'] = float(psutil.virtual_memory().percent / 2)
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
output = subprocess.check_output(['smartctl', '-A', '/dev/disk0']).decode()
|
|
219
|
+
for line in output.split('\n'):
|
|
220
|
+
if 'Temperature' in line:
|
|
221
|
+
temps['Storage'] = float(line.split()[9])
|
|
222
|
+
break
|
|
223
|
+
except:
|
|
224
|
+
temps['Storage'] = self.get_disk_load_temp()
|
|
225
|
+
|
|
226
|
+
return temps
|
|
227
|
+
except:
|
|
228
|
+
return temps # Return the fallback temps if anything fails
|
|
229
|
+
|
|
230
|
+
def get_cpu_load_temp(self) -> float:
|
|
231
|
+
return float(40 + (psutil.cpu_percent() * 0.6))
|
|
232
|
+
|
|
233
|
+
def get_gpu_load_temp(self) -> float:
|
|
234
|
+
return float(35 + (psutil.cpu_percent() * 0.5))
|
|
235
|
+
|
|
236
|
+
def get_disk_load_temp(self) -> float:
|
|
237
|
+
disk_io = psutil.disk_io_counters()
|
|
238
|
+
if disk_io:
|
|
239
|
+
return float(30 + (disk_io.read_bytes + disk_io.write_bytes) / (1024 * 1024 * 1024))
|
|
240
|
+
return 30.0
|
|
241
|
+
|
|
242
|
+
def get_fallback_temps(self) -> Dict[str, float]:
|
|
243
|
+
cpu_percent = float(psutil.cpu_percent())
|
|
244
|
+
memory_percent = float(psutil.virtual_memory().percent)
|
|
245
|
+
return {
|
|
246
|
+
'CPU': float(40 + (cpu_percent * 0.4)),
|
|
247
|
+
'GPU': float(35 + (cpu_percent * 0.3)),
|
|
248
|
+
'Motherboard': float(35 + (cpu_percent * 0.2)),
|
|
249
|
+
'Storage': float(30 + (cpu_percent * 0.1)),
|
|
250
|
+
'RAM': float(30 + (memory_percent * 0.2))
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
def get_system_temps(self) -> Dict[str, float]:
|
|
254
|
+
if self.system == "Windows":
|
|
255
|
+
return self.get_windows_temps()
|
|
256
|
+
elif self.system == "Linux":
|
|
257
|
+
return self.get_linux_temps()
|
|
258
|
+
elif self.system == "Darwin":
|
|
259
|
+
return self.get_macos_temps()
|
|
260
|
+
return self.get_fallback_temps()
|
|
261
|
+
|
|
262
|
+
def get_temp_char(self, temp: float) -> tuple:
|
|
263
|
+
if temp < 45:
|
|
264
|
+
return ('·', "green")
|
|
265
|
+
elif temp < 70:
|
|
266
|
+
return ('▒', "yellow")
|
|
267
|
+
else:
|
|
268
|
+
return ('█', "red")
|
|
269
|
+
|
|
270
|
+
def update_component_map(self, component: str, temp: float):
|
|
271
|
+
rows, cols = self.components[component]['size']
|
|
272
|
+
base_temp = float(temp) # Ensure temp is float
|
|
273
|
+
noise = np.random.normal(0, 2, (rows, cols))
|
|
274
|
+
self.temp_maps[component] = np.clip(base_temp + noise, 0, 100)
|
|
275
|
+
|
|
276
|
+
def generate_system_layout(self) -> Panel:
|
|
277
|
+
layout = [[' ' for _ in range(40)] for _ in range(25)]
|
|
278
|
+
colors = [[None for _ in range(40)] for _ in range(25)]
|
|
279
|
+
temps = self.get_system_temps()
|
|
280
|
+
|
|
281
|
+
for component, info in self.components.items():
|
|
282
|
+
pos_x, pos_y = info['position']
|
|
283
|
+
size_x, size_y = info['size']
|
|
284
|
+
|
|
285
|
+
self.update_component_map(component, temps[component])
|
|
286
|
+
|
|
287
|
+
for i in range(size_x):
|
|
288
|
+
for j in range(size_y):
|
|
289
|
+
temp = float(self.temp_maps[component][i, j]) # Ensure float conversion
|
|
290
|
+
char, color = self.get_temp_char(temp)
|
|
291
|
+
layout[pos_x + i][pos_y + j] = char
|
|
292
|
+
colors[pos_x + i][pos_y + j] = color
|
|
293
|
+
|
|
294
|
+
label_x = pos_x + size_x // 2
|
|
295
|
+
label_y = pos_y + size_y // 2
|
|
296
|
+
label = f"{component[:3]} {temps[component]:.1f}°C"
|
|
297
|
+
for idx, char in enumerate(label):
|
|
298
|
+
if 0 <= label_y + idx < len(layout[0]):
|
|
299
|
+
layout[label_x][label_y + idx] = char
|
|
300
|
+
colors[label_x][label_y + idx] = "white"
|
|
301
|
+
|
|
302
|
+
text = Text()
|
|
303
|
+
for row in range(len(layout)):
|
|
304
|
+
for col in range(len(layout[0])):
|
|
305
|
+
text.append(layout[row][col], style=colors[row][col])
|
|
306
|
+
text.append("\n")
|
|
307
|
+
|
|
308
|
+
return Panel(
|
|
309
|
+
text,
|
|
310
|
+
title=f"System Temperature Heatmap ({self.system})",
|
|
311
|
+
border_style="blue"
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
def run(self, interval: float = 1.0, duration: Optional[int] = None) -> int:
|
|
315
|
+
"""
|
|
316
|
+
Run the system heatmap visualization.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
interval (float): Update interval in seconds
|
|
320
|
+
duration (Optional[int]): Total duration to run in seconds
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
int: Number of updates performed
|
|
324
|
+
|
|
325
|
+
Raises:
|
|
326
|
+
ValueError: If duration is negative or interval is zero/negative
|
|
327
|
+
"""
|
|
328
|
+
if duration is not None and duration <= 0:
|
|
329
|
+
raise ValueError("Duration must be positive")
|
|
330
|
+
if interval <= 0:
|
|
331
|
+
raise ValueError("Interval must be positive")
|
|
332
|
+
|
|
333
|
+
start_time = time.time()
|
|
334
|
+
update_count = 0
|
|
335
|
+
|
|
336
|
+
with Live(self.generate_system_layout(), refresh_per_second=1) as live:
|
|
337
|
+
try:
|
|
338
|
+
while True:
|
|
339
|
+
live.update(self.generate_system_layout())
|
|
340
|
+
update_count += 1
|
|
341
|
+
|
|
342
|
+
if duration and (time.time() - start_time) >= duration:
|
|
343
|
+
break
|
|
344
|
+
|
|
345
|
+
time.sleep(interval)
|
|
346
|
+
except KeyboardInterrupt:
|
|
347
|
+
pass
|
|
348
|
+
|
|
349
|
+
return update_count
|
guro/core/monitor.py
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import psutil
|
|
2
|
+
import platform
|
|
3
|
+
import datetime
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
from rich.panel import Panel
|
|
7
|
+
from rich.layout import Layout
|
|
8
|
+
import os
|
|
9
|
+
import time
|
|
10
|
+
from collections import deque
|
|
11
|
+
import threading
|
|
12
|
+
import subprocess
|
|
13
|
+
from typing import Dict, List, Optional
|
|
14
|
+
import shutil
|
|
15
|
+
from rich import box
|
|
16
|
+
# import click
|
|
17
|
+
import csv
|
|
18
|
+
|
|
19
|
+
class GPUDetector:
|
|
20
|
+
@staticmethod
|
|
21
|
+
def get_nvidia_info():
|
|
22
|
+
try:
|
|
23
|
+
nvidia_smi = "nvidia-smi"
|
|
24
|
+
output = subprocess.check_output([nvidia_smi, "--query-gpu=gpu_name,memory.total,memory.used,memory.free,temperature.gpu,utilization.gpu,fan.speed,power.draw", "--format=csv,noheader,nounits"],
|
|
25
|
+
universal_newlines=True)
|
|
26
|
+
lines = output.strip().split('\n')
|
|
27
|
+
gpus = []
|
|
28
|
+
for line in lines:
|
|
29
|
+
name, total, used, free, temp, util, fan, power = line.split(',')
|
|
30
|
+
gpus.append({
|
|
31
|
+
'name': name.strip(),
|
|
32
|
+
'memory_total': float(total) * 1024**2,
|
|
33
|
+
'memory_used': float(used) * 1024**2,
|
|
34
|
+
'memory_free': float(free) * 1024**2,
|
|
35
|
+
'temperature': float(temp),
|
|
36
|
+
'utilization': float(util),
|
|
37
|
+
'fan_speed': float(fan) if fan.strip() != '[N/A]' else None,
|
|
38
|
+
'power_draw': float(power) if power.strip() != '[N/A]' else None,
|
|
39
|
+
'type': 'NVIDIA'
|
|
40
|
+
})
|
|
41
|
+
return gpus
|
|
42
|
+
except:
|
|
43
|
+
return []
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def get_amd_info():
|
|
47
|
+
try:
|
|
48
|
+
rocm_smi = "rocm-smi"
|
|
49
|
+
output = subprocess.check_output([rocm_smi, "--showuse", "--showmeminfo", "--showtemp"],
|
|
50
|
+
universal_newlines=True)
|
|
51
|
+
gpus = []
|
|
52
|
+
lines = output.strip().split('\n')
|
|
53
|
+
current_gpu = {}
|
|
54
|
+
for line in lines:
|
|
55
|
+
if 'GPU' in line and 'Card' in line:
|
|
56
|
+
if current_gpu:
|
|
57
|
+
gpus.append(current_gpu)
|
|
58
|
+
current_gpu = {'type': 'AMD'}
|
|
59
|
+
if 'GPU Memory Use' in line:
|
|
60
|
+
try:
|
|
61
|
+
used = float(line.split(':')[1].strip().split()[0]) * 1024**2
|
|
62
|
+
current_gpu['memory_used'] = used
|
|
63
|
+
except:
|
|
64
|
+
pass
|
|
65
|
+
if 'Total GPU Memory' in line:
|
|
66
|
+
try:
|
|
67
|
+
total = float(line.split(':')[1].strip().split()[0]) * 1024**2
|
|
68
|
+
current_gpu['memory_total'] = total
|
|
69
|
+
current_gpu['memory_free'] = total - current_gpu.get('memory_used', 0)
|
|
70
|
+
except:
|
|
71
|
+
pass
|
|
72
|
+
if 'Temperature' in line:
|
|
73
|
+
try:
|
|
74
|
+
temp = float(line.split(':')[1].strip().split()[0])
|
|
75
|
+
current_gpu['temperature'] = temp
|
|
76
|
+
except:
|
|
77
|
+
pass
|
|
78
|
+
if current_gpu:
|
|
79
|
+
gpus.append(current_gpu)
|
|
80
|
+
return gpus
|
|
81
|
+
except:
|
|
82
|
+
return []
|
|
83
|
+
|
|
84
|
+
@staticmethod
|
|
85
|
+
def get_all_gpus():
|
|
86
|
+
gpu_info = {
|
|
87
|
+
'available': False,
|
|
88
|
+
'gpus': []
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
nvidia_gpus = GPUDetector.get_nvidia_info()
|
|
92
|
+
amd_gpus = GPUDetector.get_amd_info()
|
|
93
|
+
|
|
94
|
+
all_gpus = nvidia_gpus + amd_gpus
|
|
95
|
+
|
|
96
|
+
if all_gpus:
|
|
97
|
+
gpu_info['available'] = True
|
|
98
|
+
gpu_info['gpus'] = all_gpus
|
|
99
|
+
|
|
100
|
+
return gpu_info
|
|
101
|
+
|
|
102
|
+
class ASCIIGraph:
|
|
103
|
+
def __init__(self, width=70, height=10):
|
|
104
|
+
self.width = width
|
|
105
|
+
self.height = height
|
|
106
|
+
self.data = deque(maxlen=width)
|
|
107
|
+
self.chars = ' ▁▂▃▄▅▆▇█'
|
|
108
|
+
|
|
109
|
+
def add_point(self, value):
|
|
110
|
+
self.data.append(value)
|
|
111
|
+
|
|
112
|
+
def render(self, title=""):
|
|
113
|
+
if not self.data:
|
|
114
|
+
return ""
|
|
115
|
+
|
|
116
|
+
# Normalize data
|
|
117
|
+
max_val = max(self.data)
|
|
118
|
+
if max_val == 0:
|
|
119
|
+
normalized = [0] * len(self.data)
|
|
120
|
+
else:
|
|
121
|
+
normalized = [min(int((val / max_val) * (len(self.chars) - 1)), len(self.chars) - 1)
|
|
122
|
+
for val in self.data]
|
|
123
|
+
|
|
124
|
+
# Generate graph
|
|
125
|
+
lines = []
|
|
126
|
+
lines.append("╔" + "═" * (self.width + 2) + "╗")
|
|
127
|
+
lines.append("║ " + title.center(self.width) + " ║")
|
|
128
|
+
lines.append("║ " + "─" * self.width + " ║")
|
|
129
|
+
|
|
130
|
+
graph_str = ""
|
|
131
|
+
for val in normalized:
|
|
132
|
+
graph_str += self.chars[val]
|
|
133
|
+
lines.append("║ " + graph_str.ljust(self.width) + " ║")
|
|
134
|
+
|
|
135
|
+
lines.append("╚" + "═" * (self.width + 2) + "╝")
|
|
136
|
+
return "\n".join(lines)
|
|
137
|
+
|
|
138
|
+
class SystemMonitor:
|
|
139
|
+
def __init__(self):
|
|
140
|
+
self.console = Console()
|
|
141
|
+
self.cpu_graph = ASCIIGraph()
|
|
142
|
+
self.memory_graph = ASCIIGraph()
|
|
143
|
+
self.monitoring_data = []
|
|
144
|
+
|
|
145
|
+
def _get_cpu_temperature(self):
|
|
146
|
+
if platform.system() == 'Linux':
|
|
147
|
+
try:
|
|
148
|
+
temp_file = '/sys/class/thermal/thermal_zone0/temp'
|
|
149
|
+
if os.path.exists(temp_file):
|
|
150
|
+
with open(temp_file, 'r') as f:
|
|
151
|
+
return float(f.read()) / 1000.0
|
|
152
|
+
except:
|
|
153
|
+
pass
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
def get_system_info(self):
|
|
157
|
+
cpu_freq = psutil.cpu_freq()
|
|
158
|
+
memory = psutil.virtual_memory()
|
|
159
|
+
|
|
160
|
+
system_info = {
|
|
161
|
+
'os': f"{platform.system()} {platform.release()}",
|
|
162
|
+
'cpu_model': platform.processor(),
|
|
163
|
+
'cpu_cores': psutil.cpu_count(),
|
|
164
|
+
'cpu_threads': psutil.cpu_count(logical=True),
|
|
165
|
+
'cpu_freq': f"{cpu_freq.current:.2f}MHz" if cpu_freq else "N/A",
|
|
166
|
+
'memory_total': f"{memory.total / (1024**3):.2f}GB",
|
|
167
|
+
'memory_available': f"{memory.available / (1024**3):.2f}GB",
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
temp = self._get_cpu_temperature()
|
|
171
|
+
if temp:
|
|
172
|
+
system_info['cpu_temp'] = f"{temp:.1f}°C"
|
|
173
|
+
|
|
174
|
+
return system_info
|
|
175
|
+
|
|
176
|
+
def export_monitoring_data(self):
|
|
177
|
+
if not self.monitoring_data:
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
with open('monitoring_data.csv', 'w', newline='') as csvfile:
|
|
181
|
+
fieldnames = ['timestamp', 'cpu_usage', 'memory_usage']
|
|
182
|
+
writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
|
|
183
|
+
|
|
184
|
+
writer.writeheader()
|
|
185
|
+
for data in self.monitoring_data:
|
|
186
|
+
writer.writerow(data)
|
|
187
|
+
|
|
188
|
+
def run_performance_test(self, interval: float = 1.0, duration: Optional[int] = 30, export_data: bool = False):
|
|
189
|
+
self.console.clear()
|
|
190
|
+
self.console.print(Panel.fit(
|
|
191
|
+
"[bold cyan]Welcome to System Performance Monitor[/bold cyan]\n" +
|
|
192
|
+
f"[yellow]Running performance test for {duration if duration else 'unlimited'} seconds...[/yellow]",
|
|
193
|
+
box=box.DOUBLE
|
|
194
|
+
))
|
|
195
|
+
|
|
196
|
+
# System Information
|
|
197
|
+
sys_info = self.get_system_info()
|
|
198
|
+
sys_table = Table(title="System Information", box=box.HEAVY)
|
|
199
|
+
sys_table.add_column("Component", style="cyan")
|
|
200
|
+
sys_table.add_column("Details", style="green")
|
|
201
|
+
|
|
202
|
+
for key, value in sys_info.items():
|
|
203
|
+
sys_table.add_row(key.replace('_', ' ').title(), str(value))
|
|
204
|
+
|
|
205
|
+
self.console.print(sys_table)
|
|
206
|
+
self.console.print("")
|
|
207
|
+
|
|
208
|
+
# GPU Information
|
|
209
|
+
gpu_info = GPUDetector.get_all_gpus()
|
|
210
|
+
if gpu_info['available']:
|
|
211
|
+
gpu_table = Table(title="GPU Information", box=box.HEAVY)
|
|
212
|
+
gpu_table.add_column("Metric", style="cyan")
|
|
213
|
+
gpu_table.add_column("Value", style="green")
|
|
214
|
+
|
|
215
|
+
for i, gpu in enumerate(gpu_info['gpus']):
|
|
216
|
+
gpu_table.add_row(f"GPU {i} Type", gpu.get('type', 'Unknown'))
|
|
217
|
+
gpu_table.add_row(f"GPU {i} Name", gpu.get('name', 'Unknown'))
|
|
218
|
+
if gpu.get('memory_total'):
|
|
219
|
+
gpu_table.add_row(f"Memory Total", f"{gpu['memory_total'] / (1024**3):.2f} GB")
|
|
220
|
+
if gpu.get('temperature'):
|
|
221
|
+
gpu_table.add_row(f"Temperature", f"{gpu['temperature']}°C")
|
|
222
|
+
|
|
223
|
+
self.console.print(gpu_table)
|
|
224
|
+
self.console.print("")
|
|
225
|
+
|
|
226
|
+
# Performance Test
|
|
227
|
+
start_time = time.time()
|
|
228
|
+
try:
|
|
229
|
+
while True:
|
|
230
|
+
current_time = time.time()
|
|
231
|
+
if duration and (current_time - start_time >= duration):
|
|
232
|
+
break
|
|
233
|
+
|
|
234
|
+
cpu_percent = psutil.cpu_percent()
|
|
235
|
+
memory_percent = psutil.virtual_memory().percent
|
|
236
|
+
|
|
237
|
+
self.cpu_graph.add_point(cpu_percent)
|
|
238
|
+
self.memory_graph.add_point(memory_percent)
|
|
239
|
+
|
|
240
|
+
if export_data:
|
|
241
|
+
self.monitoring_data.append({
|
|
242
|
+
'timestamp': datetime.datetime.now().isoformat(),
|
|
243
|
+
'cpu_usage': cpu_percent,
|
|
244
|
+
'memory_usage': memory_percent
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
self.console.clear()
|
|
248
|
+
self.console.print(Panel.fit(
|
|
249
|
+
f"[bold cyan]Performance Test Progress: {int(current_time - start_time)}s{f'/{duration}s' if duration else ''}[/bold cyan]",
|
|
250
|
+
box=box.DOUBLE
|
|
251
|
+
))
|
|
252
|
+
|
|
253
|
+
self.console.print(self.cpu_graph.render(f"CPU Usage: {cpu_percent:.1f}%"))
|
|
254
|
+
self.console.print("")
|
|
255
|
+
self.console.print(self.memory_graph.render(f"Memory Usage: {memory_percent:.1f}%"))
|
|
256
|
+
|
|
257
|
+
time.sleep(interval)
|
|
258
|
+
|
|
259
|
+
except KeyboardInterrupt:
|
|
260
|
+
self.console.print("\n[yellow]Monitoring stopped by user[/yellow]")
|
|
261
|
+
|
|
262
|
+
# Final Summary
|
|
263
|
+
self.console.clear()
|
|
264
|
+
summary_table = Table(title="Performance Test Summary", box=box.HEAVY)
|
|
265
|
+
summary_table.add_column("Metric", style="cyan")
|
|
266
|
+
summary_table.add_column("Average", style="green")
|
|
267
|
+
summary_table.add_column("Maximum", style="red")
|
|
268
|
+
|
|
269
|
+
cpu_values = list(self.cpu_graph.data)
|
|
270
|
+
mem_values = list(self.memory_graph.data)
|
|
271
|
+
|
|
272
|
+
if cpu_values:
|
|
273
|
+
summary_table.add_row(
|
|
274
|
+
"CPU Usage",
|
|
275
|
+
f"{sum(cpu_values) / len(cpu_values):.1f}%",
|
|
276
|
+
f"{max(cpu_values):.1f}%"
|
|
277
|
+
)
|
|
278
|
+
if mem_values:
|
|
279
|
+
summary_table.add_row(
|
|
280
|
+
"Memory Usage",
|
|
281
|
+
f"{sum(mem_values) / len(mem_values):.1f}%",
|
|
282
|
+
f"{max(mem_values):.1f}%"
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
self.console.print(Panel.fit(
|
|
286
|
+
"[bold green]Performance Test Completed![/bold green]",
|
|
287
|
+
box=box.DOUBLE
|
|
288
|
+
))
|
|
289
|
+
self.console.print(summary_table)
|
|
290
|
+
|
|
291
|
+
# Final Graphs
|
|
292
|
+
self.console.print(self.cpu_graph.render("CPU Usage History"))
|
|
293
|
+
self.console.print("")
|
|
294
|
+
self.console.print(self.memory_graph.render("Memory Usage History"))
|
|
295
|
+
|
|
296
|
+
if export_data:
|
|
297
|
+
self.export_monitoring_data()
|
|
298
|
+
self.console.print("\n[green]Monitoring data exported to 'monitoring_data.csv'[/green]")
|
|
299
|
+
|
|
300
|
+
# @click.group()
|
|
301
|
+
# def cli():
|
|
302
|
+
# """🖥️ System Performance Monitor CLI"""
|
|
303
|
+
# pass
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 DHANUSH KANDHAN
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: guro
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Simple System Monitoring & Benchmarking Toolkit
|
|
5
|
+
Home-page: https://github.com/dhanushk-offl/guro
|
|
6
|
+
Author: Dhanush Kandhan
|
|
7
|
+
Author-email: dhanushkandhan75@gmail.com
|
|
8
|
+
Project-URL: Bug Reports, https://github.com/dhanushk-offl/guro/issues
|
|
9
|
+
Project-URL: Source, https://github.com/dhanushk-offl/guro
|
|
10
|
+
Project-URL: Documentation, https://github.com/dhanushk-offl/guro/wiki
|
|
11
|
+
Keywords: system-monitoring,gpu-optimization,performance-tuning,hardware-monitoring,system-administration,nvidia-gpu,amd-gpu,intel-gpu,resource-monitoring,system-optimization
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
15
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
16
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
17
|
+
Classifier: Topic :: System :: Hardware
|
|
18
|
+
Classifier: Topic :: System :: Monitoring
|
|
19
|
+
Classifier: Topic :: System :: Systems Administration
|
|
20
|
+
Classifier: Topic :: System :: Operating System
|
|
21
|
+
Classifier: Topic :: System :: Hardware :: Hardware Drivers
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Classifier: Intended Audience :: System Administrators
|
|
24
|
+
Classifier: Intended Audience :: Developers
|
|
25
|
+
Classifier: Development Status :: 4 - Beta
|
|
26
|
+
Classifier: Environment :: Console
|
|
27
|
+
Classifier: Natural Language :: English
|
|
28
|
+
Requires-Python: >=3.7
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Requires-Dist: click>=8.0.0
|
|
32
|
+
Requires-Dist: rich>=10.0.0
|
|
33
|
+
Requires-Dist: psutil>=5.8.0
|
|
34
|
+
Requires-Dist: gputil>=1.4.0
|
|
35
|
+
Requires-Dist: matplotlib>=3.5.0
|
|
36
|
+
Requires-Dist: nvidia-ml-py>=11.515.0
|
|
37
|
+
Requires-Dist: pandas>=1.3.0
|
|
38
|
+
Requires-Dist: plotly>=5.5.0
|
|
39
|
+
Requires-Dist: termcolor>=2.0.0
|
|
40
|
+
Requires-Dist: tabulate>=0.8.9
|
|
41
|
+
Requires-Dist: py-cpuinfo>=8.0.0
|
|
42
|
+
Requires-Dist: setproctitle>=1.2.2
|
|
43
|
+
Requires-Dist: keyboard>=0.13.5
|
|
44
|
+
Requires-Dist: pytest>=7.0.0
|
|
45
|
+
Requires-Dist: pytest-mock>=3.10.0
|
|
46
|
+
Requires-Dist: pytest-cov>=4.0.0
|
|
47
|
+
Requires-Dist: pytest-timeout>=2.1.0
|
|
48
|
+
Provides-Extra: nvidia
|
|
49
|
+
Requires-Dist: py3nvml>=0.2.7; extra == "nvidia"
|
|
50
|
+
Requires-Dist: nvidia-ml-py>=11.515.0; extra == "nvidia"
|
|
51
|
+
Provides-Extra: amd
|
|
52
|
+
Requires-Dist: pyamdgpuinfo>=2.1.0; extra == "amd"
|
|
53
|
+
Provides-Extra: all
|
|
54
|
+
Requires-Dist: py3nvml>=0.2.7; extra == "all"
|
|
55
|
+
Requires-Dist: nvidia-ml-py>=11.515.0; extra == "all"
|
|
56
|
+
Requires-Dist: pyamdgpuinfo>=2.1.0; extra == "all"
|
|
57
|
+
Requires-Dist: matplotlib>=3.5.0; extra == "all"
|
|
58
|
+
Requires-Dist: plotly>=5.5.0; extra == "all"
|
|
59
|
+
Provides-Extra: test
|
|
60
|
+
Requires-Dist: pytest>=7.0.0; extra == "test"
|
|
61
|
+
Requires-Dist: pytest-mock>=3.10.0; extra == "test"
|
|
62
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "test"
|
|
63
|
+
Requires-Dist: pytest-timeout>=2.1.0; extra == "test"
|
|
64
|
+
Dynamic: author
|
|
65
|
+
Dynamic: author-email
|
|
66
|
+
Dynamic: classifier
|
|
67
|
+
Dynamic: description
|
|
68
|
+
Dynamic: description-content-type
|
|
69
|
+
Dynamic: home-page
|
|
70
|
+
Dynamic: keywords
|
|
71
|
+
Dynamic: project-url
|
|
72
|
+
Dynamic: provides-extra
|
|
73
|
+
Dynamic: requires-dist
|
|
74
|
+
Dynamic: requires-python
|
|
75
|
+
Dynamic: summary
|
|
76
|
+
|
|
77
|
+
# Guro - A Simple System Monitoring & Benchmarking Toolkit 🚀
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
Welcome to **Guro**, the ultimate toolkit for **system monitoring** and **benchmarking**. It’s simple, fast, and designed for developers and enthusiasts who want to monitor system performance, run benchmarks, and visualize system heatmaps in an intuitive way.
|
|
81
|
+
|
|
82
|
+
### Features:
|
|
83
|
+
- 📊 **Real-time system monitoring** – Monitor CPU, memory, and disk usage in real-time.
|
|
84
|
+
- 🔥 **Performance benchmarking** – Run benchmarks with mini and full-scale tests for your CPU and GPU.
|
|
85
|
+
- 🌡️ **Hardware heatmap** – Visualize your system's temperature with a heatmap in real-time.
|
|
86
|
+
- 💾 **Export data** – Export monitoring data to CSV for analysis.
|
|
87
|
+
|
|
88
|
+
### Installation
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pip install guro
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Usage
|
|
95
|
+
|
|
96
|
+
Run the following commands for monitoring, benchmarking, or heatmap analysis:
|
|
97
|
+
|
|
98
|
+
#### 1. **Monitor System Resources**
|
|
99
|
+
```bash
|
|
100
|
+
guro monitor --interval 1.0 --duration 60
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### 2. **Run Benchmark Tests**
|
|
104
|
+
```bash
|
|
105
|
+
guro benchmark --type mini --cpu-only
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### 3. **Visualize System Heatmap**
|
|
109
|
+
```bash
|
|
110
|
+
guro heatmap --interval 1.0 --duration 30
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
#### 4. **List All Commands**
|
|
114
|
+
```bash
|
|
115
|
+
guro list
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
For more details, check out our [documentation](https://github.com/dhanushk-offl/guro/wiki).
|
|
119
|
+
|
|
120
|
+
### Demo
|
|
121
|
+
|
|
122
|
+
[Watch the demo video here](https://www.youtube.com/watch?v=xyz123)
|
|
123
|
+
|
|
124
|
+
### License
|
|
125
|
+
|
|
126
|
+
MIT License. See LICENSE for more details.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
guro/__init__.py,sha256=jfsfKFXPEm3Vw2kGvN0tF6BdymKeuVwD-BKDa4UcdZI,41
|
|
2
|
+
guro/cli/__init__.py,sha256=Yf6zQAso40qDlQxYRO7I69vJAeZFARH3Mb4uuyVkZw8,66
|
|
3
|
+
guro/cli/main.py,sha256=uRS2YaI6ohK5b8SC7ls3v9yVaK3xeZQYeYa5v2siW8E,6454
|
|
4
|
+
guro/core/__init__.py,sha256=QnMaN9YZAoGFCQUc96je7epXpuDtxYJkIp2CmLpaJGg,88
|
|
5
|
+
guro/core/benchmark.py,sha256=HekY0qw18fZmDm6dAUPnYDWsYdxJAfZ1O0oQOKi27fQ,12079
|
|
6
|
+
guro/core/heatmap.py,sha256=x7YX7KjkBHQ89VEdISqkx8P1C2VrTNm1f6k4UdY0-Ao,13452
|
|
7
|
+
guro/core/monitor.py,sha256=ZgaF0U3lQdChDkjgP3g3VTODgclBWscRoRSlyndWY9g,11625
|
|
8
|
+
guro-0.1.0.dist-info/LICENSE,sha256=MHJpjnxHbUoDnq3h1-egX5V2xYAlKwwElYlzl_J8nHk,1093
|
|
9
|
+
guro-0.1.0.dist-info/METADATA,sha256=4OzeNSX5JxHev_Qm6NvHgHVqFfzemXAcIpdg6UNfJQs,4484
|
|
10
|
+
guro-0.1.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
11
|
+
guro-0.1.0.dist-info/entry_points.txt,sha256=9JtiZiie5WKQ-qCDaAeZJMIOggcorlq8UKCBAM0tFMs,43
|
|
12
|
+
guro-0.1.0.dist-info/top_level.txt,sha256=eUjx5tz9mwVwUwI_XxhkCZeBBH0GYj8zkwGDrsWHxOg,5
|
|
13
|
+
guro-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
guro
|