From a686e144a83c70b2da685755d3a99657c5d7d132 Mon Sep 17 00:00:00 2001 From: Linus Vogel Date: Sun, 15 Mar 2026 22:51:00 +0100 Subject: [PATCH] added comments to the main file --- main.c | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 147 insertions(+), 13 deletions(-) diff --git a/main.c b/main.c index f83c7b8..8629766 100644 --- a/main.c +++ b/main.c @@ -1,20 +1,95 @@ +/* + * ============================================================================ + * cputild - CPU Idle Time Monitor + * ============================================================================ + * + * PURPOSE: + * This program monitors and reports CPU utilization by reading from /proc/stat + * and writing the current percentage to /tmp/cputild. + * + * FUNCTIONALITY: + * - Reads CPU statistics from /proc/stat every second + * - Calculates active vs idle time deltas between readings (in jiffies) + * - Writes the percentage to an output file + * - Responds to SIGINT (Ctrl+C) for graceful shutdown + * + * TIME UNIT - JIFFY: + * The Linux system uses 'jiffies' as its time unit. One jiffy equals 1/100th of a second. + * All CPU time measurements in this program are expressed in jiffies. + * + * FILES USED: + * Input: /proc/stat (system CPU statistics) + * Output: /tmp/cputild (utilization percentage, e.g., "45.23") + * + * SIGNAL HANDLING: + * - SIGINT: Sets running flag to 0 for graceful termination + * + * AUTHOR: Linus Vogel + * DATE: 2026/03/15 + * VERSION: 1.0 + * ============================================================================ + */ + #include +#include #include #include #include #include #include +#include +#include -#define STAT_FILE "/proc/stat" -#define OUT_FILE "/tmp/cputild" +/* ============================================================================ + * Configuration Constants + * ============================================================================ */ -volatile int running = 1; +#define STAT_FILE "/proc/stat" /* Path to system CPU statistics file */ +#define OUT_FILE "/tmp/cputild" /* Output file for utilization percentage */ + + +/* ============================================================================ + * Global Variables + * ============================================================================ */ + +volatile int running = 1; /* Signal flag: 1=running, 0=stop requested */ + + +/* ============================================================================ + * Function Declarations + * ============================================================================ */ + +/** + * @brief Signal handler for SIGINT (Ctrl+C) + * + * This function is invoked when the program receives a SIGINT signal. + * It sets the running flag to 0 to initiate graceful shutdown. + * + * @param _signal The signal number received (should be SIGINT) + */ +void signal_handler_sigint(int _signal); + +/** + * @brief Main entry point of cputild - CPU idle time monitor + * + * Reads CPU statistics every second, calculates utilization, and writes + * to output file until interrupted by user. + * + * @return 0 on successful execution, 1 on error conditions + */ +int main(void); + + +/* ============================================================================ + * Function Definitions + * ============================================================================ */ void signal_handler_sigint(const int _signal) { +#ifndef NDEBUG assert(_signal == SIGINT && "ERROR: Signal except SIGINT received by the SIGINT handler"); +#endif - printf("hello there\n"); running = 0; } @@ -22,64 +97,123 @@ int main(void) { int ret = 0; // register the SIGINT handler - signal(SIGINT, signal_handler_sigint); + if (signal(SIGINT, signal_handler_sigint) == SIG_ERR) { + printf("Unable to set interrupt handler: %s\n", strerror(errno)); + ret = 1; + goto exit_label; + } + // open the files FILE *out_file = fopen(OUT_FILE, "w"); + if (out_file == NULL) { + printf("Failed to open output file: %s\n", strerror(errno)); + ret = 1; + goto out_file_close_label; + } FILE *stat_file = fopen(STAT_FILE, "r"); + if (stat_file == NULL) { + printf("Failed to open /proc/stat file: %s\n", strerror(errno)); + ret = 1; + goto stat_file_close_label; + } + // counters for storing last iterations total values (in jiffies) uint64_t total_jiffies_active = 0; uint64_t total_jiffies_idle = 0; + // Buffer to read /proc/stat lines size_t line_len = 1024; char *line_buffer = (char*)malloc(line_len); + /* ======================================================================== + * Main Monitoring Loop + * + * This loop runs continuously until running flag is set to 0 by signal handler. + * Each iteration: + * 1. Waits 1 second between measurements + * 2. Reads current CPU statistics from /proc/stat (values in jiffies) + * 3. Parses the stat line into individual components + * 4. Calculates delta in active and idle time since last reading + * 5. Computes utilization ratio in range [0,1], then converts to percentage + * 6. Writes result to output file (truncating previous content) + * ======================================================================== */ while (running) { - struct timespec now; + // Sleep duration specification + // Note that the remain variable is currently not used. It is necessary syntactically, but may be used + // used in the future for better timing precision if desired. struct timespec request; struct timespec remain; + // Wait interval: 1 second between measurements request.tv_sec = 1; request.tv_nsec = 0; nanosleep(&request, &remain); - clock_gettime(CLOCK_REALTIME, &now); - fflush(stat_file); + // Ensure flush the file to obtain up-to-date data and read the first line + // The first line accumulates over all cpu cores listed in the /proc/stat file + stat_file = freopen(STAT_FILE, "r", stat_file); fseek(stat_file, 0, SEEK_SET); + // Note: getline may reallocate the line_buffer const ssize_t read = getline(&line_buffer, &line_len, stat_file); + // check if there was an error reading from /proc/stat if (read == -1) { + // Notify the user and proceed to cleanup logic printf("Failed to read the stat file!\n"); ret = 1; - goto exit_label; + goto cleanup_label; } + // Parse /proc/stat line format: "cpu " + // All values are cumulative counters measured in jiffies (1 jiffy = 1/100 second) uint64_t user, user_nice, system, idle, iowait, irq, softirq, steal, guest, guest_nice; const int n_read = sscanf(line_buffer, "cpu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu", &user, &user_nice, &system, &idle, &iowait, &irq, &softirq, &steal, &guest, &guest_nice); // NOLINT(*-err34-c) + // Validate all fields were parsed successfully if (n_read != 10) { + // in case values were missed, there has been an unforeseen error. Notify the user and proceed to cleanup logic. printf("Failed to read the stat file: Invalid format?\n"); ret = 1; - goto exit_label; + goto cleanup_label; } + /* ==================================================================== + * CPU Utilization Calculation + * + * Active time includes: user, nice, system, irq, softirq, steal, guest, guest_nice + * Idle time includes: iowait, idle + * + * All values are in jiffies (1/100th of a second). + * Utilization is calculated as a ratio in range [0,1], then multiplied by 100. + * ==================================================================== */ const uint64_t current_total_jiffies_active = user + user_nice + system + irq + softirq + guest + guest_nice; const uint64_t current_total_jiffies_idle = iowait + idle; + // Calculate the change in active and idle time (in jiffies) since last measurement const uint64_t delta_jiffies_active = current_total_jiffies_active - total_jiffies_active; const uint64_t delta_jiffies_idle = current_total_jiffies_idle - total_jiffies_idle; - const double utilization = (double)(delta_jiffies_active) / (double)(delta_jiffies_idle + delta_jiffies_active); + // Compute utilization ratio in range [0,1], then convert to percentage + const uint64_t summed = delta_jiffies_idle + delta_jiffies_active; + const double utilization = summed != 0 ? ((double)(delta_jiffies_active) / (double)(summed)) : 0.0; + // Update cumulative counters for next iteration total_jiffies_active = current_total_jiffies_active; total_jiffies_idle = current_total_jiffies_idle; + // This prevents old values from leaking when new output is shorter out_file = freopen(OUT_FILE, "w", out_file); fseek(out_file, 0, SEEK_SET); + // Write percentage with 2 decimal places (e.g., "45.23") and flush fprintf(out_file, "%.02f", utilization * 100); fflush(out_file); } -exit_label: +cleanup_label: free(line_buffer); - +stat_file_close_label: + fclose(stat_file); +out_file_close_label: + fclose(out_file); +exit_label: printf("Terminating...\n"); return ret; } \ No newline at end of file