Cet article est paru dans le magazine
francophone consacré à Linux et aux
logiciels libres n°37 (mars 2002).

©2002
Editions Diamond


Surveillance rapprochée

Cette série a pour but de montrer la possibilité de construire un environnement utilisateur entièrement en Java/Swing au dessus de Linux et XFree. Ce second article s'intéresse à la réalisation de moniteurs.

Objectif

Il s'agit de réaliser de petits moniteurs affichant de manière graphique des informations aussi importantes que l'heure ou la mémoire disponible (java mais aussi système). Les lecteurs de la première heure pourront faire le rapprochement avec le premier article du numéro 1 de ce magazine ;-).

Avec un brin

Même après avoir lu, dans le numéro précédent, la discussion sur la meilleure traduction de thread et qui statuait sur filament (et brin alors ?), je garderais par la suite le terme anglais vu qu'il s'agit aussi du nom de la classe.

Tic tac boum

Pour commencer, notre choix s'est porté sur une horloge. Il s'agit d'un JComponent qui affiche l'heure tout en clignotant (n'est-ce pas motivant ?). À sa création, un nouveau thread lui est associé. Celui-ci reçoit une priorité minimale afin de ne pas gêner les autres tâches en cours. Il est aussi déclaré comme démon vu qu'il est perpétuel et que son existence est lié à celle du composant.

En règle général, je n'aime pas trop les articles alignant 3 pages de code source. C'est pourquoi le code inclus dans l'article précédent était limité à l'essentiel et donné à but illustratif. À titre tout à fait exceptionnel, voici toutefois un programme complet.

// Les paquets nécessaires
import java.awt.*;
import java.util.*;
import javax.swing.*;

public class WharfHorloge
    extends JComponent
    implements Runnable
{
  // délai en millisecondes
  private final static int DELAI=1000;

  private boolean clignotant_=false;
  private String  valeur_="";

  public WharfHorloge() {
    setForeground(Color.green);
    setBackground(Color.black);
    setFont(new Font("Courier",Font.PLAIN,12));
    setPreferredSize(new Dimension(48,16));

    Thread timer=new Thread(this);
    timer.setDaemon(true);
    timer.setPriority(Thread.MIN_PRIORITY);
    timer.start();
  }

  public void paintComponent(Graphics _g) {
    // Effacer le fond
    _g.setColor(getBackground());
    _g.fillRect(0,0,getWidth(),getHeight());
  
    // Récupère la taille des bords
    Insets insets=getInsets();
    _g.setColor(getForeground());
    _g.drawString(valeur_,
      insets.left+2,insets.top+12);
  }

  public String getString() {
    // Initialisée à l'heure actuelle
    Date date=new Date();
    // Construit la chaîne à afficher
    String sh="00"+date.getHours();
    sh=sh.substring(sh.length()-2);
    String sm="00"+date.getMinutes();
    sm=sm.substring(sm.length()-2);
    return sh+(clignotant_ ? ":" : " ")+sm;
  }

  public void run() {
    while (true) {
      clignotant_=!clignotant_;
      valeur_=getString();
      repaint();
      try { Thread.sleep(DELAI); }
      catch(InterruptedException ex) { }
    }
  }

  public static void main(String[] _args)
  {
    JFrame f=new JFrame("Horloge");
    f.getContentPane().add(new WharfHorloge());
    f.pack();
    f.show();
  }
}

Mais où est Tux ?

Et si on veut afficher quelque chose de plus utile ? Il nous suffit de piocher dans /proc. En effet, ce répertoire contient une mine d'informations renvoyées par le noyau. Comme par exemple, la mémoire totale et la mémoire disponible qu'il suffit de lire dans le fichier /proc/meminfo. On peut donc en déduire le pourcentage de mémoire occupée et écrire une nouvelle fonction getString().

public String getString() {
  String r="ERREUR";

  try {
    LineNumberReader lnr=new LineNumberReader
      (new FileReader("/proc/meminfo"));
    long total=0,free=0;
    while((total==0)||(free==0)) {
      String s=lnr.readLine();
      if(s==null)
        break;
      else if(s.startsWith("MemTotal:")) {
        s=s.substring(10,s.length()-2).trim();
	total=Long.valueOf(s).longValue();
      }
      else if(s.startsWith("MemFree:")) {
        s=s.substring(10,s.length()-2).trim();
	free=Long.valueOf(s).longValue();
      }
    }
    lnr.close();
    r=(100l*(total-free)/total)+"%";
  } catch(Exception ex) { }

  return r;
}

Ce code est très dépendant du formatage de meminfo. Il devra donc être adapté pour fonctionner correctement avec un autre SE. Je vous invite à m'envoyer vos contributions pour les ajouter dans Wharf.

On jette tout et on recommence!

Supposons que nous ayons beaucoup de moniteurs sur notre écran (sysadmin ?). La création d'un thread a un coût certain. Même si la gestion des threads de la MVJ est efficace (réaffection de N threads java sur M threads SE), il est préférable de limiter leur nombre.

Réveils difficiles

Swing nous fournit une solution avec la classe Timer. Celle-ci fonctionne comme un réveil qui sonne régulièrement (envoi d'un évènement). On peut donc supprimer les lignes concernant le thread et la méthode run() et les remplacer par les suivantes:

implements ActionListener
...
Timer timer=new Timer(DELAI,this);
timer.start();
...
public void actionPerformed(ActionEvent _evt) {
  clignotant_=!clignotant_;
  valeur_=getString();
  repaint();
}

Mais que se passe-t-il ?

La différence principale est que toute l'exécution, en particulier l'appel à getString(), s'effectue dans le thread (unique) de Swing, celui qui notamment gère l'affichage. Ce n'est pas gênant sur notre exemple mais supposons que notre moniteur surveille un site www et que la méthode prenne 5 secondes. Notre bureau se fige.

Faire preuve de modération en toute chose

Ces deux techniques sont en fait complémentaires et il convient de choisir au cas par cas. Si on peut garantir que le temps d'exécution de la méthode getString() est bref (inférieur à 10ms), le choix du Timer s'impose. Á l'inverse, si ce temps est supérieur ou ne peut être garanti, il est préférable d'utiliser un Thread. Enfin, il est possible d'avoir le meilleur des deux en utilisant un seul thread commun à tous les moniteurs. Les ressources sont épargnées et l'affichage n'est pas perturbé. Mais ça demande plus de code.

Conclusion

Le but de cet article était de présenter l'utilisation des classes Thread et Timer pour la réalisation de composants devant se réafficher à intervalle régulier (comme les moniteurs de Wharf).

FOSDEM

Quelques mots sur cet évènement majeur dont l'édition 2002 s'est révélée particulièrement intéressante. Concernant Java, nous avons eu le droit aux présentations d'Eclipse et de son nouveau toolkit SWT (une nouvelle approche, efficace mais incompatible) et d'OpenUSS, une application J2EE destinée aux universités. Nous avons aussi pu en savoir plus sur la compromission de Gnome avec l'ennemi et son architecture .NET et comment ses développeurs galèrent à redévelopper ce qui existe déjà depuis 5 ans et s'appelle Java.

____
Guillaume Desnoix
Février 2002
RÉFÉRENCES Ring
Online Products best, TU TUCSON, Delhi, Armband, , ...