/* * ============================================================================ * 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 /* ============================================================================ * 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 " // 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; }