Doktor Roland Huss

!! Perl und Java, zwei ungleiche Schwestern

Es war Anfang 2009 als mein Kollege Gerhard, ein ausgewiesener
Nagios-Guru, bei mir anklopfte und mehr oder weniger
verzweifelt nach einer Nagios-Lösung zur Überwachung von
Java-Applikationsservern suchte.
Das Problem mit den bisherigen Lösungen war, dass zwar
Java von Hause aus die exzellenten Monitoring Schnittstelle
JMX mitbringt, diese aber von außerhalb nur über Java selbst
nutzbar ist. Da Nagios-Checks kurzlaufende, externe Prozesse
sind, ist der Wasserkopf, den der Start einer Java Virtual Machine
(JVM) mit sich bringt insbesondere für große Nagios
Installationen mit tausenden zu überwachenden Servern
nicht verkraftbar. Gerhard suchte nach einer einfacheren Lösung,
die auch von Skriptsprachen wie Perl verwendbar ist.
Das war die Geburtsstunde von jmx4perl. Angefangen hat es
mit einem kleinen Java-Servlet (23 Kilobyte), das serverseitig
JMX über die Java-API anspricht und auf der anderen Seite
diese über HTTP exportiert, wobei die Nutzdaten in einem
JSON Format übertragen werden. Dafür bietet Perl Unterstützung
dank der CPAN-Bibliotheken LWP und JSON. Dieser
Agent ist über die Jahre auf 150 Kilobyte gewachsen und inzwischen
auch nicht nur für Perlianer interessant.
Bevor jmx4perl und seine Perl Module anhand von Beispielen
ausführlich vorgestellt werden, noch ein paar Worte zu
JMX, woher es kommt und was es kann.

! JMX

JMX (Java Management Extensions) definiert die Architektur,
die API und die Dienste zur Verwaltung und Überwachung
vom Java Anwendungen. Dabei ist JMX schon eine
sehr alte Spezifikation. In Java werden Standards in den Java
Specification Requests (JSR) festgehalten, die im Rahmen des
Java Community Processes (JCP) erstellt werden. JMX ist definiert
in JSR-3 und hatte seine Geburtsstunde bereits im Jahre
1999. Im Gegensatz zu vielen anderen Java Spezifikationen
(wie z.B. EJB oder JSF) war sie von Beginn an wohl durchdacht
und wird in nahezu unveränderter Form bis heute ausgiebig
genutzt. Seit Java 1.5 ist eine JMX Implementierung
Bestandteil der Java Laufzeitumgebug, so dass sie heute jeder
zeitgemäßen Java Anwendung ohne zusätzlichen Installationsaufwand
zur Verfügung steht.
Mithilfe von JMX haben Java Programme die Möglichkeit
einen Teil ihrer Funktionalität für Managementzwecke zur
Verfügung zu stellen. "Management" heißt hier, dass je nachdem
welche JMX Schnittstellen eine Applikation zur Verfügung
stellt, beispielsweise Komponenten gestoppt und neugestartet,
Teile der Anwendung entfernt oder hinzugefügt
und Konfigurationen zur Laufzeit verändert werden können.
Darüber hinaus können JMX Komponenten Attribute zu
Überwachungszwecken exportieren.
In der JMX Fachsprache heißen diese Komponenten
MBeans. Diese werden innerhalb der Java Virtual Machine
lokal bei einem MBeanServer registriert, der wiederum den
Zugriff auf diese MBeans regelt. Jede MBean hat innerhalb
eines MBeanServers eine eindeutigen ObjectName, über
den sie angesprochen werden kann. Dieser hat das Format
"domain:key=value,key=value,...". Dabei legt die
domain einen Namensraum fest, unter dem sich mehrere
MBeans registrieren können. Die Liste von Key-Value Paaren
zurrt dann den Namen innerhalb dieses Namensraumes
fest. Die Wahl des ObjectNames bleibt der Java Anwendung
überlassen, es gibt aber eine gewisse Namenskonvention,
auf die wir später noch stoßen werden.

Ein Nutzer kann mit MBeans über drei Arten kommunizieren:

• Lesen und Schreiben von Attributen
• Ausführen von Operationen mit Argumenten
• Registrierung für Notifikationen (Events)

