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:
commit
11926c5459
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
obj
|
||||
10
CHANGELOG.md
Normal file
10
CHANGELOG.md
Normal 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
34
Makefile
Normal 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
129
README.md
Normal 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
42
include/cpu.h
Normal 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
33
include/disk.h
Normal 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
24
include/display.h
Normal 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
28
include/filesystem.h
Normal 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
31
include/memory.h
Normal 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
35
include/network.h
Normal 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
44
include/process.h
Normal 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
27
include/system.h
Normal 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
48
include/terminal.h
Normal 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
|
||||
202
rtop.md
Normal file
202
rtop.md
Normal 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
165
src/cpu.c
Normal 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
82
src/disk.c
Normal 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
525
src/display.c
Normal 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
62
src/filesystem.c
Normal 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
228
src/main.c
Normal 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
89
src/memory.c
Normal 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
92
src/network.c
Normal 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
175
src/process.c
Normal 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
70
src/system.c
Normal 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
127
src/terminal.c
Normal 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);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user