Profilage de l'utilisation du processeur à partir d'une application Java

8 novembre 2002

Q: Comment déterminez-vous l'utilisation du processeur en Java?

R: Voici donc les bonnes et les mauvaises nouvelles. La mauvaise nouvelle est que l'interrogation par programmation de l'utilisation du processeur est impossible avec Java pur. Il n'y a tout simplement pas d'API pour cela. Une alternative suggérée pourrait être utilisée Runtime.exec()pour déterminer l'ID de processus (PID) de la machine virtuelle Java, appeler une commande externe, spécifique à la plate-forme ps, comme , et analyser sa sortie pour le PID d'intérêt. Mais cette approche est au mieux fragile.

La bonne nouvelle, cependant, est qu'une solution fiable peut être obtenue en sortant de Java et en écrivant quelques lignes de code C qui s'intègrent à l'application Java via Java Native Interface (JNI). Je montre ci-dessous à quel point il est facile de créer une bibliothèque JNI simple pour la plate-forme Win32. La section Ressources contient un lien vers la bibliothèque que vous pouvez personnaliser selon vos propres besoins et portez vers d'autres plates-formes.

En général, JNI est quelque peu complexe à utiliser. Cependant, lorsque vous appelez dans une seule direction - de Java au code natif - et que vous communiquez à l'aide de types de données primitifs, les choses restent simples. Il existe de nombreuses bonnes références (voir Ressources) sur JNI, donc je ne propose pas de tutoriel JNI ici; Je décris simplement mes étapes de mise en œuvre.

Je commence par créer une classe com.vladium.utils.SystemInformationqui déclare une méthode native, qui renvoie le nombre de millisecondes de temps processeur utilisé jusqu'à présent par le processus actuel:

 public statique natif long getProcessCPUTime (); 

J'utilise l'outil javah du JDK pour produire l'en-tête C suivant pour ma future implémentation native:

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

Sur la plupart des plates-formes Win32, cette méthode peut être implémentée à l'aide de l' GetProcessTimes()appel système et se compose littéralement de trois lignes de code 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)); }

Cette méthode ajoute le temps processeur passé à exécuter le noyau et le code utilisateur pour le compte du processus actuel, le normalise par le nombre de processeurs et convertit le résultat en millisecondes. Il fileTimeToInt64()s'agit d'une fonction d'assistance qui convertit la FILETIMEstructure en un entier 64 bits s_currentProcesset s_numberOfProcessorssont des variables globales qui peuvent être initialisées de manière pratique dans une méthode JNI appelée une fois lorsque la JVM charge la bibliothèque native:

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

Notez que si vous implémentez getProcessCPUTime()sur une plate-forme Unix, vous utiliserez probablement l' getrusageappel système comme point de départ.

Pour revenir à Java, le chargement de la bibliothèque native ( silib.dllsur Win32) se fait au mieux via l'initialiseur statique dans la SystemInformationclasse:

