commit 11926c5459c13d8758cf70c49e7d4a2a66e45731 Author: retoor Date: Sat Dec 13 05:38:27 2025 +0100 feat: implement rtop system monitoring tool feat: add basic system information display feat: implement cpu monitoring feat: implement memory monitoring feat: implement process monitoring feat: implement network monitoring feat: implement disk monitoring feat: implement filesystem monitoring feat: add terminal control handling feat: create initial project structure feat: add build system with makefile feat: add readme documentation diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b672fde --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +obj diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..bb6ecfc --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog + + + +## Version 0.1.0 - 2025-12-13 + +A new system monitoring tool, rtop, has been added. It provides real-time information about CPU, memory, processes, network, disk, and filesystem usage. + +**Changes:** 24 files, 2293 lines +**Languages:** C (1927 lines), Markdown (331 lines), Other (35 lines) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fdc6119 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +# retoor + +CC = gcc +CFLAGS = -Wall -Wextra -Werror -pedantic -std=c99 -O3 -march=native -D_POSIX_C_SOURCE=200809L -D_GNU_SOURCE +LDFLAGS = -static +SRCDIR = src +INCDIR = include +OBJDIR = obj + +SOURCES = $(wildcard $(SRCDIR)/*.c) +OBJECTS = $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) +TARGET = rtop + +.PHONY: all clean debug + +all: $(TARGET) + +debug: CFLAGS += -g -DDEBUG +debug: $(TARGET) + +$(TARGET): $(OBJECTS) + $(CC) $(OBJECTS) -o $@ $(LDFLAGS) 2>/dev/null || $(CC) $(OBJECTS) -o $@ + +$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR) + $(CC) $(CFLAGS) -I$(INCDIR) -c $< -o $@ + +$(OBJDIR): + mkdir -p $(OBJDIR) + +clean: + rm -rf $(OBJDIR) $(TARGET) + +install: $(TARGET) + install -m 755 $(TARGET) /usr/local/bin/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..fb1d9af --- /dev/null +++ b/README.md @@ -0,0 +1,129 @@ +# rtop + +**Author:** retoor + +A terminal-based system monitoring tool written in C that displays comprehensive system statistics in a dynamic, auto-refreshing interface. rtop is designed as a lightweight alternative to htop with no external dependencies beyond the standard C library. + +## Features + +- Real-time system monitoring with configurable refresh interval +- Per-core CPU utilization with temperature and frequency display +- Memory and swap usage statistics +- Process listing sorted by CPU or memory usage +- Network interface statistics with RX/TX rates +- Disk I/O statistics +- Filesystem usage information +- Color-coded output for utilization levels +- Terminal resize handling +- Keyboard controls (q to quit) + +## Requirements + +- Linux operating system +- GCC compiler (C99 compliant) +- Make build system + +## Building + +```bash +make +``` + +The build produces a single binary `rtop` with minimal dependencies. + +### Build Options + +```bash +make clean # Remove build artifacts +make debug # Build with debug symbols +make install # Install to /usr/local/bin (requires root) +``` + +## Usage + +```bash +./rtop [OPTIONS] + +Options: + -d, --delay SECS Update interval in seconds (default: 1.0) + -s, --sort MODE Sort by: cpu (default), mem + -h, --help Show help message + -v, --version Show version +``` + +### Examples + +```bash +./rtop # Run with default settings +./rtop -d 2 # Update every 2 seconds +./rtop -s mem # Sort processes by memory usage +``` + +## Display Sections + +- **Header**: Hostname, OS version, kernel, uptime, load averages +- **CPU**: Per-core utilization, total usage, temperature, frequency +- **Memory**: RAM and swap usage with buffer/cache information +- **Processes**: Summary counts (running, sleeping, stopped, zombie) +- **Network**: Interface statistics with IP addresses and transfer rates +- **Disk I/O**: Read/write rates and IOPS per device +- **Filesystems**: Mount points with usage percentages +- **Process List**: Detailed per-process information + +## Controls + +- `q` or `Q`: Quit the application +- `Ctrl+C`: Terminate + +## Technical Details + +### Data Sources + +All system information is read from the Linux `/proc` and `/sys` filesystems: + +- `/proc/stat` - CPU statistics +- `/proc/meminfo` - Memory information +- `/proc/[pid]/stat` - Process information +- `/proc/net/dev` - Network statistics +- `/proc/diskstats` - Disk I/O statistics +- `/sys/class/thermal/` - Temperature sensors + +### Performance + +- Target CPU usage: <5% +- Startup time: <100ms +- Memory footprint: <10MB RSS +- Binary size: ~1.2MB + +## Project Structure + +``` +rtop/ +├── include/ +│ ├── cpu.h +│ ├── disk.h +│ ├── display.h +│ ├── filesystem.h +│ ├── memory.h +│ ├── network.h +│ ├── process.h +│ ├── system.h +│ └── terminal.h +├── src/ +│ ├── cpu.c +│ ├── disk.c +│ ├── display.c +│ ├── filesystem.c +│ ├── main.c +│ ├── memory.c +│ ├── network.c +│ ├── process.c +│ ├── system.c +│ └── terminal.c +├── Makefile +└── README.md +``` + +## License + +This project is provided as-is for educational and personal use. diff --git a/include/cpu.h b/include/cpu.h new file mode 100644 index 0000000..48a9a6f --- /dev/null +++ b/include/cpu.h @@ -0,0 +1,42 @@ +/* retoor */ +#ifndef CPU_H +#define CPU_H + +#define MAX_CPUS 256 + +typedef struct { + unsigned long long user; + unsigned long long nice; + unsigned long long system; + unsigned long long idle; + unsigned long long iowait; + unsigned long long irq; + unsigned long long softirq; + unsigned long long steal; + unsigned long long guest; + unsigned long long guest_nice; + double usage_percent; +} CpuCore; + +typedef struct { + int num_cores; + CpuCore total; + CpuCore cores[MAX_CPUS]; + CpuCore prev_total; + CpuCore prev_cores[MAX_CPUS]; + double frequency_mhz; + double min_freq_mhz; + double max_freq_mhz; + double temperature; + unsigned long context_switches; + unsigned long prev_context_switches; + double context_switches_per_sec; + unsigned long interrupts; + unsigned long prev_interrupts; + double interrupts_per_sec; +} CpuInfo; + +int cpu_info_init(CpuInfo *info); +int cpu_info_update(CpuInfo *info, double interval); + +#endif diff --git a/include/disk.h b/include/disk.h new file mode 100644 index 0000000..55c7719 --- /dev/null +++ b/include/disk.h @@ -0,0 +1,33 @@ +/* retoor */ +#ifndef DISK_H +#define DISK_H + +#define MAX_DISKS 64 +#define MAX_DISK_NAME 64 + +typedef struct { + char name[MAX_DISK_NAME]; + unsigned long reads; + unsigned long writes; + unsigned long read_sectors; + unsigned long write_sectors; + unsigned long prev_reads; + unsigned long prev_writes; + unsigned long prev_read_sectors; + unsigned long prev_write_sectors; + double reads_per_sec; + double writes_per_sec; + double read_bytes_per_sec; + double write_bytes_per_sec; +} DiskStats; + +typedef struct { + int count; + DiskStats disks[MAX_DISKS]; + double io_wait_percent; +} DiskInfo; + +int disk_info_init(DiskInfo *info); +int disk_info_update(DiskInfo *info, double interval); + +#endif diff --git a/include/display.h b/include/display.h new file mode 100644 index 0000000..18e8679 --- /dev/null +++ b/include/display.h @@ -0,0 +1,24 @@ +/* retoor */ +#ifndef DISPLAY_H +#define DISPLAY_H + +#include "terminal.h" +#include "system.h" +#include "cpu.h" +#include "memory.h" +#include "process.h" +#include "network.h" +#include "disk.h" +#include "filesystem.h" + +void display_header(Terminal *term, SystemInfo *sys); +void display_tabs(Terminal *term, int current_tab); +void display_footer(Terminal *term, double refresh_rate); + +void display_cpu_detail(Terminal *term, CpuInfo *cpu); +void display_memory_detail(Terminal *term, MemoryInfo *mem, ProcessList *procs); +void display_network_detail(Terminal *term, NetworkInfo *net); +void display_disk_detail(Terminal *term, DiskInfo *disk, FilesystemInfo *fs); +void display_process_detail(Terminal *term, ProcessList *procs); + +#endif diff --git a/include/filesystem.h b/include/filesystem.h new file mode 100644 index 0000000..1a71ce5 --- /dev/null +++ b/include/filesystem.h @@ -0,0 +1,28 @@ +/* retoor */ +#ifndef FILESYSTEM_H +#define FILESYSTEM_H + +#define MAX_FILESYSTEMS 64 +#define MAX_MOUNT_LEN 256 +#define MAX_FS_TYPE_LEN 64 +#define MAX_DEVICE_LEN 256 + +typedef struct { + char device[MAX_DEVICE_LEN]; + char mount_point[MAX_MOUNT_LEN]; + char fs_type[MAX_FS_TYPE_LEN]; + unsigned long total; + unsigned long used; + unsigned long available; + double used_percent; +} FilesystemStats; + +typedef struct { + int count; + FilesystemStats filesystems[MAX_FILESYSTEMS]; +} FilesystemInfo; + +int filesystem_info_init(FilesystemInfo *info); +int filesystem_info_update(FilesystemInfo *info); + +#endif diff --git a/include/memory.h b/include/memory.h new file mode 100644 index 0000000..88c9efb --- /dev/null +++ b/include/memory.h @@ -0,0 +1,31 @@ +/* retoor */ +#ifndef MEMORY_H +#define MEMORY_H + +typedef struct { + unsigned long total; + unsigned long free; + unsigned long available; + unsigned long buffers; + unsigned long cached; + unsigned long used; + double used_percent; + unsigned long swap_total; + unsigned long swap_free; + unsigned long swap_used; + double swap_percent; + unsigned long page_faults; + unsigned long prev_page_faults; + double page_faults_per_sec; + unsigned long page_in; + unsigned long prev_page_in; + double page_in_per_sec; + unsigned long page_out; + unsigned long prev_page_out; + double page_out_per_sec; +} MemoryInfo; + +int memory_info_init(MemoryInfo *info); +int memory_info_update(MemoryInfo *info, double interval); + +#endif diff --git a/include/network.h b/include/network.h new file mode 100644 index 0000000..1ad68cb --- /dev/null +++ b/include/network.h @@ -0,0 +1,35 @@ +/* retoor */ +#ifndef NETWORK_H +#define NETWORK_H + +#define MAX_INTERFACES 32 +#define MAX_IF_NAME 32 +#define MAX_IP_LEN 46 + +typedef struct { + char name[MAX_IF_NAME]; + char ip[MAX_IP_LEN]; + int up; + unsigned long rx_bytes; + unsigned long tx_bytes; + unsigned long rx_packets; + unsigned long tx_packets; + unsigned long rx_errors; + unsigned long tx_errors; + unsigned long rx_dropped; + unsigned long tx_dropped; + unsigned long prev_rx_bytes; + unsigned long prev_tx_bytes; + double rx_rate; + double tx_rate; +} NetworkInterface; + +typedef struct { + int count; + NetworkInterface interfaces[MAX_INTERFACES]; +} NetworkInfo; + +int network_info_init(NetworkInfo *info); +int network_info_update(NetworkInfo *info, double interval); + +#endif diff --git a/include/process.h b/include/process.h new file mode 100644 index 0000000..93acd95 --- /dev/null +++ b/include/process.h @@ -0,0 +1,44 @@ +/* retoor */ +#ifndef PROCESS_H +#define PROCESS_H + +#define MAX_PROCESSES 4096 +#define MAX_CMD_LEN 256 +#define MAX_USER_LEN 64 + +typedef struct { + int pid; + int ppid; + int uid; + char user[MAX_USER_LEN]; + char state; + char cmd[MAX_CMD_LEN]; + int threads; + int priority; + int nice; + unsigned long vsize; + unsigned long rss; + unsigned long utime; + unsigned long stime; + unsigned long prev_utime; + unsigned long prev_stime; + double cpu_percent; + double mem_percent; +} ProcessInfo; + +typedef struct { + int total; + int running; + int sleeping; + int stopped; + int zombie; + int count; + ProcessInfo processes[MAX_PROCESSES]; +} ProcessList; + +int process_list_init(ProcessList *list); +int process_list_update(ProcessList *list, unsigned long total_mem, double interval, int num_cpus); +void process_list_sort_by_cpu(ProcessList *list); +void process_list_sort_by_mem(ProcessList *list); + +#endif diff --git a/include/system.h b/include/system.h new file mode 100644 index 0000000..4658b22 --- /dev/null +++ b/include/system.h @@ -0,0 +1,27 @@ +/* retoor */ +#ifndef SYSTEM_H +#define SYSTEM_H + +#define MAX_HOSTNAME_LEN 256 +#define MAX_KERNEL_LEN 256 +#define MAX_OS_LEN 256 +#define MAX_ARCH_LEN 64 + +typedef struct { + char hostname[MAX_HOSTNAME_LEN]; + char kernel[MAX_KERNEL_LEN]; + char os_name[MAX_OS_LEN]; + char arch[MAX_ARCH_LEN]; + long uptime_seconds; + int uptime_days; + int uptime_hours; + int uptime_minutes; + double load_1; + double load_5; + double load_15; +} SystemInfo; + +int system_info_init(SystemInfo *info); +int system_info_update(SystemInfo *info); + +#endif diff --git a/include/terminal.h b/include/terminal.h new file mode 100644 index 0000000..8504afb --- /dev/null +++ b/include/terminal.h @@ -0,0 +1,48 @@ +/* retoor */ +#ifndef TERMINAL_H +#define TERMINAL_H + +#include + +#define COLOR_RESET "\033[0m" +#define COLOR_BOLD "\033[1m" +#define COLOR_RED "\033[31m" +#define COLOR_GREEN "\033[32m" +#define COLOR_YELLOW "\033[33m" +#define COLOR_BLUE "\033[34m" +#define COLOR_MAGENTA "\033[35m" +#define COLOR_CYAN "\033[36m" +#define COLOR_WHITE "\033[37m" +#define COLOR_BOLD_RED "\033[1;31m" +#define COLOR_BOLD_GREEN "\033[1;32m" +#define COLOR_BOLD_YELLOW "\033[1;33m" +#define COLOR_BOLD_BLUE "\033[1;34m" +#define COLOR_BOLD_CYAN "\033[1;36m" +#define COLOR_BOLD_WHITE "\033[1;37m" +#define COLOR_BG_RED "\033[41m" +#define COLOR_BG_GREEN "\033[42m" +#define COLOR_BG_BLUE "\033[44m" + +typedef struct { + int rows; + int cols; + struct termios orig_termios; + char *buffer; + size_t buffer_size; + size_t buffer_pos; +} Terminal; + +int terminal_init(Terminal *term); +void terminal_cleanup(Terminal *term); +void terminal_get_size(Terminal *term); +void terminal_clear(Terminal *term); +void terminal_move_cursor(Terminal *term, int row, int col); +void terminal_hide_cursor(Terminal *term); +void terminal_show_cursor(Terminal *term); +void terminal_buffer_clear(Terminal *term); +void terminal_buffer_append(Terminal *term, const char *str); +void terminal_buffer_flush(Terminal *term); +void terminal_set_raw_mode(Terminal *term); +void terminal_restore_mode(Terminal *term); + +#endif diff --git a/rtop b/rtop new file mode 100755 index 0000000..c1e32c2 Binary files /dev/null and b/rtop differ diff --git a/rtop.md b/rtop.md new file mode 100644 index 0000000..b7c413a --- /dev/null +++ b/rtop.md @@ -0,0 +1,202 @@ +# Htop Clone System Monitor - C Implementation Specification - rtop + +## Project Overview + +Create a terminal-based system monitoring tool in C that replicates htop's visual interface and functionality. The tool displays comprehensive system statistics in a dynamic, auto-refreshing interface with no external dependencies or interactive actions. The application name will be `rtop`. + +## Core Requirements + +### Language & Constraints +- Implementation language: C (C99 or later) +- No external library dependencies beyond standard C library +- Single binary compilation with no runtime dependencies +- Statically linked where possible +- Cross-platform Linux support (primary focus: Linux kernel interfaces) + +### Data Sources +All system information must be read directly from: +- `/proc` filesystem (process information, CPU stats, memory) +- `/sys` filesystem (system-wide metrics) +- Standard C library system calls (sysconf, getrlimit, etc.) +- `/etc` system files (hostname, OS information) + +## Display Requirements + +### Terminal Interface +- Auto-refreshing display (configurable interval, default 1-2 seconds) +- Full-screen terminal rendering using raw terminal control +- Handle terminal resize events gracefully +- Color support for visual differentiation +- UTF-8 character support for borders and separators + +### Information Categories + +#### System Overview Section +- Hostname and kernel version +- Uptime (days, hours, minutes) +- Current date and time +- Load averages (1, 5, 15 minute) +- System architecture + +#### CPU Information +- Total CPU count and logical cores +- Per-core utilization percentages +- Aggregate CPU usage +- CPU frequency (current, min, max if available) +- CPU temperature (if available from thermal zones) +- Context switches per second +- Interrupts per second + +#### Memory & Swap +- Total physical memory +- Used and available memory +- Memory utilization percentage +- Cached and buffered memory +- Swap total, used, free +- Swap utilization percentage +- Page faults and page in/out statistics + +#### Process Statistics +- Total process count +- Running processes +- Sleeping processes +- Zombie/defunct processes +- Stopped processes + +#### I/O Statistics +- Disk read/write operations per second +- Disk bytes read/written per second +- I/O wait time percentage +- Network statistics (if available): bytes in/out, packets + +#### Process Table +- Scrollable list of running processes (or top N processes) +- Per-process information: + - Process ID (PID) + - Parent PID (PPID) + - User (UID or username) + - CPU usage percentage + - Memory usage percentage and absolute (RSS) + - Virtual memory size (VSZ) + - Process state (running, sleeping, zombie, etc.) + - Command name and arguments (or executable path) + - Thread count + - Priority/Nice value + +#### Network Information +- Interface names and states +- IP addresses (IPv4) +- RX/TX bytes and packets +- Network errors and dropped packets + +#### Filesystem Statistics +- Mount points and filesystems +- Total, used, available space per mount +- Utilization percentage +- Filesystem type + +## Interface Layout + +### Header Section +Display system overview with high-level metrics in a compact, scannable format. + +### Metrics Panels +Organize information into logical sections: +- CPU and thermal metrics +- Memory and swap usage +- Process counts and states +- Network and I/O throughput +- Filesystem usage + +### Process List +Table format showing top processes by CPU or memory usage, with sortable columns and clear visual hierarchy. + +### Footer +Display refresh rate, current timestamp, and interaction hints (if applicable). + +## Rendering Approach + +### Terminal Control +- Use standard ANSI escape codes for cursor positioning, colors, and formatting +- Implement double-buffering to prevent flicker +- Clear and redraw entire screen on each refresh cycle +- Handle terminal capabilities detection + +### Performance Considerations +- Minimize system calls per refresh cycle +- Cache system information that doesn't change frequently +- Read `/proc` efficiently (sequential parsing) +- Update only changed values in display +- Target <5% CPU usage for the monitor itself + +### Color Scheme +- Define a readable color palette using standard terminal colors (16-color or 256-color) +- Use colors to highlight: + - High utilization (red threshold) + - Medium utilization (yellow threshold) + - Normal ranges (green) + - Neutral information (white/default) + +## Configuration & Behavior + +### Default Behavior +- Refresh interval: 1-2 seconds (user-adjustable via command-line argument or config) +- Start with full system view +- Sort processes by CPU usage by default (or memory alternative) +- Display processes in descending utilization order + +### Graceful Shutdown +- Clean terminal state on exit (restore cursor visibility, reset colors) +- Handle SIGINT (Ctrl+C) for clean termination +- Restore terminal to pre-execution state + +### Edge Cases +- Handle systems with unusual core counts (single-core, high-core systems) +- Support systems with limited `/proc` filesystem features +- Gracefully degrade missing data sources +- Handle rapid terminal resizing without crashing + +## Data Update Strategy + +### Sampling Approach +- Read all system metrics at regular intervals (matching refresh rate) +- Calculate deltas for rate-based metrics (I/O operations, network traffic) +- Maintain rolling history for trend calculation if needed + +### Metric Calculation +- CPU usage: derived from `/proc/stat` (user, system, idle times) +- Memory: from `/proc/meminfo` +- Process metrics: from `/proc/[pid]/stat`, `/proc/[pid]/status` +- I/O: from `/proc/diskstats` and `/proc/net/dev` + +## Compilation & Deployment + +### Build Requirements +- Standard C compiler (gcc, clang) +- Produces single static binary (or minimal dynamic linking) +- No configuration files required at runtime +- No installation script needed + +### Binary Size & Performance +- Target executable size: <2MB +- Startup time: <100ms +- Refresh cycle: <100ms per iteration +- Memory footprint: <10MB RSS during operation + +## Documentation + +The tool should be self-documenting through: +- Clear, logical screen layout +- Labeled sections and columns +- Metric units displayed inline +- Help text on startup or in footer + +## Success Criteria + +- Displays 20+ distinct system metrics simultaneously +- Updates smoothly at regular intervals +- Survives 1+ hour continuous operation without memory leaks +- Responsive to terminal size changes +- No dependencies beyond libc +- Compiles cleanly with `-Wall -Wextra -std=c99` +- Provides useful system overview at a glance comparable to htop diff --git a/src/cpu.c b/src/cpu.c new file mode 100644 index 0000000..da5a608 --- /dev/null +++ b/src/cpu.c @@ -0,0 +1,165 @@ +/* retoor */ +#include +#include +#include +#include +#include "cpu.h" + +static void calculate_cpu_usage(CpuCore *curr, CpuCore *prev) { + unsigned long long prev_idle = prev->idle + prev->iowait; + unsigned long long curr_idle = curr->idle + curr->iowait; + + unsigned long long prev_non_idle = prev->user + prev->nice + prev->system + + prev->irq + prev->softirq + prev->steal; + unsigned long long curr_non_idle = curr->user + curr->nice + curr->system + + curr->irq + curr->softirq + curr->steal; + + unsigned long long prev_total = prev_idle + prev_non_idle; + unsigned long long curr_total = curr_idle + curr_non_idle; + + unsigned long long total_diff = curr_total - prev_total; + unsigned long long idle_diff = curr_idle - prev_idle; + + if (total_diff > 0) { + curr->usage_percent = 100.0 * (double)(total_diff - idle_diff) / (double)total_diff; + } else { + curr->usage_percent = 0.0; + } +} + +int cpu_info_init(CpuInfo *info) { + if (!info) return -1; + memset(info, 0, sizeof(CpuInfo)); + return cpu_info_update(info, 0); +} + +int cpu_info_update(CpuInfo *info, double interval) { + if (!info) return -1; + + info->prev_total = info->total; + memcpy(info->prev_cores, info->cores, sizeof(info->cores)); + info->prev_context_switches = info->context_switches; + info->prev_interrupts = info->interrupts; + + FILE *fp = fopen("/proc/stat", "r"); + if (!fp) return -1; + + char line[1024]; + int core_idx = 0; + + while (fgets(line, sizeof(line), fp)) { + if (strncmp(line, "cpu ", 4) == 0) { + sscanf(line + 4, "%llu %llu %llu %llu %llu %llu %llu %llu %llu %llu", + &info->total.user, &info->total.nice, &info->total.system, + &info->total.idle, &info->total.iowait, &info->total.irq, + &info->total.softirq, &info->total.steal, &info->total.guest, + &info->total.guest_nice); + calculate_cpu_usage(&info->total, &info->prev_total); + } else if (strncmp(line, "cpu", 3) == 0 && line[3] >= '0' && line[3] <= '9') { + if (core_idx < MAX_CPUS) { + int cpu_num; + sscanf(line + 3, "%d %llu %llu %llu %llu %llu %llu %llu %llu %llu %llu", + &cpu_num, + &info->cores[core_idx].user, &info->cores[core_idx].nice, + &info->cores[core_idx].system, &info->cores[core_idx].idle, + &info->cores[core_idx].iowait, &info->cores[core_idx].irq, + &info->cores[core_idx].softirq, &info->cores[core_idx].steal, + &info->cores[core_idx].guest, &info->cores[core_idx].guest_nice); + calculate_cpu_usage(&info->cores[core_idx], &info->prev_cores[core_idx]); + core_idx++; + } + } else if (strncmp(line, "ctxt ", 5) == 0) { + sscanf(line + 5, "%lu", &info->context_switches); + } else if (strncmp(line, "intr ", 5) == 0) { + sscanf(line + 5, "%lu", &info->interrupts); + } + } + + info->num_cores = core_idx; + fclose(fp); + + if (interval > 0) { + info->context_switches_per_sec = (double)(info->context_switches - info->prev_context_switches) / interval; + info->interrupts_per_sec = (double)(info->interrupts - info->prev_interrupts) / interval; + } + + fp = fopen("/proc/cpuinfo", "r"); + if (fp) { + while (fgets(line, sizeof(line), fp)) { + if (strncmp(line, "cpu MHz", 7) == 0) { + char *colon = strchr(line, ':'); + if (colon) { + info->frequency_mhz = atof(colon + 1); + break; + } + } + } + fclose(fp); + } + + fp = fopen("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq", "r"); + if (fp) { + unsigned long freq; + if (fscanf(fp, "%lu", &freq) == 1) { + info->min_freq_mhz = freq / 1000.0; + } + fclose(fp); + } + + fp = fopen("/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq", "r"); + if (fp) { + unsigned long freq; + if (fscanf(fp, "%lu", &freq) == 1) { + info->max_freq_mhz = freq / 1000.0; + } + fclose(fp); + } + + info->temperature = -1; + DIR *dir = opendir("/sys/class/thermal"); + if (dir) { + struct dirent *entry; + while ((entry = readdir(dir))) { + if (strncmp(entry->d_name, "thermal_zone", 12) == 0) { + char path[512]; + snprintf(path, sizeof(path), "/sys/class/thermal/%s/type", entry->d_name); + FILE *type_fp = fopen(path, "r"); + if (type_fp) { + char type[64]; + if (fgets(type, sizeof(type), type_fp)) { + type[strcspn(type, "\n")] = '\0'; + if (strstr(type, "x86_pkg") || strstr(type, "coretemp") || + strstr(type, "cpu") || strstr(type, "CPU")) { + fclose(type_fp); + snprintf(path, sizeof(path), "/sys/class/thermal/%s/temp", entry->d_name); + FILE *temp_fp = fopen(path, "r"); + if (temp_fp) { + int temp; + if (fscanf(temp_fp, "%d", &temp) == 1) { + info->temperature = temp / 1000.0; + } + fclose(temp_fp); + } + break; + } + } + fclose(type_fp); + } + } + } + closedir(dir); + } + + if (info->temperature < 0) { + fp = fopen("/sys/class/thermal/thermal_zone0/temp", "r"); + if (fp) { + int temp; + if (fscanf(fp, "%d", &temp) == 1) { + info->temperature = temp / 1000.0; + } + fclose(fp); + } + } + + return 0; +} diff --git a/src/disk.c b/src/disk.c new file mode 100644 index 0000000..16b90f8 --- /dev/null +++ b/src/disk.c @@ -0,0 +1,82 @@ +/* retoor */ +#include +#include +#include +#include "disk.h" +#include "cpu.h" + +int disk_info_init(DiskInfo *info) { + if (!info) return -1; + memset(info, 0, sizeof(DiskInfo)); + return disk_info_update(info, 0); +} + +int disk_info_update(DiskInfo *info, double interval) { + if (!info) return -1; + + DiskStats prev_disks[MAX_DISKS]; + int prev_count = info->count; + memcpy(prev_disks, info->disks, sizeof(prev_disks)); + + memset(info->disks, 0, sizeof(info->disks)); + info->count = 0; + + FILE *fp = fopen("/proc/diskstats", "r"); + if (!fp) return -1; + + char line[512]; + while (fgets(line, sizeof(line), fp) && info->count < MAX_DISKS) { + unsigned int major, minor; + char name[64]; + unsigned long reads, reads_merged, read_sectors, read_time; + unsigned long writes, writes_merged, write_sectors, write_time; + + int ret = sscanf(line, "%u %u %63s %lu %lu %lu %lu %lu %lu %lu %lu", + &major, &minor, name, + &reads, &reads_merged, &read_sectors, &read_time, + &writes, &writes_merged, &write_sectors, &write_time); + + if (ret < 11) continue; + + size_t name_len = strlen(name); + if (name_len >= 3 && name[name_len-1] >= '0' && name[name_len-1] <= '9' && + name[name_len-2] >= '0' && name[name_len-2] <= '9') { + continue; + } + if (strncmp(name, "loop", 4) == 0 || strncmp(name, "ram", 3) == 0 || + strncmp(name, "dm-", 3) == 0) { + continue; + } + + DiskStats *disk = &info->disks[info->count]; + strncpy(disk->name, name, MAX_DISK_NAME - 1); + disk->name[MAX_DISK_NAME - 1] = '\0'; + disk->reads = reads; + disk->writes = writes; + disk->read_sectors = read_sectors; + disk->write_sectors = write_sectors; + + if (interval > 0) { + for (int i = 0; i < prev_count; i++) { + if (strcmp(prev_disks[i].name, disk->name) == 0) { + disk->prev_reads = prev_disks[i].reads; + disk->prev_writes = prev_disks[i].writes; + disk->prev_read_sectors = prev_disks[i].read_sectors; + disk->prev_write_sectors = prev_disks[i].write_sectors; + + disk->reads_per_sec = (double)(disk->reads - disk->prev_reads) / interval; + disk->writes_per_sec = (double)(disk->writes - disk->prev_writes) / interval; + disk->read_bytes_per_sec = (double)(disk->read_sectors - disk->prev_read_sectors) * 512 / interval; + disk->write_bytes_per_sec = (double)(disk->write_sectors - disk->prev_write_sectors) * 512 / interval; + break; + } + } + } + + info->count++; + } + + fclose(fp); + + return 0; +} diff --git a/src/display.c b/src/display.c new file mode 100644 index 0000000..617ab38 --- /dev/null +++ b/src/display.c @@ -0,0 +1,525 @@ +/* retoor */ +#include +#include +#include +#include +#include "display.h" + +static void format_bytes(unsigned long bytes, char *buf, size_t len) { + const char *units[] = {"B", "K", "M", "G", "T"}; + int unit = 0; + double size = (double)bytes; + while (size >= 1024.0 && unit < 4) { + size /= 1024.0; + unit++; + } + if (unit == 0) snprintf(buf, len, "%5luB", bytes); + else snprintf(buf, len, "%5.1f%s", size, units[unit]); +} + +static void format_rate(double rate, char *buf, size_t len) { + const char *units[] = {"B/s", "K/s", "M/s", "G/s"}; + int unit = 0; + while (rate >= 1024.0 && unit < 3) { + rate /= 1024.0; + unit++; + } + snprintf(buf, len, "%7.1f%s", rate, units[unit]); +} + +static void draw_bar(Terminal *term, double pct, int w) { + if (pct < 0) pct = 0; + if (pct > 100) pct = 100; + int f = (int)(pct * w / 100); + terminal_buffer_append(term, "["); + for (int i = 0; i < w; i++) { + if (i < f) { + if (pct >= 90) terminal_buffer_append(term, COLOR_RED "|" COLOR_RESET); + else if (pct >= 70) terminal_buffer_append(term, COLOR_YELLOW "|" COLOR_RESET); + else terminal_buffer_append(term, COLOR_GREEN "|" COLOR_RESET); + } else { + terminal_buffer_append(term, " "); + } + } + terminal_buffer_append(term, "]"); +} + +void display_header(Terminal *term, SystemInfo *sys) { + char buf[128]; + time_t now = time(NULL); + struct tm *t = localtime(&now); + char ts[20]; + strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S", t); + + terminal_move_cursor(term, 1, 1); + terminal_buffer_append(term, COLOR_BOLD_WHITE); + terminal_buffer_append(term, sys->hostname); + terminal_buffer_append(term, COLOR_RESET " " COLOR_CYAN); + terminal_buffer_append(term, sys->kernel); + terminal_buffer_append(term, COLOR_RESET " " COLOR_YELLOW); + terminal_buffer_append(term, sys->arch); + terminal_buffer_append(term, COLOR_RESET " "); + snprintf(buf, sizeof(buf), "up %s%dd %02d:%02d%s ", COLOR_GREEN, sys->uptime_days, sys->uptime_hours, sys->uptime_minutes, COLOR_RESET); + terminal_buffer_append(term, buf); + snprintf(buf, sizeof(buf), "load %s%.2f %.2f %.2f%s", COLOR_BOLD_WHITE, sys->load_1, sys->load_5, sys->load_15, COLOR_RESET); + terminal_buffer_append(term, buf); + + int time_col = term->cols - 19; + if (time_col > 0) { + terminal_move_cursor(term, 1, time_col); + terminal_buffer_append(term, COLOR_BOLD_CYAN); + terminal_buffer_append(term, ts); + terminal_buffer_append(term, COLOR_RESET); + } +} + +void display_tabs(Terminal *term, int current_tab) { + const char *tabs[] = {"CPU", "Memory", "Network", "Disk", "Process"}; + char buf[32]; + + terminal_move_cursor(term, 2, 1); + for (int i = 0; i < 5; i++) { + if (i == current_tab) { + snprintf(buf, sizeof(buf), " %s[%d]%s%s ", COLOR_BOLD_WHITE, i + 1, tabs[i], COLOR_RESET); + } else { + snprintf(buf, sizeof(buf), " %s[%d]%s%s ", COLOR_CYAN, i + 1, tabs[i], COLOR_RESET); + } + terminal_buffer_append(term, buf); + } + terminal_move_cursor(term, 3, 1); + for (int i = 0; i < term->cols - 1; i++) { + terminal_buffer_append(term, "─"); + } +} + +void display_footer(Terminal *term, double refresh_rate) { + char buf[128]; + terminal_get_size(term); + terminal_move_cursor(term, term->rows, 1); + snprintf(buf, sizeof(buf), "%s◄ ► Navigate tabs | 1-5 Jump | Refresh: %.1fs | q quit%s", + COLOR_CYAN, refresh_rate, COLOR_RESET); + terminal_buffer_append(term, buf); +} + +void display_overview(Terminal *term, CpuInfo *cpu, MemoryInfo *mem, + ProcessList *procs, NetworkInfo *net, + DiskInfo *disk, FilesystemInfo *fs) { + char buf[256], tmp[16], tmp2[16]; + int row = 4; + int bar_w = 20; + int col2 = 45; + + int half = (cpu->num_cores + 1) / 2; + if (half > 4) half = 4; + for (int i = 0; i < half; i++) { + terminal_move_cursor(term, row, 1); + snprintf(buf, sizeof(buf), "%sCPU%-2d%s ", COLOR_CYAN, i, COLOR_RESET); + terminal_buffer_append(term, buf); + draw_bar(term, cpu->cores[i].usage_percent, bar_w); + snprintf(buf, sizeof(buf), " %5.1f%%", cpu->cores[i].usage_percent); + terminal_buffer_append(term, buf); + + int j = i + half; + if (j < cpu->num_cores) { + terminal_move_cursor(term, row, col2); + snprintf(buf, sizeof(buf), "%sCPU%-2d%s ", COLOR_CYAN, j, COLOR_RESET); + terminal_buffer_append(term, buf); + draw_bar(term, cpu->cores[j].usage_percent, bar_w); + snprintf(buf, sizeof(buf), " %5.1f%%", cpu->cores[j].usage_percent); + terminal_buffer_append(term, buf); + } + row++; + } + + terminal_move_cursor(term, row, 1); + snprintf(buf, sizeof(buf), "%sAvg %s ", COLOR_CYAN, COLOR_RESET); + terminal_buffer_append(term, buf); + draw_bar(term, cpu->total.usage_percent, bar_w); + snprintf(buf, sizeof(buf), " %5.1f%% %s%d cores%s", cpu->total.usage_percent, COLOR_YELLOW, cpu->num_cores, COLOR_RESET); + terminal_buffer_append(term, buf); + + terminal_move_cursor(term, row, col2); + if (cpu->temperature > 0) { + const char *tc = cpu->temperature >= 80 ? COLOR_RED : cpu->temperature >= 60 ? COLOR_YELLOW : COLOR_GREEN; + snprintf(buf, sizeof(buf), "%sTemp%s %s%3.0f°C%s ", COLOR_CYAN, COLOR_RESET, tc, cpu->temperature, COLOR_RESET); + terminal_buffer_append(term, buf); + } + if (cpu->frequency_mhz > 0) { + snprintf(buf, sizeof(buf), "%sFreq%s %4.0fMHz", COLOR_CYAN, COLOR_RESET, cpu->frequency_mhz); + terminal_buffer_append(term, buf); + } + row++; + + terminal_move_cursor(term, row, 1); + snprintf(buf, sizeof(buf), "%sCtx%s %9.0f/s %sIRQ%s %9.0f/s", + COLOR_CYAN, COLOR_RESET, cpu->context_switches_per_sec, + COLOR_CYAN, COLOR_RESET, cpu->interrupts_per_sec); + terminal_buffer_append(term, buf); + row++; + + format_bytes(mem->used * 1024, tmp, sizeof(tmp)); + format_bytes(mem->total * 1024, tmp2, sizeof(tmp2)); + terminal_move_cursor(term, row, 1); + snprintf(buf, sizeof(buf), "%sMem %s ", COLOR_CYAN, COLOR_RESET); + terminal_buffer_append(term, buf); + draw_bar(term, mem->used_percent, bar_w); + snprintf(buf, sizeof(buf), " %5.1f%% %s / %s", mem->used_percent, tmp, tmp2); + terminal_buffer_append(term, buf); + + format_bytes(mem->swap_used * 1024, tmp, sizeof(tmp)); + format_bytes(mem->swap_total * 1024, tmp2, sizeof(tmp2)); + terminal_move_cursor(term, row, col2); + snprintf(buf, sizeof(buf), "%sSwap %s ", COLOR_CYAN, COLOR_RESET); + terminal_buffer_append(term, buf); + draw_bar(term, mem->swap_percent, bar_w); + snprintf(buf, sizeof(buf), " %5.1f%% %s / %s", mem->swap_percent, tmp, tmp2); + terminal_buffer_append(term, buf); + row++; + + format_bytes(mem->available * 1024, tmp, sizeof(tmp)); + format_bytes(mem->buffers * 1024, tmp2, sizeof(tmp2)); + terminal_move_cursor(term, row, 1); + char cache[16]; + format_bytes(mem->cached * 1024, cache, sizeof(cache)); + snprintf(buf, sizeof(buf), "%sAvail%s %s %sBuf%s %s %sCache%s %s %sPgIn%s %6.0f/s %sPgOut%s %6.0f/s", + COLOR_CYAN, COLOR_RESET, tmp, + COLOR_CYAN, COLOR_RESET, tmp2, + COLOR_CYAN, COLOR_RESET, cache, + COLOR_CYAN, COLOR_RESET, mem->page_in_per_sec, + COLOR_CYAN, COLOR_RESET, mem->page_out_per_sec); + terminal_buffer_append(term, buf); + row++; + + terminal_move_cursor(term, row, 1); + snprintf(buf, sizeof(buf), "%sTasks%s %-4d %sRun%s %s%-3d%s %sSlp%s %-4d %sStp%s %s%-2d%s %sZmb%s %s%-2d%s", + COLOR_CYAN, COLOR_RESET, procs->total, + COLOR_CYAN, COLOR_RESET, COLOR_GREEN, procs->running, COLOR_RESET, + COLOR_CYAN, COLOR_RESET, procs->sleeping, + COLOR_CYAN, COLOR_RESET, COLOR_YELLOW, procs->stopped, COLOR_RESET, + COLOR_CYAN, COLOR_RESET, COLOR_RED, procs->zombie, COLOR_RESET); + terminal_buffer_append(term, buf); + row++; + + int net_start = row; + for (int i = 0; i < net->count && i < 4; i++) { + NetworkInterface *n = &net->interfaces[i]; + char rx[16], tx[16]; + format_rate(n->rx_rate, rx, sizeof(rx)); + format_rate(n->tx_rate, tx, sizeof(tx)); + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%s%-10s%s %s%-15s%s %s▼%s%-10s %s▲%s%-10s", + COLOR_CYAN, n->name, COLOR_RESET, + n->up ? COLOR_GREEN : COLOR_RED, n->ip[0] ? n->ip : "-", COLOR_RESET, + COLOR_GREEN, COLOR_RESET, rx, + COLOR_RED, COLOR_RESET, tx); + terminal_buffer_append(term, buf); + } + + row = net_start; + for (int i = 0; i < disk->count && i < 4; i++) { + DiskStats *d = &disk->disks[i]; + char rd[16], wr[16]; + format_rate(d->read_bytes_per_sec, rd, sizeof(rd)); + format_rate(d->write_bytes_per_sec, wr, sizeof(wr)); + terminal_move_cursor(term, row++, col2); + snprintf(buf, sizeof(buf), "%s%-8s%s %sR%s%-10s %sW%s%-10s %4.0f/%4.0f", + COLOR_CYAN, d->name, COLOR_RESET, + COLOR_GREEN, COLOR_RESET, rd, + COLOR_RED, COLOR_RESET, wr, + d->reads_per_sec, d->writes_per_sec); + terminal_buffer_append(term, buf); + } + + int max_net_disk = net->count > disk->count ? net->count : disk->count; + if (max_net_disk > 4) max_net_disk = 4; + row = net_start + max_net_disk; + + for (int i = 0; i < fs->count && i < 3; i++) { + FilesystemStats *f = &fs->filesystems[i]; + char used[16], total[16]; + format_bytes(f->used * 1024, used, sizeof(used)); + format_bytes(f->total * 1024, total, sizeof(total)); + const char *c = f->used_percent >= 90 ? COLOR_RED : f->used_percent >= 70 ? COLOR_YELLOW : COLOR_GREEN; + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%s%-12s%s ", COLOR_CYAN, f->mount_point, COLOR_RESET); + terminal_buffer_append(term, buf); + draw_bar(term, f->used_percent, 15); + snprintf(buf, sizeof(buf), " %s%5.1f%%%s %7s / %7s %s", + c, f->used_percent, COLOR_RESET, used, total, f->fs_type); + terminal_buffer_append(term, buf); + } + row++; + + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%s%7s %7s %-8s S %5s %5s %7s %6s %3s %3s %-20s%s", + COLOR_BOLD_WHITE, "PID", "PPID", "USER", "CPU%", "MEM%", "RSS", "VIRT", "THR", "NI", "COMMAND", COLOR_RESET); + terminal_buffer_append(term, buf); + + int max_procs = term->rows - row - 2; + if (max_procs > 10) max_procs = 10; + for (int i = 0; i < procs->count && i < max_procs; i++) { + ProcessInfo *p = &procs->processes[i]; + char rss[16], virt[16]; + format_bytes(p->rss, rss, sizeof(rss)); + format_bytes(p->vsize, virt, sizeof(virt)); + const char *sc = p->state == 'R' ? COLOR_GREEN : p->state == 'D' || p->state == 'Z' ? COLOR_RED : p->state == 'T' ? COLOR_YELLOW : COLOR_RESET; + const char *cpuc = p->cpu_percent >= 50 ? COLOR_RED : p->cpu_percent >= 20 ? COLOR_YELLOW : COLOR_RESET; + const char *memc = p->mem_percent >= 10 ? COLOR_RED : p->mem_percent >= 5 ? COLOR_YELLOW : COLOR_RESET; + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%7d %7d %-8.8s %s%c%s %s%5.1f%s %s%5.1f%s %7s %6s %3d %3d %-20.20s", + p->pid, p->ppid, p->user, + sc, p->state, COLOR_RESET, + cpuc, p->cpu_percent, COLOR_RESET, + memc, p->mem_percent, COLOR_RESET, + rss, virt, p->threads, p->nice, p->cmd); + terminal_buffer_append(term, buf); + } +} + +void display_cpu_detail(Terminal *term, CpuInfo *cpu) { + char buf[128]; + int row = 4; + int bar_w = term->cols - 20; + if (bar_w > 60) bar_w = 60; + if (bar_w < 20) bar_w = 20; + + for (int i = 0; i < cpu->num_cores && row < term->rows - 2; i++) { + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%sCPU%-3d%s ", COLOR_CYAN, i, COLOR_RESET); + terminal_buffer_append(term, buf); + draw_bar(term, cpu->cores[i].usage_percent, bar_w); + snprintf(buf, sizeof(buf), " %6.1f%%", cpu->cores[i].usage_percent); + terminal_buffer_append(term, buf); + } + + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%sAVG %s ", COLOR_BOLD_CYAN, COLOR_RESET); + terminal_buffer_append(term, buf); + draw_bar(term, cpu->total.usage_percent, bar_w); + snprintf(buf, sizeof(buf), " %6.1f%%", cpu->total.usage_percent); + terminal_buffer_append(term, buf); + row++; + + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%sCores:%s %d", COLOR_CYAN, COLOR_RESET, cpu->num_cores); + terminal_buffer_append(term, buf); + + if (cpu->temperature > 0) { + terminal_move_cursor(term, row++, 1); + const char *tc = cpu->temperature >= 80 ? COLOR_RED : cpu->temperature >= 60 ? COLOR_YELLOW : COLOR_GREEN; + snprintf(buf, sizeof(buf), "%sTemperature:%s %s%.1f°C%s", COLOR_CYAN, COLOR_RESET, tc, cpu->temperature, COLOR_RESET); + terminal_buffer_append(term, buf); + } + + if (cpu->frequency_mhz > 0) { + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%sFrequency:%s %.0f MHz (%.0f - %.0f)", + COLOR_CYAN, COLOR_RESET, cpu->frequency_mhz, cpu->min_freq_mhz, cpu->max_freq_mhz); + terminal_buffer_append(term, buf); + } + + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%sCtx Switch:%s %.0f/s", COLOR_CYAN, COLOR_RESET, cpu->context_switches_per_sec); + terminal_buffer_append(term, buf); + + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%sInterrupts:%s %.0f/s", COLOR_CYAN, COLOR_RESET, cpu->interrupts_per_sec); + terminal_buffer_append(term, buf); +} + +void display_memory_detail(Terminal *term, MemoryInfo *mem) { + char buf[128], tmp[16], tmp2[16]; + int row = 4; + int bar_w = term->cols - 25; + if (bar_w > 50) bar_w = 50; + if (bar_w < 20) bar_w = 20; + + format_bytes(mem->used * 1024, tmp, sizeof(tmp)); + format_bytes(mem->total * 1024, tmp2, sizeof(tmp2)); + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%sRAM %s ", COLOR_CYAN, COLOR_RESET); + terminal_buffer_append(term, buf); + draw_bar(term, mem->used_percent, bar_w); + snprintf(buf, sizeof(buf), " %5.1f%% %s / %s", mem->used_percent, tmp, tmp2); + terminal_buffer_append(term, buf); + + format_bytes(mem->swap_used * 1024, tmp, sizeof(tmp)); + format_bytes(mem->swap_total * 1024, tmp2, sizeof(tmp2)); + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%sSwap %s ", COLOR_CYAN, COLOR_RESET); + terminal_buffer_append(term, buf); + draw_bar(term, mem->swap_percent, bar_w); + snprintf(buf, sizeof(buf), " %5.1f%% %s / %s", mem->swap_percent, tmp, tmp2); + terminal_buffer_append(term, buf); + row++; + + format_bytes(mem->total * 1024, tmp, sizeof(tmp)); + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%sTotal: %s %s", COLOR_CYAN, COLOR_RESET, tmp); + terminal_buffer_append(term, buf); + + format_bytes(mem->used * 1024, tmp, sizeof(tmp)); + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%sUsed: %s %s (%.1f%%)", COLOR_CYAN, COLOR_RESET, tmp, mem->used_percent); + terminal_buffer_append(term, buf); + + format_bytes(mem->available * 1024, tmp, sizeof(tmp)); + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%sAvailable:%s %s", COLOR_CYAN, COLOR_RESET, tmp); + terminal_buffer_append(term, buf); + + format_bytes(mem->buffers * 1024, tmp, sizeof(tmp)); + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%sBuffers: %s %s", COLOR_CYAN, COLOR_RESET, tmp); + terminal_buffer_append(term, buf); + + format_bytes(mem->cached * 1024, tmp, sizeof(tmp)); + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%sCached: %s %s", COLOR_CYAN, COLOR_RESET, tmp); + terminal_buffer_append(term, buf); + row++; + + format_bytes(mem->swap_total * 1024, tmp, sizeof(tmp)); + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%sSwap Total:%s %s", COLOR_CYAN, COLOR_RESET, tmp); + terminal_buffer_append(term, buf); + + format_bytes(mem->swap_used * 1024, tmp, sizeof(tmp)); + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%sSwap Used: %s %s (%.1f%%)", COLOR_CYAN, COLOR_RESET, tmp, mem->swap_percent); + terminal_buffer_append(term, buf); + row++; + + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%sPage In: %s %.0f/s", COLOR_CYAN, COLOR_RESET, mem->page_in_per_sec); + terminal_buffer_append(term, buf); + + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%sPage Out: %s %.0f/s", COLOR_CYAN, COLOR_RESET, mem->page_out_per_sec); + terminal_buffer_append(term, buf); +} + +void display_network_detail(Terminal *term, NetworkInfo *net) { + char buf[256], rx[16], tx[16], rxb[16], txb[16]; + int row = 4; + + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%s%-15s %-16s %-3s %-12s %-12s %-10s %-10s%s", + COLOR_BOLD_WHITE, "INTERFACE", "IP ADDRESS", "UP", "RX RATE", "TX RATE", "RX TOTAL", "TX TOTAL", COLOR_RESET); + terminal_buffer_append(term, buf); + + for (int i = 0; i < net->count && row < term->rows - 2; i++) { + NetworkInterface *n = &net->interfaces[i]; + format_rate(n->rx_rate, rx, sizeof(rx)); + format_rate(n->tx_rate, tx, sizeof(tx)); + format_bytes(n->rx_bytes, rxb, sizeof(rxb)); + format_bytes(n->tx_bytes, txb, sizeof(txb)); + + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%s%-15s%s %-16s %s%-3s%s %s▼%s%-11s %s▲%s%-11s %-10s %-10s", + COLOR_CYAN, n->name, COLOR_RESET, + n->ip[0] ? n->ip : "-", + n->up ? COLOR_GREEN : COLOR_RED, n->up ? "Yes" : "No", COLOR_RESET, + COLOR_GREEN, COLOR_RESET, rx, + COLOR_RED, COLOR_RESET, tx, + rxb, txb); + terminal_buffer_append(term, buf); + } +} + +void display_disk_detail(Terminal *term, DiskInfo *disk, FilesystemInfo *fs) { + char buf[256], rd[16], wr[16]; + int row = 4; + int bar_w = 25; + + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%s%-12s %-14s %-14s %-12s%s", + COLOR_BOLD_WHITE, "DEVICE", "READ", "WRITE", "IOPS (R/W)", COLOR_RESET); + terminal_buffer_append(term, buf); + + for (int i = 0; i < disk->count && row < term->rows / 2; i++) { + DiskStats *d = &disk->disks[i]; + format_rate(d->read_bytes_per_sec, rd, sizeof(rd)); + format_rate(d->write_bytes_per_sec, wr, sizeof(wr)); + + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%s%-12s%s %s▼%s %-12s %s▲%s %-12s %5.0f / %5.0f", + COLOR_CYAN, d->name, COLOR_RESET, + COLOR_GREEN, COLOR_RESET, rd, + COLOR_RED, COLOR_RESET, wr, + d->reads_per_sec, d->writes_per_sec); + terminal_buffer_append(term, buf); + } + row++; + + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%s%-20s %-22s %7s %9s %9s %-8s%s", + COLOR_BOLD_WHITE, "MOUNT", "USAGE", "PERCENT", "USED", "TOTAL", "TYPE", COLOR_RESET); + terminal_buffer_append(term, buf); + + for (int i = 0; i < fs->count && row < term->rows - 2; i++) { + FilesystemStats *f = &fs->filesystems[i]; + char used[16], total[16]; + format_bytes(f->used * 1024, used, sizeof(used)); + format_bytes(f->total * 1024, total, sizeof(total)); + const char *c = f->used_percent >= 90 ? COLOR_RED : f->used_percent >= 70 ? COLOR_YELLOW : COLOR_GREEN; + + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%s%-20s%s ", COLOR_CYAN, f->mount_point, COLOR_RESET); + terminal_buffer_append(term, buf); + draw_bar(term, f->used_percent, bar_w); + snprintf(buf, sizeof(buf), " %s%5.1f%%%s %9s %9s %-8s", + c, f->used_percent, COLOR_RESET, used, total, f->fs_type); + terminal_buffer_append(term, buf); + } +} + +void display_process_detail(Terminal *term, ProcessList *procs) { + char buf[256]; + int row = 4; + + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%sTasks:%s %d total, %s%d running%s, %d sleeping, %s%d stopped%s, %s%d zombie%s", + COLOR_CYAN, COLOR_RESET, procs->total, + COLOR_GREEN, procs->running, COLOR_RESET, + procs->sleeping, + COLOR_YELLOW, procs->stopped, COLOR_RESET, + COLOR_RED, procs->zombie, COLOR_RESET); + terminal_buffer_append(term, buf); + row++; + + int cmd_w = term->cols - 68; + if (cmd_w < 10) cmd_w = 10; + if (cmd_w > 40) cmd_w = 40; + + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%s%6s %6s %-8s S %5s %5s %7s %7s %3s %3s %-*s%s", + COLOR_BOLD_WHITE, "PID", "PPID", "USER", "CPU%", "MEM%", "RSS", "VIRT", "THR", "NI", cmd_w, "COMMAND", COLOR_RESET); + terminal_buffer_append(term, buf); + + int max_procs = term->rows - row - 2; + for (int i = 0; i < procs->count && i < max_procs; i++) { + ProcessInfo *p = &procs->processes[i]; + char rss[16], virt[16]; + format_bytes(p->rss, rss, sizeof(rss)); + format_bytes(p->vsize, virt, sizeof(virt)); + + const char *sc = p->state == 'R' ? COLOR_GREEN : + p->state == 'D' || p->state == 'Z' ? COLOR_RED : + p->state == 'T' ? COLOR_YELLOW : COLOR_RESET; + const char *cpuc = p->cpu_percent >= 50 ? COLOR_RED : + p->cpu_percent >= 20 ? COLOR_YELLOW : COLOR_RESET; + const char *memc = p->mem_percent >= 10 ? COLOR_RED : + p->mem_percent >= 5 ? COLOR_YELLOW : COLOR_RESET; + + terminal_move_cursor(term, row++, 1); + snprintf(buf, sizeof(buf), "%6d %6d %-8.8s %s%c%s %s%5.1f%s %s%5.1f%s %7s %7s %3d %3d %-*.*s", + p->pid, p->ppid, p->user, + sc, p->state, COLOR_RESET, + cpuc, p->cpu_percent, COLOR_RESET, + memc, p->mem_percent, COLOR_RESET, + rss, virt, p->threads, p->nice, + cmd_w, cmd_w, p->cmd); + terminal_buffer_append(term, buf); + } +} diff --git a/src/filesystem.c b/src/filesystem.c new file mode 100644 index 0000000..6e48a34 --- /dev/null +++ b/src/filesystem.c @@ -0,0 +1,62 @@ +/* retoor */ +#include +#include +#include +#include +#include +#include "filesystem.h" + +int filesystem_info_init(FilesystemInfo *info) { + if (!info) return -1; + memset(info, 0, sizeof(FilesystemInfo)); + return filesystem_info_update(info); +} + +int filesystem_info_update(FilesystemInfo *info) { + if (!info) return -1; + + memset(info, 0, sizeof(FilesystemInfo)); + + FILE *fp = setmntent("/etc/mtab", "r"); + if (!fp) { + fp = setmntent("/proc/mounts", "r"); + if (!fp) return -1; + } + + struct mntent *mnt; + while ((mnt = getmntent(fp)) && info->count < MAX_FILESYSTEMS) { + if (strncmp(mnt->mnt_fsname, "/dev/", 5) != 0) continue; + + if (strcmp(mnt->mnt_type, "squashfs") == 0 || + strcmp(mnt->mnt_type, "devtmpfs") == 0 || + strcmp(mnt->mnt_type, "tmpfs") == 0) { + continue; + } + + struct statvfs vfs; + if (statvfs(mnt->mnt_dir, &vfs) != 0) continue; + + FilesystemStats *fs = &info->filesystems[info->count]; + + strncpy(fs->device, mnt->mnt_fsname, MAX_DEVICE_LEN - 1); + fs->device[MAX_DEVICE_LEN - 1] = '\0'; + strncpy(fs->mount_point, mnt->mnt_dir, MAX_MOUNT_LEN - 1); + fs->mount_point[MAX_MOUNT_LEN - 1] = '\0'; + strncpy(fs->fs_type, mnt->mnt_type, MAX_FS_TYPE_LEN - 1); + fs->fs_type[MAX_FS_TYPE_LEN - 1] = '\0'; + + unsigned long block_size = vfs.f_frsize; + fs->total = (vfs.f_blocks * block_size) / 1024; + fs->available = (vfs.f_bavail * block_size) / 1024; + fs->used = fs->total - (vfs.f_bfree * block_size) / 1024; + + if (fs->total > 0) { + fs->used_percent = 100.0 * (double)fs->used / (double)fs->total; + } + + info->count++; + } + + endmntent(fp); + return 0; +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..52607f9 --- /dev/null +++ b/src/main.c @@ -0,0 +1,228 @@ +/* retoor */ +#include +#include +#include +#include +#include +#include +#include +#include "terminal.h" +#include "system.h" +#include "cpu.h" +#include "memory.h" +#include "process.h" +#include "network.h" +#include "disk.h" +#include "filesystem.h" +#include "display.h" + +#define TAB_CPU 0 +#define TAB_MEMORY 1 +#define TAB_NETWORK 2 +#define TAB_DISK 3 +#define TAB_PROCESS 4 +#define TAB_COUNT 5 + +static volatile int running = 1; +static Terminal term; + +static void signal_handler(int sig) { + (void)sig; + running = 0; +} + +static void resize_handler(int sig) { + (void)sig; + terminal_get_size(&term); +} + +static void print_help(const char *program) { + printf("rtop - Terminal-based system monitor\n\n"); + printf("Usage: %s [OPTIONS]\n\n", program); + printf("Options:\n"); + printf(" -d, --delay SECS Update interval in seconds (default: 1.0)\n"); + printf(" -r, --rotate SECS Auto-rotate tabs every SECS seconds (0=off, default: 0)\n"); + printf(" -s, --sort MODE Sort by: cpu (default), mem\n"); + printf(" -h, --help Show this help message\n"); + printf(" -v, --version Show version\n"); + printf("\nNavigation:\n"); + printf(" Left/Right arrows Switch tabs\n"); + printf(" 1-5 Jump to tab\n"); + printf(" q Quit\n"); +} + +static void print_version(void) { + printf("rtop 1.0.0\n"); +} + +static int read_key(void) { + char buf[8]; + int n = read(STDIN_FILENO, buf, sizeof(buf)); + if (n <= 0) return 0; + if (n == 1) return buf[0]; + if (n >= 3 && buf[0] == 27 && buf[1] == '[') { + switch (buf[2]) { + case 'A': return 1001; + case 'B': return 1002; + case 'C': return 1003; + case 'D': return 1004; + } + } + return buf[0]; +} + +int main(int argc, char *argv[]) { + double refresh_interval = 1.0; + double rotate_interval = 0.0; + int sort_by_mem = 0; + int current_tab = TAB_OVERVIEW; + + static struct option long_options[] = { + {"delay", required_argument, 0, 'd'}, + {"rotate", required_argument, 0, 'r'}, + {"sort", required_argument, 0, 's'}, + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'v'}, + {0, 0, 0, 0} + }; + + int opt; + while ((opt = getopt_long(argc, argv, "d:r:s:hv", long_options, NULL)) != -1) { + switch (opt) { + case 'd': + refresh_interval = atof(optarg); + if (refresh_interval < 0.1) refresh_interval = 0.1; + if (refresh_interval > 10.0) refresh_interval = 10.0; + break; + case 'r': + rotate_interval = atof(optarg); + if (rotate_interval < 0) rotate_interval = 0; + break; + case 's': + if (strcmp(optarg, "mem") == 0 || strcmp(optarg, "memory") == 0) { + sort_by_mem = 1; + } + break; + case 'h': + print_help(argv[0]); + return 0; + case 'v': + print_version(); + return 0; + default: + print_help(argv[0]); + return 1; + } + } + + signal(SIGINT, signal_handler); + signal(SIGTERM, signal_handler); + signal(SIGWINCH, resize_handler); + + if (terminal_init(&term) != 0) { + fprintf(stderr, "Failed to initialize terminal\n"); + return 1; + } + + SystemInfo sys; + CpuInfo cpu; + MemoryInfo mem; + ProcessList procs; + NetworkInfo net; + DiskInfo disk; + FilesystemInfo fs; + + if (system_info_init(&sys) != 0 || + cpu_info_init(&cpu) != 0 || + memory_info_init(&mem) != 0 || + process_list_init(&procs) != 0 || + network_info_init(&net) != 0 || + disk_info_init(&disk) != 0 || + filesystem_info_init(&fs) != 0) { + terminal_cleanup(&term); + fprintf(stderr, "Failed to initialize system info\n"); + return 1; + } + + struct timeval tv; + double rotate_elapsed = 0.0; + + while (running) { + system_info_update(&sys); + cpu_info_update(&cpu, refresh_interval); + memory_info_update(&mem, refresh_interval); + process_list_update(&procs, mem.total, refresh_interval, cpu.num_cores); + network_info_update(&net, refresh_interval); + disk_info_update(&disk, refresh_interval); + filesystem_info_update(&fs); + + if (sort_by_mem) { + process_list_sort_by_mem(&procs); + } else { + process_list_sort_by_cpu(&procs); + } + + if (rotate_interval > 0) { + rotate_elapsed += refresh_interval; + if (rotate_elapsed >= rotate_interval) { + current_tab = (current_tab + 1) % TAB_COUNT; + rotate_elapsed = 0.0; + } + } + + terminal_get_size(&term); + terminal_buffer_clear(&term); + terminal_buffer_append(&term, "\033[2J\033[H"); + + display_header(&term, &sys); + display_tabs(&term, current_tab); + + switch (current_tab) { + case TAB_CPU: + display_cpu_detail(&term, &cpu); + break; + case TAB_MEMORY: + display_memory_detail(&term, &mem, &procs); + break; + case TAB_NETWORK: + display_network_detail(&term, &net); + break; + case TAB_DISK: + display_disk_detail(&term, &disk, &fs); + break; + case TAB_PROCESS: + display_process_detail(&term, &procs); + break; + } + + display_footer(&term, refresh_interval); + terminal_buffer_flush(&term); + + fd_set fds; + FD_ZERO(&fds); + FD_SET(STDIN_FILENO, &fds); + + tv.tv_sec = (long)refresh_interval; + tv.tv_usec = (long)((refresh_interval - tv.tv_sec) * 1000000); + + int ret = select(STDIN_FILENO + 1, &fds, NULL, NULL, &tv); + if (ret > 0) { + int key = read_key(); + if (key == 'q' || key == 'Q') { + running = 0; + } else if (key == 1003) { + current_tab = (current_tab + 1) % TAB_COUNT; + rotate_elapsed = 0.0; + } else if (key == 1004) { + current_tab = (current_tab - 1 + TAB_COUNT) % TAB_COUNT; + rotate_elapsed = 0.0; + } else if (key >= '1' && key <= '5') { + current_tab = key - '1'; + rotate_elapsed = 0.0; + } + } + } + + terminal_cleanup(&term); + return 0; +} diff --git a/src/memory.c b/src/memory.c new file mode 100644 index 0000000..ad326e3 --- /dev/null +++ b/src/memory.c @@ -0,0 +1,89 @@ +/* retoor */ +#include +#include +#include +#include "memory.h" + +int memory_info_init(MemoryInfo *info) { + if (!info) return -1; + memset(info, 0, sizeof(MemoryInfo)); + return memory_info_update(info, 0); +} + +int memory_info_update(MemoryInfo *info, double interval) { + if (!info) return -1; + + info->prev_page_faults = info->page_faults; + info->prev_page_in = info->page_in; + info->prev_page_out = info->page_out; + + FILE *fp = fopen("/proc/meminfo", "r"); + if (!fp) return -1; + + char line[256]; + unsigned long mem_total = 0, mem_free = 0, mem_available = 0; + unsigned long buffers = 0, cached = 0, slab_reclaimable = 0; + unsigned long swap_total = 0, swap_free = 0; + + while (fgets(line, sizeof(line), fp)) { + if (strncmp(line, "MemTotal:", 9) == 0) { + sscanf(line + 9, "%lu", &mem_total); + } else if (strncmp(line, "MemFree:", 8) == 0) { + sscanf(line + 8, "%lu", &mem_free); + } else if (strncmp(line, "MemAvailable:", 13) == 0) { + sscanf(line + 13, "%lu", &mem_available); + } else if (strncmp(line, "Buffers:", 8) == 0) { + sscanf(line + 8, "%lu", &buffers); + } else if (strncmp(line, "Cached:", 7) == 0) { + sscanf(line + 7, "%lu", &cached); + } else if (strncmp(line, "SReclaimable:", 13) == 0) { + sscanf(line + 13, "%lu", &slab_reclaimable); + } else if (strncmp(line, "SwapTotal:", 10) == 0) { + sscanf(line + 10, "%lu", &swap_total); + } else if (strncmp(line, "SwapFree:", 9) == 0) { + sscanf(line + 9, "%lu", &swap_free); + } + } + fclose(fp); + + info->total = mem_total; + info->free = mem_free; + info->available = mem_available; + info->buffers = buffers; + info->cached = cached + slab_reclaimable; + info->used = mem_total - mem_available; + + if (mem_total > 0) { + info->used_percent = 100.0 * (double)info->used / (double)mem_total; + } + + info->swap_total = swap_total; + info->swap_free = swap_free; + info->swap_used = swap_total - swap_free; + + if (swap_total > 0) { + info->swap_percent = 100.0 * (double)info->swap_used / (double)swap_total; + } + + fp = fopen("/proc/vmstat", "r"); + if (fp) { + while (fgets(line, sizeof(line), fp)) { + if (strncmp(line, "pgfault ", 8) == 0) { + sscanf(line + 8, "%lu", &info->page_faults); + } else if (strncmp(line, "pgpgin ", 7) == 0) { + sscanf(line + 7, "%lu", &info->page_in); + } else if (strncmp(line, "pgpgout ", 8) == 0) { + sscanf(line + 8, "%lu", &info->page_out); + } + } + fclose(fp); + } + + if (interval > 0) { + info->page_faults_per_sec = (double)(info->page_faults - info->prev_page_faults) / interval; + info->page_in_per_sec = (double)(info->page_in - info->prev_page_in) / interval; + info->page_out_per_sec = (double)(info->page_out - info->prev_page_out) / interval; + } + + return 0; +} diff --git a/src/network.c b/src/network.c new file mode 100644 index 0000000..9d15f65 --- /dev/null +++ b/src/network.c @@ -0,0 +1,92 @@ +/* retoor */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "network.h" + +int network_info_init(NetworkInfo *info) { + if (!info) return -1; + memset(info, 0, sizeof(NetworkInfo)); + return network_info_update(info, 0); +} + +int network_info_update(NetworkInfo *info, double interval) { + if (!info) return -1; + + NetworkInterface prev_ifs[MAX_INTERFACES]; + int prev_count = info->count; + memcpy(prev_ifs, info->interfaces, sizeof(prev_ifs)); + + memset(info, 0, sizeof(NetworkInfo)); + + FILE *fp = fopen("/proc/net/dev", "r"); + if (!fp) return -1; + + char line[1024]; + if (!fgets(line, sizeof(line), fp)) { fclose(fp); return -1; } + if (!fgets(line, sizeof(line), fp)) { fclose(fp); return -1; } + + while (fgets(line, sizeof(line), fp) && info->count < MAX_INTERFACES) { + NetworkInterface *iface = &info->interfaces[info->count]; + + char *name_end = strchr(line, ':'); + if (!name_end) continue; + + char *name_start = line; + while (*name_start == ' ') name_start++; + + size_t name_len = name_end - name_start; + if (name_len >= MAX_IF_NAME) name_len = MAX_IF_NAME - 1; + memcpy(iface->name, name_start, name_len); + iface->name[name_len] = '\0'; + + if (strcmp(iface->name, "lo") == 0) continue; + + sscanf(name_end + 1, "%lu %lu %lu %lu %*u %*u %*u %*u %lu %lu %lu %lu", + &iface->rx_bytes, &iface->rx_packets, &iface->rx_errors, &iface->rx_dropped, + &iface->tx_bytes, &iface->tx_packets, &iface->tx_errors, &iface->tx_dropped); + + if (interval > 0) { + for (int i = 0; i < prev_count; i++) { + if (strcmp(prev_ifs[i].name, iface->name) == 0) { + iface->prev_rx_bytes = prev_ifs[i].rx_bytes; + iface->prev_tx_bytes = prev_ifs[i].tx_bytes; + iface->rx_rate = (double)(iface->rx_bytes - iface->prev_rx_bytes) / interval; + iface->tx_rate = (double)(iface->tx_bytes - iface->prev_tx_bytes) / interval; + break; + } + } + } + + info->count++; + } + + fclose(fp); + + struct ifaddrs *ifaddr, *ifa; + if (getifaddrs(&ifaddr) == 0) { + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if (!ifa->ifa_addr) continue; + if (ifa->ifa_addr->sa_family != AF_INET) continue; + + for (int i = 0; i < info->count; i++) { + if (strcmp(info->interfaces[i].name, ifa->ifa_name) == 0) { + struct sockaddr_in *addr = (struct sockaddr_in *)ifa->ifa_addr; + inet_ntop(AF_INET, &addr->sin_addr, info->interfaces[i].ip, MAX_IP_LEN); + info->interfaces[i].up = (ifa->ifa_flags & IFF_UP) ? 1 : 0; + break; + } + } + } + freeifaddrs(ifaddr); + } + + return 0; +} diff --git a/src/process.c b/src/process.c new file mode 100644 index 0000000..b9d13b6 --- /dev/null +++ b/src/process.c @@ -0,0 +1,175 @@ +/* retoor */ +#include +#include +#include +#include +#include +#include "process.h" + +static int compare_by_cpu(const void *a, const void *b) { + const ProcessInfo *pa = (const ProcessInfo *)a; + const ProcessInfo *pb = (const ProcessInfo *)b; + if (pb->cpu_percent > pa->cpu_percent) return 1; + if (pb->cpu_percent < pa->cpu_percent) return -1; + return 0; +} + +static int compare_by_mem(const void *a, const void *b) { + const ProcessInfo *pa = (const ProcessInfo *)a; + const ProcessInfo *pb = (const ProcessInfo *)b; + if (pb->mem_percent > pa->mem_percent) return 1; + if (pb->mem_percent < pa->mem_percent) return -1; + return 0; +} + +static void get_username(int uid, char *username, size_t len) { + FILE *fp = fopen("/etc/passwd", "r"); + if (fp) { + char line[1024]; + while (fgets(line, sizeof(line), fp)) { + char *name = strtok(line, ":"); + strtok(NULL, ":"); + char *uid_str = strtok(NULL, ":"); + if (name && uid_str && atoi(uid_str) == uid) { + strncpy(username, name, len - 1); + username[len - 1] = '\0'; + fclose(fp); + return; + } + } + fclose(fp); + } + snprintf(username, len, "%d", uid); +} + +int process_list_init(ProcessList *list) { + if (!list) return -1; + memset(list, 0, sizeof(ProcessList)); + return 0; +} + +int process_list_update(ProcessList *list, unsigned long total_mem, double interval, int num_cpus) { + if (!list) return -1; + + ProcessInfo prev_procs[MAX_PROCESSES]; + int prev_count = list->count; + memcpy(prev_procs, list->processes, sizeof(prev_procs)); + + memset(list, 0, sizeof(ProcessList)); + + DIR *proc_dir = opendir("/proc"); + if (!proc_dir) return -1; + + struct dirent *entry; + long page_size = sysconf(_SC_PAGESIZE); + long clock_ticks = sysconf(_SC_CLK_TCK); + + while ((entry = readdir(proc_dir)) && list->count < MAX_PROCESSES) { + int pid = atoi(entry->d_name); + if (pid <= 0) continue; + + char path[512]; + snprintf(path, sizeof(path), "/proc/%d/stat", pid); + + FILE *fp = fopen(path, "r"); + if (!fp) continue; + + char stat_buf[2048]; + if (!fgets(stat_buf, sizeof(stat_buf), fp)) { + fclose(fp); + continue; + } + fclose(fp); + + ProcessInfo *proc = &list->processes[list->count]; + proc->pid = pid; + + char *comm_start = strchr(stat_buf, '('); + char *comm_end = strrchr(stat_buf, ')'); + + if (!comm_start || !comm_end) continue; + + size_t comm_len = comm_end - comm_start - 1; + if (comm_len >= MAX_CMD_LEN) comm_len = MAX_CMD_LEN - 1; + strncpy(proc->cmd, comm_start + 1, comm_len); + proc->cmd[comm_len] = '\0'; + + char *fields = comm_end + 2; + + unsigned long utime, stime, vsize; + long rss, priority, nice; + int ppid, num_threads; + + int ret = sscanf(fields, "%c %d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu %lu %*d %*d %ld %ld %d %*d %*u %lu %ld", + &proc->state, &ppid, &utime, &stime, &priority, &nice, &num_threads, &vsize, &rss); + + if (ret < 9) continue; + + proc->ppid = ppid; + proc->priority = (int)priority; + proc->nice = (int)nice; + proc->threads = num_threads; + proc->vsize = vsize; + proc->rss = rss * page_size; + proc->utime = utime; + proc->stime = stime; + + snprintf(path, sizeof(path), "/proc/%d/status", pid); + fp = fopen(path, "r"); + if (fp) { + char line[256]; + while (fgets(line, sizeof(line), fp)) { + if (strncmp(line, "Uid:", 4) == 0) { + sscanf(line + 4, "%d", &proc->uid); + break; + } + } + fclose(fp); + } + + get_username(proc->uid, proc->user, MAX_USER_LEN); + + if (total_mem > 0) { + proc->mem_percent = 100.0 * (double)proc->rss / ((double)total_mem * 1024); + } + + if (interval > 0 && clock_ticks > 0 && num_cpus > 0) { + for (int i = 0; i < prev_count; i++) { + if (prev_procs[i].pid == pid) { + proc->prev_utime = prev_procs[i].utime; + proc->prev_stime = prev_procs[i].stime; + unsigned long total_time = (utime + stime) - (proc->prev_utime + proc->prev_stime); + proc->cpu_percent = 100.0 * ((double)total_time / clock_ticks) / interval; + break; + } + } + } + + switch (proc->state) { + case 'R': list->running++; break; + case 'S': + case 'D': + case 'I': list->sleeping++; break; + case 'T': + case 't': list->stopped++; break; + case 'Z': list->zombie++; break; + } + + list->count++; + } + + closedir(proc_dir); + + list->total = list->count; + return 0; +} + +void process_list_sort_by_cpu(ProcessList *list) { + if (!list || list->count == 0) return; + qsort(list->processes, list->count, sizeof(ProcessInfo), compare_by_cpu); +} + +void process_list_sort_by_mem(ProcessList *list) { + if (!list || list->count == 0) return; + qsort(list->processes, list->count, sizeof(ProcessInfo), compare_by_mem); +} diff --git a/src/system.c b/src/system.c new file mode 100644 index 0000000..c16cbb7 --- /dev/null +++ b/src/system.c @@ -0,0 +1,70 @@ +/* retoor */ +#include +#include +#include +#include +#include +#include "system.h" + +int system_info_init(SystemInfo *info) { + if (!info) return -1; + memset(info, 0, sizeof(SystemInfo)); + return system_info_update(info); +} + +int system_info_update(SystemInfo *info) { + if (!info) return -1; + + struct utsname uts; + if (uname(&uts) == 0) { + strncpy(info->hostname, uts.nodename, MAX_HOSTNAME_LEN - 1); + info->hostname[MAX_HOSTNAME_LEN - 1] = '\0'; + strncpy(info->kernel, uts.release, MAX_KERNEL_LEN - 1); + info->kernel[MAX_KERNEL_LEN - 1] = '\0'; + strncpy(info->arch, uts.machine, MAX_ARCH_LEN - 1); + info->arch[MAX_ARCH_LEN - 1] = '\0'; + } + + FILE *fp = fopen("/etc/os-release", "r"); + if (fp) { + char line[512]; + while (fgets(line, sizeof(line), fp)) { + if (strncmp(line, "PRETTY_NAME=", 12) == 0) { + char *start = strchr(line, '"'); + if (start) { + start++; + char *end = strchr(start, '"'); + if (end) { + *end = '\0'; + strncpy(info->os_name, start, MAX_OS_LEN - 1); + info->os_name[MAX_OS_LEN - 1] = '\0'; + } + } + break; + } + } + fclose(fp); + } + + fp = fopen("/proc/uptime", "r"); + if (fp) { + double uptime; + if (fscanf(fp, "%lf", &uptime) == 1) { + info->uptime_seconds = (long)uptime; + info->uptime_days = info->uptime_seconds / 86400; + info->uptime_hours = (info->uptime_seconds % 86400) / 3600; + info->uptime_minutes = (info->uptime_seconds % 3600) / 60; + } + fclose(fp); + } + + fp = fopen("/proc/loadavg", "r"); + if (fp) { + if (fscanf(fp, "%lf %lf %lf", &info->load_1, &info->load_5, &info->load_15) != 3) { + info->load_1 = info->load_5 = info->load_15 = 0.0; + } + fclose(fp); + } + + return 0; +} diff --git a/src/terminal.c b/src/terminal.c new file mode 100644 index 0000000..340362b --- /dev/null +++ b/src/terminal.c @@ -0,0 +1,127 @@ +/* retoor */ +#include +#include +#include +#include +#include +#include "terminal.h" + +#define INITIAL_BUFFER_SIZE 65536 +#define IGNORE_RESULT(x) do { if (x) {} } while(0) + +int terminal_init(Terminal *term) { + if (!term) return -1; + + term->buffer = malloc(INITIAL_BUFFER_SIZE); + if (!term->buffer) return -1; + + term->buffer_size = INITIAL_BUFFER_SIZE; + term->buffer_pos = 0; + term->buffer[0] = '\0'; + + terminal_get_size(term); + terminal_set_raw_mode(term); + terminal_hide_cursor(term); + + return 0; +} + +void terminal_cleanup(Terminal *term) { + if (!term) return; + + terminal_show_cursor(term); + terminal_restore_mode(term); + IGNORE_RESULT(write(STDOUT_FILENO, "\033[0m", 4)); + IGNORE_RESULT(write(STDOUT_FILENO, "\033[2J", 4)); + IGNORE_RESULT(write(STDOUT_FILENO, "\033[H", 3)); + + if (term->buffer) { + free(term->buffer); + term->buffer = NULL; + } +} + +void terminal_get_size(Terminal *term) { + struct winsize ws; + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) { + term->rows = ws.ws_row; + term->cols = ws.ws_col; + } else { + term->rows = 24; + term->cols = 80; + } +} + +void terminal_clear(Terminal *term) { + (void)term; + IGNORE_RESULT(write(STDOUT_FILENO, "\033[2J", 4)); + IGNORE_RESULT(write(STDOUT_FILENO, "\033[H", 3)); +} + +void terminal_move_cursor(Terminal *term, int row, int col) { + char buf[32]; + int len = snprintf(buf, sizeof(buf), "\033[%d;%dH", row, col); + if (len > 0 && (size_t)len < sizeof(buf)) { + terminal_buffer_append(term, buf); + } +} + +void terminal_hide_cursor(Terminal *term) { + (void)term; + IGNORE_RESULT(write(STDOUT_FILENO, "\033[?25l", 6)); +} + +void terminal_show_cursor(Terminal *term) { + (void)term; + IGNORE_RESULT(write(STDOUT_FILENO, "\033[?25h", 6)); +} + +void terminal_buffer_clear(Terminal *term) { + if (!term || !term->buffer) return; + term->buffer_pos = 0; + term->buffer[0] = '\0'; +} + +void terminal_buffer_append(Terminal *term, const char *str) { + if (!term || !term->buffer || !str) return; + + size_t len = strlen(str); + + if (term->buffer_pos + len + 1 > term->buffer_size) { + size_t new_size = term->buffer_size * 2; + while (new_size < term->buffer_pos + len + 1) { + new_size *= 2; + } + char *new_buffer = realloc(term->buffer, new_size); + if (!new_buffer) return; + term->buffer = new_buffer; + term->buffer_size = new_size; + } + + memcpy(term->buffer + term->buffer_pos, str, len); + term->buffer_pos += len; + term->buffer[term->buffer_pos] = '\0'; +} + +void terminal_buffer_flush(Terminal *term) { + if (!term || !term->buffer || term->buffer_pos == 0) return; + IGNORE_RESULT(write(STDOUT_FILENO, term->buffer, term->buffer_pos)); + terminal_buffer_clear(term); +} + +void terminal_set_raw_mode(Terminal *term) { + struct termios raw; + + if (tcgetattr(STDIN_FILENO, &term->orig_termios) == -1) return; + + raw = term->orig_termios; + raw.c_lflag &= ~(ECHO | ICANON); + raw.c_cc[VMIN] = 0; + raw.c_cc[VTIME] = 0; + + tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw); +} + +void terminal_restore_mode(Terminal *term) { + tcsetattr(STDIN_FILENO, TCSAFLUSH, &term->orig_termios); +}