martedì 2 novembre 2010

Monitoraggio e controllo delle applicazioni tramite JMX - Introduzione

JMX (Java Management Extensions) è una tecnologia introdotta nella versione 5.0 di Java che permette il monitoraggio ed il controllo delle applicazioni in maniera standardizzata.

Ogni applicazione, tramite gli MBean (Managed Beans) può esporre all'esterno sia delle proprietà (che possono essere monitorate) che delle operazioni (che si possono eseguire per modificare il comportamento dell'applicazione in runtime). L'applicazione che espone gli MBean può essere vista come un server a cui i client JMX (come, ad esempio, il tool jconsole) possono collegarsi.


La tecnologia JMX offre una maniera semplice e standardizzata per gestire le applicazioni. Il vantaggio per lo sviluppatore è che consente di implementare solo la parte strettamente legata all'applicazione da controllare, mentre fornisce tutta l'infrastruttura per esporre all'esterno le interfacce, gestendo in maniera (quasi) trasparente tutta la parte relativa alla comunicazione tra server e client.

Questa tecnologia è utilizzata anche all'interno della stessa JRE, ad esempio per permettere di controllare l'occupazione della memoria o per invocare su richiesta il garbage collector.


Vediamo un esempio di utilizzo di JMX per il controllo di una semplice applicazione, supponiamo di avere un semplice oggetto Printer che modella la scrittura periodica di una stampa su console:

package it.sinossi.poc.mbeanpoc;

public class Printer {

 private String text;

 private boolean printEnabled = true;

 private int sleepTime = 500;

 private boolean stop = false;

 public Printer(String text) {
  this.text = text;
 }

 public String getText() {
  return text;
 }

 public void setText(String text) {
  if (text != null && !text.isEmpty()) {
   this.text = text;
  } else {
   throw new IllegalArgumentException("text cannot be null or empty");
  }
 }

 public boolean isPrintEnabled() {
  return printEnabled;
 }

 public void setPrintEnabled(boolean printEnabled) {
  this.printEnabled = printEnabled;
 }

 public int getSleepTime() {
  return sleepTime;
 }

 public void setSleepTime(int sleepTime) {
  if (sleepTime >= 100 && sleepTime <= 1000) {
   this.sleepTime = sleepTime;
  } else {
   throw new IllegalArgumentException(Integer.toString(sleepTime) + " is not in range [100, 1000]");
  }
 }

 public void stop() {
  stop = true;
 }

 public boolean shouldStop() {
  return stop;
 }

 public void print() {
  System.out.println(text);
 }

}

La classe permette di modificare alcuni parametri come il testo ed il tempo di attesa, in più si possono eseguire le azioni di pausa, ripresa e stop, notare che i metodi setText e setSleepTime lanciano delle eccezioni nel caso in cui siano invocate con argomenti non validi. Sottolineo come non sia presente in nessuna parte di codice relativa a JMX.

Scriviamo un semplice main che permetta di vedere il codice all'opera

package it.sinossi.poc.mbeanpoc;

public class Main {

 public static void main(String[] args) throws Exception {

  Printer p = new Printer("Ciao");

  int counter = 0;

  while (!p.shouldStop()) {
   if (p.isPrintEnabled()) {
    System.out.print(Integer.toString(counter) + "  ");
    p.print();
   }
   counter++;
   Thread.sleep(p.getSleepTime());
  }
  System.out.println("Exiting");
 }

}

Avviando la classe Main si vedrà in console la stampa periodica della scritta "Ciao".


A questo punto si deve introdurre la componente fondamentale della tecnologia JMX: gli MBean. Gli MBean sono delle normali classi, simili ai java bean, che però devono implementare una interfaccia che sarà appunto quella che definisce i metodi di monitoraggio e controllo esposti all'esterno. Nel nostro caso l'interfaccia si chiamerà PrinterControlMBean e l'implementazione PrinterControl (questo tipo di nomenclatura è obbligatoria, l'interfaccia deve chiamarsi MBean e la relativa implementazione deve chiamarsi ).


