PI Guide/ Monitorare il PC con Arduino

Come tenere sotto controllo i parametri che segnalano il corretto funzionamento del computer Linux realizzando un PC Meter dal gusto un po' retrò

Gli utenti che usano le moderne distribuzioni GNU/Linux non si preoccupano minimamente, almeno in buona parte dei casi, di mettere a punto le prestazioni della macchina attraverso le usuali ottimizzazioni, semplicemente perché la distribuzione si comporta più che bene con le impostazioni predefinite pensate dagli sviluppatori. A volte, tuttavia, è bene avere sott'occhio alcuni aspetti delle risorse hardware come il quantitativo di RAM libera (o utilizzata), il carico sulla CPU e l'eventuale uso dello spazio di swap.

Esempi di monitoraggio
In ambiente GNU/Linux, per monitorare il sistema abbiamo a disposizione una pletora di programmi da shell, così come interfacce grafiche che si appoggiano ai primi per visualizzare i parametri di interesse. Ad esempio, il modo più semplice e immediato per misurare il carico medio è utilizzare il comando uptime (man uptime) che fornisce un output del tipo:

17.25 up 0:24, 3 utenti, carico medio: 0,32, 0,19, 0,25
laddove gli ultimi 3 valori associati alla sezione carico medio indicano rispettivamente il carico medio nell'ultimo minuto, negli ultimi 5 minuti e negli ultimi 15. Ad esempio, il valore 0,25 indica che negli ultimi 15 minuti la CPU è stata impegnata per il 25 per cento del tempo ed è rimasta in stato di idle per il rimanente 75 per cento.
La maggior parte dei sistemi in genere mostra una media di carico poco più dello 0 se non si stanno eseguendo azioni o programmi impegnativi: una suite per ufficio non impegna quasi per nulla la CPU poiché la maggior parte del tempo il sistema è in attesa di un input da parte dell'utente. Diverso è il discorso nel caso della compilazione di un programma, esecuzione di un gioco di ultima generazione o programmi di calcolo: in questi casi non è raro avere una media di carico prossima a 1. La media di carico può assumere anche valori maggiori di 1, ma non necessariamente indica una sofferenza del sistema a meno che il carico medio risulti maggiore del numero di CPU (o core) presenti nel sistema. I valori di uptime danno una buona indicazione del carico sul sistema, ma necessitano di essere integrati con ulteriori strumenti per poter affinare l'investigazione. Ad esempio, il comando top permette di verificare il consumo di ogni processo e capire quali sono quelli che stanno consumando il maggior numero di cicli CPU. Se la media di carico è alta e il sistema appare sofferente potrebbe essere un problema di RAM insufficiente. Il modo più semplice per controllarla è con il comando free e/o con cat /proc/meminfo dai quali possiamo valutare anche la memoria di swap utilizzata.
Un programma, quando è in esecuzione, viene caricato nella RAM: se tale programma, per le sue necessità, richiede ulteriore RAM e questa non è presente a sufficienza nei banchi fisici, ecco che si ricade nei cosiddetti page fault.
Il kernel è costretto a spostare pagine di memoria su disco utilizzando quindi la partizione o il file di swap. In presenza di molti page fault (uso intensivo della swap) il sistema rallenta in maniera vistosa. Per controllare i page fault si può usare il comando vmstat che fornisce svariate informazioni. Lanciandolo con vmstat x, dove x è un numero, riporta le statistiche ogni "x secondi".


L'output dei comandi shell riportati

L'output del comando abbraccia diverse categorie: le colonne procs per i processi, la sezione memory per la RAM, per lo swap le colonne di interesse sono swpd per la memoria soggetta a swapping dal disco e in particolare si (swap in), memoria spostata dal disco alla RAM, e so (swap out) memoria spostata dalla RAM al disco. A seguire troviamo la sezione io per l'utilizzo del disco, quindi system che interessa il kernel e cpu per le distribuzioni di tempo utente (us), attività di sistema (sy) e stato di idle (id). Vi sono molti altri comandi da poter utilizzare anche per la rete, ma non è questo il contesto.

