En este post, con la excusa de mencionar al legendario Dawn of the Dead del grande G.A.Romero, hablaremos sobre cuándo nuestra PC se convierte en zombi, volviéndose realmente difícil de usar. Un zombie de supermercado, capaz de hacer solo la actividad mínima de los buenos tiempos en que estaba vivo...Francine: Pero porque vienen aquí?Stephen: Una especie de instinto primitivo... De memoria, de lo que solían hacer, tal vez este era un lugar importante en sus vidas.
...zombis de supermercado... |
- si estáis usando un sistema de la familia UNIX (Linux, FreeBSD, macOS, etc.) y no estáis pidiendo demasiado al Hardware disponible (como abrir 50 aplicaciones a la vez en un PC Pentium III de 1999) es probable que una aplicación que no funciona bien se está comiendo la CPU y/o la Memoria: intentad descubrir qué aplicación es y matadla (usando el monitor del sistema o, si sois de la vieja escuela, usando top y kill en una terminal). Dicho hecho.
- si estáis usando ese sistema innominable (que comienza con W y termina con s, ya sabéis), entonces tenéis que superarlo: es su comportamiento normal, baby... la única solución es pasar a usar algo un poco más serio (ver el punto 1). ¡Masoquistas!
(...um, lo siento si insisto pero, volviendo a los dos puntos descritos anteriormente: el tema ahora es: aplicaciones embedded basadas en UNIX (entonces Linux Embedded, QNX, LynxOS, etc..). Si, por otro lado, realmente os queréis lastimar y escribís aplicaciones de este tipo usando la versión CE del sistema innominable (W ... s, ¿recordáis?) Bien, ¿qué puedo deciros? ...si te lo has buscado no te quejes...)
Bueno, dividiremos el post en dos partes: en esta primera parte propondré una función (que llamaremos testSys()) que hace todo el trabajo necesario. En la segunda parte haremos un main(), que simula el de una simple aplicación embedded, en la que introduciremos un thread escrito ad-hoc para sobrecargar el sistema, y nuestro main(), utilizando la testSys(), debería notarlo sin problemas. ¡Vamos con el código!
// struct para los resultados typedef struct { int total_cpu; // cpu total (val.porcentual x prec) int proc_cpu; // cpu proceso (val.porcentual x prec) int mem_system; // mem total (val.porcentual x prec) int mem_proc; // mem proceso (val.porcentual x prec) float prec; // precisión (10=1dec) int loads[3]; // cargas avg (val.porcentual x loads_prec) float loads_prec; // precisión para cargas (100=2dec) } Results; // función de test del sistema void testSys( Results *results) // destinación de los resultados { static unsigned long long total_cpu_last; /* system total cpu-time incluido idle-time en jiffies (típicamente centésimos de segundo)) */ static unsigned long long idle_cpu_last; /* system idle cpu-time (in jiffies (tipicamente centésimos de segundo)) */ static unsigned long proc_times_last; /* cpu-time del proceso corriente (calculado sumando usertime y systemtime del proceso) */ FILE *fp1; FILE *fp2; // set resultados de default (así el que llama puede tratar los errores) results->total_cpu = -1; results->proc_cpu = -1; results->mem_system = -1; results->mem_proc = -1; results->prec = 10.; results->loads[0] = -1; results->loads[1] = -1; results->loads[2] = -1; results->loads_prec = 100.; /* lee /proc/self/stat (estadísticas de proceso di este proceso) y /proc/stat (estadísticas de procesos de esta maquina) */ if ( ((fp1 = fopen("/proc/self/stat", "r")) != NULL) && ((fp2 = fopen("/proc/stat", "r")) != NULL)) { // lee user time e system time unsigned long utime, stime; fscanf(fp1, "%*d %*s %*c %*d %*d %*d %*d %*d %*u %*u %*u %*u %*u %lu %lu", &utime, &stime); unsigned long proc_times_cur = utime + stime; // lee i valori di user, nice, system, idle, iowait, irq e softirq unsigned long long user, nice, system, idle, iowait, irq, softirq; fscanf(fp2,"%*s %llu %llu %llu %llu %llu %llu %llu", &user, &nice, &system, &idle, &iowait, &irq, &softirq); unsigned long long total_cpu_cur = user + nice + system + idle + iowait + irq + softirq; unsigned long long idle_cpu_cur = idle; /* calcula uso cpu total (%cpu = work_over_period / total_over_period * 100 (adonde work_over_period es (total - idle)) NOTA: el campo iowait di /proc/stat es incluido nel calculo, aunque sea un valor poco fiable (pero el error es trascurable) */ results->total_cpu = (float)((total_cpu_cur - total_cpu_last) - (idle_cpu_cur - idle_cpu_last)) / (total_cpu_cur - total_cpu_last) * 100 * results->prec; /* calcula uso cpu del proceso ((proc_times2 - proc_times1) * 100 / (float) (total_cpu_usage2 - total_cpu_usage1)) NOTA: nel programa "top" este valor es multiplicado por NPROCESSORS (usar el comando interactivo "I" para deshabilitar esta característica) */ results->proc_cpu = (float)(proc_times_cur - proc_times_last) / (total_cpu_cur - total_cpu_last) * 100 * results->prec; // salva valores y cierra files total_cpu_last = total_cpu_cur; idle_cpu_last = idle_cpu_cur; proc_times_last = proc_times_cur; fclose(fp1); fclose(fp2); } // lee /proc/self/statm (estadísticas de memoria de este proceso) struct sysinfo si; // destinazione per dati ottenuti dalla system call sysinfo() if (((fp1 = fopen( "/proc/self/statm", "r")) != NULL) && (sysinfo(&si) != -1)) { /* lee el resident set size corriente (physical memory use) medido en bytes. NOTA: nel programa "top" el valor llamado RES depende del valor del RSS (resident set size) de las estructuras internas de Linux */ long resident; fscanf(fp1, "%*s%ld", &resident); long res = resident * (size_t)sysconf(_SC_PAGESIZE) / 1024; // code+data // calcula valores de referencia unsigned long totalram = si.totalram * si.mem_unit; unsigned long freeram = si.freeram * si.mem_unit; results->mem_system = (float)(totalram - freeram) / totalram * 100 * results->prec; /* calcula %mem: nel programa "top" este valor es calculado como: %mem = RES / totphisicalmem (adonde RES es CODE + DATA (adonde DATA es data + stack)) */ results->mem_proc = (float)res / (totalram / (float)1024) * 100 * results->prec; fclose(fp1); } // lee cpu loadavg double loads[3]; // cargas avg de CPU a 1, 5, e 15 minutos if (getloadavg(loads, 3) != -1) { // copia las cargas en los resultados results->loads[0] = loads[0] * results->loads_prec; results->loads[1] = loads[1] * results->loads_prec; results->loads[2] = loads[2] * results->loads_prec; } }¿Que pensáis de eso? Puse tantos comentarios, que casi no queda nada por decir. ¿Y qué puedo añadir? De los comentarios se desprende que los datos obtenidos utilizan el mismo sistema de cálculo que usa top (que es una referencia casi obligatoria) y, en particular, nos referimos a top con la opción "I" (Irix mode Off) habilitada: esto evita , simplemente, tener indicaciones (extrañas a primera vista, pero correctas) como "% CPU = 800" que corresponde a un consumo de 100% de CPU en una máquina con 8 núcleos: con la opción "I" estamos seguros de que el el valor máximo indicado será del 100%, independientemente de la cantidad de núcleos disponibles (para que no se pueda malinterpretar).
La función testSys() escribe los resultados en una estructura TestResults pasada como argumento: esto nos permite presentar y/o procesar datos de manera adecuada en el nivel del llamante. En el ejemplo que veremos proximamente, el main() llama testSys() y enseña los resultados, para que se pueda verificar si coinciden con los de top (que podemos ejecutar simultáneamente en otra terminal). En un caso real los datos deberían, en vez, procesarse, por ejemplo comparándolos con valores de referencia para generar alarmas. Precisamente por esta razón los datos se registran como int multiplicados por un factor de precisión apropiado: esta es una forma normal y clásica de procesar datos que nacen float, ya que la comparación (y cualquier otra operación) entre valores enteros es mucho más simple de realizar y permite una mayor flexibilidad de uso.
Bueno, para hoy puede ser suficiente. Como prometido en el próximo post os mostraré un ejemplo de uso, algunos resultados de pruebas reales y un pequeño estudio sobre la interpretación de datos, especialmente los de la Memoria.
¡Hasta el próximo post!