Seit Java 1.5 sind in der JVM sogenannte MXBeans vom
Start weg registriert. Das sind MBeans mit festgelegtem
ObjectName, die Zugriff auf verschiedene Aspekte der JVM
erlauben, u.a. die Abfrage des Heap-Speichers, Anzahl der
Threads, Umgebungsvariablen oder auch z.B. die Ausführung
einer Garbage Collection.
Auch viele Applikationsserver wie z.B. Tomcat, JBoss, Weblogic
oder Websphere kommen bereits mit einer breiten
Palette von weiteren vordefinierten MBeans die sich sofort
nutzen lassen. Damit lässt sich ein Vielzahl interessanter
Messgrößen abfragen wie beispielsweise die Anzahl von Requests
pro Minute einer Webanwendung oder die Größe von
Datenbankverbindungs-Pools.
Bislang befinden wir uns noch innerhalb der JVM. Wie
kommt man nun von außerhalb an diese Informationen ?
Dazu hat Sun ebenfalls einen Java-Standard definiert, die
sog. JSR-160 Konnektoren. Unglücklicherweise liegt der Fokus
dieser Spezifikationen auf Java-Clients und nutzt das im letzten
Jahrtausend recht populäre Programmiermodell der Remote
Method Invocation (RMI), bei der der Client über die nahezu
identischen Java-Interfaces auf den MBeanServer und
die MBeans zugreift. Die Idee dahinter ist eine transparente
Remoteness, so dass der Nutzer sich keine Gedanken machen
muss, ob er lokal oder entfernt auf MBeans zugreift. Im Laufe
der Jahre hat sich gezeigt, dass dieser Ansatz im realen Einsatz
viele Tücken hat, so dass er aktuell nicht mehr State-ofthe-
Art ist. Leider ist hier JMX zurückgeblieben, die einzige
standardisierte Möglichkeit, entfernt auf JMX zuzugreifen
bleiben die RMI basierten JSR-160 Konnektoren. Die Spezifikation
JSR-262, JMX auch über WebServices (WS-Management)
zu exportieren ist im Jahre 2008 stecken geblieben.
Für uns Perl-Anwender bedeutet das, dass wir für den JMX
Zugriff mit JSR-160 immer eine Java Virtual Machine starten
müssen. Dies passiert entweder über einen externen Prozess,
bei dem ein (selbst geschriebenes) Java Programm gestartet
wird, das die Ergebnisse z.B. auf die Standardausgabe
schreibt und unser Perl-Programm diese verarbeitet. Oder
aber es wird eine der Perl-Java Integrationsmöglichkeiten
wie Inline::Java eingesetzt. Egal wie, diese Ansätze
sind sehr schwerfällig und verlangen zudem einiges an Java
Kenntnissen.
An dieser Stelle springt nun jmx4perl in die Bresche.

! Jmx4perl als Mittler zweier Welten

Jmx4perl ist eine Lösung für den entfernten JMX-Zugriff jenseits
der JSR-160 Konnektoren. Dazu muss ein Agent auf dem
Zielserver oder einem Proxy-Server installiert werden, der
dann entfernte HTTP Anfragen in lokale JMX Aufruf übersetzt
und die Ergebnisse über HTTP zurückliefert. Die Nutzlast
der Anfragen und Resultate werden in einem JSON-Format
repräsentiert. Dieser Ansatz hat mehrere Vorteile. Zum
einen ist die Kombination HTTP/JSON ein wohl etabliertes
Gespann, das auch ausserhalb der Java Welt eine breite Unterstützung
besitzt. Damit ist es Clients, die in anderen Programmiersprachen
entwickelt sind, ohne großen Aufwand
möglich auf JMX Informationen zuzugreifen. Dadurch, dass
die Anforderung einer lokalen Java Installation wegfällt, ist
diese Lösung auch wesentlich schlanker. Andererseits ist es
dem Agenten aber auch möglich, Funktionen anzubieten, die
in JSR-160 fehlen. Der Jmx4perl Agent bietet beispielsweise
zusätzlich Bulk-Requests, ein verfeinertes Sicherheitsmodell
und noch einiges mehr.
Ein weiterer großer Unterschied zwischen der Kommunikation
über HTTP/JSON im Vergleich zu JSR-160, bei dem im
wesentlichen serialisierte Java-Objekte übertragen werden,
ist, dass sie typlos ist. Das hat Vor- und Nachteile. Der Vorteil
ist, dass auf der Client-Seite keine Typinformationen vorliegen
müssen, was den Installationsaufwand bei komplexen
MBeans weiter reduziert. Der Nachteil ist, dass für die Deserialisierung
der Anwender selber wissen muss, mit welchem
Datentypen er zu tun hat und diesbezüglich keine Unterstützung
von der Laufzeitumgebung erhält.
Für jmx4perl stehen verschiedene Agenten zur Verfügung:

__WAR Agent__
Dieser Agent unterstützt alle Java Applikationsserver, die
einen sogenannten Servlet-Container mitbringen. Dazu
gehören ausgewachsene JEE Server wie JBoss, Weblogic
oder Websphere aber auch reine Servlet-Container wie
Tomcat oder Jetty. Die Installation funktioniert wie bei
jeder anderen Java-Webanwendung auch. Es wird ein WAR
(Web-ARchive) installiert, das dann von außen über eine
bestimmte URL erreichbar ist. Das Deployment funktioniert
typischerweise einfach über das Kopieren der Datei in ein
bestimmtes Verzeichnis, über Kommandozeilen-Tools oder
über eine Weboberfläche.

