219 lines
8.5 KiB
C
219 lines
8.5 KiB
C
/*
|
|
* ============================================================================
|
|
* 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 <linus@linvogel.ch>
|
|
* DATE: 2026/03/15
|
|
* VERSION: 1.0
|
|
* ============================================================================
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <time.h>
|
|
#include <signal.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
|
|
|
|
/* ============================================================================
|
|
* Configuration Constants
|
|
* ============================================================================ */
|
|
|
|
#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
|
|
|
|
running = 0;
|
|
}
|
|
|
|
int main(void) {
|
|
int ret = 0;
|
|
|
|
// register the SIGINT handler
|
|
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) {
|
|
// 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);
|
|
|
|
// 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 cleanup_label;
|
|
}
|
|
|
|
// Parse /proc/stat line format: "cpu <user> <nice> <system> <idle> <iowait> <irq> <softirq> <steal> <guest> <guest_nice>"
|
|
// 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 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;
|
|
|
|
// 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);
|
|
}
|
|
|
|
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;
|
|
} |