En los títulos y los textos vais a encontrar unas cuantas citaciones cinematográficas (y si, soy un cinéfilo). Si no os interesan podéis fingir no verlas, ya que no son fundamentales para la comprensión de los post...

Este blog es la versión en Español de mi blog en Italiano L'arte della programmazione in C. Espero que mis traducciones sean comprensibles...

domingo, 15 de abril de 2018

Dawn of the CPU
cómo testear el uso de CPU y Memoria en C - pt.2

Aquí estamos, después de la presentación en el último post, ha llegado el momento de ver si nuestro sistema de test continuo de CPU y Memoria funciona. Y si funciona bien. Os acordais el tema, ¿no? Zombies de supermercado, Dawn of the Dead, Dawn of the CPU... sí, eso.
...¿pero realmente tenemos que ir armados para comprar algo de RAM?...
Para probar el funcionamiento de la función de test descrita en el último post, he escrito un pequeño programa de test (testsys.c) que crea un thread que puede estresar la CPU y la Memoria de un sistema y luego, directamente en el main(), llama en un loop infinito la nuestra función testSys(), enseñando los resultados del test cada dos segundos. En el siguiente código solo falta la función de test: podéis ir a buscarla en el último post y aprovechar para volver a leer las partes más importantes (¡bravo quien lo hace!). ¡Vamos con el código!
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>
#include <linux/kernel.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/sysinfo.h>

// struct para los resultados
typedef struct {
    ...
} Results;

// prototipos locales
void testSys(Results *results);
void *tMyThread(void *arg);

// función main()
int main(int argc, char *argv[])
{
    // init thread
    pthread_t tid;
    int error;
    if ((error = pthread_create(&tid, NULL, &tMyThread, NULL)) != 0)
        printf("%s: no puedo crear el thread (%s)\n", argv[0], strerror(error));

    // llama testSys() para primer set valores estáticos
    Results results;
    testSys(&results);
    sleep(2);

    // testSys() loop para testear repetidamente el sistema
    for (;;) {
        // get valores
        testSys(&results);
        printf("cpu: total usage = %.1f\n", results.total_cpu / results.prec);
        printf("mem: total usage = %.1f\n", results.mem_system / results.prec);
        printf("cpu: proc  usage = %.1f\n", results.proc_cpu / results.prec);
        printf("mem: proc  usage = %.1f\n", results.mem_proc / results.prec);
        printf("load average: %.2f , %.2f , %.2f\n", 
                results.loads[0] / results.loads_prec,
                results.loads[1] / results.loads_prec, 
                results.loads[2] / results.loads_prec);

        // sleep 2 segundos
        sleep(2);
    }

    // exit
    exit(EXIT_SUCCESS);
}

// función de test del sistema
void testSys(
    Results *results)   // destinación de los resultados
{
    ...
}

// thread routine
void *tMyThread(void *arg)
{
    // alloc memoria
    unsigned long mem = 1024 * 1024 * 512;  // 512 mb
    char *ptr = malloc(mem);

    // thread loop infinito
    for (;;) {
        // usa memoria
        memset(ptr, 0, mem);

        // thread sleep
        usleep(10);    // NOTA: sleep muy pequeña para forzar mucha actividad de cpu
    }

    return NULL;
}
Como podeis ver, el programa es de una simplicidad desarmante (y con excelentes comentarios, como de costumbre). Para estresar la CPU y la Memoria he utilizado algunos simples trucos: asigno (con malloc()) un megabúfer de 512MB, y luego en un loop infinito lo uso intensamente (con una memset() completa). El ciclo del thread  una usleep muuuuy pequeña (10 us) que carga mucho la CPU (que nos odiará un poco, pero es el objetivo del nuestro test, ¿no?).

