Profilazione dell'utilizzo della CPU dall'interno di un'applicazione Java

8 novembre 2002

D: Come si determina l'utilizzo della CPU in Java?

A: Quindi, ecco la buona e la cattiva notizia. La cattiva notizia è che l'esecuzione di query a livello di codice per l'utilizzo della CPU è impossibile utilizzando Java puro. Semplicemente non esiste un'API per questo. Un'alternativa suggerita potrebbe essere utilizzata Runtime.exec()per determinare l'ID di processo (PID) della JVM, chiamare un comando esterno specifico della piattaforma come pse analizzarne l'output per il PID di interesse. Ma questo approccio è, nella migliore delle ipotesi, fragile.

La buona notizia, tuttavia, è che una soluzione affidabile può essere ottenuta uscendo da Java e scrivendo alcune righe di codice C che si integrano con l'applicazione Java tramite Java Native Interface (JNI). Di seguito mostro quanto sia facile creare una semplice libreria JNI per la piattaforma Win32. La sezione Risorse contiene un collegamento alla libreria che puoi personalizzare in base alle tue esigenze e portarla su altre piattaforme.

In generale, JNI è piuttosto complesso da usare. Tuttavia, quando si chiama in una sola direzione, da Java al codice nativo, e si comunica utilizzando tipi di dati primitivi, le cose rimangono semplici. Ci sono molti buoni riferimenti (vedi Risorse) su JNI, quindi non fornisco un tutorial su JNI qui; Mi limito a delineare le mie fasi di implementazione.

Comincio creando una classe com.vladium.utils.SystemInformationche dichiara un metodo nativo, che restituisce il numero di millisecondi di tempo della CPU utilizzati finora dal processo corrente:

 getProcessCPUTime () nativo statico pubblico lungo; 

Uso lo strumento javah dal JDK per produrre la seguente intestazione C per la mia futura implementazione nativa:

JNIEXPORT jlong ​​JNICALL Java_com_vladium_utils_SystemInformation_getProcessCPUTime (JNIEnv * env, jclass cls) 

Sulla maggior parte delle piattaforme Win32, questo metodo può essere implementato utilizzando la GetProcessTimes()chiamata di sistema ed è letteralmente composto da tre righe di codice C:

JNIEXPORT jlong ​​JNICALL Java_com_vladium_utils_SystemInformation_getProcessCPUTime (JNIEnv * env, jclass cls) {FILETIME creationTime, exitTime, kernelTime, userTime; GetProcessTimes (s_currentProcess, & creationTime, & exitTime, & kernelTime, & userTime); return (jlong) ((fileTimeToInt64 (& kernelTime) + fileTimeToInt64 (& userTime)) / (s_numberOfProcessors * 10000)); }

Questo metodo aggiunge il tempo di CPU impiegato per eseguire il kernel e il codice utente per conto del processo corrente, lo normalizza in base al numero di processori e converte il risultato in millisecondi. La fileTimeToInt64()è una funzione di supporto che converte la FILETIMEstruttura di un intero a 64 bit, ed s_currentProcesse s_numberOfProcessorssono variabili globali che possono essere convenientemente inizializzati in un metodo JNI che è chiamato una volta quando il JVM carica la libreria nativa:

static HANDLE s_currentProcess; static int s_numberOfProcessors; JNIEXPORT jint JNICALL JNI_OnLoad (JavaVM * vm, void * reserved) {SYSTEM_INFO systemInfo; s_currentProcess = GetCurrentProcess (); GetSystemInfo (& systemInfo); s_numberOfProcessors = systemInfo.dwNumberOfProcessors; return JNI_VERSION_1_2; }

Nota che se esegui l'implementazione getProcessCPUTime()su una piattaforma Unix, probabilmente utilizzerai la getrusagechiamata di sistema come punto di partenza.

Tornando a Java, il caricamento della libreria nativa ( silib.dllsu Win32) si ottiene al meglio tramite l'inizializzatore statico nella SystemInformationclasse:

