Jane: Ma perché ritornano in un grande magazzino?In questo post, con la scusa di citare il mitico Dawn of the Dead del grande G.A.Romero, parleremo di quando il nostro PC si trasforma in uno zombi, diventando veramente difficile da usare. Uno zombi da supermercato, in grado solo di fare l'attività minimale dei bei tempi in cui era vivo...
Stephen: Dev'essere l'istinto... Il ricordo di quello che erano abituati a fare. Era un posto importate quando erano vivi.
...zombi da supermercato... |
- se state usando un sistema della famiglia UNIX (Linux, FreeBSD, macOS, ecc.) e non state chiedendo troppo rispetto all'Hardware disponibile (tipo aprire 50 applicazioni alla volta su un recentissimo PC Pentium III del 1999) è probabile che una applicazione malfunzionante vi stia mangiando la CPU e/o la Memoria: cercate di scoprire quale applicazione è e uccidetela (usando il monitor di sistema oppure, se siete della vecchia scuola, usando top e kill in un terminale). Detto fatto.
- se state usando quel sistema innominabile (che comincia con W e finisce con s, già sapete), beh allora dovete farvene una ragione: è il suo comportamento normale, baby... l'unica soluzione è passare a usare qualcosa di un po' più serio (vedi al punto 1). Masochisti!
(...ehm, scusate se insisto ma, tornando ai due punti descritti qua sopra: l'argomento ora è: applicazioni embedded a base UNIX (quindi Linux Embedded, QNX, LynxOS, ecc.). Se invece volete proprio farvi del male e scrivete applicazioni di questo tipo usando la versione CE del sistema innominabile (W...s, ricordate?) va beh, cosa vi devo dire? chi è causa del suo mal pianga se stesso...)
Allora, divideremo il post in due parti: in questa prima parte vi proporrò una funzione (che chiameremo testSys()) che fa tutto il lavoro necessario. Nella seconda parte faremo un main(), che simula quello di una semplice applicazione embedded, in cui introdurremo un thread fatto ad-hoc per appesantire il sistema, e il nostro main(), usando la testSys(), dovrebbe accorgersene senza problemi. Vai col codice!
// struct per i risultati typedef struct { int total_cpu; // cpu totale (val.percentuale x prec) int proc_cpu; // cpu processo (val.percentuale x prec) int mem_system; // mem totale (val.percentuale x prec) int mem_proc; // mem processo (val.percentuale x prec) float prec; // precisione (10=1dec) int loads[3]; // carichi medi (val.percentuale x loads_prec) float loads_prec; // precisione per carichi (100=2dec) } Results; // funzione di test del sistema void testSys( Results *results) // destinazione dei risultati { static unsigned long long total_cpu_last; /* system total cpu-time incluso idle-time in jiffies (tipicamente centesimi di secondo)) */ static unsigned long long idle_cpu_last; /* system idle cpu-time (in jiffies (tipicamente centesimi di secondo)) */ static unsigned long proc_times_last; /* cpu-time del processo corrente (calcolato sommando usertime e systemtime del processo) */ FILE *fp1; FILE *fp2; // set risultati di default (così il chiamante può trattare gli errori) 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.; /* legge /proc/self/stat (statistiche di processo di questo processo) e /proc/stat (statistiche dei processi di questa macchina) */ if ( ((fp1 = fopen("/proc/self/stat", "r")) != NULL) && ((fp2 = fopen("/proc/stat", "r")) != NULL)) { // legge 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; // legge 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; /* calcola uso cpu totale (%cpu = work_over_period / total_over_period * 100 (dove work_over_period è (total - idle)) NOTA: il campo iowait di /proc/stat è incluso nel calcolo, anche se è un valore inaffidabile (ma l'errore è trascurabile) */ 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; /* calcola uso cpu del processo ((proc_times2 - proc_times1) * 100 / (float) (total_cpu_usage2 - total_cpu_usage1)) NOTA: nel programma "top" questo valore è moltiplicato per NPROCESSORS (usare il comando interattivo "I" per disabilitare questa caratteristica) */ results->proc_cpu = (float)(proc_times_cur - proc_times_last) / (total_cpu_cur - total_cpu_last) * 100 * results->prec; // save valori e chiude files total_cpu_last = total_cpu_cur; idle_cpu_last = idle_cpu_cur; proc_times_last = proc_times_cur; fclose(fp1); fclose(fp2); } // legge /proc/self/statm (statistiche di memoria di questo processo) struct sysinfo si; // destinazione per dati ottenuti dalla system call sysinfo() if (((fp1 = fopen( "/proc/self/statm", "r")) != NULL) && (sysinfo(&si) != -1)) { /* legge il resident set size corrente (physical memory use) misurato in bytes. NOTA: nel programma "top" il valore chiamato RES dipende dal valore del RSS (resident set size) delle strutture interne di Linux */ long resident; fscanf(fp1, "%*s%ld", &resident); long res = resident * (size_t)sysconf(_SC_PAGESIZE) / 1024; // code+data // calcola valori di riferimento 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; /* calcola %mem: nel programma "top" questo valore è calculato come: %mem = RES / totphisicalmem (dove RES è CODE + DATA (dove DATA è data + stack)) */ results->mem_proc = (float)res / (totalram / (float)1024) * 100 * results->prec; fclose(fp1); } // legge cpu loadavg double loads[3]; // lcarichi medi di CPU a 1, 5, e 15 minuti if (getloadavg(loads, 3) != -1) { // copia i carichi nei risulatati results->loads[0] = loads[0] * results->loads_prec; results->loads[1] = loads[1] * results->loads_prec; results->loads[2] = loads[2] * results->loads_prec; } }Che ne pensate? Ho messo così tanti commenti che quasi non c'è più nulla da dire. E cosa si può aggiungere? È evidente dai commenti che i dati ricavati usano lo stesso sistema di calcolo che usa top (che è un riferimento quasi obbligato) e, in particolare, ci riferiamo a top con l'opzione "I" (Irix mode Off) abilitata: questo evita, semplicemente, di avere indicazioni (strane a prima vista, ma corrette) come "%CPU=800" che corrisponde a un consumo di CPU del 100% su una macchina con 8 cores: con l'opzione "I" siamo sicuri che il valore massimo indicato sarà 100%, indipendentemente dal numero di cores disponibili (così non si può fraintendere).
La funzione testSys() scrive i risultati in una struttura TestResults passata come argomento: questo ci permette di poter presentare e/o processare opportunamente i dati a livello del chiamante. Nell'esempio che vedremo prossimamente il main() chiama testSys() e mostra i risultati, così si può verificare se corrispondono a quelli di top (che possiamo eseguire in contemporanea in un altro terminale). In un caso reale i dati si dovrebbero, invece, processare, ad esempio confrontandoli con dei valori di riferimento per poter alzare degli allarmi. Proprio per questo motivo i dati vengono registrati come int moltiplicati per un opportuno fattore di precisione: questa è una maniera normale e classica di trattare dati che nascono float, visto che il confronto (e qualunque altra operazione) tra valori interi è molto più semplice da realizzare e permette più flessibilità d'uso.
Va beh, per oggi può bastare. Come promesso nel prossimo post vi mostrerò un esempio d'uso, un po' di risultati di test reali e un piccolo approfondimento sulla interpretazione dei dati, specialmente su quelli della Memoria.
Ciao, e al prossimo post!
Nessun commento:
Posta un commento