system-monitor
/
monitor.py
343 строки · 13.1 Кб
1import psutil2import platform3import tkinter as tk4from tkinter import ttk, filedialog5import os6import datetime7import threading8import pynvml9
10# Initialize NVML
11pynvml.nvmlInit()12
13output_file = "" # Глобальная переменная для пути сохранения файла14dynamic_update_enabled = False # Флаг для отслеживания состояния динамического обновления15current_sort_column = 'PID' # Колонка, по которой сортируем в данный момент16current_sort_order = 'asc' # Порядок сортировки: 'asc' или 'desc'17
18def clear_console():19os.system('cls' if os.name == 'nt' else 'clear')20
21
22def sort_processes(processes):23global current_sort_column, current_sort_order24if current_sort_column == 'PID':25processes.sort(key=lambda x: int(x[0]), reverse=(current_sort_order == 'desc'))26else:27index = {'Name': 1, 'Memory%': 2, 'CPU%': 3}[current_sort_column]28processes.sort(key=lambda x: x[index], reverse=(current_sort_order == 'desc'))29return processes30
31
32def update_treeview(tree, processes):33selected_item = tree.selection()34selected_pid = None35if selected_item:36selected_pid = tree.item(selected_item, 'values')[0]37
38for row in tree.get_children():39tree.delete(row)40
41for pid, name, memory_percent, cpu_percent in processes:42tree.insert('', 'end', values=(pid, name, f"{memory_percent:.2f}", f"{cpu_percent:.2f}"))43
44if selected_pid:45for row in tree.get_children():46if tree.item(row, 'values')[0] == selected_pid:47tree.selection_set(row)48break49
50
51def fetch_system_usage():52cpu_usage = psutil.cpu_percent()53memory_info = psutil.virtual_memory()54disk_usage = psutil.disk_usage('/')55net_io = psutil.net_io_counters()56return cpu_usage, memory_info, disk_usage, net_io57
58
59def fetch_processes():60processes = []61for proc in psutil.process_iter(['pid', 'name', 'memory_percent', 'cpu_percent']):62try:63processes.append(64(proc.info['pid'], proc.info['name'], proc.info['memory_percent'], proc.info['cpu_percent']))65except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):66continue67return processes68
69
70def update_system_info():71cpu_usage, memory_info, disk_usage, net_io = fetch_system_usage()72
73# Добавляем информацию о процессоре74cpu_info = f"Processor: {platform.processor()}"75
76# Добавляем информацию об оперативной памяти77memory_info_str = f"Memory: Total = {get_size(memory_info.total)}, " \78f"Available = {get_size(memory_info.available)}"79
80# Добавляем информацию о видеокарте81gpu_info = ""82try:83import pynvml84
85pynvml.nvmlInit()86device_count = pynvml.nvmlDeviceGetCount()87for i in range(device_count):88handle = pynvml.nvmlDeviceGetHandleByIndex(i)89gpu_name = pynvml.nvmlDeviceGetName(handle)90mem_info = pynvml.nvmlDeviceGetMemoryInfo(handle)91gpu_info += f"GPU {i + 1}: {gpu_name}, Memory Total = {get_size(mem_info.total)}, " \92f"Memory Used = {get_size(mem_info.used)}, Memory Free = {get_size(mem_info.free)}\n"93
94pynvml.nvmlShutdown()95except ImportError:96gpu_info = "pynvml library not found"97
98# Добавляем информацию о дисках99disk_info = ""100partitions = psutil.disk_partitions()101for partition in partitions:102try:103partition_usage = psutil.disk_usage(partition.mountpoint)104disk_info += f"Device: {partition.device}, Total: {get_size(partition_usage.total)}, " \105f"Used: {get_size(partition_usage.used)}, Free: {get_size(partition_usage.free)}\n"106except PermissionError:107disk_info = "Permission denied"108
109# Добавляем информацию об операционной системе110os_info = f"OS: {platform.system()} {platform.release()} {platform.version()}"111
112label_cpu.config(text=f"CPU Usage: {cpu_usage}%\n{cpu_info}")113label_memory.config(text=f"Memory Usage: {memory_info.percent}%\n{memory_info_str}")114label_disk.config(text=f"Disk Usage: {disk_usage.percent}%\n{disk_info}")115label_network.config(116text=f"Network: Sent = {net_io.bytes_sent / (1024 * 1024):.2f} MB, Received = {net_io.bytes_recv / (1024 * 1024):.2f} MB")117label_gpu.config(text=gpu_info)118label_os.config(text=os_info)119
120
121def get_size(bytes, suffix="B"):122# Функция для форматирования размера файла123factor = 1024124for unit in ["", "K", "M", "G", "T", "P"]:125if bytes < factor:126return f"{bytes:.2f} {unit}{suffix}"127bytes /= factor128return f"{bytes:.2f} {unit}{suffix}"129
130
131def refresh_data():132processes = fetch_processes()133processes = sort_processes(processes)134update_treeview(tree, processes)135
136
137def print_system_usage():138global dynamic_update_enabled139
140update_system_info()141refresh_data()142
143if dynamic_update_enabled:144root.after(1000, print_system_usage) # Планируем следующее обновление через 1 секунду145
146
147def toggle_dynamic_update():148global dynamic_update_enabled149dynamic_update_enabled = not dynamic_update_enabled150if dynamic_update_enabled:151label_update_status.config(text="Dynamic update: ON", foreground="green")152btn_toggle_update.config(text="Disable Dynamic Update\n(Enabled)")153btn_manual_update.config(state=tk.DISABLED) # Блокируем кнопку ручного обновления154threading.Thread(target=print_system_usage).start() # Начать динамическое обновление в отдельном потоке155else:156label_update_status.config(text="Dynamic update: OFF", foreground="red")157btn_toggle_update.config(text="Enable Dynamic Update\n(Disabled)")158btn_manual_update.config(state=tk.NORMAL) # Разблокируем кнопку ручного обновления159
160
161def manual_update():162if dynamic_update_enabled:163return # Игнорируем ручное обновление, если включено динамическое164update_system_info() # Обновляем системную информацию165refresh_data() # Выполняем ручное обновление данных166
167
168def save_to_file():169global output_file170if not output_file:171output_file = filedialog.asksaveasfilename(defaultextension=".txt",172filetypes=[("Text files", "*.txt"), ("All files", "*.*")])173if not output_file:174return # Если пользователь отменил выбор файла, выходим из функции175
176current_datetime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")177
178with open(output_file, 'a') as f:179f.write(f"=== Data saved at {current_datetime} ===\n")180f.write(label_cpu.cget("text") + "\n")181f.write(label_memory.cget("text") + "\n")182f.write(label_disk.cget("text") + "\n")183f.write(label_network.cget("text") + "\n")184f.write(label_gpu.cget("text") + "\n")185f.write(label_os.cget("text") + "\n")186
187f.write("\nRunning Processes:\n")188f.write(f"{'PID':>6} {'Name':<25} {'Memory%':>8} {'CPU%':>8}\n")189
190processes = fetch_processes()191for pid, name, memory_percent, cpu_percent in processes:192f.write(f"{pid:6} {name:<25} {memory_percent:>8.2f} {cpu_percent:>8.2f}\n")193
194f.write("\n")195
196
197def sort_column(tree, col):198global current_sort_column, current_sort_order199if current_sort_column == col:200current_sort_order = 'desc' if current_sort_order == 'asc' else 'asc'201else:202current_sort_column = col203current_sort_order = 'asc'204refresh_data()205update_column_headings(tree)206
207
208def update_column_headings(tree):209columns = ['PID', 'Name', 'Memory%', 'CPU%']210for col in columns:211heading = col212if col == current_sort_column:213if current_sort_order == 'asc':214heading += " ↑"215else:216heading += " ↓"217tree.heading(col, text=heading, command=lambda _col=col: sort_column(tree, _col))218
219
220def kill_process():221selected_item = tree.selection()222if not selected_item:223return224
225pid = tree.item(selected_item, 'values')[0]226try:227pid = int(pid) # Преобразуем PID к целочисленному типу228if pid < 0:229return230
231proc = psutil.Process(pid)232
233# Рекурсивно завершаем процесс и все его дочерние процессы234for child in proc.children(recursive=True):235child.terminate()236proc.terminate()237
238refresh_data()239except ValueError:240print(f"Invalid PID value: {pid}")241except psutil.NoSuchProcess:242print(f"Process with PID {pid} no longer exists.")243except psutil.AccessDenied:244print(f"Access to terminate process with PID {pid} denied.")245
246
247# Создание GUI
248root = tk.Tk()249root.title("System Monitor")250
251# Фрейм с основными метками
252frame_stats = ttk.Frame(root, padding="10")253frame_stats.grid(row=0, column=0, sticky="nsew")254
255label_cpu = ttk.Label(frame_stats, text="CPU Usage: ")256label_cpu.grid(row=0, column=0, sticky="w")257
258label_memory = ttk.Label(frame_stats, text="Memory Usage: ")259label_memory.grid(row=1, column=0, sticky="w")260
261label_disk = ttk.Label(frame_stats, text="Disk Usage: ")262label_disk.grid(row=2, column=0, sticky="w")263
264label_network = ttk.Label(frame_stats, text="Network: ")265label_network.grid(row=3, column=0, sticky="w")266
267# Добавляем метки для информации об устройствах
268label_gpu = ttk.Label(frame_stats, text="GPU: ")269label_gpu.grid(row=4, column=0, sticky="w")270
271label_os = ttk.Label(frame_stats, text="OS: ")272label_os.grid(row=5, column=0, sticky="w")273
274# Фрейм с таблицей процессов
275frame_processes = ttk.Frame(root, padding="10")276frame_processes.grid(row=0, column=1, sticky="nsew")277
278tree = ttk.Treeview(frame_processes, columns=('PID', 'Name', 'Memory%', 'CPU%'), show='headings')279tree.heading('PID', text='PID', command=lambda: sort_column(tree, 'PID'))280tree.heading('Name', text='Name', command=lambda: sort_column(tree, 'Name'))281tree.heading('Memory%', text='Memory%', command=lambda: sort_column(tree, 'Memory%'))282tree.heading('CPU%', text='CPU%', command=lambda: sort_column(tree, 'CPU%'))283
284tree.column('#0', stretch=tk.YES)285tree.column('#1', stretch=tk.YES)286tree.column('#2', stretch=tk.YES)287tree.column('#3', stretch=tk.YES)288tree.grid(row=0, column=0, sticky="nsew")289
290scrollbar = ttk.Scrollbar(frame_processes, orient="vertical", command=tree.yview)291scrollbar.grid(row=0, column=1, sticky='ns')292tree.configure(yscrollcommand=scrollbar.set)293
294# Кнопка для включения/отключения динамического обновления
295btn_toggle_update = ttk.Button(root, text="Enable Dynamic Update\n(Disabled)", command=toggle_dynamic_update)296btn_toggle_update.grid(row=1, column=0, sticky="ew", padx=10, pady=10)297
298# Кнопка для ручного обновления данных
299btn_manual_update = ttk.Button(root, text="Manual Update", command=manual_update)300btn_manual_update.grid(row=1, column=1, sticky="ew", padx=10, pady=10)301
302# Кнопка для завершения процесса
303btn_kill_process = ttk.Button(root, text="Kill Process", command=kill_process)304btn_kill_process.grid(row=1, column=2, sticky="ew", padx=10, pady=10)305
306# Метка для отображения состояния динамического обновления
307label_update_status = ttk.Label(root, text="Dynamic update: OFF", foreground="red")308label_update_status.grid(row=2, column=0, columnspan=3, pady=5)309
310# Настройка растягиваемости и выравнивания элементов
311root.columnconfigure(0, weight=1)312root.columnconfigure(1, weight=1)313root.columnconfigure(2, weight=1)314root.rowconfigure(0, weight=1)315
316frame_stats.columnconfigure(0, weight=1)317frame_stats.rowconfigure(0, weight=1)318frame_stats.rowconfigure(1, weight=1)319frame_stats.rowconfigure(2, weight=1)320frame_stats.rowconfigure(3, weight=1)321frame_stats.rowconfigure(4, weight=1)322frame_stats.rowconfigure(5, weight=1)323
324frame_processes.columnconfigure(0, weight=1)325frame_processes.rowconfigure(0, weight=1)326
327# При запуске приложения сразу подтягиваем данные
328print_system_usage() # Обновление данных329update_column_headings(tree) # Обновить заголовки столбцов330
331# Меню для выбора места сохранения лог-файла
332menu_bar = tk.Menu(root)333root.config(menu=menu_bar)334
335file_menu = tk.Menu(menu_bar, tearoff=0)336menu_bar.add_cascade(label="File", menu=file_menu)337file_menu.add_command(label="Save Log As...", command=save_to_file)338
339# Запуск основного цикла GUI
340root.mainloop()341
342# Выход из NVML
343pynvml.nvmlShutdown()344