Ahora viene lo bueno: como se ha mencionado varias veces en el post anterior, nuestra referencia es el comando top de la familia UNIX así que tenemos que abrir dos terminales, y en una ejecutamos nuestro programa de test, y en la otra ejecutamos top para poder comparar en tiempo real si los resultados coinciden (recordaros de activar la opción "I" de top, como descrito en el otro post). Veamos qué ha pasado en mi ordenador:
en la terminal con testsys
...
load average: 0.85 , 0.42 , 0.32
cpu: total usage = 12.9
mem: total usage = 47.8
cpu: proc  usage = 12.5
mem: proc  usage = 6.9
...

en la terminal con top
top - 18:45:16 up 39 min,  2 users,  load average: 0,85, 0,42, 0,32
Tasks: 236 total,   1 running, 235 sleeping,   0 stopped,   0 zombie
%Cpu(s): 12,5 us,  0,1 sy,  0,0 ni, 87,2 id,  0,0 wa,  0,0 hi,  0,1 si,  0,0 st
KiB Mem :  7600656 total,  3962420 free,  1705536 used,  1932700 buff/cache
KiB Swap:        0 total,        0 free,        0 used.  5384092 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND            
 5266 aldo      20   0  604548 524744    628 S 12,5  6,9   0:43.01 testpstat          
 1380 root      20   0  559772  94056  82400 S  0,1  1,2   0:43.89 Xorg               
 1012 root      20   0    4396   1276   1196 S  0,0  0,0   0:00.07 acpid              
 2628 aldo      20   0 2542528 358152 128056 S  0,0  4,7   3:18.25 firefox            
 ...
dado que la salida es continua, he seleccionado solo una parte y he agregado las elipsis, en cualquier caso podéis repetir fácilmente el test en el vuestro ordenador para verificar la consistencia de los resultados: yo diría que el resultado es más que satisfactorio, ¿no? ¡Misión cumplida!

¿Qué falta? Ah, sí, prometí algunas notas sobre los resultados que muestra top (y, consecuentemente, también el nuestro testsys): en lo que concierne a la CPU, los comentarios en el codigo de la testSys() son suficientes (descripción de las cargas medias, opción "I", etc.). Además, podemos agregar que la línea %Cpu(s) mostrada arriba confirma las fórmulas contenidas (y comentadas) en testSys(): por ejemplo, la suma de los diversos componentes (user, idle, sys, etc.) vale, como esperado, 100.

Para la memoria, sin embargo, el discurso es un poco más complejo, y habria que dedicar un post dedicado (tal vez lo haré en el futuro): por el momento os paso un enlace interesante: understanding-memory-usage-on-linux, y os añado sólo una explicación muy simple de algo (extraño) que a veces ocurre en sistemas embedded echos con BusyBox: en algunos casos parece que algunos procesos usan más del 100% de la memoria (en la columna %MEM): esto se debe al hecho de que está utilizando una versión antigua de top proporcionada por BusyBox que calcula %MEM como VSZ/MemTotal en lugar de RSS/MemTotal: debemos tener en cuenta que RSS es un valor residente mientras VSZ es un valor virtual, que por lo tanto está influenciado, por ejemplo, de eventuales shared library que se cargan y se comparten entre varias aplicaciones, de páginas de memoria no utilizadas en ese momento, etc., por lo que no es sorprendente que el valor exceda el 100%. Sin embargo, las nuevas versiones de top para BusyBox redefinen la columna %MEM en %VSZ solucionando así cualquier malentendido (oops... el significado de las siglas extrañas anteriores lo vais a encontrar en los comentarios de testSys(), en el manual de top y en el enlace que os he pasado sobre la memoria de Linux).

Bueno, yo diría que para este post es suficiente. Os dejo con una pequeña nota: dado que he utilizado solamente loop infinitos en el programa de test, podéis modificarlo a vuestro gusto añadiendo condiciones de salida. O, en alternativa, no tengáis miedo de detenerlo con CTL-C, no creo que Linux se enfade mucho...

¡Hasta el próximo post!