From 739f1b289b2787706c276355a08047225d977b89 Mon Sep 17 00:00:00 2001 From: whoismz Date: Mon, 29 Jun 2026 16:29:04 +0800 Subject: [PATCH] cpu_usage: reuse previous values on rapid refreshes cpu_usage is calculated from the delta between two CPU time samples. When i3status is refreshed again before the configured interval has elapsed, the sampling window can be too small and the reported percentage becomes noisy. This can be observed when changing volume while both the volume and cpu_usage modules are enabled. Reuse the previous overall and per-CPU usage values for refreshes that happen before the configured interval, and avoid advancing the previous CPU sample in that case. This keeps CPU usage sampling aligned with the configured interval and avoids artificial spikes on rapid refreshes. Fixes #442 --- src/print_cpu_usage.c | 76 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 69 insertions(+), 7 deletions(-) diff --git a/src/print_cpu_usage.c b/src/print_cpu_usage.c index a7ee2e46..6b9ee30d 100644 --- a/src/print_cpu_usage.c +++ b/src/print_cpu_usage.c @@ -10,6 +10,7 @@ #include #include #include +#include #if defined(__FreeBSD__) || defined(__OpenBSD__) #include @@ -64,6 +65,18 @@ static struct cpu_usage prev_all = {0, 0, 0, 0, 0}; static struct cpu_usage *prev_cpus = NULL; static struct cpu_usage *curr_cpus = NULL; +#if defined(__linux__) +static int last_diff_usage = 0; +static int *last_cpu_diff_usage = NULL; +static struct timespec last_sample_time = {0, 0}; +static bool has_last_sample = false; + +static long elapsed_ms(const struct timespec *start, const struct timespec *end) { + return (end->tv_sec - start->tv_sec) * 1000 + + (end->tv_nsec - start->tv_nsec) / 1000000; +} +#endif + /* * Reads the CPU utilization from /proc/stat and returns the usage as a * percentage. @@ -81,6 +94,9 @@ void print_cpu_usage(cpu_usage_ctx_t *ctx) { #endif int diff_usage; bool colorful_output = false; +#if defined(__linux__) + bool update_sample = true; +#endif #if defined(__linux__) @@ -92,6 +108,18 @@ void print_cpu_usage(cpu_usage_ctx_t *ctx) { prev_cpus = (struct cpu_usage *)calloc(cpu_count, sizeof(struct cpu_usage)); free(curr_cpus); curr_cpus = (struct cpu_usage *)calloc(cpu_count, sizeof(struct cpu_usage)); + free(last_cpu_diff_usage); + last_cpu_diff_usage = (int *)calloc(cpu_count, sizeof(int)); + + if (prev_cpus == NULL || curr_cpus == NULL || last_cpu_diff_usage == NULL) { + cpu_count = 0; + goto error; + } + + prev_all = (struct cpu_usage){0, 0, 0, 0, 0}; + last_diff_usage = 0; + last_sample_time = (struct timespec){0, 0}; + has_last_sample = false; } memcpy(curr_cpus, prev_cpus, cpu_count * sizeof(struct cpu_usage)); @@ -138,10 +166,28 @@ void print_cpu_usage(cpu_usage_ctx_t *ctx) { curr_all.total += curr_cpus[cpu_idx].total; } - diff_idle = curr_all.idle - prev_all.idle; - diff_total = curr_all.total - prev_all.total; - diff_usage = (diff_total ? (1000 * (diff_total - diff_idle) / diff_total + 5) / 10 : 0); - prev_all = curr_all; + struct timespec now; + if (clock_gettime(CLOCK_MONOTONIC, &now) != 0) + goto error; + + int interval_ms = cfg_getint(cfg_general, "interval") * 1000; + if (interval_ms <= 0) + interval_ms = 1000; + + if (has_last_sample && + elapsed_ms(&last_sample_time, &now) < interval_ms) { + diff_usage = last_diff_usage; + update_sample = false; + } else { + diff_idle = curr_all.idle - prev_all.idle; + diff_total = curr_all.total - prev_all.total; + diff_usage = (diff_total ? (1000 * (diff_total - diff_idle) / diff_total + 5) / 10 : 0); + last_diff_usage = diff_usage; + last_sample_time = now; + has_last_sample = true; + prev_all = curr_all; + } + #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) #if defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) @@ -214,9 +260,17 @@ void print_cpu_usage(cpu_usage_ctx_t *ctx) { } else if (number >= cpu_count) { fprintf(stderr, "i3status: provided CPU number '%d' above detected number of CPU %d\n", number, cpu_count); } else { - int cpu_diff_idle = curr_cpus[number].idle - prev_cpus[number].idle; - int cpu_diff_total = curr_cpus[number].total - prev_cpus[number].total; - int cpu_diff_usage = (cpu_diff_total ? (1000 * (cpu_diff_total - cpu_diff_idle) / cpu_diff_total + 5) / 10 : 0); + int cpu_diff_usage; + + if (!update_sample) { + cpu_diff_usage = last_cpu_diff_usage[number]; + } else { + int cpu_diff_idle = curr_cpus[number].idle - prev_cpus[number].idle; + int cpu_diff_total = curr_cpus[number].total - prev_cpus[number].total; + cpu_diff_usage = (cpu_diff_total ? (1000 * (cpu_diff_total - cpu_diff_idle) / cpu_diff_total + 5) / 10 : 0); + last_cpu_diff_usage[number] = cpu_diff_usage; + } + outwalk += sprintf(outwalk, "%02d%s", cpu_diff_usage, pct_mark); } walk += length; @@ -227,9 +281,17 @@ void print_cpu_usage(cpu_usage_ctx_t *ctx) { } } +#if defined(__linux__) + if (update_sample) { + struct cpu_usage *temp_cpus = prev_cpus; + prev_cpus = curr_cpus; + curr_cpus = temp_cpus; + } +#else struct cpu_usage *temp_cpus = prev_cpus; prev_cpus = curr_cpus; curr_cpus = temp_cpus; +#endif if (colorful_output) END_COLOR;