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 No InterWiki reference defined in properties for Wiki called "localhost"! : ls .... java.lang: type=Memory type=OperatingSystem type=Runtime type=Threading ..... No InterWiki reference defined in properties for Wiki called "localhost"! : cd java.lang:type=Memory No InterWiki reference defined in properties for Wiki called "localhost"! : ls java.lang:type=Memory
Attributes: NonHeapMemoryUsage CompositeData ro ObjectPendingFinalizationCount int ro Verbose boolean HeapMemoryUsage CompositeData ro
Operations: void gc() No InterWiki reference defined in properties for Wiki called "localhost"! : 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":No InterWiki reference defined in properties for Wiki called " {"timestamp""!, "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";