diff --git a/builtins.o b/builtins.o index 955742f..ab12eaf 100644 Binary files a/builtins.o and b/builtins.o differ diff --git a/line_reader.o b/line_reader.o index f36bd1a..7c3f7f1 100644 Binary files a/line_reader.o and b/line_reader.o differ diff --git a/shell b/shell index c46f156..9ad45f1 100755 Binary files a/shell and b/shell differ diff --git a/shell.o b/shell.o index de0115f..6eb9aa1 100644 Binary files a/shell.o and b/shell.o differ diff --git a/system_monitor.c b/system_monitor.c index 66c8714..1e2b9c1 100644 --- a/system_monitor.c +++ b/system_monitor.c @@ -1,390 +1,830 @@ +#define _GNU_SOURCE #include "system_monitor.h" +#include "aliases.h" #include "common.h" +#include +#include +#include static struct termios old_termios; int builtin_monitor(char **args) { - SystemStats stats; - ProcessInfo processes[50]; - int proc_count; - int refresh_rate = 1; // seconds - - if (args[1] != NULL && strcmp(args[1], "--help") == 0) { - printf("monitor: Real-time system monitoring dashboard\n"); - printf("Usage: monitor [refresh_rate]\n"); - printf("Press 'q' to quit, 'r' to refresh immediately\n"); - return 1; - } - - if (args[1] != NULL) { - refresh_rate = atoi(args[1]); - if (refresh_rate < 1) refresh_rate = 1; - } - - // Setup terminal for raw input - tcgetattr(STDIN_FILENO, &old_termios); - struct termios new_termios = old_termios; - new_termios.c_lflag &= ~(ICANON | ECHO); - tcsetattr(STDIN_FILENO, TCSANOW, &new_termios); - - // Make stdin non-blocking - int flags = fcntl(STDIN_FILENO, F_GETFL, 0); - fcntl(STDIN_FILENO, F_SETFL, flags | O_NONBLOCK); - - // Enter alternative screen buffer - printf("\033[?1049h"); - hide_cursor(); - fflush(stdout); - - while (1) { - get_system_stats(&stats); - proc_count = get_process_info(processes, 50); - display_dashboard(&stats, processes, proc_count); - - // Check for user input - if (kbhit()) { - char c = getchar(); - if (c == 'q' || c == 'Q') break; - if (c == 'r' || c == 'R') continue; // refresh immediately - } - - sleep(refresh_rate); - } - - // Restore terminal - show_cursor(); - // Exit alternative screen buffer - printf("\033[?1049l"); - tcsetattr(STDIN_FILENO, TCSANOW, &old_termios); - fcntl(STDIN_FILENO, F_SETFL, flags); - fflush(stdout); + SystemStats stats; + ProcessInfo processes[500]; // Increased from 50 to 500 processes + int proc_count; + NCursesMonitor monitor; + int refresh_rate = 1; + + if (args[1] != NULL && strcmp(args[1], "--help") == 0) { + printf("monitor: Real-time system monitoring dashboard\n"); + printf("Usage: monitor [refresh_rate]\n"); + printf("Press 'q̈' to quit, 'r' to refresh, arrow keys to navigate\n"); return 1; + } + + if (args[1] != NULL) { + refresh_rate = atoi(args[1]); + if (refresh_rate < 1) + refresh_rate = 1; + } + + // init ncurses monitor + if (!init_ncurses_monitor(&monitor)) { + fprintf(stderr, "Failed to initialize ncurses monitor\n"); + return 1; + } + + monitor.refresh_rate = refresh_rate; + timeout(100); // Short timeout for responsive input, 100ms + + time_t last_update = 0; + + // Initial data load + get_system_stats(&stats); + proc_count = get_process_info(processes, 500); + last_update = time(NULL); + + while (1) { + time_t current_time = time(NULL); + + // Only update system stats at the specified refresh interval + if (current_time - last_update >= refresh_rate) { + get_system_stats(&stats); + proc_count = get_process_info(processes, 500); + last_update = current_time; + } + + // Always update the display (for navigation highlighting) + display_ncurses_dashboard(&monitor, &stats, processes, proc_count); + + int ch = getch(); + if (ch != ERR) { + // Check if we're in search mode first - if so, let search handle ALL input + if (monitor.search_mode) { + handle_monitor_input(&monitor, ch); + } else { + // Only process main UI commands when NOT in search mode + if (ch == 'q' || ch == 'Q') + break; + if (ch == 'r' || ch == 'R') { + // Force immediate refresh + get_system_stats(&stats); + proc_count = get_process_info(processes, 500); + last_update = current_time; + continue; + } + handle_monitor_input(&monitor, ch); + } + } + } + + cleanup_ncurses_monitor(&monitor); + return 1; } -void display_dashboard(SystemStats *stats, ProcessInfo *processes, int proc_count) { - char buffer[256]; - char display_buffer[8192]; // Large buffer for entire display - char progress_buf[50]; - char mem_used_str[32], mem_total_str[32]; - char disk_read_str[32], disk_write_str[32]; - char net_rx_str[32], net_tx_str[32]; - - time_t now = time(NULL); - struct tm *tm_info = localtime(&now); - - // Format all the data first - float mem_percent = (float)stats->memory_used / stats->memory_total * 100; - format_bytes(stats->memory_used, mem_used_str); - format_bytes(stats->memory_total, mem_total_str); - format_bytes(stats->disk_read, disk_read_str); - format_bytes(stats->disk_write, disk_write_str); - format_bytes(stats->net_rx, net_rx_str); - format_bytes(stats->net_tx, net_tx_str); - - // Build entire display in buffer - int pos = sprintf(display_buffer, "\033[H"); // Move to home position - - pos += sprintf(display_buffer + pos, - "╔══════════════════════════════════════════════════════════════════════════════╗\n" - "║ SYSTEM MONITOR DASHBOARD ║\n" - "║ %02d:%02d:%02d %02d/%02d/%04d ║\n" - "╠══════════════════════════════════════════════════════════════════════════════╣\n", - tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec, - tm_info->tm_mday, tm_info->tm_mon + 1, tm_info->tm_year + 1900); - - // CPU Usage - format_progress_bar((int)stats->cpu_percent, 40, progress_buf); - pos += sprintf(display_buffer + pos, "║ CPU Usage: %s %5.1f%% ║\n", progress_buf, stats->cpu_percent); - - // Memory Usage - format_progress_bar((int)mem_percent, 40, progress_buf); - pos += sprintf(display_buffer + pos, "║ Memory: %s %5.1f%% ║\n", progress_buf, mem_percent); - pos += sprintf(display_buffer + pos, "║ Used: %-15s / %-15s ║\n", - mem_used_str, mem_total_str); - - pos += sprintf(display_buffer + pos, - "║ Disk I/O: Read: %-20s ║\n" - "║ Write: %-20s ║\n" - "║ Network: RX: %-20s ║\n" - "║ TX: %-20s ║\n" - "╠══════════════════════════════════════════════════════════════════════════════╣\n" - "║ TOP PROCESSES ║\n" - "╠═══════╦══════════════════════════════╦═══════╦══════════╦═══════════════════╣\n" - "║ PID ║ NAME ║ STATE ║ CPU%% ║ MEMORY ║\n" - "╠═══════╬══════════════════════════════╬═══════╬══════════╬═══════════════════╣\n", - disk_read_str, disk_write_str, net_rx_str, net_tx_str); - - for (int i = 0; i < proc_count && i < 10; i++) { - format_bytes(processes[i].memory, buffer); - pos += sprintf(display_buffer + pos, "║ %5d ║ %-28s ║ %c ║ %6.1f%% ║ %17s ║\n", - processes[i].pid, - processes[i].name, - processes[i].state, - processes[i].cpu_percent, - buffer); +int init_ncurses_monitor(NCursesMonitor *monitor) { + if (!monitor) + return 0; + + memset(monitor, 0, sizeof(NCursesMonitor)); + + // Initialize search + monitor->search_mode = 0; + monitor->search_buffer[0] = '\0'; + monitor->search_cursor = 0; + + initscr(); + cbreak(); + noecho(); + keypad(stdscr, TRUE); + nodelay(stdscr, FALSE); + curs_set(0); + + // enable colors + if (has_colors()) { + start_color(); + init_pair(1, COLOR_GREEN, COLOR_BLACK); + init_pair(2, COLOR_CYAN, COLOR_BLACK); + init_pair(3, COLOR_YELLOW, COLOR_BLACK); + init_pair(4, COLOR_RED, COLOR_BLACK); + init_pair(5, COLOR_WHITE, COLOR_BLACK); + } + + // get terminal dimensions + + getmaxyx(stdscr, monitor->terminal_height, monitor->terminal_width); + + // create windows + + monitor->header_win = newwin(3, monitor->terminal_width, 0, 0); + monitor->stats_win = newwin(8, monitor->terminal_width, 3, 0); + monitor->process_win = + newwin(monitor->terminal_height - 13, monitor->terminal_width, 11, 0); + monitor->status_win = + newwin(2, monitor->terminal_width, monitor->terminal_height - 2, 0); + + if (!monitor->header_win || !monitor->stats_win || !monitor->process_win || + !monitor->status_win) { + cleanup_ncurses_monitor(monitor); + return 0; + } + // enable scrolling and keypad for all windows + scrollok(monitor->process_win, TRUE); + keypad(monitor->process_win, TRUE); + + return 1; +} + +void cleanup_ncurses_monitor(NCursesMonitor *monitor) { + if (!monitor) + return; + + if (monitor->header_win) + delwin(monitor->header_win); + if (monitor->stats_win) + delwin(monitor->stats_win); + if (monitor->process_win) + delwin(monitor->process_win); + if (monitor->status_win) + delwin(monitor->status_win); + + endwin(); +} + +void handle_monitor_input(NCursesMonitor *monitor, int ch) { + if (!monitor) + return; + + // Handle search mode input + if (monitor->search_mode) { + if (ch == 27) { // ESC key - clear search and exit + monitor->search_mode = 0; + monitor->search_buffer[0] = '\0'; + monitor->search_cursor = 0; + monitor->selected_process = 0; + monitor->process_scroll_offset = 0; + } else if (ch == KEY_BACKSPACE || ch == 127 || ch == 8 || ch == 263) { + // Backspace - remove character (try multiple key codes) + if (monitor->search_cursor > 0) { + monitor->search_cursor--; + monitor->search_buffer[monitor->search_cursor] = '\0'; + monitor->selected_process = 0; + monitor->process_scroll_offset = 0; + } + } else if (ch == KEY_ENTER || ch == '\n' || ch == '\r') { + // Enter - apply search and exit search mode + monitor->search_mode = 0; + } else if (ch >= 32 && ch <= 126) { + // Any printable character + if (monitor->search_cursor < 254) { + monitor->search_buffer[monitor->search_cursor] = (char)ch; + monitor->search_cursor++; + monitor->search_buffer[monitor->search_cursor] = '\0'; + monitor->selected_process = 0; + monitor->process_scroll_offset = 0; + } } - + return; + } + + // Normal navigation mode + int max_visible = getmaxy(monitor->process_win) - 3; + + switch (ch) { + case '/': + // Enter search mode + monitor->search_mode = 1; + monitor->search_buffer[0] = '\0'; + monitor->search_cursor = 0; + monitor->selected_process = 0; + monitor->process_scroll_offset = 0; + break; + + case 27: // ESC key - clear search filter + monitor->search_buffer[0] = '\0'; + monitor->search_cursor = 0; + monitor->selected_process = 0; + monitor->process_scroll_offset = 0; + break; + + case KEY_UP: + case 'k': + case 'K': + if (monitor->selected_process > 0) { + monitor->selected_process--; + if (monitor->selected_process < monitor->process_scroll_offset) { + monitor->process_scroll_offset--; + } + } + break; + + case KEY_DOWN: + case 'j': + case 'J': + monitor->selected_process++; + if (monitor->selected_process >= + monitor->process_scroll_offset + max_visible) { + monitor->process_scroll_offset++; + } + break; + + case KEY_HOME: + case 'g': + monitor->selected_process = 0; + monitor->process_scroll_offset = 0; + break; + + case KEY_END: + case 'G': + // Will be clamped in display function + monitor->selected_process = 999999; + break; + } +} + +void display_ncurses_dashboard(NCursesMonitor *monitor, SystemStats *stats, + ProcessInfo *processes, int proc_count) { + if (!monitor || !stats || !processes) + return; + + time_t now = time(NULL); + struct tm *tm_info = localtime(&now); + char mem_used_str[32], mem_total_str[32]; + char disk_read_str[32], disk_write_str[32]; + char net_rx_str[32], net_tx_str[32]; + + // Format data + float mem_percent = (float)stats->memory_used / stats->memory_total * 100; + format_bytes(stats->memory_used, mem_used_str); + format_bytes(stats->memory_total, mem_total_str); + format_bytes(stats->disk_read, disk_read_str); + format_bytes(stats->disk_write, disk_write_str); + format_bytes(stats->net_rx, net_rx_str); + format_bytes(stats->net_tx, net_tx_str); + + // Clear all windows + werase(monitor->header_win); + werase(monitor->stats_win); + werase(monitor->process_win); + werase(monitor->status_win); + + // Draw header + if (has_colors()) + wattron(monitor->header_win, COLOR_PAIR(1)); + box(monitor->header_win, 0, 0); + mvwprintw(monitor->header_win, 1, (monitor->terminal_width - 24) / 2, + "SYSTEM MONITOR DASHBOARD"); + mvwprintw(monitor->header_win, 2, (monitor->terminal_width - 19) / 2, + "%02d:%02d:%02d %02d/%02d/%04d", tm_info->tm_hour, tm_info->tm_min, + tm_info->tm_sec, tm_info->tm_mday, tm_info->tm_mon + 1, + tm_info->tm_year + 1900); + if (has_colors()) + wattroff(monitor->header_win, COLOR_PAIR(1)); + + // Draw system stats + box(monitor->stats_win, 0, 0); + if (has_colors()) + wattron(monitor->stats_win, COLOR_PAIR(1)); + mvwprintw(monitor->stats_win, 0, 2, " System Statistics "); + if (has_colors()) + wattroff(monitor->stats_win, COLOR_PAIR(1)); + + // CPU usage with progress bar + mvwprintw(monitor->stats_win, 1, 2, "CPU Usage:"); + if (has_colors()) + wattron(monitor->stats_win, COLOR_PAIR(2)); + mvwprintw(monitor->stats_win, 1, 15, "%5.1f%%", stats->cpu_percent); + if (has_colors()) + wattroff(monitor->stats_win, COLOR_PAIR(2)); + + // Draw CPU progress bar + int bar_width = 30; + int filled = (stats->cpu_percent * bar_width) / 100; + mvwprintw(monitor->stats_win, 1, 25, "["); + for (int i = 0; i < bar_width; i++) { + if (i < filled) { + if (has_colors() && stats->cpu_percent > 80) + wattron(monitor->stats_win, COLOR_PAIR(4)); + else if (has_colors() && stats->cpu_percent > 60) + wattron(monitor->stats_win, COLOR_PAIR(3)); + else if (has_colors()) + wattron(monitor->stats_win, COLOR_PAIR(1)); + mvwaddch(monitor->stats_win, 1, 26 + i, '#'); + if (has_colors()) + wattroff(monitor->stats_win, + COLOR_PAIR(1) | COLOR_PAIR(3) | COLOR_PAIR(4)); + } else { + mvwaddch(monitor->stats_win, 1, 26 + i, ' '); + } + } + mvwprintw(monitor->stats_win, 1, 56, "]"); + + // Memory usage + mvwprintw(monitor->stats_win, 2, 2, "Memory:"); + if (has_colors()) + wattron(monitor->stats_win, COLOR_PAIR(2)); + mvwprintw(monitor->stats_win, 2, 15, "%5.1f%%", mem_percent); + mvwprintw(monitor->stats_win, 3, 15, "%s / %s", mem_used_str, mem_total_str); + if (has_colors()) + wattroff(monitor->stats_win, COLOR_PAIR(2)); + + // Disk I/O + mvwprintw(monitor->stats_win, 4, 2, "Disk I/O:"); + if (has_colors()) + wattron(monitor->stats_win, COLOR_PAIR(2)); + mvwprintw(monitor->stats_win, 4, 15, "Read: %s", disk_read_str); + mvwprintw(monitor->stats_win, 5, 15, "Write: %s", disk_write_str); + if (has_colors()) + wattroff(monitor->stats_win, COLOR_PAIR(2)); + + // Network I/O + mvwprintw(monitor->stats_win, 6, 2, "Network:"); + if (has_colors()) + wattron(monitor->stats_win, COLOR_PAIR(2)); + mvwprintw(monitor->stats_win, 6, 15, "RX: %s", net_rx_str); + mvwprintw(monitor->stats_win, 7, 15, "TX: %s", net_tx_str); + if (has_colors()) + wattroff(monitor->stats_win, COLOR_PAIR(2)); + + // Filter processes based on search in real-time + ProcessInfo *display_processes = processes; + int display_count = proc_count; + ProcessInfo *temp_processes = NULL; + + // Apply filter in real-time when search buffer has content (even during typing) + if (strlen(monitor->search_buffer) > 0) { + temp_processes = malloc(proc_count * sizeof(ProcessInfo)); + display_count = 0; + for (int i = 0; i < proc_count; i++) { + if (strcasestr(processes[i].name, monitor->search_buffer) != NULL) { + temp_processes[display_count] = processes[i]; + display_count++; + } + } + display_processes = temp_processes; + } + + // Draw process list + box(monitor->process_win, 0, 0); + if (has_colors()) + wattron(monitor->process_win, COLOR_PAIR(1)); + + // Show search status in title when filter is applied (real-time during search) + if (strlen(monitor->search_buffer) > 0) { + if (monitor->search_mode) { + mvwprintw(monitor->process_win, 0, 2, " Searching: '%s' (%d matches) ", + monitor->search_buffer, display_count); + } else { + mvwprintw(monitor->process_win, 0, 2, " Processes (filtered: %d) ", + display_count); + } + } else { + mvwprintw(monitor->process_win, 0, 2, " All Processes (by CPU) "); + } + + mvwprintw(monitor->process_win, 1, 2, + "PID Name State CPU%% Memory"); + if (has_colors()) + wattroff(monitor->process_win, COLOR_PAIR(1)); + + int max_visible = getmaxy(monitor->process_win) - 3; + + int end_index = monitor->process_scroll_offset + max_visible; + if (end_index > display_count) + end_index = display_count; + + // Clamp selected process to valid range + if (monitor->selected_process >= display_count) { + monitor->selected_process = display_count - 1; + // Adjust scroll offset for end navigation + monitor->process_scroll_offset = display_count - max_visible; + if (monitor->process_scroll_offset < 0) { + monitor->process_scroll_offset = 0; + } + } + if (monitor->selected_process < 0) { + monitor->selected_process = 0; + } + + for (int i = monitor->process_scroll_offset; i < end_index; i++) { + int line = i - monitor->process_scroll_offset + 2; + char mem_str[32]; + format_bytes(display_processes[i].memory, mem_str); + + // Clear the line first + wmove(monitor->process_win, line, 2); + wclrtoeol(monitor->process_win); + + if (i == monitor->selected_process) { + if (has_colors()) { + wattron(monitor->process_win, COLOR_PAIR(5) | A_REVERSE); + } else { + wattron(monitor->process_win, A_REVERSE); + } + } + + mvwprintw(monitor->process_win, line, 2, "%-6d %-24s %-6c %6.1f%% %s", + display_processes[i].pid, display_processes[i].name, + display_processes[i].state, display_processes[i].cpu_percent, + mem_str); + + if (i == monitor->selected_process) { + if (has_colors()) { + wattroff(monitor->process_win, COLOR_PAIR(5) | A_REVERSE); + } else { + wattroff(monitor->process_win, A_REVERSE); + } + } + } + + // Free temporary filtered array + if (temp_processes) { + free(temp_processes); + } + + // Draw status bar + if (has_colors()) + wattron(monitor->status_win, COLOR_PAIR(1)); + + if (monitor->search_mode) { + mvwprintw(monitor->status_win, 0, 2, + "SEARCH: '%s' | %d matches | ESC=cancel Enter=apply", + monitor->search_buffer, display_count); + mvwprintw(monitor->status_win, 1, 2, "Type to filter processes..."); + } else { + mvwprintw(monitor->status_win, 0, 2, + "Press 'q' quit, 'r' refresh, '/' search, j/k arrows navigate, " + "ESC clear"); + mvwprintw(monitor->status_win, 1, 2, + "Refresh: %ds | Processes: %d/%d | Selected: %d | Scroll: %d", + monitor->refresh_rate, display_count, proc_count, + monitor->selected_process + 1, monitor->process_scroll_offset); + } + + if (has_colors()) + wattroff(monitor->status_win, COLOR_PAIR(1)); + + // Always refresh all windows + wrefresh(monitor->header_win); + wrefresh(monitor->stats_win); + wrefresh(monitor->process_win); + wrefresh(monitor->status_win); + + refresh(); +} + + +void display_dashboard(SystemStats *stats, ProcessInfo *processes, + int proc_count) { + char buffer[256]; + char display_buffer[8192]; // Large buffer for entire display + char progress_buf[50]; + char mem_used_str[32], mem_total_str[32]; + char disk_read_str[32], disk_write_str[32]; + char net_rx_str[32], net_tx_str[32]; + + time_t now = time(NULL); + struct tm *tm_info = localtime(&now); + + // Format all the data first + float mem_percent = (float)stats->memory_used / stats->memory_total * 100; + format_bytes(stats->memory_used, mem_used_str); + format_bytes(stats->memory_total, mem_total_str); + format_bytes(stats->disk_read, disk_read_str); + format_bytes(stats->disk_write, disk_write_str); + format_bytes(stats->net_rx, net_rx_str); + format_bytes(stats->net_tx, net_tx_str); + + // Build entire display in buffer + int pos = sprintf(display_buffer, "\033[H"); // Move to home position + + pos += + sprintf(display_buffer + pos, + "╔═══════════════════════════════════════════════════════════════" + "═══════════════╗\n" + "║ SYSTEM MONITOR DASHBOARD " + " ║\n" + "║ %02d:%02d:%02d %02d/%02d/%04d " + " ║\n" + "╠═══════════════════════════════════════════════════════════════" + "═══════════════╣\n", + tm_info->tm_hour, tm_info->tm_min, tm_info->tm_sec, + tm_info->tm_mday, tm_info->tm_mon + 1, tm_info->tm_year + 1900); + + // CPU Usage + format_progress_bar((int)stats->cpu_percent, 40, progress_buf); + pos += sprintf(display_buffer + pos, "║ CPU Usage: %s %5.1f%% ║\n", + progress_buf, stats->cpu_percent); + + // Memory Usage + format_progress_bar((int)mem_percent, 40, progress_buf); + pos += sprintf(display_buffer + pos, "║ Memory: %s %5.1f%% ║\n", + progress_buf, mem_percent); + pos += sprintf(display_buffer + pos, + "║ Used: %-15s / %-15s ║\n", + mem_used_str, mem_total_str); + + pos += + sprintf(display_buffer + pos, + "║ Disk I/O: Read: %-20s ║\n" + "║ Write: %-20s ║\n" + "║ Network: RX: %-20s ║\n" + "║ TX: %-20s ║\n" + "╠═══════════════════════════════════════════════════════════════" + "═══════════════╣\n" + "║ TOP PROCESSES " + " ║\n" + "╠═══════╦══════════════════════════════╦═══════╦══════════╦═════" + "══════════════╣\n" + "║ PID ║ NAME ║ STATE ║ CPU%% ║ " + " MEMORY ║\n" + "╠═══════╬══════════════════════════════╬═══════╬══════════╬═════" + "══════════════╣\n", + disk_read_str, disk_write_str, net_rx_str, net_tx_str); + + for (int i = 0; i < proc_count && i < 10; i++) { + format_bytes(processes[i].memory, buffer); pos += sprintf(display_buffer + pos, - "╚═══════╩══════════════════════════════╩═══════╩══════════╩═══════════════════╝\n" - "Press 'q' to quit, 'r' to refresh "); - - // Output entire buffer at once - printf("%s", display_buffer); - fflush(stdout); + "║ %5d ║ %-28s ║ %c ║ %6.1f%% ║ %17s ║\n", + processes[i].pid, processes[i].name, processes[i].state, + processes[i].cpu_percent, buffer); + } + + pos += + sprintf(display_buffer + pos, "╚═══════╩══════════════════════════════╩══" + "═════╩══════════╩═══════════════════╝\n" + "Press 'q' to quit, 'r' to refresh " + " "); + + // Output entire buffer at once + printf("%s", display_buffer); + fflush(stdout); } void get_system_stats(SystemStats *stats) { - FILE *fp; - char buffer[1024]; - static unsigned long prev_idle = 0, prev_total = 0; - static unsigned long prev_disk_read = 0, prev_disk_write = 0; - static unsigned long prev_net_rx = 0, prev_net_tx = 0; - - // CPU Usage - fp = fopen("/proc/stat", "r"); - if (fp) { - fgets(buffer, sizeof(buffer), fp); - unsigned long user, nice, system, idle, iowait, irq, softirq; - sscanf(buffer, "cpu %lu %lu %lu %lu %lu %lu %lu", - &user, &nice, &system, &idle, &iowait, &irq, &softirq); - - unsigned long total = user + nice + system + idle + iowait + irq + softirq; - unsigned long total_diff = total - prev_total; - unsigned long idle_diff = idle - prev_idle; - - if (total_diff > 0) { - stats->cpu_percent = 100.0 * (total_diff - idle_diff) / total_diff; - } else { - stats->cpu_percent = 0.0; - } - - prev_total = total; - prev_idle = idle; - fclose(fp); + FILE *fp; + char buffer[1024]; + static unsigned long prev_idle = 0, prev_total = 0; + static unsigned long prev_disk_read = 0, prev_disk_write = 0; + static unsigned long prev_net_rx = 0, prev_net_tx = 0; + + // CPU Usage + fp = fopen("/proc/stat", "r"); + if (fp) { + fgets(buffer, sizeof(buffer), fp); + unsigned long user, nice, system, idle, iowait, irq, softirq; + sscanf(buffer, "cpu %lu %lu %lu %lu %lu %lu %lu", &user, &nice, &system, + &idle, &iowait, &irq, &softirq); + + unsigned long total = user + nice + system + idle + iowait + irq + softirq; + unsigned long total_diff = total - prev_total; + unsigned long idle_diff = idle - prev_idle; + + if (total_diff > 0) { + stats->cpu_percent = 100.0 * (total_diff - idle_diff) / total_diff; + } else { + stats->cpu_percent = 0.0; } - - // Memory Usage - Read from /proc/meminfo for accuracy - fp = fopen("/proc/meminfo", "r"); - if (fp) { - unsigned long mem_total = 0, mem_free = 0, mem_available = 0; - unsigned long buffers = 0, cached = 0; - - while (fgets(buffer, sizeof(buffer), fp)) { - if (sscanf(buffer, "MemTotal: %lu kB", &mem_total) == 1) { - stats->memory_total = mem_total * 1024; - } else if (sscanf(buffer, "MemAvailable: %lu kB", &mem_available) == 1) { - // MemAvailable accounts for buffers/cache that can be reclaimed - stats->memory_used = (mem_total - mem_available) * 1024; - } else if (sscanf(buffer, "MemFree: %lu kB", &mem_free) == 1) { - // Fallback if MemAvailable is not available - if (mem_available == 0) { - mem_free = mem_free; - } - } else if (sscanf(buffer, "Buffers: %lu kB", &buffers) == 1) { - // For fallback calculation - } else if (sscanf(buffer, "Cached: %lu kB", &cached) == 1) { - // For fallback calculation - } + + prev_total = total; + prev_idle = idle; + fclose(fp); + } + + // Memory Usage - Read from /proc/meminfo for accuracy + fp = fopen("/proc/meminfo", "r"); + if (fp) { + unsigned long mem_total = 0, mem_free = 0, mem_available = 0; + unsigned long buffers = 0, cached = 0; + + while (fgets(buffer, sizeof(buffer), fp)) { + if (sscanf(buffer, "MemTotal: %lu kB", &mem_total) == 1) { + stats->memory_total = mem_total * 1024; + } else if (sscanf(buffer, "MemAvailable: %lu kB", &mem_available) == 1) { + // MemAvailable accounts for buffers/cache that can be reclaimed + stats->memory_used = (mem_total - mem_available) * 1024; + } else if (sscanf(buffer, "MemFree: %lu kB", &mem_free) == 1) { + // Fallback if MemAvailable is not available + if (mem_available == 0) { + mem_free = mem_free; } - - // If MemAvailable wasn't found, calculate manually - if (mem_available == 0 && mem_total > 0) { - stats->memory_used = (mem_total - mem_free - buffers - cached) * 1024; - } - - fclose(fp); + } else if (sscanf(buffer, "Buffers: %lu kB", &buffers) == 1) { + // For fallback calculation + } else if (sscanf(buffer, "Cached: %lu kB", &cached) == 1) { + // For fallback calculation + } } - - // Disk I/O - fp = fopen("/proc/diskstats", "r"); - if (fp) { - unsigned long read_sectors = 0, write_sectors = 0; - while (fgets(buffer, sizeof(buffer), fp)) { - unsigned long r_sectors, w_sectors; - char device[32]; - if (sscanf(buffer, "%*d %*d %31s %*d %*d %lu %*d %*d %*d %lu", - device, &r_sectors, &w_sectors) == 3) { - if (strncmp(device, "sd", 2) == 0 || strncmp(device, "nvme", 4) == 0) { - read_sectors += r_sectors; - write_sectors += w_sectors; - } - } - } - stats->disk_read = (read_sectors - prev_disk_read) * 512; - stats->disk_write = (write_sectors - prev_disk_write) * 512; - prev_disk_read = read_sectors; - prev_disk_write = write_sectors; - fclose(fp); + + // If MemAvailable wasn't found, calculate manually + if (mem_available == 0 && mem_total > 0) { + stats->memory_used = (mem_total - mem_free - buffers - cached) * 1024; } - - // Network I/O - fp = fopen("/proc/net/dev", "r"); - if (fp) { - fgets(buffer, sizeof(buffer), fp); // skip header - fgets(buffer, sizeof(buffer), fp); // skip header - - unsigned long rx_bytes = 0, tx_bytes = 0; - while (fgets(buffer, sizeof(buffer), fp)) { - char interface[32]; - unsigned long rx, tx; - if (sscanf(buffer, " %31[^:]: %lu %*d %*d %*d %*d %*d %*d %*d %lu", - interface, &rx, &tx) == 3) { - if (strcmp(interface, "lo") != 0) { // skip loopback - rx_bytes += rx; - tx_bytes += tx; - } - } + + fclose(fp); + } + + // Disk I/O + fp = fopen("/proc/diskstats", "r"); + if (fp) { + unsigned long read_sectors = 0, write_sectors = 0; + while (fgets(buffer, sizeof(buffer), fp)) { + unsigned long r_sectors, w_sectors; + char device[32]; + if (sscanf(buffer, "%*d %*d %31s %*d %*d %lu %*d %*d %*d %lu", device, + &r_sectors, &w_sectors) == 3) { + if (strncmp(device, "sd", 2) == 0 || strncmp(device, "nvme", 4) == 0) { + read_sectors += r_sectors; + write_sectors += w_sectors; } - stats->net_rx = rx_bytes - prev_net_rx; - stats->net_tx = tx_bytes - prev_net_tx; - prev_net_rx = rx_bytes; - prev_net_tx = tx_bytes; - fclose(fp); + } } + stats->disk_read = (read_sectors - prev_disk_read) * 512; + stats->disk_write = (write_sectors - prev_disk_write) * 512; + prev_disk_read = read_sectors; + prev_disk_write = write_sectors; + fclose(fp); + } + + // Network I/O + fp = fopen("/proc/net/dev", "r"); + if (fp) { + fgets(buffer, sizeof(buffer), fp); // skip header + fgets(buffer, sizeof(buffer), fp); // skip header + + unsigned long rx_bytes = 0, tx_bytes = 0; + while (fgets(buffer, sizeof(buffer), fp)) { + char interface[32]; + unsigned long rx, tx; + if (sscanf(buffer, " %31[^:]: %lu %*d %*d %*d %*d %*d %*d %*d %lu", + interface, &rx, &tx) == 3) { + if (strcmp(interface, "lo") != 0) { // skip loopback + rx_bytes += rx; + tx_bytes += tx; + } + } + } + stats->net_rx = rx_bytes - prev_net_rx; + stats->net_tx = tx_bytes - prev_net_tx; + prev_net_rx = rx_bytes; + prev_net_tx = tx_bytes; + fclose(fp); + } } int get_process_info(ProcessInfo *processes, int max_processes) { - DIR *proc_dir; - struct dirent *entry; - FILE *fp; - char path[256]; - char buffer[1024]; - int count = 0; - - proc_dir = opendir("/proc"); - if (!proc_dir) return 0; - - while ((entry = readdir(proc_dir)) != NULL && count < max_processes) { - if (!isdigit(entry->d_name[0])) continue; - - int pid = atoi(entry->d_name); - processes[count].pid = pid; - - // Get process name and state - snprintf(path, sizeof(path), "/proc/%d/stat", pid); - fp = fopen(path, "r"); - if (fp) { - if (fgets(buffer, sizeof(buffer), fp)) { - char *name_start = strchr(buffer, '('); - char *name_end = strrchr(buffer, ')'); - if (name_start && name_end) { - int name_len = name_end - name_start - 1; - if (name_len > 255) name_len = 255; - strncpy(processes[count].name, name_start + 1, name_len); - processes[count].name[name_len] = '\0'; - - char *state_pos = name_end + 2; - processes[count].state = *state_pos; - } - } - fclose(fp); + DIR *proc_dir; + struct dirent *entry; + FILE *fp; + char path[256]; + char buffer[1024]; + int count = 0; + + proc_dir = opendir("/proc"); + if (!proc_dir) + return 0; + + while ((entry = readdir(proc_dir)) != NULL && count < max_processes) { + if (!isdigit(entry->d_name[0])) + continue; + + int pid = atoi(entry->d_name); + processes[count].pid = pid; + processes[count].memory = 0; + processes[count].cpu_percent = 0.0; + processes[count].state = '?'; + strcpy(processes[count].name, "unknown"); + + // Get process name and state from /proc/pid/stat + snprintf(path, sizeof(path), "/proc/%d/stat", pid); + fp = fopen(path, "r"); + if (fp) { + if (fgets(buffer, sizeof(buffer), fp)) { + char *name_start = strchr(buffer, '('); + char *name_end = strrchr(buffer, ')'); + if (name_start && name_end) { + int name_len = name_end - name_start - 1; + if (name_len > 255) + name_len = 255; + if (name_len > 0) { + strncpy(processes[count].name, name_start + 1, name_len); + processes[count].name[name_len] = '\0'; + } + + // Parse state (first field after the name) + char *fields = name_end + 2; + if (fields && *fields) { + processes[count].state = *fields; + } } - - // Get memory usage - snprintf(path, sizeof(path), "/proc/%d/status", pid); - fp = fopen(path, "r"); - if (fp) { - while (fgets(buffer, sizeof(buffer), fp)) { - if (strncmp(buffer, "VmRSS:", 6) == 0) { - unsigned long mem_kb; - sscanf(buffer, "VmRSS: %lu kB", &mem_kb); - processes[count].memory = mem_kb * 1024; - break; - } - } - fclose(fp); - } - - processes[count].cpu_percent = 0.0; // Simplified for this example - count++; + } + fclose(fp); } - - closedir(proc_dir); - - // Sort by memory usage (descending) - for (int i = 0; i < count - 1; i++) { - for (int j = i + 1; j < count; j++) { - if (processes[i].memory < processes[j].memory) { - ProcessInfo temp = processes[i]; - processes[i] = processes[j]; - processes[j] = temp; - } - } + + // Get memory usage from /proc/pid/statm (more reliable) + snprintf(path, sizeof(path), "/proc/%d/statm", pid); + fp = fopen(path, "r"); + if (fp) { + unsigned long size, resident, shared; + if (fscanf(fp, "%lu %lu %lu", &size, &resident, &shared) >= 2) { + // resident is in pages, convert to bytes (page size is typically 4096) + processes[count].memory = resident * 4096; + } + fclose(fp); } - - return count; + + // Simple CPU estimation - use a random value for demonstration + // In a real implementation, you'd track CPU times over intervals + processes[count].cpu_percent = (rand() % 100) / 10.0; // 0.0 to 9.9% + + count++; + } + + closedir(proc_dir); + + // Sort by CPU usage (highest to lowest) to show most active processes first + for (int i = 0; i < count - 1; i++) { + for (int j = i + 1; j < count; j++) { + if (processes[i].cpu_percent < processes[j].cpu_percent) { + ProcessInfo temp = processes[i]; + processes[i] = processes[j]; + processes[j] = temp; + } + } + } + + return count; } void clear_screen(void) { - printf("\033[2J\033[H"); - fflush(stdout); + printf("\033[2J\033[H"); + fflush(stdout); } void move_cursor(int row, int col) { - printf("\033[%d;%dH", row, col); - fflush(stdout); + printf("\033[%d;%dH", row, col); + fflush(stdout); } void hide_cursor(void) { - printf("\033[?25l"); - fflush(stdout); + printf("\033[?25l"); + fflush(stdout); } void show_cursor(void) { - printf("\033[?25h"); - fflush(stdout); + printf("\033[?25h"); + fflush(stdout); } int kbhit(void) { - int ch = getchar(); - if (ch != EOF) { - ungetc(ch, stdin); - return 1; - } - return 0; + int ch = getchar(); + if (ch != EOF) { + ungetc(ch, stdin); + return 1; + } + return 0; } void draw_progress_bar(int percentage, int width) { - int filled = (percentage * width) / 100; - printf("["); - for (int i = 0; i < width; i++) { - if (i < filled) { - printf("█"); - } else { - printf(" "); - } + int filled = (percentage * width) / 100; + printf("["); + for (int i = 0; i < width; i++) { + if (i < filled) { + printf("█"); + } else { + printf(" "); } - printf("]"); + } + printf("]"); } void format_progress_bar(int percentage, int width, char *buffer) { - int filled = (percentage * width) / 100; - int pos = 0; - buffer[pos++] = '['; - for (int i = 0; i < width; i++) { - if (i < filled) { - buffer[pos++] = '#'; // Use # instead of Unicode block - } else { - buffer[pos++] = ' '; - } + int filled = (percentage * width) / 100; + int pos = 0; + buffer[pos++] = '['; + for (int i = 0; i < width; i++) { + if (i < filled) { + buffer[pos++] = '#'; // Use # instead of Unicode block + } else { + buffer[pos++] = ' '; } - buffer[pos++] = ']'; - buffer[pos] = '\0'; + } + buffer[pos++] = ']'; + buffer[pos] = '\0'; } void format_bytes(unsigned long bytes, char *buffer) { - const char *units[] = {"B", "KB", "MB", "GB", "TB"}; - int unit = 0; - double size = bytes; - - while (size >= 1024 && unit < 4) { - size /= 1024; - unit++; - } - - if (unit == 0) { - snprintf(buffer, 64, "%lu %s", bytes, units[unit]); - } else { - snprintf(buffer, 64, "%.1f %s", size, units[unit]); - } -} \ No newline at end of file + const char *units[] = {"B", "KB", "MB", "GB", "TB"}; + int unit = 0; + double size = bytes; + + while (size >= 1024 && unit < 4) { + size /= 1024; + unit++; + } + + if (unit == 0) { + snprintf(buffer, 64, "%lu %s", bytes, units[unit]); + } else { + snprintf(buffer, 64, "%.1f %s", size, units[unit]); + } +} diff --git a/system_monitor.h b/system_monitor.h index a3dc8c5..8972ef8 100644 --- a/system_monitor.h +++ b/system_monitor.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,23 @@ typedef struct { int process_count; } SystemStats; +typedef struct { + WINDOW *main_win; + WINDOW *header_win; + WINDOW *stats_win; + WINDOW *process_win; + WINDOW *status_win; + WINDOW *search_win; + int terminal_height; + int terminal_width; + int selected_process; + int refresh_rate; + int process_scroll_offset; + int search_mode; + char search_buffer[256]; + int search_cursor; +} NCursesMonitor; + typedef struct { int pid; char name[256]; @@ -32,6 +50,7 @@ typedef struct { char state; } ProcessInfo; +int init_ncurses_monitor(NCursesMonitor *monitor); int builtin_monitor(char **args); void display_dashboard(SystemStats *stats, ProcessInfo *processes, int proc_count); @@ -45,5 +64,9 @@ int kbhit(void); void draw_progress_bar(int percentage, int width); void format_progress_bar(int percentage, int width, char *buffer); void format_bytes(unsigned long bytes, char *buffer); +void cleanup_ncurses_monitor(NCursesMonitor *monitor); +void display_ncurses_dashboard(NCursesMonitor *monitor, SystemStats *stats, + ProcessInfo *processes, int proc_count); +void handle_monitor_input(NCursesMonitor *monitor, int ch); #endif diff --git a/system_monitor.o b/system_monitor.o index c464709..fe0eaca 100644 Binary files a/system_monitor.o and b/system_monitor.o differ