syscheck-cli 0.2.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.
- syscheck/__init__.py +2 -0
- syscheck/__main__.py +4 -0
- syscheck/cli.py +134 -0
- syscheck/cmdlang.py +109 -0
- syscheck/commands/__init__.py +8 -0
- syscheck/commands/cpu.py +45 -0
- syscheck/commands/disk.py +61 -0
- syscheck/commands/net.py +59 -0
- syscheck/commands/proc.py +35 -0
- syscheck/commands/ram.py +49 -0
- syscheck/commands/sys.py +43 -0
- syscheck/commands/temp.py +50 -0
- syscheck/commands/watch.py +115 -0
- syscheck/palette.py +30 -0
- syscheck/plugin.py +89 -0
- syscheck/plugins/__init__.py +1 -0
- syscheck/plugins/example.py +38 -0
- syscheck/providers.py +705 -0
- syscheck/screens.py +265 -0
- syscheck/shellguard.py +126 -0
- syscheck/tui.py +1086 -0
- syscheck/utils.py +111 -0
- syscheck_cli-0.2.0.dist-info/METADATA +14 -0
- syscheck_cli-0.2.0.dist-info/RECORD +27 -0
- syscheck_cli-0.2.0.dist-info/WHEEL +5 -0
- syscheck_cli-0.2.0.dist-info/entry_points.txt +2 -0
- syscheck_cli-0.2.0.dist-info/top_level.txt +1 -0
syscheck/__init__.py
ADDED
syscheck/__main__.py
ADDED
syscheck/cli.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Главный entry point для syscheck CLI."""
|
|
2
|
+
import sys
|
|
3
|
+
import typer
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
|
|
6
|
+
# Windows console encoding fix (support emoji/unicode)
|
|
7
|
+
if sys.platform == "win32":
|
|
8
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
9
|
+
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
|
10
|
+
|
|
11
|
+
from syscheck import __version__
|
|
12
|
+
from syscheck.plugin import get_plugins
|
|
13
|
+
|
|
14
|
+
app = typer.Typer(
|
|
15
|
+
name="syscheck",
|
|
16
|
+
help="syscheck — CLI-утилита диагностики системы",
|
|
17
|
+
no_args_is_help=True,
|
|
18
|
+
rich_markup_mode="rich",
|
|
19
|
+
)
|
|
20
|
+
console = Console()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def version_callback(value: bool):
|
|
24
|
+
if value:
|
|
25
|
+
console.print(f"syscheck v{__version__}")
|
|
26
|
+
raise typer.Exit()
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.callback()
|
|
30
|
+
def main_cb(
|
|
31
|
+
version: bool = typer.Option(
|
|
32
|
+
False, "--version", "-v", callback=version_callback, is_eager=True,
|
|
33
|
+
help="Показать версию",
|
|
34
|
+
),
|
|
35
|
+
):
|
|
36
|
+
"""syscheck — диагностика системы из терминала.
|
|
37
|
+
|
|
38
|
+
Запуск без аргументов открывает интерактивную оболочку (TUI).
|
|
39
|
+
syscheck <команда> — одиночная проверка. --help по командам.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# Импорт и регистрация команд
|
|
44
|
+
from syscheck.commands.cpu import cpu_cmd
|
|
45
|
+
from syscheck.commands.ram import ram_cmd
|
|
46
|
+
from syscheck.commands.disk import disk_cmd
|
|
47
|
+
from syscheck.commands.net import net_cmd
|
|
48
|
+
from syscheck.commands.proc import proc_cmd
|
|
49
|
+
from syscheck.commands.temp import temp_cmd
|
|
50
|
+
from syscheck.commands.sys import sys_cmd
|
|
51
|
+
from syscheck.commands.watch import watch_cmd
|
|
52
|
+
|
|
53
|
+
app.command("cpu")(cpu_cmd)
|
|
54
|
+
app.command("ram")(ram_cmd)
|
|
55
|
+
app.command("disk")(disk_cmd)
|
|
56
|
+
app.command("net")(net_cmd)
|
|
57
|
+
app.command("proc")(proc_cmd)
|
|
58
|
+
app.command("temp")(temp_cmd)
|
|
59
|
+
app.command("sys")(sys_cmd)
|
|
60
|
+
app.command("watch")(watch_cmd)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@app.command("all")
|
|
64
|
+
def all_cmd(
|
|
65
|
+
output_json: bool = typer.Option(False, "--json", "-j", help="Вывод в JSON"),
|
|
66
|
+
):
|
|
67
|
+
"""Показать все метрики системы."""
|
|
68
|
+
from syscheck.commands.cpu import show_cpu
|
|
69
|
+
from syscheck.commands.ram import show_ram
|
|
70
|
+
from syscheck.commands.disk import show_disk
|
|
71
|
+
from syscheck.commands.net import show_net
|
|
72
|
+
from syscheck.commands.temp import show_temp
|
|
73
|
+
from syscheck.commands.sys import show_sys
|
|
74
|
+
|
|
75
|
+
console.rule("syscheck — Полная диагностика")
|
|
76
|
+
console.print()
|
|
77
|
+
show_cpu()
|
|
78
|
+
console.print()
|
|
79
|
+
show_ram()
|
|
80
|
+
console.print()
|
|
81
|
+
show_disk()
|
|
82
|
+
console.print()
|
|
83
|
+
show_net()
|
|
84
|
+
console.print()
|
|
85
|
+
show_temp()
|
|
86
|
+
console.print()
|
|
87
|
+
show_sys()
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@app.command("plugins")
|
|
91
|
+
def plugins_cmd(
|
|
92
|
+
list_plugins: bool = typer.Option(False, "--list", "-l", help="Список плагинов"),
|
|
93
|
+
plugin_name: str = typer.Option(None, "--run", "-r", help="Запустить плагин"),
|
|
94
|
+
):
|
|
95
|
+
"""Управление плагинами."""
|
|
96
|
+
plugins = get_plugins()
|
|
97
|
+
|
|
98
|
+
if not plugins:
|
|
99
|
+
console.print("[dim]Плагины не найдены. Добавьте .py файлы в syscheck/plugins/[/]")
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
if list_plugins or (not plugin_name):
|
|
103
|
+
from rich.table import Table
|
|
104
|
+
table = Table(title="Доступные плагины", show_header=True, header_style="bold cyan")
|
|
105
|
+
table.add_column("Имя", style="green")
|
|
106
|
+
table.add_column("Описание")
|
|
107
|
+
table.add_column("Версия", style="dim")
|
|
108
|
+
for name, p in plugins.items():
|
|
109
|
+
table.add_row(name, p.description, p.version)
|
|
110
|
+
console.print(table)
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
if plugin_name:
|
|
114
|
+
plugin = plugins.get(plugin_name)
|
|
115
|
+
if plugin:
|
|
116
|
+
plugin.execute()
|
|
117
|
+
else:
|
|
118
|
+
console.print(f"[red]Плагин '{plugin_name}' не найден.[/]")
|
|
119
|
+
console.print(f"Доступные: {', '.join(plugins.keys())}")
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def main():
|
|
123
|
+
"""Точка входа: без аргументов → TUI, с аргументами → CLI."""
|
|
124
|
+
enable_shell = False
|
|
125
|
+
args = list(sys.argv[1:])
|
|
126
|
+
if "--enable-shell" in args:
|
|
127
|
+
enable_shell = True
|
|
128
|
+
args.remove("--enable-shell")
|
|
129
|
+
sys.argv = [sys.argv[0]] + args
|
|
130
|
+
if len(args) <= 0:
|
|
131
|
+
from syscheck.tui import run as run_tui
|
|
132
|
+
run_tui(enable_shell=enable_shell)
|
|
133
|
+
else:
|
|
134
|
+
app()
|
syscheck/cmdlang.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Командный язык syscheck.
|
|
2
|
+
|
|
3
|
+
Грамматика разделена на группы:
|
|
4
|
+
|
|
5
|
+
Info cpu, ram, gpu, disk, battery, temp
|
|
6
|
+
Processes process list/find/kill/stop/resume/restart/details/sort
|
|
7
|
+
Network network (speeds/interfaces/connections/ping)
|
|
8
|
+
Display watch, refresh
|
|
9
|
+
System system, all, help, clear, exit
|
|
10
|
+
|
|
11
|
+
Список COMMANDS — единый источник: он же питает Command Palette (Ctrl+P)
|
|
12
|
+
и справку `help`. Обработчики реализованы в TUI как методы `_cmd_*`;
|
|
13
|
+
этот модуль только описывает команды и генерирует текст справки/палитры.
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
CATEGORIES = ["Info", "Processes", "Network", "Battery", "Display", "System"]
|
|
18
|
+
|
|
19
|
+
# (команда, категория, описание, нужен ли фоновый поток)
|
|
20
|
+
COMMANDS = [
|
|
21
|
+
("cpu", "Info", "CPU information", True),
|
|
22
|
+
("ram", "Info", "Memory information", True),
|
|
23
|
+
("gpu", "Info", "GPU information", True),
|
|
24
|
+
("disk", "Info", "disk list | info X: | io", True),
|
|
25
|
+
("battery", "Battery", "Battery status", True),
|
|
26
|
+
("temp", "Info", "Temperatures", True),
|
|
27
|
+
("network", "Network", "network | connections | interfaces | speeds | ping", True),
|
|
28
|
+
("net", "Network", "alias: net [host]", True),
|
|
29
|
+
("process", "Processes", "process list | find | kill | stop | resume | restart | details | sort", True),
|
|
30
|
+
("proc", "Processes", "top processes by cpu|ram|name", True),
|
|
31
|
+
("system", "System", "System information", True),
|
|
32
|
+
("all", "System", "everything at once", True),
|
|
33
|
+
("watch", "Display", "watch cpu | network | process [name] | off", False),
|
|
34
|
+
("settings", "Display", "show settings & config path", False),
|
|
35
|
+
("refresh", "Display", "refresh dashboard now", False),
|
|
36
|
+
("help", "System", "show this help", False),
|
|
37
|
+
("clear", "System", "clear terminal output", False),
|
|
38
|
+
("exit", "System", "quit (alias: quit)", False),
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
# скрытые алиасы (не показываются в справке/палитре)
|
|
42
|
+
_ALIASES = {
|
|
43
|
+
"quit": ("exit", ""),
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def command_list() -> list:
|
|
48
|
+
"""Полный список команд палитры (без алиасов)."""
|
|
49
|
+
out = []
|
|
50
|
+
for cmd, cat, desc, thread in COMMANDS:
|
|
51
|
+
out.append({"cmd": cmd, "cat": cat, "desc": desc, "thread": thread})
|
|
52
|
+
return out
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def lookup(words: list) -> dict | None:
|
|
56
|
+
"""Находит запись команды по первым словам (с учётом алиасов)."""
|
|
57
|
+
first = (words[0] if words else "").lower()
|
|
58
|
+
if first in _ALIASES:
|
|
59
|
+
cmd, _ = _ALIASES[first]
|
|
60
|
+
else:
|
|
61
|
+
cmd = first
|
|
62
|
+
for c, cat, desc, thread in COMMANDS:
|
|
63
|
+
if c == cmd:
|
|
64
|
+
return {"cmd": c, "cat": cat, "desc": desc, "thread": thread}
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def expected_args(cmd: str) -> str:
|
|
69
|
+
"""Подсказка по аргументам команды (для справки по одной команде)."""
|
|
70
|
+
guide = {
|
|
71
|
+
"disk": "disk list | disk info C: | disk io",
|
|
72
|
+
"network": "network | network connections | network interfaces | network speeds | network ping <host>",
|
|
73
|
+
"net": "net [host]",
|
|
74
|
+
"process": "process list [name] | process find <name|pid> | process sort cpu|ram|name |"
|
|
75
|
+
" process details <name|pid> | process kill|stop|resume|restart <name|pid>",
|
|
76
|
+
"proc": "proc cpu | proc ram | proc name",
|
|
77
|
+
"watch": "watch cpu | watch network | watch process [name] | watch off",
|
|
78
|
+
"cpu": "cpu",
|
|
79
|
+
"ram": "ram",
|
|
80
|
+
"gpu": "gpu",
|
|
81
|
+
"battery": "battery",
|
|
82
|
+
"temp": "temp",
|
|
83
|
+
"system": "system",
|
|
84
|
+
"all": "all",
|
|
85
|
+
"settings": "settings",
|
|
86
|
+
"refresh": "refresh",
|
|
87
|
+
}
|
|
88
|
+
return guide.get(cmd, "")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def help_text() -> str:
|
|
92
|
+
"""Полная справка по группам."""
|
|
93
|
+
lines = ["[bold]syscheck commands[/]"]
|
|
94
|
+
for cat in CATEGORIES:
|
|
95
|
+
items = [(c, d, t) for c, cc, d, t in COMMANDS if cc == cat]
|
|
96
|
+
if not items:
|
|
97
|
+
continue
|
|
98
|
+
lines.append(f" [dim]{cat}[/]")
|
|
99
|
+
for c, d, t in items:
|
|
100
|
+
lines.append(f" [cyan]{c:<10}[/] {d}")
|
|
101
|
+
lines.append("")
|
|
102
|
+
lines.append("[dim]ctrl+p command palette ↑↓ history · in processes table:[/]")
|
|
103
|
+
lines.append("[dim] Enter details, K kill, S stop, R restart, / search[/]")
|
|
104
|
+
return "\n".join(lines)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def help_for(cmd: str) -> str:
|
|
108
|
+
args = expected_args(cmd)
|
|
109
|
+
return f"[cyan]{cmd}[/] {args}" if args else f"[dim]nothing more about '{cmd}'[/]"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from syscheck.commands.cpu import cpu_cmd
|
|
2
|
+
from syscheck.commands.ram import ram_cmd
|
|
3
|
+
from syscheck.commands.disk import disk_cmd
|
|
4
|
+
from syscheck.commands.net import net_cmd
|
|
5
|
+
from syscheck.commands.proc import proc_cmd
|
|
6
|
+
from syscheck.commands.temp import temp_cmd
|
|
7
|
+
from syscheck.commands.sys import sys_cmd
|
|
8
|
+
from syscheck.commands.watch import watch_cmd
|
syscheck/commands/cpu.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Команда syscheck cpu — диагностика процессора."""
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from syscheck import providers
|
|
7
|
+
from syscheck.utils import console, make_bar, print_value, print_header, get_color_for_percent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def show_cpu() -> None:
|
|
11
|
+
data = providers.cpu_info()
|
|
12
|
+
per_cpu = data["per_cpu"] or []
|
|
13
|
+
|
|
14
|
+
print_header("cpu")
|
|
15
|
+
|
|
16
|
+
colour = get_color_for_percent(data["percent"])
|
|
17
|
+
console.print(
|
|
18
|
+
f"load [{colour}]{data['percent']}%[/] {make_bar(data['percent'])}"
|
|
19
|
+
)
|
|
20
|
+
print_value("cores", f"{data['count_physical'] or '?'} physical / {data['count_logical']} logical")
|
|
21
|
+
if data["freq_current"]:
|
|
22
|
+
freq = f"{data['freq_current']:.0f} MHz"
|
|
23
|
+
if data["freq_max"]:
|
|
24
|
+
freq += f" (max {data['freq_max']:.0f} MHz)"
|
|
25
|
+
print_value("freq", freq)
|
|
26
|
+
if per_cpu:
|
|
27
|
+
parts = " ".join(
|
|
28
|
+
f"[{get_color_for_percent(p)}]{i}: {p:.0f}[/]" for i, p in enumerate(per_cpu)
|
|
29
|
+
)
|
|
30
|
+
console.print(f"per-core {parts}")
|
|
31
|
+
|
|
32
|
+
if data["percent"] >= 90:
|
|
33
|
+
console.print(" [red][!] CPU сильно нагружен[/]")
|
|
34
|
+
elif data["percent"] >= 75:
|
|
35
|
+
console.print(" [yellow][!] CPU заметно нагружен[/]")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def cpu_cmd(
|
|
39
|
+
output_json: bool = typer.Option(False, "--json", "-j", help="Вывод в JSON"),
|
|
40
|
+
):
|
|
41
|
+
"""Диагностика процессора (нагрузка, ядра, частота)."""
|
|
42
|
+
if output_json:
|
|
43
|
+
console.print_json(json.dumps(providers.cpu_info(), indent=2))
|
|
44
|
+
else:
|
|
45
|
+
show_cpu()
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Команда syscheck disk — диагностика дисков."""
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from syscheck import providers
|
|
7
|
+
from syscheck.utils import (
|
|
8
|
+
console, make_bar, print_value, print_header, get_color_for_percent, bytes_to_human,
|
|
9
|
+
print_error, print_warning,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def show_disk() -> None:
|
|
14
|
+
data = providers.disk_info()
|
|
15
|
+
|
|
16
|
+
print_header("disks")
|
|
17
|
+
|
|
18
|
+
if not data["disks"]:
|
|
19
|
+
console.print("[dim]диски не найдены[/]")
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
for d in data["disks"]:
|
|
23
|
+
colour = get_color_for_percent(d["percent"])
|
|
24
|
+
console.print(
|
|
25
|
+
f"{d['device'].ljust(8)} [{colour}]{d['percent']:4.1f}%[/] {make_bar(d['percent'])} "
|
|
26
|
+
f"{bytes_to_human(d['used'])} / {bytes_to_human(d['total'])} "
|
|
27
|
+
f"(free {bytes_to_human(d['free'])})"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
io = data["io"]
|
|
31
|
+
if io:
|
|
32
|
+
console.print()
|
|
33
|
+
print_header("io")
|
|
34
|
+
print_value("read", bytes_to_human(io.read_bytes))
|
|
35
|
+
print_value("write", bytes_to_human(io.write_bytes))
|
|
36
|
+
|
|
37
|
+
for d in data["disks"]:
|
|
38
|
+
if d["percent"] > 95:
|
|
39
|
+
print_error(f"{d['device']}: критически мало места!")
|
|
40
|
+
elif d["percent"] > 85:
|
|
41
|
+
print_warning(f"{d['device']}: мало свободного места")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def disk_cmd(
|
|
45
|
+
output_json: bool = typer.Option(False, "--json", "-j", help="Вывод в JSON"),
|
|
46
|
+
):
|
|
47
|
+
"""Диагностика дисков (место, состояние, I/O)."""
|
|
48
|
+
if output_json:
|
|
49
|
+
out = []
|
|
50
|
+
for d in providers.disk_info()["disks"]:
|
|
51
|
+
out.append({
|
|
52
|
+
"device": d["device"],
|
|
53
|
+
"mountpoint": d["mountpoint"],
|
|
54
|
+
"total": d["total"],
|
|
55
|
+
"used": d["used"],
|
|
56
|
+
"free": d["free"],
|
|
57
|
+
"percent": d["percent"],
|
|
58
|
+
})
|
|
59
|
+
console.print_json(json.dumps(out, indent=2))
|
|
60
|
+
else:
|
|
61
|
+
show_disk()
|
syscheck/commands/net.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""Команда syscheck net — диагностика сети."""
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from syscheck import providers
|
|
7
|
+
from syscheck.utils import (
|
|
8
|
+
console, print_value, print_header, bytes_to_human, print_ok, print_warning, print_error,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def show_net(ping_host: str = "8.8.8.8") -> None:
|
|
13
|
+
data = providers.net_info()
|
|
14
|
+
|
|
15
|
+
print_header("network")
|
|
16
|
+
|
|
17
|
+
for i in data["interfaces"]:
|
|
18
|
+
status = "[green]up[/]" if i["up"] else "[dim]down[/]"
|
|
19
|
+
ip = ", ".join(i["ipv4"]) if i["ipv4"] else "[dim]N/A[/]"
|
|
20
|
+
speed = f"{i['speed']} Mbps" if i["speed"] else "-"
|
|
21
|
+
console.print(f" {status} {i['name'].ljust(28)} {ip.ljust(18)} {speed}")
|
|
22
|
+
if i["mac"]:
|
|
23
|
+
console.print(f"{'':2} mac: {', '.join(i['mac'])}")
|
|
24
|
+
|
|
25
|
+
io = data["io"]
|
|
26
|
+
if io:
|
|
27
|
+
console.print()
|
|
28
|
+
print_value("sent", bytes_to_human(io["bytes_sent"]))
|
|
29
|
+
print_value("recv", bytes_to_human(io["bytes_recv"]))
|
|
30
|
+
print_value("packets", f"{io['packets_sent']} / {io['packets_recv']}")
|
|
31
|
+
|
|
32
|
+
console.print()
|
|
33
|
+
print_header(f"ping {ping_host}")
|
|
34
|
+
result = providers.ping(ping_host)
|
|
35
|
+
if result["success"]:
|
|
36
|
+
if result["avg_ms"] is not None:
|
|
37
|
+
print_ok(f"{result['host']}: {result['avg_ms']:.1f} ms "
|
|
38
|
+
f"(min {result['min_ms']:.1f}, max {result['max_ms']:.1f})")
|
|
39
|
+
else:
|
|
40
|
+
print_ok(f"{result['host']}: пинг прошёл (время не определено)")
|
|
41
|
+
if result["lost"] > 0:
|
|
42
|
+
print_warning(f"потеряно {result['lost']}/{result['total']} пакетов")
|
|
43
|
+
if result["avg_ms"] and result["avg_ms"] > 100:
|
|
44
|
+
print_warning("высокий пинг")
|
|
45
|
+
else:
|
|
46
|
+
print_error(f"{ping_host}: недоступен")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def net_cmd(
|
|
50
|
+
output_json: bool = typer.Option(False, "--json", "-j", help="Вывод в JSON"),
|
|
51
|
+
ping_host: str = typer.Option("8.8.8.8", "--ping", "-p", help="Хост для пинга"),
|
|
52
|
+
):
|
|
53
|
+
"""Диагностика сети (интерфейсы, трафик, пинг)."""
|
|
54
|
+
if output_json:
|
|
55
|
+
data = providers.net_info()
|
|
56
|
+
data["ping"] = providers.ping(ping_host)
|
|
57
|
+
console.print_json(json.dumps(data, indent=2))
|
|
58
|
+
else:
|
|
59
|
+
show_net(ping_host)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Команда syscheck proc — топ процессов."""
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from syscheck import providers
|
|
7
|
+
from syscheck.utils import console, print_header, get_color_for_percent
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def show_proc(sort_by: str = "cpu", limit: int = 15) -> None:
|
|
11
|
+
data = providers.processes(sort_by=sort_by, limit=limit)
|
|
12
|
+
|
|
13
|
+
print_header(f"processes by {sort_by}")
|
|
14
|
+
|
|
15
|
+
for p in data:
|
|
16
|
+
cpu = p.get("cpu_percent") or 0
|
|
17
|
+
ram = p.get("memory_percent") or 0
|
|
18
|
+
status = p.get("status", "?")
|
|
19
|
+
console.print(
|
|
20
|
+
f" {p['pid']:<8} {(p['name'] or '?')[:28]:<28} "
|
|
21
|
+
f"cpu[{get_color_for_percent(cpu)}]{cpu:5.1f}[/] "
|
|
22
|
+
f"ram[{get_color_for_percent(ram)}]{ram:5.1f}[/] {status}"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def proc_cmd(
|
|
27
|
+
output_json: bool = typer.Option(False, "--json", "-j", help="Вывод в JSON"),
|
|
28
|
+
sort_by: str = typer.Option("cpu", "--sort", "-s", help="Сортировка: cpu, ram, name"),
|
|
29
|
+
limit: int = typer.Option(15, "--limit", "-n", help="Количество процессов"),
|
|
30
|
+
):
|
|
31
|
+
"""Топ процессов по CPU/RAM."""
|
|
32
|
+
if output_json:
|
|
33
|
+
console.print_json(json.dumps(providers.processes(sort_by=sort_by, limit=limit), indent=2))
|
|
34
|
+
else:
|
|
35
|
+
show_proc(sort_by, limit)
|
syscheck/commands/ram.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Команда syscheck ram — диагностика оперативной памяти."""
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from syscheck import providers
|
|
7
|
+
from syscheck.utils import (
|
|
8
|
+
console, make_bar, print_value, print_header, get_color_for_percent, bytes_to_human,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def show_ram() -> None:
|
|
13
|
+
data = providers.ram_info()
|
|
14
|
+
|
|
15
|
+
print_header("ram")
|
|
16
|
+
|
|
17
|
+
colour = get_color_for_percent(data["percent"])
|
|
18
|
+
console.print(
|
|
19
|
+
f"usage [{colour}]{data['percent']}%[/] {make_bar(data['percent'])}"
|
|
20
|
+
)
|
|
21
|
+
print_value("used", f"{bytes_to_human(data['used'])} / {bytes_to_human(data['total'])}")
|
|
22
|
+
print_value("free", bytes_to_human(data["available"]))
|
|
23
|
+
if data["swap_total"] > 0:
|
|
24
|
+
print_value("swap", f"{data['swap_percent']}% ({bytes_to_human(data['swap_total'])})")
|
|
25
|
+
|
|
26
|
+
if data["top_procs"]:
|
|
27
|
+
console.print()
|
|
28
|
+
print_header("top by ram")
|
|
29
|
+
for p in data["top_procs"]:
|
|
30
|
+
pct = p["memory_percent"] or 0
|
|
31
|
+
pc = get_color_for_percent(pct)
|
|
32
|
+
console.print(
|
|
33
|
+
f" {p['pid']:<8} {(p['name'] or '?')[:28]:<28} [{pc}]{pct:5.1f}%[/]"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if data["percent"] >= 90:
|
|
37
|
+
console.print(" [red][!] память почти исчерпана[/]")
|
|
38
|
+
elif data["percent"] >= 85:
|
|
39
|
+
console.print(" [yellow][!] много памяти используется[/]")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def ram_cmd(
|
|
43
|
+
output_json: bool = typer.Option(False, "--json", "-j", help="Вывод в JSON"),
|
|
44
|
+
):
|
|
45
|
+
"""Диагностика оперативной памяти."""
|
|
46
|
+
if output_json:
|
|
47
|
+
console.print_json(json.dumps(providers.ram_info(), indent=2))
|
|
48
|
+
else:
|
|
49
|
+
show_ram()
|
syscheck/commands/sys.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Команда syscheck sys — информация о системе."""
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from syscheck import providers
|
|
7
|
+
from syscheck.utils import (
|
|
8
|
+
console, print_header, print_value, seconds_to_human, get_color_for_percent, bytes_to_human,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def show_sys() -> None:
|
|
13
|
+
data = providers.system_info()
|
|
14
|
+
|
|
15
|
+
print_header("system")
|
|
16
|
+
|
|
17
|
+
print_value("os", f"{data['os']} ({data['machine']})")
|
|
18
|
+
print_value("hostname", data["node"])
|
|
19
|
+
print_value("processor", data["processor"])
|
|
20
|
+
print_value("uptime", f"{seconds_to_human(data['uptime_seconds'])} (since {data['boot_datetime']})")
|
|
21
|
+
print_value("python", data["python"])
|
|
22
|
+
if data["users"]:
|
|
23
|
+
print_value("users", ", ".join(data["users"]))
|
|
24
|
+
|
|
25
|
+
console.print()
|
|
26
|
+
quick = providers.system_quick()
|
|
27
|
+
print_header("summary")
|
|
28
|
+
console.print(f"cpu [{get_color_for_percent(quick['cpu_percent'])}]{quick['cpu_percent']}%[/]")
|
|
29
|
+
console.print(f"ram [{get_color_for_percent(quick['ram_percent'])}]{quick['ram_percent']}%[/] (free {bytes_to_human(quick['ram_available'])})")
|
|
30
|
+
if quick.get("main_percent") is not None:
|
|
31
|
+
console.print(f"disk [{get_color_for_percent(quick['main_percent'])}]{quick['main_percent']}%[/] (free {bytes_to_human(quick['main_free'])})")
|
|
32
|
+
if quick.get("net_sent") is not None:
|
|
33
|
+
console.print(f"network sent {bytes_to_human(quick['net_sent'])} recv {bytes_to_human(quick['net_recv'])}")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def sys_cmd(
|
|
37
|
+
output_json: bool = typer.Option(False, "--json", "-j", help="Вывод в JSON"),
|
|
38
|
+
):
|
|
39
|
+
"""Информация о системе (ОС, uptime, hostname)."""
|
|
40
|
+
if output_json:
|
|
41
|
+
console.print_json(json.dumps(providers.system_info(), indent=2))
|
|
42
|
+
else:
|
|
43
|
+
show_sys()
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Команда syscheck temp — температура сенсоров."""
|
|
2
|
+
import json
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from syscheck import providers
|
|
7
|
+
from syscheck.utils import console, print_header, print_value, print_warning, print_error
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def show_temp() -> None:
|
|
11
|
+
data = providers.temperatures()
|
|
12
|
+
|
|
13
|
+
print_header("temperatures")
|
|
14
|
+
|
|
15
|
+
if not data["available"]:
|
|
16
|
+
console.print("[dim]температуры не поддерживаются на этой системе через psutil[/]")
|
|
17
|
+
console.print("[dim](hwinfo / coretemp на windows)[/]")
|
|
18
|
+
return
|
|
19
|
+
|
|
20
|
+
printed = False
|
|
21
|
+
for name, entries in data["temps"].items():
|
|
22
|
+
for e in entries:
|
|
23
|
+
current = e.current
|
|
24
|
+
high = e.high
|
|
25
|
+
critical = e.critical
|
|
26
|
+
printed = True
|
|
27
|
+
print_value(name, f"{current:.0f}°C")
|
|
28
|
+
if high and current >= high:
|
|
29
|
+
print_warning(f"{name}: выше нормы ({current}°C >= {high}°C)")
|
|
30
|
+
if critical and current >= critical:
|
|
31
|
+
print_error(f"{name}: критическая! ({current}°C >= {critical}°C)")
|
|
32
|
+
if not printed:
|
|
33
|
+
console.print("[dim]датчиков температуры нет[/]")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def temp_cmd(
|
|
37
|
+
output_json: bool = typer.Option(False, "--json", "-j", help="Вывод в JSON"),
|
|
38
|
+
):
|
|
39
|
+
"""Температура сенсоров (CPU, GPU, SSD)."""
|
|
40
|
+
if output_json:
|
|
41
|
+
data = providers.temperatures()
|
|
42
|
+
out = {}
|
|
43
|
+
for name, entries in data["temps"].items():
|
|
44
|
+
out[name] = [
|
|
45
|
+
{"current": e.current, "high": e.high, "critical": e.critical}
|
|
46
|
+
for e in entries
|
|
47
|
+
]
|
|
48
|
+
console.print_json(json.dumps(out, indent=2))
|
|
49
|
+
else:
|
|
50
|
+
show_temp()
|