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