finale statico privato String SILIB = "silib"; static {try {System.loadLibrary (SILIB); } catch (UnsatisfiedLinkError e) {System.out.println ("native lib '" + SILIB + "' non trovato in 'java.library.path':" + System.getProperty ("java.library.path")); lanciare e; // rilancia}}

Notare che getProcessCPUTime()restituisce il tempo della CPU utilizzato dalla creazione del processo JVM. Di per sé, questi dati non sono particolarmente utili per la profilazione. Ho bisogno di più metodi Java di utilità per registrare istantanee dei dati in momenti diversi e segnalare l'utilizzo della CPU tra due punti temporali:

public static final class CPUUsageSnapshot {private CPUUsageSnapshot (long time, long CPUTime) {m_time = time; m_CPUTime = CPUTime; } public final long m_time, m_CPUTime; } // fine della classe annidata public static CPUUsageSnapshot makeCPUUsageSnapshot () {return new CPUUsageSnapshot (System.currentTimeMillis (), getProcessCPUTime ()); } public static double getProcessCPUUsage (CPUUsageSnapshot start, CPUUsageSnapshot end) {return ((double) (end.m_CPUTime - start.m_CPUTime)) / (end.m_time - start.m_time); }

La "CPU monitor API" è quasi pronta per l'uso! Come tocco finale, creo una classe di thread singleton CPUUsageThread, che acquisisce automaticamente snapshot dei dati a intervalli regolari (0,5 secondi per impostazione predefinita) e li riporta a un set di listener di eventi di utilizzo della CPU (il familiare pattern Observer). La CPUmonclasse è un listener demo che stampa semplicemente l'utilizzo della CPU per System.out:

public static void main (String [] args) genera un'eccezione {if (args.length == 0) genera una nuova IllegalArgumentException ("usage: CPUmon"); Monitor CPUUsageThread = CPUUsageThread.getCPUThreadUsageThread (); CPUmon _this = nuovo CPUmon (); App di classe = Class.forName (args [0]); Metodo appmain = app.getMethod ("main", new Class [] {String []. Class}); String [] appargs = new String [args.length - 1]; System.arraycopy (args, 1, appargs, 0, appargs.length); monitor.addUsageEventListener (_this); monitor.start (); appmain.invoke (null, nuovo oggetto [] {appargs}); }

Inoltre, CPUmon.main()"avvolge" un'altra classe principale Java con l'unico scopo di avviare CPUUsageThreadprima di lanciare l'applicazione originale.

Come dimostrazione, ho eseguito CPUmonla demo SwingSet2 Swing da JDK 1.3.1 (non dimenticare di installare silib.dllin una posizione coperta dalla PATHvariabile di ambiente del sistema operativo o dalla java.library.pathproprietà Java):

> java -Djava.library.path =. -cp silib.jar; (la mia directory di installazione JDK) \ demo \ jfc \ SwingSet2 \ SwingSet2.jar CPUmon SwingSet2 [PID: 339] Utilizzo CPU: 46,8% [PID: 339] Utilizzo CPU: 51,4% [PID: 339] CPU utilizzo: 54,8% (durante il caricamento, la demo utilizza quasi il 100% di una delle due CPU sulla mia macchina) ... [PID: 339] utilizzo CPU: 46,8% [PID: 339] utilizzo CPU: 0% [PID: 339] Utilizzo CPU: 0% (la demo ha terminato il caricamento di tutti i pannelli ed è per lo più inattiva) ... [PID: 339] Utilizzo CPU: 100% [PID: 339] Utilizzo CPU: 98,4% [PID: 339] CPU utilizzo: 97% (sono passato al pannello ColorChooserDemo che ha eseguito un'animazione ad alta intensità di CPU che utilizzava entrambe le mie CPU) ... [PID: 339] Utilizzo CPU: 81,4% [PID: 339] Utilizzo CPU: 50% [PID : 339] Utilizzo CPU: 50% (ho utilizzato Task Manager di Windows NT per regolare l'affinità della CPU per il processo "java" per utilizzare una singola CPU) ...

Naturalmente, posso guardare gli stessi numeri di utilizzo tramite il task manager, ma il punto qui è che ora ho un modo programmatico per registrare gli stessi dati. Risulterà utile per test di lunga durata e diagnostica delle applicazioni server. La libreria completa (disponibile in Risorse) aggiunge alcuni altri metodi nativi utili, incluso uno per ottenere il PID del processo (per l'integrazione con strumenti esterni).

Vladimir Roubtsov ha programmato in una varietà di lingue per più di 12 anni, incluso Java dal 1995. Attualmente sviluppa software aziendale come sviluppatore senior per Trilogy ad Austin, in Texas. Quando si codifica per divertimento, Vladimir sviluppa strumenti software basati sul codice byte Java o sulla strumentazione del codice sorgente.

Ulteriori informazioni su questo argomento

  • Scarica la libreria completa che accompagna questo articolo

    //images.techhive.com/downloads/idge/imported/article/jvw/2002/11/01-qa-1108-cpu.zip

  • Specifiche e tutorial JNI

    //java.sun.com/j2se/1.4/docs/guide/jni/index.html

  • Per una buona panoramica di JNI, vedere Stuart Dabbs Halloway's Component Development for the Java Platform (Addison-Wesley, December 2001; ISBN0201753065)

    //www.amazon.com/exec/obidos/ASIN/0201753065/javaworld

  • In "Java Tip 92Use the JVM Profiler Interface for Accurate Timing", Jesper Gortz esplora una direzione alternativa per la profilazione dell'utilizzo della CPU. (Tuttavia, l'utilizzo di JVMPI richiede più lavoro per calcolare l'utilizzo della CPU per l'intero processo rispetto alla soluzione di questo articolo)

    //www.javaworld.com/javaworld/javatips/jw-javatip92.html

  • Vedere la pagina dell'indice delle domande e risposte Java per il catalogo completo delle domande e risposte

    //www.javaworld.com/columns/jw-qna-index.shtml

  • Per più di 100 consigli Java penetranti, visita 'JavaWorld s Tips Java pagina indice

    //www.javaworld.com/columns/jw-tips-index.shtml

  • Sfoglia la sezione Core Java dell'Indice degli argomenti di JavaWorld

    //www.javaworld.com/channel_content/jw-core-index.shtml

  • Ottieni più risposte alle tue domande nella nostra discussione per principianti su Java

    //forums.devworld.com/[email protected]@.ee6b804

  • Iscriviti alla newsletter settimanale gratuita di JavaWorld

    //www.javaworld.com/subscribe

  • Troverai una vasta gamma di articoli relativi all'IT tratti dalle nostre pubblicazioni gemelle su .net

Questa storia, "Profilatura dell'utilizzo della CPU dall'interno di un'applicazione Java" è stata originariamente pubblicata da JavaWorld.