Un oggetto di tipo ProcessMonitor non è altro che un monitor di processi. Per poter funzionare il codice Java deve caricare una DLL appropriata (ad esempio ps.dll in Windows, o libps.so nei sistemi UNIX). Se tale DLL non viene trovata, ad esempio perchè è stata posizionata in un percorso non corretto, verrà lanciata un'eccezione di avvertimento.
Una volta creato, il monitor è pronto a raccogliere informazioni su processi, thread e traffico di rete.
Il meccanismo utilizzato è quello del polling, cioè le misurazioni vanno effettuate a intervalli regolari, passando l'intervallo di campionamento come parametro.
Ad esempio le istruzioni:
permettono di raccogliere statistiche sulla rete ogni 5 secondi.
Analogamente le istruzioni:
ProcessMonitor mon;
NetworkInfo info;
mon=new ProcessMonitor();
for(;;)
{
info=mon.getNetworkInfo(5000);
System.out.println("in: "+info.tcp_seg_in+" out: "+info.tcp_seg_out);
}
mostrano per tutti i processi il consumo di cpu, in percentuale, negli ultimi 7 secondi.
ProcessMonitor mon;
ProcessInfo[] array;
mon=new ProcessMonitor();
array=mon.getNetworkInfo(7000);
for(i=0;i < array.length;i++)
System.out.println("Process: "+array[i].name+" cpu%: "+array[i].cpu);
Capito il meccanismo, la cosa interessante è osservare quali misure siano disponibili e quale sia la loro semantica.
Per quanto riguarda la rete, le misure sono disponibili solo in forma aggregata (non è cioè possibile ottenere delle statistiche "per-processo"). La tabella mostra il contatore di interesse e il campo dell'oggetto NetworkInfo ove è disponibile.
misura | campo di NetworkInfo |
---|---|
Numero di pacchetti IP trasmessi | int ip_pack_out |
Numero totale di pacchetti IP ricevuti | int ip_pack_in |
Numero di pacchetti IP errati ricevuti | int ip_pack_err |
Numero di connessioni TCP attive | int tcp_conn |
Numero di segmenti TCP trasmessi | int tcp_seg_out |
Numero di segmenti TCP ricevuti | int tcp_seg_in |
Numero di pacchetti UDP trasmessi | int udp_pack_out |
Numero totale di pacchetti UDP ricevuti | int udp_pack_in |
Numero di pacchetti UDP errati ricevuti | int udp_pack_err |
Per quanto riguarda i processi, le informazioni sono restituite mediante un array di oggetti ProcessInfo, ognuno riferito ad un ben preciso processo di sistema, identificato univocamente dal PID (Process IDentifier), e meglio riconosciuto dal suo nome. La tabella mostra il contatore di interesse e il campo dell'oggetto ProcessInfo ove è disponibile.
misura | campo di ProcessInfo |
---|---|
il PID | int pid |
il nome (generalmente equivale alla riga di comando | String name |
Il tempo di CPU, cumulativo, consumato dal processo dal suo inizio ad ora, in millisecondi | long time |
Il tempo di CPU, in percentuale, consumato dal processo nell'intervallo di misurazione. Il 100% è riferito alla somma dei tempi di tutti i processi | float cpu |
La memoria fisica allocata per questo processo (detta anche working set), in bytes | long phys_mem |
La memoria virtuale (o image o spazio di indirizzamento), in bytes | long virt_mem |
Un array i cui elementi contengono informazioni sui thread componenti | ThreadInfo[] thread |
L'i-esimo elemento dell'array thread sarà strutturato, a sua volta, come segue:
misura | campo di ThreadInfo |
---|---|
il TID | int tid |
Il tempo di CPU, cumulativo, consumato dal thread dal suo inizio ad ora, in millisecondi | long time |
Il tempo di CPU, in percentuale, consumato dal thread nell'intervallo di misurazione. Il 100% è riferito alla somma dei tempi di tutti i processi | float cpu |
Queste non sono le uniche caratteristiche della classe ProcessMonitor. Per una trattazione più esaustiva si rimanda alla guida generata da javadoc.
Un oggetto di tipo JvmMonitor è un monitor di thread Java. Per essere correttamente inizializzato, deve essere caricata la DLL appropriata già in fase di startup. Per fare ciò bisogna utilizzare il comando
quando si lancia l'applicazione. Se non si utilizza l'opzione -Xrunjvmmonitor la DLL viene caricata comunque in un secondo tempo, ma non si possono più eseguire alcune operazioni che consentono la corretta inizializzazione del monitor, cosicchè alcuni contatori possono rimanere a zero.
java -Xrunjvmmonitor NomeClasse
Il funzionamento del monitor è continuativo, nel senso che esso tiene aggiornato il suo database interno con lo stato della JVM man mano che viene interpretato il bytecode. La lettura dei dati può essere fatta in qualsiasi momento, e rispecchia la situazione attuale.
Ad esempio le seguenti istruzioni:
fotografano lo stato attuale della JVM fornendo, per ciascun thread attivo, il numero di classi da esso caricate.
JvmMonitor mon;
ThreadStat[] stat;
mon=new JvmMonitor();
stat=mon.getThreadStat();
for(i=0;i < stat.length;i++)
System.out.println("Thread: "+stat[i].thread.getName()+" #classes: "+stat[i].num_obj);
A questo punto è interessante conoscere quali misure si possono ottenere dal monitor. La tabella mostra il contatore di interesse e il campo dell'oggetto ThreadStat ove è disponibile.
misura | campo di ThreadStat |
---|---|
Il riferimento all'oggetto java.lang.Thread, che identifica il thread Java. | Thread thread |
Il numero di classi caricate, dalla creazione del thread | int class_num |
Il numero di oggetti creati, dalla creazione del thread. Togliere da questo valore gli oggetti rilasciati dal Garbage Collector si è rivelato troppo oneroso e fonte di instabilità | int obj_num |
La memoria occupata dagli oggetti. Questo valore è poco significativo, perchè non tiene conto della memoria rilasciata dal Garbage Collector | int obj_size |
Il numero di metodi chiamati, dalla creazione del thread | int meth_num |
Il numero di letture da file, dalla creazione del thread. Una lettura riguarda un numero imprecisato di bytes | int file_in |
Il numero di scritture su file, dalla creazione del thread. Una scrittura riguarda un numero imprecisato di bytes | int file_out |
Il numero di letture da socket TCP, dalla creazione del thread. Una lettura riguarda da 1 a 1024 bytes | int tcp_in |
Il numero di scritture su socket TCP, dalla creazione del thread. Una scrittura riguarda da 1 a 1024 bytes | int tcp_out |
Il numero di pacchetti UDP ricevuti, dalla creazione del thread. La dimensione del pacchetto è sconosciuta | int udp_in |
Il numero di pacchetti UDP inviati, dalla creazione del thread. La dimensione del pacchetto è sconosciuta | int udp_out |
Il numero di attese su sezioni di codice synchronized, dalla creazione del thread | int monitor_num |
Il tempo di CPU consumato dal thread, dalla sua creazione, in millisecondi. Attualmente questo dato è disponibile solo sotto Windows NT, mentre negli altri casi viene restituito il valore zero | long time |
Il tempo di CPU, in percentuale, consumato dal thread, relativo alla precedente chiamata alla funzione getThreadStat. Il 100% è riferito alla somma dei tempi di tutti i thread Java monitorati. Attualmente questo dato è disponibile solo sotto Windows NT, mentre negli altri casi viene restituito il valore zero | float cpu |
Bisogna ricordare che i precedenti contatori devono essere considerati con la giusta semantica.
Ad esempio bisogna ricordare che gli oggetti sono creati da un thread, ma possono benissimo essere passati ad altri thread; un oggetto non ha infatti un thread proprietario, ed è quindi sbagliato stabilire che un thread possa usare solo gli oggetti da lui stesso creati; molto spesso è infatti vero il contrario. Quindi è inesatto dire che il thread X usa 75K di memoria per i suoi oggetti, a meno di forti ipotesi sul tipo di applicazione. I contatori sugli oggetti sono stati teatro di forti dubbi in fase di sviluppo del monitor; la scelta attuale è prevalsa più che altro per semplificazoioni implementative.
Allo stesso modo non è detto che le operazioni I/O vengano dal thread che ci si aspetta. Ad esempio, se si sta lavorando con l'interfaccia grafica, si noterà che quasi tutte le operazioni di I/O sono eseguite dal thread AWT-Event-Queue, perchè è effettivamente a questo che tali operazioni sono delegate. La parola d'ordine è dunque ATTENZIONE: meglio fare delle prove per assicurarsi che i valori restituiti dal monitor siano quelli attesi.
Queste non sono le uniche caratteristiche della classe JvmMonitor. Per una trattazione più esaustiva si rimanda alla guida generata da javadoc.
Il seguente è un'esempio di applicazione che utilizza la classe res.ProcessMonitor per monitorare i processi di Windows NT. L'idea è solo quella di presentare, sfruttando l'"effetto" dell'interfaccia grafica, le potenzialità del pacchetto.