devpulse-tui 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.
- devpulse/__init__.py +0 -0
- devpulse/activity.py +50 -0
- devpulse/database.py +75 -0
- devpulse/devinfo.py +240 -0
- devpulse/doctor.py +71 -0
- devpulse/live.py +579 -0
- devpulse/main.py +32 -0
- devpulse/stats.py +84 -0
- devpulse_tui-0.1.0.dist-info/METADATA +305 -0
- devpulse_tui-0.1.0.dist-info/RECORD +13 -0
- devpulse_tui-0.1.0.dist-info/WHEEL +5 -0
- devpulse_tui-0.1.0.dist-info/entry_points.txt +2 -0
- devpulse_tui-0.1.0.dist-info/top_level.txt +1 -0
devpulse/live.py
ADDED
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
import socket
|
|
4
|
+
import platform
|
|
5
|
+
import psutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import readchar
|
|
8
|
+
import sys
|
|
9
|
+
import select
|
|
10
|
+
import termios
|
|
11
|
+
import tty
|
|
12
|
+
from devpulse.database import log_system_stats
|
|
13
|
+
from devpulse.devinfo import (
|
|
14
|
+
get_git_info,
|
|
15
|
+
get_shell,
|
|
16
|
+
get_terminal,
|
|
17
|
+
get_current_directory,
|
|
18
|
+
get_last_commit,
|
|
19
|
+
get_changed_files_count,
|
|
20
|
+
get_repo_status,
|
|
21
|
+
get_untracked_files_count,
|
|
22
|
+
get_insertions_deletions,
|
|
23
|
+
get_last_commit_author,
|
|
24
|
+
get_last_commit_age,
|
|
25
|
+
)
|
|
26
|
+
from rich.panel import Panel
|
|
27
|
+
from rich.table import Table
|
|
28
|
+
from rich.layout import Layout
|
|
29
|
+
from rich.live import Live
|
|
30
|
+
from rich.columns import Columns
|
|
31
|
+
from rich.text import Text
|
|
32
|
+
from rich.align import Align
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
from py3nvml.py3nvml import (
|
|
36
|
+
nvmlInit,
|
|
37
|
+
nvmlDeviceGetHandleByIndex,
|
|
38
|
+
nvmlDeviceGetUtilizationRates,
|
|
39
|
+
nvmlDeviceGetMemoryInfo,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
nvmlInit()
|
|
43
|
+
NVIDIA_AVAILABLE = True
|
|
44
|
+
|
|
45
|
+
except Exception:
|
|
46
|
+
NVIDIA_AVAILABLE = False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
previous_net = psutil.net_io_counters()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
_last_bytes_sent = 0
|
|
53
|
+
_last_bytes_recv = 0
|
|
54
|
+
_last_time = time.time()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_network_speed():
|
|
58
|
+
global _last_bytes_sent
|
|
59
|
+
global _last_bytes_recv
|
|
60
|
+
global _last_time
|
|
61
|
+
|
|
62
|
+
current = psutil.net_io_counters()
|
|
63
|
+
|
|
64
|
+
now = time.time()
|
|
65
|
+
|
|
66
|
+
time_diff = now - _last_time
|
|
67
|
+
|
|
68
|
+
upload_speed = (
|
|
69
|
+
(current.bytes_sent - _last_bytes_sent)
|
|
70
|
+
* 8
|
|
71
|
+
/ time_diff
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
download_speed = (
|
|
75
|
+
(current.bytes_recv - _last_bytes_recv)
|
|
76
|
+
* 8
|
|
77
|
+
/ time_diff
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
_last_bytes_sent = current.bytes_sent
|
|
81
|
+
_last_bytes_recv = current.bytes_recv
|
|
82
|
+
_last_time = now
|
|
83
|
+
|
|
84
|
+
return upload_speed, download_speed
|
|
85
|
+
|
|
86
|
+
def get_gpu_stats():
|
|
87
|
+
if not NVIDIA_AVAILABLE:
|
|
88
|
+
return "N/A", "N/A"
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
handle = nvmlDeviceGetHandleByIndex(0)
|
|
92
|
+
|
|
93
|
+
utilization = nvmlDeviceGetUtilizationRates(handle)
|
|
94
|
+
memory = nvmlDeviceGetMemoryInfo(handle)
|
|
95
|
+
|
|
96
|
+
gpu_percent = utilization.gpu
|
|
97
|
+
mem_percent = round((memory.used / memory.total) * 100)
|
|
98
|
+
|
|
99
|
+
return f"{gpu_percent}%", f"{mem_percent}%"
|
|
100
|
+
|
|
101
|
+
except Exception:
|
|
102
|
+
return "Error", "Error"
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def format_bits(bits):
|
|
107
|
+
if bits >= 1_000_000:
|
|
108
|
+
return f"{bits / 1_000_000:.2f} Mbps"
|
|
109
|
+
|
|
110
|
+
if bits >= 1_000:
|
|
111
|
+
return f"{bits / 1_000:.2f} Kbps"
|
|
112
|
+
|
|
113
|
+
return f"{bits:.0f} bps"
|
|
114
|
+
|
|
115
|
+
def run_git(command):
|
|
116
|
+
try:
|
|
117
|
+
result = subprocess.check_output(
|
|
118
|
+
command,
|
|
119
|
+
shell=True,
|
|
120
|
+
stderr=subprocess.DEVNULL
|
|
121
|
+
)
|
|
122
|
+
return result.decode().strip()
|
|
123
|
+
except:
|
|
124
|
+
return "N/A"
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def build_system_table():
|
|
128
|
+
upload, download = get_network_speed()
|
|
129
|
+
git_info = get_git_info()
|
|
130
|
+
cpu_percent = psutil.cpu_percent()
|
|
131
|
+
ram_percent = psutil.virtual_memory().percent
|
|
132
|
+
disk_percent = psutil.disk_usage('/').percent
|
|
133
|
+
gpu_usage, gpu_mem = get_gpu_stats()
|
|
134
|
+
log_system_stats(
|
|
135
|
+
cpu=cpu_percent,
|
|
136
|
+
ram=ram_percent,
|
|
137
|
+
disk=disk_percent,
|
|
138
|
+
gpu=gpu_usage,
|
|
139
|
+
upload=upload,
|
|
140
|
+
download=download,
|
|
141
|
+
repo=git_info["repo"],
|
|
142
|
+
branch=git_info["branch"],
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
table = Table(
|
|
146
|
+
expand=True,
|
|
147
|
+
show_edge=False,
|
|
148
|
+
pad_edge=False
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
table.add_column(
|
|
152
|
+
"Metric",
|
|
153
|
+
style="cyan",
|
|
154
|
+
width=18
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
table.add_column(
|
|
158
|
+
"Value",
|
|
159
|
+
style="green",
|
|
160
|
+
overflow="fold"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
table.add_row("Hostname", socket.gethostname())
|
|
164
|
+
table.add_row("OS", platform.system())
|
|
165
|
+
table.add_row("Kernel", platform.release())
|
|
166
|
+
|
|
167
|
+
table.add_row(
|
|
168
|
+
"Git Repo",
|
|
169
|
+
git_info["repo"]
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
table.add_row(
|
|
173
|
+
"Git Branch",
|
|
174
|
+
git_info["branch"]
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
table.add_row(
|
|
178
|
+
"Shell",
|
|
179
|
+
get_shell()
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
table.add_row(
|
|
183
|
+
"Terminal",
|
|
184
|
+
get_terminal()
|
|
185
|
+
)
|
|
186
|
+
table.add_row(
|
|
187
|
+
"Directory",
|
|
188
|
+
get_current_directory()[:40]
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
table.add_row(
|
|
192
|
+
"CPU Usage",
|
|
193
|
+
f"{cpu_percent}%"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
table.add_row(
|
|
197
|
+
"RAM Usage",
|
|
198
|
+
f"{ram_percent}%"
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
table.add_row(
|
|
202
|
+
"Disk Usage",
|
|
203
|
+
f"{disk_percent}%"
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
table.add_row(
|
|
207
|
+
"GPU Usage",
|
|
208
|
+
gpu_usage
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
table.add_row(
|
|
212
|
+
"GPU Memory",
|
|
213
|
+
gpu_mem
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
table.add_row(
|
|
217
|
+
"Upload",
|
|
218
|
+
format_bits(upload)
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
table.add_row(
|
|
222
|
+
"Download",
|
|
223
|
+
format_bits(download)
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
return table
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def build_cpu_table():
|
|
230
|
+
table = Table(expand=True)
|
|
231
|
+
|
|
232
|
+
table.add_column("Core")
|
|
233
|
+
table.add_column("Usage")
|
|
234
|
+
|
|
235
|
+
for i, percent in enumerate(psutil.cpu_percent(percpu=True)):
|
|
236
|
+
bar = "█" * int(percent / 5)
|
|
237
|
+
|
|
238
|
+
table.add_row(
|
|
239
|
+
f"CPU {i}",
|
|
240
|
+
f"{bar} {percent}%"
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
return table
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def build_process_table():
|
|
247
|
+
table = Table(expand=True)
|
|
248
|
+
|
|
249
|
+
table.add_column("PID", style="magenta")
|
|
250
|
+
table.add_column("Name", style="cyan")
|
|
251
|
+
table.add_column("CPU %", style="green")
|
|
252
|
+
|
|
253
|
+
processes = []
|
|
254
|
+
|
|
255
|
+
for proc in psutil.process_iter(
|
|
256
|
+
['pid', 'name', 'cpu_percent']
|
|
257
|
+
):
|
|
258
|
+
try:
|
|
259
|
+
processes.append(proc.info)
|
|
260
|
+
except Exception:
|
|
261
|
+
pass
|
|
262
|
+
|
|
263
|
+
processes = sorted(
|
|
264
|
+
processes,
|
|
265
|
+
key=lambda p: p['cpu_percent'],
|
|
266
|
+
reverse=True
|
|
267
|
+
)[:18]
|
|
268
|
+
|
|
269
|
+
for proc in processes:
|
|
270
|
+
table.add_row(
|
|
271
|
+
str(proc['pid']),
|
|
272
|
+
str(proc['name']),
|
|
273
|
+
str(proc['cpu_percent'])
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
return table
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def build_header():
|
|
280
|
+
text = Text(
|
|
281
|
+
" DEVPULSE ",
|
|
282
|
+
style="bold black on cyan"
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
return Panel(
|
|
286
|
+
Align.center(text),
|
|
287
|
+
border_style="cyan"
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
def build_footer():
|
|
291
|
+
footer_text = Text()
|
|
292
|
+
|
|
293
|
+
footer_text.append("[Q] Quit", style="bold cyan")
|
|
294
|
+
footer_text.append(" ")
|
|
295
|
+
footer_text.append("[R] Refresh", style="bold green")
|
|
296
|
+
footer_text.append(" ")
|
|
297
|
+
footer_text.append("DevPulse v0.1", style="bold magenta")
|
|
298
|
+
|
|
299
|
+
return Panel(
|
|
300
|
+
Align.center(footer_text),
|
|
301
|
+
border_style="cyan"
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def build_git_activity_table():
|
|
306
|
+
git_table = Table(expand=True)
|
|
307
|
+
|
|
308
|
+
git_table.add_column("Field", style="cyan", no_wrap=True)
|
|
309
|
+
git_table.add_column("Value", style="green")
|
|
310
|
+
|
|
311
|
+
repo = run_git(
|
|
312
|
+
"git rev-parse --show-toplevel"
|
|
313
|
+
).split("/")[-1]
|
|
314
|
+
|
|
315
|
+
branch = run_git(
|
|
316
|
+
"git branch --show-current"
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
last_commit = run_git(
|
|
320
|
+
"git log -1 --pretty=%s"
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
author = run_git(
|
|
324
|
+
"git log -1 --pretty=%an"
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
commit_age = run_git(
|
|
328
|
+
"git log -1 --pretty=%cr"
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
status = run_git(
|
|
332
|
+
"git status --short"
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
changed_files = (
|
|
336
|
+
len(status.splitlines())
|
|
337
|
+
if status and status != "N/A"
|
|
338
|
+
else 0
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
untracked = len([
|
|
342
|
+
x for x in status.splitlines()
|
|
343
|
+
if x.startswith("??")
|
|
344
|
+
]) if status and status != "N/A" else 0
|
|
345
|
+
|
|
346
|
+
insertions = run_git(
|
|
347
|
+
"git diff --shortstat | grep -o '[0-9]* insertion' | awk '{print $1}'"
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
deletions = run_git(
|
|
351
|
+
"git diff --shortstat | grep -o '[0-9]* deletion' | awk '{print $1}'"
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
git_table.add_row(
|
|
355
|
+
"Status",
|
|
356
|
+
"DIRTY" if changed_files else "CLEAN"
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
git_table.add_row("Repository", repo)
|
|
360
|
+
git_table.add_row("Branch", branch)
|
|
361
|
+
git_table.add_row("Author", author)
|
|
362
|
+
git_table.add_row("Commit Age", commit_age)
|
|
363
|
+
git_table.add_row("Last Commit", last_commit[:40])
|
|
364
|
+
git_table.add_row("Files Changed", str(changed_files))
|
|
365
|
+
git_table.add_row("Untracked", str(untracked))
|
|
366
|
+
git_table.add_row("Insertions", f"+{insertions or 0}")
|
|
367
|
+
git_table.add_row("Deletions", f"-{deletions or 0}")
|
|
368
|
+
|
|
369
|
+
return git_table
|
|
370
|
+
|
|
371
|
+
def build_dashboard():
|
|
372
|
+
layout = Layout()
|
|
373
|
+
|
|
374
|
+
layout.split_column(
|
|
375
|
+
Layout(name="header", size=3),
|
|
376
|
+
Layout(name="main", ratio=1),
|
|
377
|
+
Layout(name="footer", size=3)
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
layout["main"].split_row(
|
|
381
|
+
Layout(name="left", ratio=1),
|
|
382
|
+
Layout(name="right", ratio=1)
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
layout["left"].split_column(
|
|
386
|
+
Layout(name="system", ratio=3),
|
|
387
|
+
Layout(name="network", ratio=2),
|
|
388
|
+
Layout(name="cpu", ratio=4)
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
layout["right"].split_column(
|
|
392
|
+
Layout(name="processes", ratio=5),
|
|
393
|
+
Layout(name="git", ratio=4)
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
layout["header"].update(build_header())
|
|
397
|
+
layout["footer"].update(build_footer())
|
|
398
|
+
|
|
399
|
+
layout["system"].update(
|
|
400
|
+
Panel(
|
|
401
|
+
build_system_table(),
|
|
402
|
+
title="System",
|
|
403
|
+
border_style="green"
|
|
404
|
+
)
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
layout["right"]["git"].update(
|
|
408
|
+
build_git_panel()
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
layout["network"].update(
|
|
412
|
+
build_network_panel()
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
layout["cpu"].update(
|
|
416
|
+
Panel(
|
|
417
|
+
build_cpu_table(),
|
|
418
|
+
title="CPU Cores",
|
|
419
|
+
border_style="yellow"
|
|
420
|
+
)
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
layout["right"]["processes"].update(
|
|
424
|
+
Panel(
|
|
425
|
+
build_process_table(),
|
|
426
|
+
title="Top Processes",
|
|
427
|
+
border_style="magenta"
|
|
428
|
+
)
|
|
429
|
+
)
|
|
430
|
+
layout["git"].update(
|
|
431
|
+
Panel(
|
|
432
|
+
build_git_activity_table(),
|
|
433
|
+
title="Git Activity",
|
|
434
|
+
border_style="bright_blue"
|
|
435
|
+
)
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
return layout
|
|
439
|
+
|
|
440
|
+
def key_pressed():
|
|
441
|
+
dr, _, _ = select.select([sys.stdin], [], [], 0)
|
|
442
|
+
return dr != []
|
|
443
|
+
|
|
444
|
+
def run_live_dashboard():
|
|
445
|
+
psutil.cpu_percent(interval=None)
|
|
446
|
+
|
|
447
|
+
fd = sys.stdin.fileno()
|
|
448
|
+
old_settings = termios.tcgetattr(fd)
|
|
449
|
+
|
|
450
|
+
try:
|
|
451
|
+
tty.setcbreak(fd)
|
|
452
|
+
|
|
453
|
+
with Live(
|
|
454
|
+
build_dashboard(),
|
|
455
|
+
refresh_per_second=2,
|
|
456
|
+
screen=True,
|
|
457
|
+
) as live:
|
|
458
|
+
|
|
459
|
+
while True:
|
|
460
|
+
live.update(build_dashboard())
|
|
461
|
+
|
|
462
|
+
if key_pressed():
|
|
463
|
+
key = sys.stdin.read(1)
|
|
464
|
+
|
|
465
|
+
if key.lower() == "q":
|
|
466
|
+
break
|
|
467
|
+
|
|
468
|
+
elif key.lower() == "r":
|
|
469
|
+
live.update(build_dashboard())
|
|
470
|
+
|
|
471
|
+
time.sleep(0.5)
|
|
472
|
+
|
|
473
|
+
finally:
|
|
474
|
+
termios.tcsetattr(
|
|
475
|
+
fd,
|
|
476
|
+
termios.TCSADRAIN,
|
|
477
|
+
old_settings
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
def build_git_panel():
|
|
481
|
+
git_info = get_git_info()
|
|
482
|
+
insertions, deletions = get_insertions_deletions()
|
|
483
|
+
table = Table(expand=True)
|
|
484
|
+
|
|
485
|
+
table.add_column("Field", style="cyan")
|
|
486
|
+
table.add_column("Value", style="green")
|
|
487
|
+
|
|
488
|
+
table.add_row(
|
|
489
|
+
"Status",
|
|
490
|
+
get_repo_status()
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
table.add_row(
|
|
494
|
+
"Untracked",
|
|
495
|
+
str(get_untracked_files_count())
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
table.add_row(
|
|
499
|
+
"Insertions",
|
|
500
|
+
f"+{insertions}"
|
|
501
|
+
)
|
|
502
|
+
|
|
503
|
+
table.add_row(
|
|
504
|
+
"Deletions",
|
|
505
|
+
f"-{deletions}"
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
table.add_row(
|
|
509
|
+
"Author",
|
|
510
|
+
get_last_commit_author()
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
table.add_row(
|
|
514
|
+
"Commit Age",
|
|
515
|
+
get_last_commit_age()
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
table.add_row(
|
|
519
|
+
"Repository",
|
|
520
|
+
git_info["repo"]
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
table.add_row(
|
|
524
|
+
"Branch",
|
|
525
|
+
git_info["branch"]
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
table.add_row(
|
|
529
|
+
"Last Commit",
|
|
530
|
+
get_last_commit()
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
table.add_row(
|
|
534
|
+
"Files Changed",
|
|
535
|
+
str(get_changed_files_count())
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
return Panel(
|
|
539
|
+
table,
|
|
540
|
+
title="Git Activity",
|
|
541
|
+
border_style="blue"
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
def build_network_panel():
|
|
545
|
+
net_io = psutil.net_io_counters()
|
|
546
|
+
|
|
547
|
+
upload, download = get_network_speed()
|
|
548
|
+
|
|
549
|
+
table = Table(expand=True)
|
|
550
|
+
|
|
551
|
+
table.add_column("Metric", style="cyan")
|
|
552
|
+
table.add_column("Value", style="green")
|
|
553
|
+
|
|
554
|
+
table.add_row(
|
|
555
|
+
"Upload Speed",
|
|
556
|
+
f"{upload:.2f} KB/s"
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
table.add_row(
|
|
560
|
+
"Download Speed",
|
|
561
|
+
f"{download:.2f} KB/s"
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
table.add_row(
|
|
565
|
+
"Total Sent",
|
|
566
|
+
f"{net_io.bytes_sent / (1024**3):.2f} GB"
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
table.add_row(
|
|
570
|
+
"Total Received",
|
|
571
|
+
f"{net_io.bytes_recv / (1024**3):.2f} GB"
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
return Panel(
|
|
575
|
+
table,
|
|
576
|
+
title="Network",
|
|
577
|
+
border_style="magenta"
|
|
578
|
+
)
|
|
579
|
+
|
devpulse/main.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import typer
|
|
2
|
+
|
|
3
|
+
from devpulse.live import run_live_dashboard
|
|
4
|
+
from devpulse.database import init_db
|
|
5
|
+
from devpulse.stats import show_stats
|
|
6
|
+
from devpulse.doctor import run_doctor
|
|
7
|
+
app = typer.Typer(no_args_is_help=True)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@app.command()
|
|
11
|
+
def live():
|
|
12
|
+
"""Launch live dashboard"""
|
|
13
|
+
run_live_dashboard()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.command()
|
|
17
|
+
def version():
|
|
18
|
+
print("DevPulse v0.1")
|
|
19
|
+
|
|
20
|
+
@app.command()
|
|
21
|
+
def stats():
|
|
22
|
+
"""Show DevPulse analytics"""
|
|
23
|
+
show_stats()
|
|
24
|
+
|
|
25
|
+
@app.command()
|
|
26
|
+
def doctor():
|
|
27
|
+
"""Run DevPulse diagnostics"""
|
|
28
|
+
run_doctor()
|
|
29
|
+
|
|
30
|
+
if __name__ == "__main__":
|
|
31
|
+
init_db()
|
|
32
|
+
app()
|
devpulse/stats.py
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from sqlalchemy import func
|
|
2
|
+
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
from rich.table import Table
|
|
5
|
+
|
|
6
|
+
from devpulse.database import (
|
|
7
|
+
SessionLocal,
|
|
8
|
+
SystemStat,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
console = Console()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def show_stats():
|
|
16
|
+
session = SessionLocal()
|
|
17
|
+
|
|
18
|
+
total_rows = session.query(SystemStat).count()
|
|
19
|
+
|
|
20
|
+
avg_cpu = session.query(
|
|
21
|
+
func.avg(SystemStat.cpu)
|
|
22
|
+
).scalar()
|
|
23
|
+
|
|
24
|
+
avg_ram = session.query(
|
|
25
|
+
func.avg(SystemStat.ram)
|
|
26
|
+
).scalar()
|
|
27
|
+
|
|
28
|
+
peak_cpu = session.query(
|
|
29
|
+
func.max(SystemStat.cpu)
|
|
30
|
+
).scalar()
|
|
31
|
+
|
|
32
|
+
peak_ram = session.query(
|
|
33
|
+
func.max(SystemStat.ram)
|
|
34
|
+
).scalar()
|
|
35
|
+
|
|
36
|
+
top_repo = (
|
|
37
|
+
session.query(
|
|
38
|
+
SystemStat.repo,
|
|
39
|
+
func.count(SystemStat.repo)
|
|
40
|
+
)
|
|
41
|
+
.group_by(SystemStat.repo)
|
|
42
|
+
.order_by(func.count(SystemStat.repo).desc())
|
|
43
|
+
.first()
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
table = Table(title="DevPulse Analytics")
|
|
47
|
+
|
|
48
|
+
table.add_column("Metric", style="cyan")
|
|
49
|
+
table.add_column("Value", style="green")
|
|
50
|
+
|
|
51
|
+
table.add_row(
|
|
52
|
+
"Rows Logged",
|
|
53
|
+
str(total_rows)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
table.add_row(
|
|
57
|
+
"Average CPU",
|
|
58
|
+
f"{avg_cpu:.2f}%"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
table.add_row(
|
|
62
|
+
"Average RAM",
|
|
63
|
+
f"{avg_ram:.2f}%"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
table.add_row(
|
|
67
|
+
"Peak CPU",
|
|
68
|
+
f"{peak_cpu:.2f}%"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
table.add_row(
|
|
72
|
+
"Peak RAM",
|
|
73
|
+
f"{peak_ram:.2f}%"
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
if top_repo:
|
|
77
|
+
table.add_row(
|
|
78
|
+
"Top Repo",
|
|
79
|
+
top_repo[0]
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
console.print(table)
|
|
83
|
+
|
|
84
|
+
session.close()
|