__OS Gi Agent__
OSGi ist eine Java-Servertechnologie, die einen starken Fokus
auf Modularität legt. Dieser Agent wird in der Form eines
OSGi-Bundles installiert und unterstützt alle gängigen OSGi-
Plattformen (Felix, Equinox, Vertigo).

__Mule Agent__
Mule ist ein weitverbreiteter Open-Source Enterprise Service
Bus (ESB), der eine eigene Schnittstelle für JMX Agenten
anbietet. Diese nutzt dieser jmx4perl-Agent, um sich nahtlos
in den Mule ESB zu integrieren.

__JVM Agent__
Schließlich existiert ein sehr generischer Agent, der die JVM
Agentenschnittstelle nutzt um seine Dienste anzubieten. Diese
Schnittstelle existiert seit Java 5 und wird hauptsächlich
von Java Entwicklungstools wie Profilern genutzt, um in
den Classloading Prozess einzugreifen. Zusätzlich nutzt dieser
Agent den in Sun/Oracle JVM ab Version 6 integrierten
HTTP-Server. Mit diesem Agenten ist es möglich jeder beliebige
Java 6 Applikation zu instrumentieren, z.B. auch Desktop
Applikationen wie Eclipse oder Cloud Anwendungen wie
Hadoop.

Alle Java-Agenten sind Bestandteil von jmx4perl bis Version
0.74. Diese wurden im Oktober 2010 in ein neues Projekt
namens Jolokia ausgelagert. Ab der Version 0.80 greift
jmx4perl somit auf diese Agenten zurück.
Der andere Bestandteil von jmx4perl ist die Perl-Schnittstelle.
Diese kann auf verschiedenen Ebenen genutzt werden.


Auf der obersten Ebene existieren mitgelieferten Kommandozeilenprogramme,
die einen komfortablen Zugriff auf die
Agenten ermöglichen. jmx4perl erhält die Requestparameter
über Kommadozeilen-Optionen, während j4psh einen
interaktiven Zugriff erlaubt. check_jmx4perl ist ein mächtiges
Nagios Plugin, das eine Vielzahl von Konfigurationsmöglichkeiten
kennt.

Diese Tools nutzen die Perl-Module unterhalb von JMX::
Jmx4Perl, die einen programmatischen Zugriff auf den
Agenten realisieren. Diese Module werden im Folgenden anhand
von Beispielen ausführlich vorgestellt.

Schließlich ist es aber auch möglich direkt über HTTP/JSON
Bibliotheken mit dem Agenten zu sprechen, wobei diese
Möglichkeiten natürlich nicht auf Perl beschränkt ist.

! JMX::Jmx4Perl

Zum Ausprobieren der folgenden Beispiele brauchen wir
zunächst einen Java Applikationsserver. Dies kann z.B. ein
einfacher Tomcat sein, auf den der jmx4perl Agent deployed
wird:

• Tomcat 7 herunterladen und installieren - siehe Listing 1.
• Download des j4p.war Agenten - siehe Listing 2.
• Starten des Tomcat:

 $ cd ..
 $ bin/catalina.sh start

• Überprüfung des Agenten im Browser - siehe Listing 3.

Die Perl-Module werden entweder über cpan JMX::
Jmx4Perl automatisch installiert bzw. manuell aus dem
Archiv http://search.cpan.org/~roland/jmx4perl. Für die JMX
Shell j4psh und das Nagios-Plugin check_jmx4perl sollten
die optionalen Module ebenfalls installiert werden.

Listing 1

 $ wget http://www.apache.org/dist/tomcat/tomcat-6/v6.0.29/bin/apache-tomcat-6.0.29.tar.gz
 $ tar zxvf apache-tomcat-6.0.29.tar.gz
 $ cd apache-tomcat-6.0.29

Listing 2

 $ cd webapps
 $ wget http://labs.consol.de/maven/repository/org/jmx4perl/j4p-war/0.74.0/j4p-war-0.74.0.war
 $ mv j4p-war-0.74.0.war j4p.war

Listing 3

 $ wget -q -O - http://localhost:8080/j4p/version
 {"timestamp":1290421423,"status":200,"request":{"type":"version"},
	"value":{"protocol":"4.0","agent":"0.74"}}

Listing 4

 use JMX::Jmx4Perl;
 my $j4p = new JMX::Jmx4Perl(url => "http://localhost:8080/j4p");
 print "Heap-Memory used: ",
	$j4p->get_attribute("java.lang:type=Memory","HeapMemoryUsage","used")," Bytes\n";

Listing 5

# Zähle die alle Anfragen an alle Servlets zusammen:
use JMX::Jmx4Perl;