Diamo il via al nostro progetto
Abbiamo visto quali e quanti tipi di dati è possibile ottenere con alcuni comandi da shell. E se provassimo a visualizzare questi dati, o alcuni di loro, in un progetto esterno, ad esempio un display LCD e/o su un classico strumento analogico per dargli un tocco vintage, quali dovrebbero essere gli aspetti da conoscere e i passi da seguire? In tale prospettiva almeno tre possono essere gli attori in gioco: da un lato un hardware (un minimo di elettronica) che possa pilotare il display LCD o qualsiasi altra cosa si voglia utilizzare per la visualizzazione. Deve esserci un affiancamento con un software che acquisendo dal computer sotto test i valori di interesse li invii alla porta USB. Il terzo elemento riguarda chi riceverà dalla porta USB quei dati, ovvero l'elettronica di cui sopra: in qualche modo dovrà predisporsi alla ricezione e organizzarli al meglio per una corretta visualizzazione. In quest'ottica è auspicabile l'uso di un microcontrollore (μC) che vada a supervisionare le operazioni necessarie attraverso un'altra porzione di software da caricare nel μC (firmware). Visto le funzioni da espletare si può optare per il classico μC a bordo di una Arduino Uno oppure, per chi già la possiede, la scheda stessa. Avendo a disposizione qualche ATMega328P-PU, abbiamo realizzato una soluzione stand-alone: il costo di questi μC si aggira intorno ai 2 euro per la versione senza bootloader e di circa 3,5 euro per la versione con bootloader già caricato.

Per facilitarci la vita nella realizzazione di questo progetto, all'apparenza semplice ma che cela tutta una serie di concetti e conoscenze hardware/software, abbiamo preparato un archivio scaricabile da qui che contiene i sorgenti e i file utili alla sua replicazione.

Acquisire e trasmettere
Il primo passo vede la realizzazione di un software che possa ricavare i dati che si vuole avere sotto controllo per poi trasmetterli al sistema esterno per la visualizzazione. Vediamo come procedere per gradi... Per ricavare le informazioni possiamo utilizzare uno o più dei comandi shell riportati in precedenza, ma dovendoli poi trasmettere alla porta USB allora è suggerito l'utilizzo di un linguaggio ad-hoc, facile, veloce e completo delle librerie necessarie ad espletare le funzioni di cui necessitiamo. La scelta non può che ricadere su Python a causa dello sterminato numero di moduli che permette di orientarsi in ogni aspetto dello scibile umano. Nel nostro caso specifico faremo riferimento solo a Python 3: il recupero delle informazioni sull'utilizzazione del sistema (CPU, memoria, dischi e rete) e sui processi attivi si possono ottenere attraverso il modulo psutil. Per poterlo utilizzare occorre installare il pacchetto python3-psutil (il riferimento qui e nel seguito sarà ad una OpenSUSE 13.2) avvalendosi del gestore software della propria distribuzione. Le potenzialità di questo modulo sono notevoli tant'è che implementa molti dei comandi offerti dalla shell, come ps, top, free, nice, uptime, ed è utilizzato principalmente in software per il monitoraggio, il profiling e la gestione dei processi in esecuzione, come ad esempio nel progetto Glances.


Glances, uno dei programmi che si appoggia a psutil

L'utilizzo di psutil da riga di comando, che riflette poi la riga di codice da inserire nel programma, è piuttosto semplice. Entriamo nella modalità interattiva di Python 3, ovvero diamo il comando pyhton3 seguito da Invio, e digitiamo nell'ordine le due righe seguenti:


import psutil
psutil.virtual_memory()


dopo aver premuto Invio l'output sarà un qualcosa del tipo:


svmem(total=3523723264, available=2914365440, percent=17.3, used=1550401536, free=1973321728, active=899354624, inactive=439988224, buffers=79044608, cached=861999104)


