Carga inicial al repo
This commit is contained in:
@@ -1,3 +1,29 @@
|
||||
# cpustress
|
||||
|
||||
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
|
||||
|
||||
+884
@@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
#include <signal.h>
|
||||
#include <dirent.h>
|
||||
#include <getopt.h>
|
||||
#include <locale.h>
|
||||
#include <langinfo.h>
|
||||
#include <pthread.h>
|
||||
#include <sched.h>
|
||||
#include <ncurses.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user