my $j4p = new JMX::Jmx4Perl(url => "http://localhost:8080/j4p");
my $val_hash = $j4p->get_attribute("*:j2eeType=Servlet,*",undef);
map { $total += $_ } map { $_->{requestCount} } values %$val_hash;
print "Gesamtzahl der Servlet-Requests: ",$total,"\n";

Im ersten Beispiel sehen wir uns die Hauptspeicherauslastung
mit einer einfacher JMX `READ` Operation an - siehe
Listing 4.

Zunächst wird der jmx4perl Client $j4p erzeugt. Er benötigt
als einziges Pflichtargument die URL zu dem jmx4perl
Agenten (http://localhost:8080/j4p). Mit $j4p-
>get_attribute() kann nun ein Attribut ausgelesen
werden. Das erste Argument ist der MBean-Name (java.
lang:type=Memory), das Zweite bezeichnet das Attribut
selbst (HeapMemoryUsage) und das dritte, optionale
Argument spezifiziert einen sogenannten Pfad. Viele JMX
Attribute sind komplexere Datentypen als Strings oder
Zahlen. Auf der Seite des Agenten werden diese in JSONMaps
umgesetzt, wobei die Schlüssel die Namen der inneren
Attribute dieser zusammengesetzten Datentypen sind. Java
Collections werden in Arrays umgesetzt. In diesem Fall
liefert die MBean java.lang:type=Memory für das Attribut
HeapMemoryUsage ein Objekt des Java Typs CompositeData
zurück. Dieses setzt der Client in einen Hash der folgenden
Struktur um:

 {
     committed => 85000192,
     init => 0,
     max => 129957888,
     used => 24647976
 }

Mit einem Pfad kann nun schon auf der Serverseite auf einen
Teil der gesamten Datenstruktur zugegriffen werden. Der
Pfad used in unserem Beispiel führt dazu, dass nur der Wert
dieses Schlüssels zurückgeliefert wird. Ein Pfad kann auch
noch tiefer in die Datenstruktur zeigen. Zum Beispiel liefert
der Pfad loader/classloader/resources/uRLs/2/url
eine URL zurück, die tief in einem komplexen Objekt versteckt
ist. 2 an der vorletzten Stelle wird genutzt, um das
dritte Element eines Arrays in dieser Ebene auszuwählen.
Ohne Pfad wird die gesamte Datenstruktur als Kombination
von Hashes, Arrays und skalaren Werten zurückgegeben.
Eine interessante Variante des obigen Beispiels ist die Nutzung
von Wildcards im MBeans oder die Abfrage von mehr
als einen Attributnamen - siehe Listing 5.
Enthält ein MBean-Name Wildcards, wie sie in der JMX Spezifikation
http://download.oracle.com/javase/6/docs/api/javax/
management/ObjectName.html festgelegt sind, dann ist
der Rückgabewert von get_attribute ein Hash, der die
konkreten MBean-Namen als Schlüssel hat. Die Werte sind
wiederum Hashes, die einen oder mehrere Attributnamen
als Schlüssel und die Attributwerte als Werte besitzen.
Ein Attributname undef bezeichnet alle vorhandenen Attribute
der MBean. Ist der Attributname ein einzelner String,
dann wird dieses Attribut abgefragt. Ist es ein Arrayref von
Strings, dann wird der Wert all dieser Attribute geholt. Wie
man sieht, ist es sehr feingranular möglich, mehrere Attribute
von mehreren MBeans in einem Rutsch zu lesen. Eine
noch flexiblere Möglichkeit bieten Bulk Requests, die weiter
unten vorgestellt werden (siehe Listing 6.)
Zu beachten ist noch, wenn der MBean-Name kein MBean-
Muster ist, dann fällt die erste Ebene mit dem MBean Namen
weg, da dieser ja schon beim Request festgelegt ist.
Analog zum Lesen von Attributen gibt es u.a. noch folgende
Operationen die vom JMX::Jmx4Perl Client zur Verfügung
gestellt werden:

set_attribute($mbean,$attribute,$value,$path)
Damit können Attributwerte gesetzt werden. Die Argumente
bezeichnen den MBean- und Attributnamen, den zu
setzenden Wert und wiederum einen optionalen Pfad. Dabei
bezeichnet der $path Pfad zu dem inneren Attribut dem der
Wert zugewiesen werden soll.

Listing 6

use JMX::Jmx4Perl;
use Data::Dumper;

my $j4p = new JMX::Jmx4Perl(url => "http://localhost:8080/j4p");
print Dumper($j4p->get_attribute("java.lang:*",["Verbose","Uptime"]));
$VAR1 = {
	'java.lang:type=Memory' => { 'Verbose' => 'false' },
	'java.lang:type=ClassLoading' => { 'Verbose' => 'false' },
	'java.lang:type=Runtime' => { 'Uptime' => '1460852'};
	}

Listing 7

use JMX::Jmx4Perl;
use JMX::Jmx4Perl::Request;
use Data::Dumper;
my $j4p = new JMX::Jmx4Perl(url => "http://localhost:8080/j4p");
my $response =
	$j4p->request(new JMX::Jmx4Perl::Request(READ,"java.lang:type=Threading","ThreadCount"));
print Dumper($response);

execute($mbean,$operation,@arguments)
JMX Operationen werden mit execute ausgeführt. Neben
den MBean- und Operationsnamen können ein oder mehrere
Argumente übergeben werden, je nachdem welche Argumente
die Operation benötigt.

search($mbean_pattern)
Bei search können MBean Namen mit einem Muster gesucht
werden. Dass Muster kann die Wildcards * und ? enthalten,
jedoch sind diese nur an bestimmten Stellen erlaubt.

__info()__
Mit info enthält man eine Kurzbeschreibung der Serverumgebung
als String.

__list()__
list liefert Meta-Informationen über alle verfügbaren MBeans.
Der Rückgabewert ist ein Hash, der in der Dokumentation
vom JMX::Jmx4Perl ausführlich beschrieben ist. list
wird z.B. von den Tools jmx4perl und j4psh genutzt um
das Stöbern in der Liste von vorhandenen MBeans zu erleichtern,
um "interessante" MBeans zu finden.

! Eine Stufe tiefer ...

Diese High-Level Methoden bieten einen guten Einstieg
und sind für viele Anwendungsfälle ausreichend. Um
JMX::Jmx4Perl voll auszureizen, müssen wir eine Stufe
tiefer gehen. Jmx4Perl kommuniziert mit dem Agenten
über HTTP GET oder POST Request, und erhält eine HTTP
Antwort zurück. Diese werden über die Perl-Module JMX::
Jmx4Perl::Request und JMX::Jmx4Perl::Response
gekapselt - siehe Listing 7.
Ein JMX::Jmx4Perl::Request hat einen Typ (READ) und
typspezifische Parameter. Der Konstruktor kann Argumente
in verschiedenen Formaten auswerten, mehr dazu in
der Dokumentation zu JMX::Jmx4Perl::Request. Die
Methode $j4p->request() nimmt nun solch ein Request-
Objekt, wandelt es in einen HTTP Request um, kontaktiert
den Agenten und packt die HTTP Antwort in eine JMX::
Jmx4Perl::Response ein. Die JMX::Jmx4Perl::
Response beinhaltet neben dem Ergebnis auch weitere
Informationen, wie den Request selbst, einen Timestamp
und den Ergebnis-Status (Listing 8).
Die status Codes sind an HTTP Ergebniscodes orientiert:
200 wird bei einer erfolgreichen Operation zurückgegeben,
alles andere sind Error-Codes (z.B. 404 wenn die MBean oder
ein Attribut nicht gefunden werden kann).
Neben einzelnen Requests kann die request Methode des
Clients auch eine Liste von Anfragen verarbeiten. In diesem
Fall wird auch ein Liste von JMX::Jmx4Perl::Response
Objekten zurückgegeben. Hier können auch Anfragen mit
verschiedenen Typen gemischt werden (Listing 9).
Das letzte Beispiel (Listing 10) zeigt eine kleine Anwendung,
die bei der Fehlersuche einer Java Anwendung hilfreich sein
kann. Wenn seltsame Dinge in einer Java Applikation passie -
ren, dann fragen die Java Entwickler meist als erstes nach
einem Thread-Dump um die Laufzeitsituation analysieren
zu können. Dieser Thread-Dump sollte zeitnah zum Problem
geholt werden, auch sind mehrere Thread-Dumps kurz
hintereinander sinnvoll um die Dynamik der Applikation zu
verstehen. Dieses Beispiel überprüft periodisch, ob es Probleme
gibt. In diesem Fall wird ein Problem erkannt, wenn
der Hauptspeicher zu mehr als 90% ausgelastet ist oder
wenn Threads gefunden werden, die sich gegenseitig in den
Schwanz beißen (deadlocks). Ist dies der Fall wird via JMX
ein Thread Dump erzeugt und als Perl-Hash in einer Datei abgelegt,
die post-mortem analysiert werden kann. Zu beachten
ist, dass das Skript nur mit Java 6 aufwärts funktioniert,
da erst ab dieser Version die entsprechenden JMX Methoden
zur Verfügung stehen. Optimiert werden kann hier noch die
initiale Abfrage der KO-Kriterien, die dank Bulk-Request auf
einzige HTTP-Abfrage reduziert werden kann. Auch kann
man sich zusätzliche oder alternative Kriterien überlegen,
wie beispielsweise die Anzahl der Sessions einer Webapplikation
oder die Anzahl der offenen Datenbankverbindungen.

! Tools, Tools, Tools

Alles schön und gut, aber was sind denn nun die
"interessanten" MBeans, die so Java-Server typischerweise
im Bauch haben ? Unglücklicherweise kocht hier jeder
Hersteller sein etwas anderes Süppchen. Der Standard JSR-
77, der sich um eine einheitliche Namensgebung der MBeans
kümmert, hat es leider nie richtig zum Durchbruch gebracht
und wird jetzt auch für zukünftige Versionen eingestampft.
Zudem lässt es JSR-77 auch jedem Server freigestellt, die
interessantesten MBeans, die sog. Statistics Provider zu
implementieren. Die wenigsten tun dies jedoch und wenn
dann auch nur für Teilaspekte wie Servletstatistiken. Daher

Listing 8

bless({
	'request' => bless( {
		'attribute' => 'ThreadCount',
		'mbean' => 'java.lang:type=Threading',
		'path' => undef,
	'status' => 200,
	'value' => '14'
	}, 'JMX::Jmx4Perl::Response')

Listing 9

use JMX::Jmx4Perl;
use JMX::Jmx4Perl::Request;
use Data::Dumper;
my $j4p = new JMX::Jmx4Perl(url => "http://localhost:8080/j4p");
my @requests = (
	new JMX::Jmx4Perl::Request(READ,"java.lang:type=Threading","ThreadCount"),
	new JMX::Jmx4Perl::Request(READ,"java.lang:type=Runtime","Uptime"),
	new JMX::Jmx4Perl::Request(AGENT_VERSION)
);
my @responses = $j4p->request(@requests);
print Dumper(\@responses);

Listing 10

use JMX::Jmx4Perl;
use Data::Dumper;

my $j4p = new JMX::Jmx4Perl(url => "http://localhost:8080/j4p");

while (1) {
	my $memory =
		$j4p->get_attribute("java.lang:type=Memory","HeapMemoryUsage");
	my $dead_locked =
		$j4p->execute("java.lang:type=Threading","findDeadlockedThreads");
	if ($memory->{used} / $memory->{max} > 0.9 || @$dead_locked) {
		my $thread_dump = $j4p->execute("java.lang:type=Threading",
						"dumpAllThreads",1,1);
		open(F,">/tmp/thread_dump_".time);
		print F Dumper($thread_dump);
		close F;
	}
	sleep(5 * 60);
}

bleibt einem nichts anderes übrig als selbst auf die Suche zu
gehen. Jmx4perl bietet hierfür verschiedene Möglichkeiten.
Zunächst einmal kann man sich mit jmx4perl list die Namen
aller MBeans inklusive ihrer Attribute und Operationen
ausgeben lassen - siehe Listing 11.
Mit jmx4perl attributes können zusätzlich die Werte aller
Attribute abgefragt werden. Aber Vorsicht, dabei können
durchaus Datenmengen von 200 MB und größer entstehen.
Komfortabler durchwandert man den JMX Namensraum mit
j4psh - siehe Listing 12. Das ist eine interaktive, readlinebasierte
Shell mit Syntax-Highlighting und kontextsensitiver
Vervollständigung.
Auch lohnt sich ein Blick in die vordefinierten Nagios-Checks,
die sich im jmx4perl Build-Verzeichnis unter config/
befinden. Für Tomcat zum Beispiel, existieren aktuell 17
vordefinierte Checks in config/tomcat.cfg, die Attribute
wie die Anzahl der Session und Requests pro Sekunde
einer Webapplikation oder auch die Größe der Datenbank
Verbindungspools ansprechen (Listing 13).
Der Syntax ist ausführlich in der Manpage zu check_
jmx4perl beschrieben, das Beispiel hier greift auf MBeans
mit dem Muster *:type=GlobalRequestProcessor,na
me=$0/requestCount zu, wobei $0 durch den Namen des
Tomcat Konnektors ersetzt werden muss (z.B. http-8080).

! Was fehlt ?

In diesem Artikel kann naturgemäss nur ein kleiner Teil der
jmx4perl Funktionen ausführlich vorgestellt werden. Darüberhinaus
bietet jmx4perl noch weitere Möglichkeiten:

Listing 11

$ jmx4perl http://localhost:8080/j4p list | grep -i session
cookies 			boolean, "Should we attempt to use cookies for session id communication?"
emptySessionPath 		boolean, "The 'empty session path' flag for this Connector"
pathname 		java.lang.String, "Path name of the disk file in which active sessions"
activeSessions 		int [ro], "Number of active sessions at this moment"
maxActive 		int, "Maximum number of active sessions so far"
sessionCounter 		int, "Total number of sessions created by this manager"
duplicates 		int, "Number of duplicated session ids generated"
distributable 		boolean, "The distributable flag for Sessions created by this Manager"
sessionMaxAliveTime 	int, "Longest time an expired session had been alive"
....

Listing 12

$ j4psh http://localhost:8080/j4p
[localhost:8080] : ls
....
java.lang:
	type=Memory
	type=OperatingSystem
	type=Runtime
	type=Threading
.....
	[localhost:8080] : cd java.lang:type=Memory
	[localhost:8080 java.lang:type=Memory] : ls
	java.lang:type=Memory

Attributes:
	NonHeapMemoryUsage CompositeData [ro]
	ObjectPendingFinalizationCount int [ro]
	Verbose boolean
	HeapMemoryUsage CompositeData [ro]

Operations:
	void gc()
[localhost:8080 java.lang:type=Memory] : cat HeapMemoryUsage
{
	committed => 85000192,
	init => 0,
	max => 129957888,
	used => 28258696
}

! Feingranulare Sicherheit
Der Agent kann neben der Standard HTTP-Security auch auf
MBean-Ebene abgesichert werden, in dem man eine XML-Policy-
Datei in den WAR-Agenten packt. Dabei kann man den
Zugriff auf bestimmte jmx4perl Operationen (read, write,
exec,..), auf bestimmte MBeans und/oder bestimmte Attribute
oder JMX Operationen einschränken. Auch ist es möglich,
den Zugriff nur von bestimmten IP-Addressen oder Subnetzen
zu erlauben. Mehr zu den Policy-Dateien findet sich
in der man page JMX::Jmx4Perl::Manual.

! JMX Proxy
Wenn es aus welchen Gründen auch immer nicht möglich
ist, einen Agenten auf der Zielplattform zu platzieren, dann
kann jmx4perl mit Hilfe des Proxy-Modes trotzdem verwendet
werden. Dazu wird der Agent auf einem dedizierten,
kleinen Servlet-Container (Tomcat oder Jetty) installiert.
Dieser kommuniziert mit dem Zielserver über JSR-160, der
dafür aber eingerichtet sein muss. Das Einschalten von JSR-
160 JMX Konnektoren ist für jeden Applikationsserver-Typ
unterschiedlich. Für JBoss und Weblogic finden sich Anleitungen
dafür unter http://labs.consol.de/tags/jsr-160/. Ein
JMX::Jmx4Perl::Request bei Verwendung eines Proxies
sieht etwas anders aus - siehe Listing 14.

Dabei ist proxy-host der Host, auf dem der Agent installiert
ist, während target-host den abzufragenden Server bezeichnet.
Die JMX Service URL enthält darüber hinaus noch
das Protokol (RMI) und den Port (1099) mit dem die JMX
MBeans exportiert sind.

! Severseitige Historie
Manchmal reicht es nicht aus, nur den Wert eines Attributes
zu messen, sondern die Zuwachsrate ist das Interessante.
Dazu kann man natürlich auf der Clientseite sich den Wert
speichern und diesen von dem Neuen, der etwas später
ausgelesen wird, abziehen. Dazu muss der Wert aber lokal
gespeichert werden, was z.B. für das Nagios-Plugin check_
jmx4perl zusätzlich Aufwand bedeutet. Daher bietet der
Agent die Möglichkeit, den Wert eines Attributes bzw. den
Rückgabewert einer Operation serverseitig im Speicher zu
merken und diesen alten Wert bei dem nächsten Request
für das gleiche Attribut oder Operation in der Antwort mit
zurückzuliefern. Dieser sog. History-Mode muss dediziert
eingeschaltet werden. Dazu hat der Agent eine eigene MBean
registriert, die man z.B. mit jmx4perl ansprechen kann
- siehe Listing 15.
In diesem Beispiel wird der History-Mode für das Attribut
ThreadCount der MBean java.lang:type=Threading
eingeschaltet. Dabei werden max. 10 historische Werte vorgehalten.
Die beiden [null] Werte sind optionale Argumente,
die hier nicht genutzt werden. Mit dem ersten kann ein Pfad
angegeben werden, der zweite bezeichnet eine JSR-160 URL
falls der Proxy-Mode verwendet wird. Wird nun dieses Attribut
ausgelesen, werden bis zu 10 historische Werte zusammen
mit einem Zeitstempel in der Antwort zurückgeliefert
(Listing 16).

Listing 13

# Requests per minute for a connector
# $0: Connector name
# $1: Critical (default: 1000)
# $2: Warning (default: 900)
<Check tc_connector_requests>
	Use = count_per_minute("requests")
	Label = Connector $0 : $BASE
	Value = *:type=GlobalRequestProcessor,name=$0/requestCount
	Critical = ${1:1000}
	Warning = ${2:900}
</Check>

Listing 14

my $j4p = new JMX::Jmx4Perl(url => "http://proxy-host:8080/j4p");
my $response = $j4p->request(
	new JMX::Jmx4Perl::Request(
		READ,"java.lang:type=Threading","ThreadCount",
		{
			target => {
				url => "service:jmx:rmi:///jndi/rmi://target-host:1099/jmxrmi",
				env => { user => "roland", password => "s3cr3!"}
				}
	));

! Aliase
Wie wir gesehen haben, sind die Namen der MBeans oft recht
komplex und es gibt viele davon. Um sich das Leben etwas
einfacher zu machen gibt es sogenannte Aliase, die Kombinationen
von MBean Namen, Attributen oder Operationen
und eventuell einem Pfad unter einer festen Konstante zusammenfassen.
Dabei funktionieren diese Aliase unabhängig
vom konkreten Typ des Applikationsservers. Bisher gibt
es Aliase für die MXBeans, die in jeder JVM vorkommen. Alle
vorhanden Aliase lassen sich mit jmx4perl aliases anzeigen
und sind im Modul JMX::Jmx4Perl::Alias definiert.
Verwendet werden können Aliase in JMX::Jmx4Perl als Ersatz
für die volle Spezifikation der MBean (Listing 17).
Aliase können übrigens auch vom Nagios Plugin check_
jmx4perl genutzt werden. Hier bietet es sich jedoch an, den
flexibleren und neueren Konfigurationsmechanismus zu
nutzen, der auch Parameter (z.B. den Namen einer Webapplikation)
für vordefinierte Checks verarbeiten kann.

! Wie geht's weiter ?

Unsere beiden Schwestern, die Perl-Bibliothek und die Java-
Agentin, vertragen sich bestens und bilden ein starkes Duo.
Dennoch ist es Zeit weiterzugehen, und so ist die Java-Agentin
ohne Groll ausgezogen und hat sich in Jolokia (http://www.
jolokia.org) umbenannt. Auch ein Grund für die Trennung ist
der neue Bekanntenkreis, den sie sich gesucht hat. Der Ansatz,
JMX über JSON und HTTP zugänglich zu machen ist
nämlich auch für andere Sprachen interessant. Jolokia bietet
zusätzlich eine Java-Client Library, eine JavaScript Library
ist in Arbeit, andere (Scala, Groovy, Python) sind in Planung.
Jedoch bleibt jmx4perl die gute Schwester, die ab Version
0.80 auf Jolokia angewiesen ist.

! Fazit

Auch wenn ein agentenbasierter Ansatz zunächst Mehraufwand
bedeutet, ist er für alle ausserhalb der Java-Welt der
optimale Weg in die innere Maschinerie der JVM. Die Installation
des Java-Agentens bedarf zugegebermassen Überzeugungsarbeit
und bedeutet extra Arbeit z.B. beim Upgrade des
Agenten auf eine neuere Version, aber die Vorteile sind immens.
Und selbst wenn es die Coporate Policy nicht erlaubt,
'Fremdsoftware' auf den heiligen JEE Servern zu installieren
oder ein Genehmigungsprozess zum Spiessrutenlauf wird,
dann bleibt ja immer noch der Proxy-Mode.
Gerhard ist nun zufrieden. Seine JMX Checks laufen in weniger
als zwei Sekunden durch und er überwacht damit über
500 JEE-Server mit mehr als 10 Checks in einer einzigen
Nagios Server Installation. Dank des ausgereiften Nagios-
Checks check_jmx4perl kann er täglich seinen Java-Entwicklern
auf die Finger klopfen, aber auch selbst dank JMX::
Jmx4Perl die tollsten Dinge mit seiner Java-Server Landschaft
anstellen. Selbst 'Guerilla-Remoting' über selbstgeschriebene
MBeans, die man innerhalb einer Java Applikation
von überall heraus registrieren kann, wäre möglich. Aber,
psst, erzähl das niemals deinem Java-Architekten.

Listing 15

$ jmx4perl http://localhost:8080/j4p exec jmx4perl:type=Config \
	setHistoryEntriesForAttribute \
	java.lang:type=Threading ThreadCount [null] [null] 10

Listing 16

$ jmx4perl -v http://localhost:8080/j4p \
read java.lang:type=ThreadingThreadCount
....
{"history":[ {"timestamp":1290495645,"value":"10"},
	{"timestamp":1290495640,"value":"12"} ],
"timestamp":1290495930,
"status":200,
"request":{"mbean":"java.lang:type=Threading",
	"attribute":"ThreadCount","type":"read"},
"value":"14"}
....

Listing 17

 use JMX::Jmx4Perl;
 use JMX::Jmx4Perl::Alias;
 my $j4p = new JMX::Jmx4Perl(url => "http://localhost:8080/j4p");
 print "Heap-Memory used: ",$j4p->get_attribute(MEMORY_HEAP_USED)," Bytes\n";