In questo caso gli elementi di interesse per GNU/Linux, da visualizzare su display LCD, potrebbero essere le voci percent eavailable che indicano, rispettivamente, la percentuale di memoria RAM utilizzata e l'attuale ammontare di memoria disponibile che può essere fornita istantaneamente a un processo/programma che ne faccia richiesta. Per approfondire in maniera puntuale il significato di tutte le voci, impartiamo, sempre nella modalità interattiva, il comando help('psutil.virtual_memory'). Analogo discorso per l'uso della CPU il cui valore di interesse potrebbe essere la percentuale d'uso della CPU. Allora, sempre in modalità interattiva con la riga:

psutil.cpu_percent(interval=None, percpu=True)

seguita da Invio potrebbe mostrarci un output del tipo 17.0, 21.6, laddove il primo elemento della lista si riferisce alla prima CPU (o al primo core, in presenza di un multicore) e, ovviamente, il secondo termine alla seconda CPU (o core). In presenza di 4 core avremo una lista con 4 valori.
Ora che sappiamo come ottenere i dati dobbiamo risolvere lo step successivo: come trasmetterli alla porta USB affinché possano essere ricevuti dal microcontrollore? Anche per questa funzione Python ci mette a disposizione diversi moduli per l'estensione della porta seriale ad esempio pyserial. Al solito, per poterlo utilizzare è d'obbligo installare il pacchetto corrispondente python3-pyserial. In allegato nel nostro archivio, il sorgente PC-meter.py con tutti i commenti complementari a queste pagine (quindi da integrare nella lettura) e da lanciare con il comando:

python3 PC-meter.py --port=/dev/ttyUSB0

La porta /dev/ttyUSB0 è quella di default quando viene collegata la scheda Arduino: va da sé che se abbiamo altri dispositivi collegati potrebbe essere differente. In questi casi, per capire quale sia o ci affidiamo all'IDE Arduino che elenca la porta disponibile alla voce Porta seriale del menu Strumenti oppure, prima di collegare la scheda, diamo il comando dmesg. A questo punto colleghiamo la scheda (sia essa la Arduino o una breakout board qualora si volesse optare per una realizzazione custom) e subito dopo di nuovo il comando dmesg. Dovremmo vedere diverse righe aggiuntive del tipo:


usb 5-1: New USB device found, idVendor=0403, idProduct=6001
ftdi_sio 5-1:1.0: FTDI USB Serial Device converter detected
usb 5-1: Detected FT232RL
usb 5-1: FTDI USB Serial Device converter now attached to ttyUSB0


laddove nelle prime viene evidenziato Vendor e Product, viene caricato il modulo ftdi_sio (modinfo ftdi_sio) per far funzionare la nuova periferica con chip FT232RL, quindi agganciata al device file che nello specifico è /dev/ttyUSB0 (ultima riga). Il programma presentato, sia esso il sorgente Python o lo sketch Arduino, sono programmi di "principio" adatti al prototipo realizzato e che vuole essere solo uno spunto per mettere su delle proprie idee. Niente e nessuno vieta di modificarli a seconda delle proprie necessità così come estendere l'hardware a seconda dei propri obiettivi in funzione, anche, delle informazioni che vogliamo estrarre con psutil. Va da sé che occorrerà modificare in maniera congruente anche il firmware al fine di elaborare e visualizzare correttamente i nuovi dati che provengono dal collegamento USB: ad esempio, anche realizzando una visualizzazione scorrevole delle righe sul display.

Lo schema pratico
Dopo questa lunga introduzione, che ha richiamato un indispensabile insieme di concetti da dobbiamo padroneggiarne almeno un minimo, entriamo nel vivo del progetto. Avviamo il software Fritzing e carichiamo il file allegato PC-meter.fzz presente nel nostro archivio.


Fig. A - Programma Fritzing: vista Breadboard del primo prototipo