private static final String SILIB = "silib"; static {essayez {System.loadLibrary (SILIB); } catch (UnsatisfiedLinkError e) {System.out.println ("lib native '" + SILIB + "' introuvable dans 'java.library.path':" + System.getProperty ("java.library.path")); jeter e; // relancer}}

Notez que getProcessCPUTime()renvoie le temps CPU utilisé depuis la création du processus JVM. En soi, ces données ne sont pas particulièrement utiles pour le profilage. J'ai besoin de plus de méthodes Java utilitaires pour enregistrer des instantanés de données à différents moments et signaler l'utilisation du processeur entre deux moments:

classe finale publique statique CPUUsageSnapshot {private CPUUsageSnapshot (long time, long CPUTime) {m_time = time; m_CPUTime = CPUTime; } public final long m_time, m_CPUTime; } // fin de la classe imbriquée 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); }

L '"API du moniteur CPU" est presque prête à être utilisée! Pour finir, je crée une classe de threads singleton CPUUsageThread, qui prend automatiquement des instantanés de données à intervalles réguliers (0,5 seconde par défaut) et les signale à un ensemble d'écouteurs d'événements d'utilisation du processeur (le modèle Observer familier). La CPUmonclasse est un écouteur de démonstration qui imprime simplement l'utilisation du processeur sur System.out:

public static void main (String [] args) jette Exception {if (args.length == 0) throw new IllegalArgumentException ("usage: CPUmon"); CPUUsageThread monitor = CPUUsageThread.getCPUThreadUsageThread (); CPUmon _this = nouveau CPUmon (); Classe app = Class.forName (args [0]); Méthode 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, nouvel objet [] {appargs}); }

De plus, CPUmon.main()"encapsule" une autre classe principale Java dans le seul but de démarrer CPUUsageThreadavant de lancer l'application d'origine.

En guise de démonstration, j'ai couru CPUmonavec la démo SwingSet2 Swing du JDK 1.3.1 (n'oubliez pas d'installer silib.dlldans un emplacement couvert par la PATHvariable d'environnement du système d'exploitation ou la java.library.pathpropriété Java):

> java -Djava.library.path =. -cp silib.jar; (mon répertoire d'installation JDK) \ demo \ jfc \ SwingSet2 \ SwingSet2.jar CPUmon SwingSet2 [PID: 339] Utilisation du processeur: 46,8% [PID: 339] Utilisation du processeur: 51,4% [PID: 339] CPU utilisation: 54,8% (lors du chargement, la démo utilise presque 100% de l'un des deux processeurs de ma machine) ... [PID: 339] Utilisation du processeur: 46,8% [PID: 339] Utilisation du processeur: 0% [PID: 339] Utilisation du processeur: 0% (la démo a fini de charger tous ses panneaux et est presque inactive) ... [PID: 339] Utilisation du processeur: 100% [PID: 339] Utilisation du processeur: 98,4% [PID: 339] CPU utilisation: 97% (je suis passé au panneau ColorChooserDemo qui a exécuté une animation gourmande en CPU qui utilisait mes deux processeurs) ... [PID: 339] Utilisation du processeur: 81,4% [PID: 339] Utilisation du processeur: 50% [PID : 339] Utilisation du processeur: 50% (j'ai utilisé le Gestionnaire des tâches de Windows NT pour ajuster l'affinité du processeur pour que le processus "java" utilise un seul processeur) ...

Bien sûr, je peux regarder les mêmes numéros d'utilisation via le gestionnaire de tâches, mais le fait est que j'ai maintenant un moyen programmatique d'enregistrer les mêmes données. Il sera utile pour les tests de longue durée et les diagnostics des applications serveur. La bibliothèque complète (disponible dans Ressources) ajoute quelques autres méthodes natives utiles, dont une pour obtenir le processus PID (pour l'intégration avec des outils externes).

Vladimir Roubtsov a programmé dans une variété de langages pendant plus de 12 ans, y compris Java depuis 1995. Actuellement, il développe des logiciels d'entreprise en tant que développeur senior pour Trilogy à Austin, Texas. Lors du codage pour le plaisir, Vladimir développe des outils logiciels basés sur le code d'octet Java ou l'instrumentation de code source.

En savoir plus sur ce sujet

  • Téléchargez la bibliothèque complète qui accompagne cet article

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

  • Spécification JNI et tutoriels

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

  • Pour un bon aperçu de JNI, voir le développement de composants de Stuart Dabbs Halloway pour la plate-forme Java (Addison-Wesley, décembre 2001; ISBN0201753065)

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

  • Dans «Java Tip 92Use the JVM Profiler Interface for Precurate Timing», Jesper Gortz explore une autre direction pour le profilage de l'utilisation du processeur. (Cependant, l'utilisation de JVMPI nécessite plus de travail pour calculer l'utilisation du processeur pour l'ensemble du processus par rapport à la solution de cet article)

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

  • Consultez la page d'index des questions-réponses Java pour consulter le catalogue complet des questions-réponses

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

  • Pour plus de 100 conseils Java perspicaces, visitez le « JavaWorld de Java Conseils page d' index

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

  • Parcourir le noyau Java section « JavaWorld Index par sujet s

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

  • Obtenez plus de réponses à vos questions dans notre discussion Java pour débutants

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

  • Inscrivez-vous aux newsletters hebdomadaires gratuites de JavaWorld

    //www.javaworld.com/subscribe

  • Vous trouverez une multitude d'articles liés à l'informatique provenant de nos publications sœurs sur .net

Cette histoire, "Profilage de l'utilisation du processeur à partir d'une application Java" a été publiée à l'origine par JavaWorld.