Nei titoli e nei testi troverete qualche rimando cinematografico (ebbene si, sono un cinefilo). Se non vi interessano fate finta di non vederli, già che non sono fondamentali per la comprensione dei post...

Di questo blog ho mandato avanti, fino a Settembre 2018, anche una versione in Spagnolo. Potete trovarla su El arte de la programación en C. Buona lettura.

domenica 15 aprile 2018

Dawn of the CPU
come testare l'uso di CPU e Memoria in C - pt.2

Ci siamo, dopo la presentazione nello scorso post è venuta l'ora di vedere se il nostro sistema di test continuo di CPU e Memoria funziona. E se funziona bene. Ricordate l'argomento, no? Gli zombi da supermercato, Dawn of the Dead, Dawn of the CPU... si, quello.
...ma veramente dobbiamo andare armati per comprare un po' di RAM?...
Per verificare il funzionamento della funzione di test descritta nello scorso post, ho scritto un piccolo programma di test (testsys.c) che crea un thread in grado di stressare CPU e Memoria di un sistema e che poi, direttamente nel main(), chiama in un loop infinito la nostra funzione testSys(), mostrando ogni due secondi i risultati del test. Nel codice che segue manca solo la funzione di test: quella potete andare a ripescarla nell'ultimo post e così potete approfittare per rileggerne le parti salienti (bravi se lo fate!). Vai col codice!
#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 per i risultati
typedef struct {
    ...
} Results;

// prototipi locali
void testSys(Results *results);
void *tMyThread(void *arg);

// funzione 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: non posso creare il thread (%s)\n", argv[0], strerror(error));

    // chiama testSys() per primo set valori statici
    Results results;
    testSys(&results);
    sleep(2);

    // testSys() loop per testare ripetutamente il sistema
    for (;;) {
        // get valori
        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 secondi
        sleep(2);
    }

    // exit
    exit(EXIT_SUCCESS);
}

// funzione di test del sistema
void testSys(
    Results *results)   // destinazione dei risultati
{
    ...
}

// 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 molto piccola per forzare molta attività di cpu
    }

    return NULL;
}
Come potete ben vedere il programma è di una semplicità disarmante (e con  ottimi commenti, al solito). Per stressare CPU e Memoria ho solo usato alcuni semplici trucchetti: alloco (con malloc()) un bufferone di 512MB, e poi in un loop infinito lo uso intensamente (con una memset() completa). Il loop del thread usa una usleep moooolto piccola (10 us) che carica non poco la CPU (che ci odierà un poco, ma è l'obiettivo del nostro test, no?).

Adesso viene il bello: come detto varie volte nel post precedente, il nostro riferimento è il comando top della famiglia UNIX, quindi dobbiamo aprire due terminali, e in uno eseguiamo il nostro programma di test, e nell'altro eseguiamo top e così possiamo confrontare in tempo reale se i risultati corrispondono (ricordatevi di attivare l'opzione "I" di top, come descritto nell'altro post). Vediamo cosa è successo sulla mia macchina:
nel terminale 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
...

nel terminale 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            
 ...
visto che l'output è continuo ne ho selezionato solo una parte e ho aggiunto i puntini di sospensione, comunque potete ripetere facilmente il test sulla vostra macchina per verificare la costanza dei risultati: direi che il risultato è più che soddisfacente, no? Missione compiuta!

Cosa manca? Ah, si, avevo promesso qualche appunto sui risultati che mostra top (e per riflesso anche il nostro testsys): per quanto riguarda la CPU sono sufficienti i commenti nel codice della testSys() (descrizione dei carichi medi, opzione "I", ecc.). In più si può aggiungere che la linea %Cpu(s) mostrata qua sopra conferma le formule contenute (e commentate) nella testSys(): ad esempio la somma dei vari componenti (user, idle, sys, ecc.) vale, guarda caso, 100.

Per la memoria, invece, il discorso è un po' più complesso, e bisognerebbe dedicargli un post apposito (magari lo farò in futuro): per il momento vi passo un link interessante: understanding-memory-usage-on-linux, e vi aggiungo solo una spiegazione semplicissima di una cosa (strana) che a volte succede su sistemi embedded realizzati con BusyBox: in alcuni casi sembra che alcuni processi usino più del 100% della memoria (nella colonna %MEM): questo è dovuto al fatto che si sta usando una vecchia versione di top fornita da BusyBox che calcola %MEM come VSZ/MemTotal invece di RSS/MemTotal: bisogna considerare che RSS è un valore residente mentre VSZ è un valore virtuale, quindi influenzato, ad esempio, da eventuali shared library che vengono caricate e condivise tra varie applicazioni, pagine di memoria non usate al momento, ecc., per cui non c'è da stupirsi se il valore supera il 100%. Comunque le versioni più recenti di top per BusyBox ridefiniscono la colonna %MEM in %VSZ risolvendo così eventuali incomprensioni (oops... si, i significati delle strane sigle qui sopra li trovate nei commenti della testSys(), nel manuale di top e nel link che vi ho passato sulla memoria di Linux).

Beh, direi che per questo post può bastare. Vi lascio con una piccola nota: visto che ho usato solo loop infiniti nel programma di test potete modificarlo a piacere aggiungendo condizioni di uscita. Oppure non abbiate paura a fermarlo con CTL-C, non credo che Linux si arrabbi molto...

Ciao, e al prossimo post!