Sia dalla vista Breadboard che da quella Schema possiamo prendere nota dei valori dei componenti. Nella vista Breadboard è sufficiente cliccare su ognuno di essi e nel pannello Inspector in basso a destra di Fritzing leggerne il valore. Per lo strumento analogico è stata utilizzata una libreria scaricata ad-hoc che troviamo sempre in allegato, il file Ampmeter.fzpz.
Prima di procedere al cablaggio del circuito è opportuno fare un nuovo pit stop al fine di introdurre un concetto che permetta di capire come un dispositivo digitale possa creare un'uscita analogica, al fine di pilotare uno strumento analogico quale un classico milliamperometro e come questo possa mostrare, ad esempio, la percentuale d'uso della RAM.

La tecnica PWM
Acronimo di Pulse Width Modulation, la PWM è un tipo di modulazione nella quale a variare è, come riportato nell'acronimo, la larghezza (durata) degli impulsi mentre il periodo (ovvero la frequenza) e l'altezza (il valore) sono mantenuti costanti. Il microcontrollore ATMega328P-PU, così come molti altri modelli anche non Atmel, può abilitare questa modalità di uscita. Ne consegue che la scheda Arduino Uno, ma non solo, ha la possibilità di sfruttare questa funzione attraverso i pin 3, 5, 6, 9, 10 e 11 del connettore delle uscite digitali (i pin che hanno questa funzione sono indicati dal simbolo ~, una tilde).


Fig. B - Circuito di test per milliamperometri

Il microcontrollore in uso presenta 3 timer interni due dei quali, Timer0 e Timer2 a 8 bit e Timer1 a 16bit. Senza entrare nei dettagli implementativi del μC, che esulerebbe da questo contesto e per i quali rimandiamo al datasheet completo allegato, prendendo come riferimento un timer a 8 bit quello che accade è quanto segue.

Poiché 8 bit corrispondono a 256 valori (2^8=256), quindi da 0 a 255, allora il timer conta i tick fino al suo valore massimo di 255 oltre il quale va in overflow e ricomincia di nuovo da zero. Unitamente a questo timer/contatore esiste un registro comparatore che può contenere anch'esso valori compresi da 0 a 255. Mentre il timer/contatore procede nel suo lavoro ripetendo infinite volte il conteggio 0-255, il registro comparatore è possibile programmarlo con un dato valore. In questo modo quando il contatore raggiunge il valore impostato nel registro comparatore accade un qualche cosa, ad esempio per i pin di uscita un cambio di stato da alto a basso o viceversa a seconda della implementazione, che in questa sede non interessa. Ipotizziamo che fino a quando il contatore presenti un valore minore rispetto al registro comparatore lo stato d'uscita rimanga alto. Allora se andiamo ad impostare via software il valore del registro di comparazione a 128, il pin d'uscita rimane alto per il 50 per cento del tempo che il contatore impiega a contare da 0 a 255 e per il rimanente 50 per cento (conteggio del timer da 129 a 255) il pin è portato al livello logico basso. Questo rapporto pieno-vuoto è noto tecnicamente con il nome di duty-cycle, viene espresso in termini percentuali e si indica in genere con D. Ad esempio avere un duty-cycle del 25 per cento vuol dire che per il 25 per cento del tempo l'uscita è alta (livello logico 1, ovvero 5 volt) mentre per il rimanente 75 per cento l'uscita è portata al livello logico basso (0V). A quanto deve essere impostato il registro di comparazione per avere un duty-cycle del 25 per cento? Ad un valore pari a 64. Infatti 64 è il 25 per cento di 256. Nel passaggio del duty-cycle dal 50 per cento al 25 per cento varia anche il valore medio della tensione di uscita che, in presenza di un'alimentazione pari a 5V, è rispettivamente di 2,5V e 1,25V. In sostanza, nella nostra ipotesi, un valore vicino allo 0 memorizzato nel registro di comparazione vuol dire tensione d'uscita prossima allo zero e viceversa.

Una prima prova
Detto ciò usciamo dalla tediosa, ma a volte indispensabile, teoria e vediamo il tutto da un punto di vista pratico. Prima di iniziare il cablaggio complessivo visibile nella Fig. A guardiamo il semplice circuito di Fig. B per capire al meglio il funzionamento della modulazione PWM: è caratterizzato da due resistenze di cui una variabile (trimmer o potenziometro), collegata a uno strumento analogico da un lato, e l'altra resistenza collegata al pin 15 del microcontrollore e l'altro terminale al rimanente del potenziometro/trimmer.

