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/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()