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
This commit is contained in:
retoor 2025-12-13 05:38:27 +01:00
commit 11926c5459
25 changed files with 2303 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
obj

10
CHANGELOG.md Normal file
View File

@ -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)

34
Makefile Normal file
View File

@ -0,0 +1,34 @@
# retoor <retoor@molodetz.nl>
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/

129
README.md Normal file
View File

@ -0,0 +1,129 @@
# rtop
**Author:** retoor <retoor@molodetz.nl>
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.

42
include/cpu.h Normal file
View File

@ -0,0 +1,42 @@
/* retoor <retoor@molodetz.nl> */
#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

33
include/disk.h Normal file
View File

@ -0,0 +1,33 @@
/* retoor <retoor@molodetz.nl> */
#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

24
include/display.h Normal file
View File

@ -0,0 +1,24 @@
/* retoor <retoor@molodetz.nl> */
#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

28
include/filesystem.h Normal file
View File

@ -0,0 +1,28 @@
/* retoor <retoor@molodetz.nl> */
#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

31
include/memory.h Normal file
View File

@ -0,0 +1,31 @@
/* retoor <retoor@molodetz.nl> */
#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

35
include/network.h Normal file
View File

@ -0,0 +1,35 @@
/* retoor <retoor@molodetz.nl> */
#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

44
include/process.h Normal file
View File

@ -0,0 +1,44 @@
/* retoor <retoor@molodetz.nl> */
#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

27
include/system.h Normal file
View File

@ -0,0 +1,27 @@
/* retoor <retoor@molodetz.nl> */
#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

48
include/terminal.h Normal file
View File

@ -0,0 +1,48 @@
/* retoor <retoor@molodetz.nl> */
#ifndef TERMINAL_H
#define TERMINAL_H
#include <termios.h>
#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

BIN
rtop Executable file

Binary file not shown.

202
rtop.md Normal file
View File

@ -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

165
src/cpu.c Normal file
View File

@ -0,0 +1,165 @@
/* retoor <retoor@molodetz.nl> */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#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;
}

82
src/disk.c Normal file
View File

@ -0,0 +1,82 @@
/* retoor <retoor@molodetz.nl> */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

525
src/display.c Normal file
View File

@ -0,0 +1,525 @@
/* retoor <retoor@molodetz.nl> */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#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);
}
}

62
src/filesystem.c Normal file
View File

@ -0,0 +1,62 @@
/* retoor <retoor@molodetz.nl> */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/statvfs.h>
#include <mntent.h>
#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;
}

228
src/main.c Normal file
View File

@ -0,0 +1,228 @@
/* retoor <retoor@molodetz.nl> */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <getopt.h>
#include <sys/select.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"
#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;
}

89
src/memory.c Normal file
View File

@ -0,0 +1,89 @@
/* retoor <retoor@molodetz.nl> */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}

92
src/network.c Normal file
View File

@ -0,0 +1,92 @@
/* retoor <retoor@molodetz.nl> */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <unistd.h>
#include <ifaddrs.h>
#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;
}

175
src/process.c Normal file
View File

@ -0,0 +1,175 @@
/* retoor <retoor@molodetz.nl> */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <unistd.h>
#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);
}

70
src/system.c Normal file
View File

@ -0,0 +1,70 @@
/* retoor <retoor@molodetz.nl> */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/utsname.h>
#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;
}

127
src/terminal.c Normal file
View File

@ -0,0 +1,127 @@
/* retoor <retoor@molodetz.nl> */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#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);
}