package it.sinossi.poc.mbeanpoc;

public interface PrinterControlMBean {

 String getText();

 void setText(String text);

 int getSleepTime();

 void setSleepTime(int millis);

 void pause();

 void resume();

 public void stop();

}


package it.sinossi.poc.mbeanpoc;

public class PrinterControl implements PrinterControlMBean {

 private Printer printer;

 public PrinterControl(Printer printer) {
  this.printer = printer;
 }

 public String getText() {
  return printer.getText();
 }

 public void pause() {
  printer.setPrintEnabled(false);
 }

 public void resume() {
  printer.setPrintEnabled(true);
 }

 public void stop() {
  printer.stop();
 }

 public void setText(String text) {
  printer.setText(text);
 }

 public int getSleepTime() {
  return printer.getSleepTime();
 }

 public void setSleepTime(int millis) {
  printer.setSleepTime(millis);
 }

}

L'interfaccia PrinterControlMBean definisce le funzionalità della nostra classe Printer che vogliamo siano controllabili dall'esterno, in questo caso l'implementazione di questa interfaccia è molto semplice in quanto tutti i metodi delegano all'oggetto Printer, passato nel costruttore. Ancora una volta non è presente codice relativo alle API JMX.

Ora che abbiamo una classe che vogliamo sia gestita dall'esterno (Printer) e l'MBean che determina le funzionalità esposte all'esterno (PrinterControlMBean, PrinterControl), dobbiamo completare con la registrazione sul sistema del nostro MBean in modo che questo venga esposto all'esterno.

Questo verrà fatto nella classe Main, in cui viene istanziato l'MBean e viene registrato nell'MBean Server, che è il componente fornito dal sistema che si occupa di esporre all'esterno gli MBean.

public class Main {

 public static void main(String[] args) throws Exception {

  Printer p = new Printer("Ciao");

  registerMBean(p);

  int counter = 0;

  [...]
 }

 private static void registerMBean(Printer p) throws Exception {
  PrinterControl mbean = new PrinterControl(p);
  ObjectName objectName = new ObjectName("printercontrol:type=printer,name=console_printer");
  MBeanServer server = ManagementFactory.getPlatformMBeanServer();
  server.registerMBean(mbean, objectName);
 }

}

Ogni MBean viene registrato in associazione ad un'istanza di ObjectName, questo permette l'identificazione univoca della risorsa gestita dall'MBean. L'ObjectName è costruito con una stringa che codifica un dominio (in questo caso il dominio è "printercontrol") più delle coppie chiave=valore (type = printer, name = console_printer); gli spazi presenti nella stringa non vengono eliminati, quindi meglio che non ce ne siano.

A questo punto non resta che far pratire l'applicazione e successivamente avviamo jconsole.





Connettiamoci alla JVM relativa al nostro progetto.
Dal tab MBeans si possono vedere e controllare tutti gli MBeans registrati sul sistema, il nostro si trova sotto il dominio it.sinossi.
Jconsole permette di vedere e modificare gli attributi e di invocare i metodi esposti.



Proviamo a mettere in pausa, modificare la stringa che viene stampata, riattivare la stampa e poi stoppare il programma. Nella console si può vedere come le azioni svolte si ripercuotono sul comportamento del programma.


Ricordiamo che la classe Printer permette di impostare uno sleepTime compreso tra 100 e 1000 millisecondi, nel caso in cui si inserisca un valore non valido viene lanciata un'eccezione. Per vedere come si comporta il sistema proviamo ad impostare il valore 10. Il risultato è quello che si vede in figura: l'eccezione impedisce di impostare il valore non corretto, questo senza influire sul regolare funzionamento dell'applicazione che continua a girare senza accorgersi di niente.



See also:
Sorgenti del progetto su github
JMX Home Pape
Java Tutorial on JMX
JMX Best Practices
Java theory and practice: Instrumenting applications with JMX

Nessun commento:

Posta un commento