L'obiettivo è quello di far oscillare l'ago dello strumento analogico tra i due estremi della scala utilizzando un'uscita PWM del μC. La prova la si può fare anche con un tester analogico commutato sulla portata 1mA, 500μA o 5mA fondo scala purché non si vada oltre poiché le uscite del μC hanno un limite in corrente intorno alla decina di mA. Nel nostro caso si aveva a disposizione uno strumento di classe 1.5 da 100μA (microampere) fondo scala. Cabliamo il circuito secondo il semplice schema di Fig. B quindi scriviamo nell'IDE Arduino le righe di codice riportate più avanti che provvederemo poi a caricare nel μC. Per poter caricare lo sketch, qualora optassimo per un μC stand-alone, necessiteremo di cablare anche tutto il circuito d'ingresso a cui fa capo la breakout board con chip FTDI nonché il quarzo con i due condensatori. Prima di riportare il codice, un'ulteriore nota: attenzione se utilizziamo un μC stand-alone poiché, lo ricordiamo, non c'è corrispondenza diretta tra la numerazione sui connettori della scheda Arduino e i piedini del μC (verificarlo con lo schema allegato della Arduino Uno). Infatti le uscite PWM per la scheda ai pin 3, 5, 6, 9, 10 e 11 corrispondono nel μC rispettivamente ai pin 5, 11, 12, 15, 16 e 17. Detto ciò, se vogliamo fare riferimento all'uscita 9 sul connettore, dobbiamo considerare il pin 15 sul μC stand-alone. Analogo discorso per le altre corrispondenze. Sulla vista Breadboard di Fritzing, una volta caricato il file allegato, spostando il cursore sul piedino 15 del μC, apparirà una piccola pop-up con la scritta D9 (PWM): pin 15: i primi due caratteri riguardano proprio il connettore sulla scheda Arduino seguito dalla funzione e dal corrispondente piedino del μC. Nel firmware, poiché stiamo utilizzando l'IDE Arduino, dobbiamo considerare sempre il pin corrispondente della scheda e non il numero del pin del μC.


int valore=1;
int uscita=0;
void setup()
{
pinMode(9, OUTPUT);
}
void loop()
{
uscita+=valore;
if (uscita == 255 || uscita == 0)
indice=-indice;
analogWrite (9, uscita);
delay (20);
}


Osserviamo, di nuovo, seppur stiamo collegando l'uscita al pin 15 del μC (circuito di Fig. B) nel codice riportiamo il 9 del connettore. Ripetiamo questo concetto per evitare di commettere grossolani errori di collegamento che potrebbero portare al danneggiamento delle porte del μC. Con questo semplice sketch possiamo testare lo strumento analogico in nostro possesso: la resistenza variabile è necessaria perché non sappiamo a priori il fondo scala dello strumento di ognuno che vorrà provare il circuito. Prima di dare tensione ricordiamo sempre di ruotare il cursore del trimmer/potenziometro verso massa, in questo modo eviteremo che l'ago possa sbattere violentemente a fondo scala danneggiandosi. Lanciato il firmware inizieremo a ruotare lentamente il cursore fino a far coincidere l'ago sul fondo scala quando il duty-cycle sarà il 100 per cento. Nella figura seguente possiamo osservare l'andamento all'oscilloscopio in due diversi istanti dell'uscita del pin 15.


La schermata in alto mostra un duty-cycle del 10 per cento, in basso del 70 per cento

Il prototipo
A questo punto possiamo cablare il circuito di Fig. A, caricare lo sketch e lanciare il programma Python secondo quanto riportato in precedenza per avere un risultato visibile nella figura seguente.


Prima versione in funzione del nostro "PC meter"
6 Commenti alla Notizia PI Guide/ Monitorare il PC con Arduino
Ordina