From 0ddde1f705cd1556fdc457a29e17669cfafb625d Mon Sep 17 00:00:00 2001 From: lastdragon Date: Mon, 7 Sep 2026 16:32:06 -0600 Subject: [PATCH] Carga inicial al repo --- README.md | 28 +- cpustress.c | 884 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 911 insertions(+), 1 deletion(-) create mode 100644 cpustress.c diff --git a/README.md b/README.md index 3d105e0..ebed040 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,29 @@ # cpustress -Programa de stres para CPU en Linux \ No newline at end of file +Programa de stres para CPU en Linux + +Requiere + +# Debian / Ubuntu +sudo apt install build-essential libncursesw5-dev + +# Fedora / RHEL +sudo dnf install gcc ncurses-devel + +# Arch +sudo pacman -S base-devel ncurses + +Compilación + +gcc -O2 -Wall -o cpustress cpustress.c -lncursesw -lpthread -lm + +Uso: + +./cpustress # todos los núcleos detectados +./cpustress -n 4 # los primeros 4 +./cpustress 4 # equivalente, número suelto +./cpustress -c 0,2,4-7 # núcleos concretos +./cpustress -m 2 # 2 hilos por núcleo (satura SMT) +./cpustress -t 60 # 60 segundos y termina +./cpustress -l # solo diagnóstico: sensores y RAPL detectados +sudo ./cpustress # necesario para leer el consumo en vatios diff --git a/cpustress.c b/cpustress.c new file mode 100644 index 0000000..ce042d6 --- /dev/null +++ b/cpustress.c @@ -0,0 +1,884 @@ +/* + * cpustress.c - Estresador de CPU con monitor ncurses para Linux + * + * - Detecta automaticamente el numero de nucleos logicos. + * - Lanza un hilo por nucleo, fijado con afinidad (pthread_setaffinity_np). + * - Monitoriza uso (/proc/stat), temperatura (hwmon / thermal_zone), + * frecuencia (cpufreq) y consumo energetico (Intel RAPL / powercap). + * - Interfaz responsiva: se adapta al tamano de la terminal (consola o + * ventana grafica) y colapsa a una sola fila promedio si no caben todos + * los nucleos. + * + * Compilar: gcc -O2 -Wall -o cpustress cpustress.c -lncursesw -lpthread -lm + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_CPUS 512 +#define MAX_SENSORS 256 +#define MAX_RAPL 16 + +/* ------------------------------------------------------------------ */ +/* Pares de color */ +/* ------------------------------------------------------------------ */ +#define CP_GREEN 1 +#define CP_YELLOW 2 +#define CP_RED 3 +#define CP_HEADER 4 +#define CP_DIM 5 +#define CP_TITLE 6 +#define CP_CYAN 7 + +/* ------------------------------------------------------------------ */ +/* Estado global */ +/* ------------------------------------------------------------------ */ +static volatile sig_atomic_t g_run = 1; + +static int g_ncpu = 0; /* nucleos logicos detectados */ +static char g_model[256] = "CPU desconocida"; +static int g_use_color = 1; +static int g_ascii = 0; +static const char *g_bar_full = "#"; +static const char *g_bar_empty = "."; + +static unsigned char g_sel[MAX_CPUS]; /* 1 = nucleo estresado */ +static int g_nsel = 0; +static int g_threads_per_core = 1; + +static double g_usage[MAX_CPUS]; +static double g_temp[MAX_CPUS]; /* -1 = desconocida */ +static double g_freq[MAX_CPUS]; /* MHz, -1 = desconocida */ +static double g_usage_avg = 0.0; +static double g_pkg_temp = -1.0; +static double g_power_w = -1.0; + +static double g_sink[MAX_CPUS]; /* evita que GCC elimine el bucle */ +static int g_topo_core[MAX_CPUS]; + +/* ------------------------------------------------------------------ */ +/* Utilidades de lectura de sysfs / procfs */ +/* ------------------------------------------------------------------ */ +static int read_line_file(const char *path, char *buf, size_t n) +{ + FILE *f = fopen(path, "r"); + if (!f) return -1; + if (!fgets(buf, (int)n, f)) { fclose(f); return -1; } + fclose(f); + buf[strcspn(buf, "\n")] = '\0'; + return 0; +} + +static int read_ll_file(const char *path, long long *out) +{ + char buf[64]; + if (read_line_file(path, buf, sizeof buf) != 0) return -1; + errno = 0; + long long v = strtoll(buf, NULL, 10); + if (errno) return -1; + *out = v; + return 0; +} + +/* ------------------------------------------------------------------ */ +/* Modelo de CPU */ +/* ------------------------------------------------------------------ */ +static void detect_model(void) +{ + FILE *f = fopen("/proc/cpuinfo", "r"); + if (!f) return; + char line[512]; + while (fgets(line, sizeof line, f)) { + char *c = NULL; + if (!strncmp(line, "model name", 10) || + !strncmp(line, "Model", 5) || + !strncmp(line, "Processor", 9) || + !strncmp(line, "cpu model", 9)) { + c = strchr(line, ':'); + } + if (c) { + c++; + while (*c == ' ' || *c == '\t') c++; + c[strcspn(c, "\n")] = '\0'; + if (*c) { + snprintf(g_model, sizeof g_model, "%s", c); + break; + } + } + } + fclose(f); +} + +/* ------------------------------------------------------------------ */ +/* Topologia: cpu logico -> core_id fisico */ +/* ------------------------------------------------------------------ */ +static void detect_topology(void) +{ + char p[256], buf[64]; + for (int i = 0; i < g_ncpu; i++) { + g_topo_core[i] = i; + snprintf(p, sizeof p, + "/sys/devices/system/cpu/cpu%d/topology/core_id", i); + if (read_line_file(p, buf, sizeof buf) == 0) + g_topo_core[i] = atoi(buf); + } +} + +/* ------------------------------------------------------------------ */ +/* Sensores de temperatura */ +/* ------------------------------------------------------------------ */ +typedef struct { + char path[600]; + int core_id; /* -1 = paquete / global */ +} sensor_t; + +static sensor_t g_sensors[MAX_SENSORS]; +static int g_nsensors = 0; + +static int is_cpu_hwmon(const char *name) +{ + static const char *ok[] = { + "coretemp", "k10temp", "k8temp", "zenpower", "cpu_thermal", + "soc_thermal", "cpu-thermal", "cpu_temp_thermal", "scpi_sensors", + "nct6", "it87", NULL + }; + for (int i = 0; ok[i]; i++) + if (strstr(name, ok[i])) return 1; + return 0; +} + +static void add_sensor(const char *path, int core_id) +{ + if (g_nsensors >= MAX_SENSORS) return; + snprintf(g_sensors[g_nsensors].path, sizeof g_sensors[0].path, "%s", path); + g_sensors[g_nsensors].core_id = core_id; + g_nsensors++; +} + +static void scan_hwmon(void) +{ + DIR *d = opendir("/sys/class/hwmon"); + if (!d) return; + struct dirent *e; + while ((e = readdir(d))) { + if (strncmp(e->d_name, "hwmon", 5)) continue; + + char base[512], p[600], name[128]; + snprintf(base, sizeof base, "/sys/class/hwmon/%s", e->d_name); + snprintf(p, sizeof p, "%s/name", base); + if (read_line_file(p, name, sizeof name) != 0) continue; + if (!is_cpu_hwmon(name)) continue; + + DIR *hd = opendir(base); + if (!hd) continue; + struct dirent *he; + while ((he = readdir(hd))) { + int idx; + if (sscanf(he->d_name, "temp%d_input", &idx) != 1) continue; + + char lpath[700], label[128]; + int core_id = -1; + snprintf(lpath, sizeof lpath, "%s/temp%d_label", base, idx); + if (read_line_file(lpath, label, sizeof label) == 0) { + int n; + if (sscanf(label, "Core %d", &n) == 1) core_id = n; + else if (sscanf(label, "core %d", &n) == 1) core_id = n; + } + char ipath[700]; + snprintf(ipath, sizeof ipath, "%s/temp%d_input", base, idx); + long long dummy; + if (read_ll_file(ipath, &dummy) == 0) + add_sensor(ipath, core_id); + } + closedir(hd); + } + closedir(d); +} + +static void scan_thermal_zones(void) +{ + DIR *d = opendir("/sys/class/thermal"); + if (!d) return; + struct dirent *e; + while ((e = readdir(d))) { + if (strncmp(e->d_name, "thermal_zone", 12)) continue; + char p[600], type[128]; + snprintf(p, sizeof p, "/sys/class/thermal/%s/type", e->d_name); + if (read_line_file(p, type, sizeof type) != 0) continue; + if (!strstr(type, "cpu") && !strstr(type, "x86_pkg_temp") && + !strstr(type, "acpitz") && !strstr(type, "soc")) + continue; + snprintf(p, sizeof p, "/sys/class/thermal/%s/temp", e->d_name); + long long dummy; + if (read_ll_file(p, &dummy) == 0) + add_sensor(p, -1); + } + closedir(d); +} + +static void init_sensors(void) +{ + scan_hwmon(); + if (g_nsensors == 0) scan_thermal_zones(); +} + +static void update_temps(void) +{ + for (int i = 0; i < g_ncpu; i++) g_temp[i] = -1.0; + g_pkg_temp = -1.0; + + for (int s = 0; s < g_nsensors; s++) { + long long raw; + if (read_ll_file(g_sensors[s].path, &raw) != 0) continue; + double c = (raw > 1000) ? raw / 1000.0 : (double)raw; + + if (g_sensors[s].core_id >= 0) { + for (int i = 0; i < g_ncpu; i++) + if (g_topo_core[i] == g_sensors[s].core_id) + g_temp[i] = c; + } else { + if (c > g_pkg_temp) g_pkg_temp = c; + } + } + /* si no hay sensor por nucleo, usar la del paquete */ + if (g_pkg_temp > 0.0) + for (int i = 0; i < g_ncpu; i++) + if (g_temp[i] < 0.0) g_temp[i] = g_pkg_temp; + + if (g_pkg_temp < 0.0) { + double mx = -1.0; + for (int i = 0; i < g_ncpu; i++) if (g_temp[i] > mx) mx = g_temp[i]; + g_pkg_temp = mx; + } +} + +/* ------------------------------------------------------------------ */ +/* Frecuencia */ +/* ------------------------------------------------------------------ */ +static int g_have_cpufreq = 0; + +static void init_freq(void) +{ + long long v; + if (read_ll_file("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq", + &v) == 0) + g_have_cpufreq = 1; +} + +static void update_freq(void) +{ + for (int i = 0; i < g_ncpu; i++) g_freq[i] = -1.0; + + if (g_have_cpufreq) { + char p[256]; + long long khz; + for (int i = 0; i < g_ncpu; i++) { + snprintf(p, sizeof p, + "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq", i); + if (read_ll_file(p, &khz) == 0) g_freq[i] = khz / 1000.0; + } + return; + } + FILE *f = fopen("/proc/cpuinfo", "r"); + if (!f) return; + char line[512]; + int idx = -1; + while (fgets(line, sizeof line, f)) { + char *c = strchr(line, ':'); + if (!c) continue; + if (!strncmp(line, "processor", 9)) idx = atoi(c + 1); + else if (!strncmp(line, "cpu MHz", 7) && idx >= 0 && idx < g_ncpu) + g_freq[idx] = atof(c + 1); + } + fclose(f); +} + +/* ------------------------------------------------------------------ */ +/* Consumo energetico (powercap / RAPL) */ +/* ------------------------------------------------------------------ */ +typedef struct { + char path[600]; + long long last; + long long max; +} rapl_t; + +static rapl_t g_rapl[MAX_RAPL]; +static int g_nrapl = 0; +static struct timespec g_rapl_ts; + +static void init_rapl(void) +{ + DIR *d = opendir("/sys/class/powercap"); + if (!d) return; + struct dirent *e; + while ((e = readdir(d)) && g_nrapl < MAX_RAPL) { + /* solo dominios de paquete de primer nivel: "intel-rapl:0" */ + if (strncmp(e->d_name, "intel-rapl:", 11) && + strncmp(e->d_name, "amd-rapl:", 9)) continue; + if (strchr(strchr(e->d_name, ':') + 1, ':')) continue; /* subdominio */ + + char ep[600], mp[600]; + long long v, mx; + snprintf(ep, sizeof ep, "/sys/class/powercap/%s/energy_uj", e->d_name); + snprintf(mp, sizeof mp, "/sys/class/powercap/%s/max_energy_range_uj", + e->d_name); + if (read_ll_file(ep, &v) != 0) continue; /* sin permisos -> N/A */ + if (read_ll_file(mp, &mx) != 0) mx = 0; + + snprintf(g_rapl[g_nrapl].path, sizeof g_rapl[0].path, "%s", ep); + g_rapl[g_nrapl].last = v; + g_rapl[g_nrapl].max = mx; + g_nrapl++; + } + closedir(d); + clock_gettime(CLOCK_MONOTONIC, &g_rapl_ts); +} + +static void update_power(void) +{ + if (g_nrapl == 0) { g_power_w = -1.0; return; } + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + double dt = (now.tv_sec - g_rapl_ts.tv_sec) + + (now.tv_nsec - g_rapl_ts.tv_nsec) / 1e9; + if (dt <= 0.02) return; + g_rapl_ts = now; + + double total_uj = 0.0; + for (int i = 0; i < g_nrapl; i++) { + long long v; + if (read_ll_file(g_rapl[i].path, &v) != 0) continue; + long long d = v - g_rapl[i].last; + if (d < 0) d = (g_rapl[i].max > 0) ? (g_rapl[i].max - g_rapl[i].last + v) : 0; + g_rapl[i].last = v; + total_uj += (double)d; + } + g_power_w = (total_uj / 1e6) / dt; +} + +/* ------------------------------------------------------------------ */ +/* Uso de CPU via /proc/stat */ +/* ------------------------------------------------------------------ */ +static unsigned long long g_idle_prev[MAX_CPUS + 1]; +static unsigned long long g_tot_prev[MAX_CPUS + 1]; + +static void sample_stat(int first) +{ + FILE *f = fopen("/proc/stat", "r"); + if (!f) return; + + char line[512]; + while (fgets(line, sizeof line, f)) { + if (strncmp(line, "cpu", 3)) break; + + int idx; + char *p = line + 3; + if (*p == ' ') idx = g_ncpu; /* linea agregada */ + else { + idx = atoi(p); + if (idx < 0 || idx >= g_ncpu) continue; + } + while (*p && !isdigit((unsigned char)*p)) p++; + if (idx != g_ncpu) while (*p && isdigit((unsigned char)*p)) p++; + + unsigned long long v[10] = {0}; + int n = sscanf(p, "%llu %llu %llu %llu %llu %llu %llu %llu %llu %llu", + &v[0], &v[1], &v[2], &v[3], &v[4], + &v[5], &v[6], &v[7], &v[8], &v[9]); + if (n < 4) continue; + + unsigned long long total = 0; + for (int i = 0; i < n; i++) total += v[i]; + unsigned long long idle = v[3] + v[4]; /* idle + iowait */ + + if (!first) { + unsigned long long dt = total - g_tot_prev[idx]; + unsigned long long di = idle - g_idle_prev[idx]; + double u = (dt > 0) ? 100.0 * (double)(dt - di) / (double)dt : 0.0; + if (u < 0.0) u = 0.0; + if (u > 100.0) u = 100.0; + if (idx == g_ncpu) g_usage_avg = u; + else g_usage[idx] = u; + } + g_tot_prev[idx] = total; + g_idle_prev[idx] = idle; + } + fclose(f); +} + +/* ------------------------------------------------------------------ */ +/* Hilos de estres */ +/* ------------------------------------------------------------------ */ +static void *stress_worker(void *arg) +{ + long cpu = (long)arg; + + cpu_set_t set; + CPU_ZERO(&set); + CPU_SET((int)cpu, &set); + pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &set); + + double a = 1.0000001, b = 0.9999999, c = 1.0000003; + unsigned long long x = 0x9E3779B97F4A7C15ULL ^ (unsigned long long)cpu; + + while (g_run) { + for (int i = 0; i < 20000; i++) { + /* Carga mixta: FPU (mul/div/sqrt) + ALU entera de 64 bits */ + c = c * a + b; + c = sqrt(c * c + 1.0); + c = c / 1.0000007 + 1e-7; + x = x * 6364136223846793005ULL + 1442695040888963407ULL; + x ^= x >> 33; + x += (unsigned long long)(c); + if (c > 1e8) c = 1.0000003; + } + /* barrera: impide que el optimizador elimine el bucle */ + g_sink[cpu] = c + (double)(x & 0xFFFF); + __asm__ __volatile__("" ::: "memory"); + } + return NULL; +} + +/* ------------------------------------------------------------------ */ +/* Interfaz ncurses */ +/* ------------------------------------------------------------------ */ +static int color_for_usage(double u) +{ + if (u < 50.0) return CP_GREEN; + if (u < 85.0) return CP_YELLOW; + return CP_RED; +} + +static int color_for_temp(double t) +{ + if (t < 0.0) return CP_DIM; + if (t < 60.0) return CP_GREEN; + if (t < 80.0) return CP_YELLOW; + return CP_RED; +} + +static void draw_bar(int y, int x, int w, double pct, int cp) +{ + if (w <= 0) return; + int fill = (int)((pct / 100.0) * w + 0.5); + if (fill < 0) fill = 0; + if (fill > w) fill = w; + + mvaddch(y, x - 1, '['); + if (g_use_color) attron(COLOR_PAIR(cp) | A_BOLD); + for (int i = 0; i < fill; i++) mvaddstr(y, x + i, g_bar_full); + if (g_use_color) attroff(COLOR_PAIR(cp) | A_BOLD); + + if (g_use_color) attron(COLOR_PAIR(CP_DIM)); + for (int i = fill; i < w; i++) mvaddstr(y, x + i, g_bar_empty); + if (g_use_color) attroff(COLOR_PAIR(CP_DIM)); + mvaddch(y, x + w, ']'); +} + +static void draw_row(int y, const char *label, int marked, + double usage, double temp, double freq, + int wbar, int show_temp, int show_freq) +{ + int x = 1; + + if (marked >= 0) { + int cp = marked ? CP_RED : CP_DIM; + if (g_use_color) attron(COLOR_PAIR(cp) | A_BOLD); + mvaddstr(y, x, marked ? (g_ascii ? "*" : "\xe2\x97\x8f") + : (g_ascii ? " " : "\xc2\xb7")); + if (g_use_color) attroff(COLOR_PAIR(cp) | A_BOLD); + } + x += 2; + + if (g_use_color) attron(COLOR_PAIR(CP_CYAN)); + mvprintw(y, x, "%-6.6s", label); + if (g_use_color) attroff(COLOR_PAIR(CP_CYAN)); + x += 7; + + draw_bar(y, x + 1, wbar, usage, color_for_usage(usage)); + x += wbar + 2; + + int cp = color_for_usage(usage); + if (g_use_color) attron(COLOR_PAIR(cp)); + mvprintw(y, x, "%6.1f%%", usage); + if (g_use_color) attroff(COLOR_PAIR(cp)); + x += 8; + + if (show_temp) { + int ct = color_for_temp(temp); + if (g_use_color) attron(COLOR_PAIR(ct)); + if (temp >= 0.0) mvprintw(y, x, "%5.1fC", temp); + else mvprintw(y, x, " n/d"); + if (g_use_color) attroff(COLOR_PAIR(ct)); + x += 7; + } + if (show_freq) { + if (g_use_color) attron(COLOR_PAIR(CP_DIM)); + if (freq >= 0.0) mvprintw(y, x, "%7.0f MHz", freq); + else mvprintw(y, x, " n/d"); + if (g_use_color) attroff(COLOR_PAIR(CP_DIM)); + } +} + +static void draw_screen(double elapsed, double limit) +{ + erase(); + + int rows = LINES, cols = COLS; + if (rows < 6 || cols < 24) { + mvprintw(0, 0, "Terminal muy pequena"); + refresh(); + return; + } + + int show_freq = (cols >= 84); + int show_temp = (cols >= 62); + + /* ancho de barra: cols - (margen + marca + etiqueta + corchetes + % + extras) */ + int fixed = 1 + 2 + 7 + 2 + 8 + (show_temp ? 7 : 0) + (show_freq ? 11 : 0) + 1; + int wbar = cols - fixed; + if (wbar < 6) { show_freq = 0; wbar = cols - (1 + 2 + 7 + 2 + 8 + (show_temp ? 7 : 0) + 1); } + if (wbar < 6) { show_temp = 0; wbar = cols - (1 + 2 + 7 + 2 + 8 + 1); } + if (wbar < 4) wbar = 4; + + /* --- barra de titulo --- */ + if (g_use_color) attron(COLOR_PAIR(CP_TITLE) | A_BOLD); + for (int i = 0; i < cols; i++) mvaddch(0, i, ' '); + mvprintw(0, 1, " CPU STRESS "); + char rt[64]; + if (limit > 0.0) snprintf(rt, sizeof rt, "%.0fs / %.0fs ", elapsed, limit); + else snprintf(rt, sizeof rt, "%02d:%02d:%02d ", + (int)elapsed / 3600, ((int)elapsed / 60) % 60, + (int)elapsed % 60); + mvprintw(0, cols - (int)strlen(rt) - 1, "%s", rt); + if (g_use_color) attroff(COLOR_PAIR(CP_TITLE) | A_BOLD); + + /* --- modelo --- */ + if (g_use_color) attron(COLOR_PAIR(CP_HEADER) | A_BOLD); + mvprintw(1, 1, "%-*.*s", cols - 2, cols - 2, g_model); + if (g_use_color) attroff(COLOR_PAIR(CP_HEADER) | A_BOLD); + + /* --- resumen --- */ + char line[512]; + char tbuf[32], pbuf[32]; + if (g_pkg_temp >= 0.0) snprintf(tbuf, sizeof tbuf, "%.1f C", g_pkg_temp); + else snprintf(tbuf, sizeof tbuf, "n/d"); + if (g_power_w >= 0.0) snprintf(pbuf, sizeof pbuf, "%.1f W", g_power_w); + else snprintf(pbuf, sizeof pbuf, "n/d"); + + snprintf(line, sizeof line, + "Nucleos: %d | Estresados: %d | Hilos: %d | Paquete: %s | Consumo: %s", + g_ncpu, g_nsel, g_nsel * g_threads_per_core, tbuf, pbuf); + if (g_use_color) attron(COLOR_PAIR(CP_DIM)); + mvprintw(2, 1, "%-*.*s", cols - 2, cols - 2, line); + if (g_use_color) attroff(COLOR_PAIR(CP_DIM)); + + /* --- cabecera de tabla --- */ + if (g_use_color) attron(COLOR_PAIR(CP_HEADER) | A_BOLD | A_UNDERLINE); + for (int i = 0; i < cols; i++) mvaddch(3, i, ' '); + mvprintw(3, 3, "%-6.6s", "CORE"); + mvprintw(3, 11, "%-*.*s", wbar, wbar, "USO"); + mvprintw(3, 11 + wbar + 2, "%7.7s", " CARGA"); + if (show_temp) mvprintw(3, 11 + wbar + 10, "%6.6s", " TEMP"); + if (show_freq) mvprintw(3, 11 + wbar + 10 + (show_temp ? 7 : 0), "%11.11s", " FREQ"); + if (g_use_color) attroff(COLOR_PAIR(CP_HEADER) | A_BOLD | A_UNDERLINE); + + /* --- filas --- */ + int first_row = 4; + int footer_row = rows - 1; + int avail = footer_row - first_row; /* filas utiles */ + + if (avail >= g_ncpu) { + char lbl[16]; + for (int i = 0; i < g_ncpu; i++) { + snprintf(lbl, sizeof lbl, "CPU%d", i); + draw_row(first_row + i, lbl, g_sel[i] ? 1 : 0, + g_usage[i], g_temp[i], g_freq[i], + wbar, show_temp, show_freq); + } + } else { + /* No caben: una sola fila con el promedio global */ + double tsum = 0.0, fsum = 0.0; + int tn = 0, fn = 0; + for (int i = 0; i < g_ncpu; i++) { + if (g_temp[i] >= 0.0) { tsum += g_temp[i]; tn++; } + if (g_freq[i] >= 0.0) { fsum += g_freq[i]; fn++; } + } + int y = first_row + (avail > 1 ? 1 : 0); + draw_row(y, "PROM", -1, g_usage_avg, + tn ? tsum / tn : g_pkg_temp, + fn ? fsum / fn : -1.0, + wbar, show_temp, show_freq); + if (avail > 2 && g_use_color) attron(COLOR_PAIR(CP_DIM)); + if (avail > 2) + mvprintw(y + 2, 3, + "%-*.*s", cols - 4, cols - 4, + "(terminal demasiado corta: mostrando promedio de todos los nucleos)"); + if (avail > 2 && g_use_color) attroff(COLOR_PAIR(CP_DIM)); + } + + /* --- pie --- */ + if (g_use_color) attron(COLOR_PAIR(CP_TITLE)); + for (int i = 0; i < cols; i++) mvaddch(footer_row, i, ' '); + mvprintw(footer_row, 1, + " q/ESC salir r redibujar verde<50%% amarillo<85%% rojo>=85%% "); + if (g_use_color) attroff(COLOR_PAIR(CP_TITLE)); + + refresh(); +} + +/* ------------------------------------------------------------------ */ +/* Argumentos */ +/* ------------------------------------------------------------------ */ +static int parse_core_list(const char *s) +{ + const char *p = s; + int count = 0; + while (*p) { + while (*p == ',' || *p == ' ') p++; + if (!*p) break; + char *end; + long a = strtol(p, &end, 10); + if (end == p) return -1; + long b = a; + p = end; + if (*p == '-') { + p++; + b = strtol(p, &end, 10); + if (end == p) return -1; + p = end; + } + if (a < 0 || b < a || b >= g_ncpu) return -1; + for (long i = a; i <= b; i++) + if (!g_sel[i]) { g_sel[i] = 1; count++; } + } + return count; +} + +static void usage_msg(const char *prog) +{ + printf( +"Uso: %s [opciones]\n" +"\n" +"Estresa los nucleos de la CPU al 100%% y muestra un monitor en tiempo real.\n" +"Sin opciones, estresa TODOS los nucleos logicos detectados.\n" +"\n" +"Opciones:\n" +" -n, --num N Estresa los primeros N nucleos.\n" +" -c, --cores LISTA Estresa nucleos concretos. Ej: -c 0,2,4-7\n" +" -m, --multi K K hilos por nucleo seleccionado (def. 1).\n" +" -t, --time SEG Duracion en segundos (0 = indefinido).\n" +" -i, --interval MS Intervalo de refresco en ms (def. 500, min. 100).\n" +" -a, --ascii Barras ASCII (para consolas sin UTF-8).\n" +" -l, --list Lista nucleos y sensores detectados y sale.\n" +" -h, --help Muestra esta ayuda.\n" +"\n" +"Teclas: q o ESC para salir, r para redibujar.\n" +"Nota: el consumo (RAPL) suele requerir permisos de root.\n", prog); +} + +static void on_signal(int s) { (void)s; g_run = 0; } + +/* ------------------------------------------------------------------ */ +/* main */ +/* ------------------------------------------------------------------ */ +int main(int argc, char **argv) +{ + setlocale(LC_ALL, ""); + + long nc = sysconf(_SC_NPROCESSORS_ONLN); + if (nc < 1) nc = 1; + if (nc > MAX_CPUS) nc = MAX_CPUS; + g_ncpu = (int)nc; + + int opt_n = -1, opt_list = 0; + const char *opt_c = NULL; + double opt_time = 0.0; + int interval_ms = 500; + + static struct option lo[] = { + {"num", required_argument, 0, 'n'}, + {"cores", required_argument, 0, 'c'}, + {"multi", required_argument, 0, 'm'}, + {"time", required_argument, 0, 't'}, + {"interval", required_argument, 0, 'i'}, + {"ascii", no_argument, 0, 'a'}, + {"list", no_argument, 0, 'l'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + int c, li; + while ((c = getopt_long(argc, argv, "n:c:m:t:i:alh", lo, &li)) != -1) { + switch (c) { + case 'n': opt_n = atoi(optarg); break; + case 'c': opt_c = optarg; break; + case 'm': g_threads_per_core = atoi(optarg); break; + case 't': opt_time = atof(optarg); break; + case 'i': interval_ms = atoi(optarg); break; + case 'a': g_ascii = 1; break; + case 'l': opt_list = 1; break; + case 'h': usage_msg(argv[0]); return 0; + default: usage_msg(argv[0]); return 1; + } + } + /* un numero suelto tambien vale: ./cpustress 4 */ + if (opt_n < 0 && !opt_c && optind < argc && isdigit((unsigned char)argv[optind][0])) + opt_n = atoi(argv[optind]); + + if (interval_ms < 100) interval_ms = 100; + if (g_threads_per_core < 1) g_threads_per_core = 1; + if (g_threads_per_core > 16) g_threads_per_core = 16; + + detect_model(); + detect_topology(); + init_sensors(); + init_freq(); + init_rapl(); + + /* --- seleccion de nucleos --- */ + if (opt_c) { + int n = parse_core_list(opt_c); + if (n <= 0) { + fprintf(stderr, "Lista de nucleos invalida: '%s' (rango 0-%d)\n", + opt_c, g_ncpu - 1); + return 1; + } + g_nsel = n; + } else if (opt_n > 0) { + if (opt_n > g_ncpu) opt_n = g_ncpu; + for (int i = 0; i < opt_n; i++) g_sel[i] = 1; + g_nsel = opt_n; + } else { + for (int i = 0; i < g_ncpu; i++) g_sel[i] = 1; + g_nsel = g_ncpu; + } + + if (opt_list) { + printf("CPU : %s\n", g_model); + printf("Nucleos : %d\n", g_ncpu); + printf("Sensores : %d\n", g_nsensors); + for (int i = 0; i < g_nsensors; i++) + printf(" [core %3d] %s\n", g_sensors[i].core_id, g_sensors[i].path); + printf("RAPL : %s (%d dominios)\n", + g_nrapl ? "disponible" : "no disponible (probar con sudo)", g_nrapl); + printf("cpufreq : %s\n", g_have_cpufreq ? "si" : "no (/proc/cpuinfo)"); + return 0; + } + + /* --- barras unicode si el terminal es UTF-8 --- */ + if (!g_ascii) { + const char *cs = nl_langinfo(CODESET); + if (cs && (strstr(cs, "UTF-8") || strstr(cs, "utf8"))) { + g_bar_full = "\xe2\x96\x88"; /* U+2588 bloque lleno */ + g_bar_empty = "\xe2\x96\x91"; /* U+2591 sombreado */ + } else { + g_ascii = 1; + } + } + + signal(SIGINT, on_signal); + signal(SIGTERM, on_signal); + + /* --- lanzar hilos de estres --- */ + int nthreads = g_nsel * g_threads_per_core; + pthread_t *th = calloc((size_t)nthreads, sizeof(pthread_t)); + int *tid_cpu = calloc((size_t)nthreads, sizeof(int)); + if (!th || !tid_cpu) { fprintf(stderr, "Sin memoria\n"); return 1; } + + int k = 0; + for (int i = 0; i < g_ncpu; i++) { + if (!g_sel[i]) continue; + for (int r = 0; r < g_threads_per_core; r++) { + tid_cpu[k] = i; + if (pthread_create(&th[k], NULL, stress_worker, (void *)(long)i) != 0) { + fprintf(stderr, "No se pudo crear el hilo para CPU%d\n", i); + g_run = 0; + break; + } + k++; + } + } + nthreads = k; + + /* --- ncurses --- */ + initscr(); + cbreak(); + noecho(); + nonl(); + curs_set(0); + keypad(stdscr, TRUE); + nodelay(stdscr, TRUE); + + g_use_color = has_colors(); + if (g_use_color) { + start_color(); + use_default_colors(); + init_pair(CP_GREEN, COLOR_GREEN, -1); + init_pair(CP_YELLOW, COLOR_YELLOW, -1); + init_pair(CP_RED, COLOR_RED, -1); + init_pair(CP_HEADER, COLOR_WHITE, -1); + init_pair(CP_DIM, COLOR_BLUE, -1); + init_pair(CP_TITLE, COLOR_BLACK, COLOR_CYAN); + init_pair(CP_CYAN, COLOR_CYAN, -1); + } + + sample_stat(1); + update_temps(); + update_freq(); + + struct timespec t0; + clock_gettime(CLOCK_MONOTONIC, &t0); + + while (g_run) { + int ch; + while ((ch = getch()) != ERR) { + if (ch == 'q' || ch == 'Q' || ch == 27) { g_run = 0; break; } + if (ch == 'r' || ch == 'R' || ch == KEY_RESIZE) { clear(); } + } + if (!g_run) break; + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + double elapsed = (now.tv_sec - t0.tv_sec) + + (now.tv_nsec - t0.tv_nsec) / 1e9; + if (opt_time > 0.0 && elapsed >= opt_time) { g_run = 0; break; } + + sample_stat(0); + update_temps(); + update_freq(); + update_power(); + draw_screen(elapsed, opt_time); + + struct timespec ts = { interval_ms / 1000, + (long)(interval_ms % 1000) * 1000000L }; + nanosleep(&ts, NULL); + } + + g_run = 0; + endwin(); + + for (int i = 0; i < nthreads; i++) pthread_join(th[i], NULL); + free(th); + free(tid_cpu); + + printf("Prueba finalizada. Nucleos estresados: %d, hilos: %d\n", + g_nsel, nthreads); + return 0; +}