jeudi 29 août 2013

SED - Filtrer du contenu entre deux chaînes pattern.

Dans Sed, il y a la possibilité de supprimer le contenu d'un fichier situé entre deux chaines de caractères. Pratique lorsque vous voulez filtrer une partie d'un fichier de manière conditionnelle. Par exemple dans le fichier suivant :

#-- Texte rigolo 

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ut volutpat dui. Integer nec neque eu arcu mollis tincidunt eget et odio. Proin facilisis rutrum felis eget volutpat. Quisque et elit mi. Nunc metus libero, tempor vel interdum et, lacinia vel nisl. Nam consequat non lorem vel scelerisque. Vestibulum rutrum velit at varius semper. Nullam auctor nulla urna, eu auctor justo dictum nec. Sed luctus sem in magna dignissim, vel commodo lacus accumsan. Sed gravida nunc nec velit sagittis accumsan. Aliquam aliquet euismod venenatis. Praesent sit amet ullamcorper nisi.

%SUPPR% - Après cette ligne on supprime tout
Duis pretium sodales lacinia. Nunc est enim, dapibus et sodales sed, sagittis sed massa. Fusce vestibulum metus sit amet porttitor porta. Ut molestie eleifend faucibus. Vivamus ut urna purus. Sed aliquet volutpat lacus, in sollicitudin erat cursus vel. Sed ac lectus vel nunc consequat convallis a sit amet ante. Duis cursus malesuada sodales. Sed dictum eu ipsum sit amet luctus. Mauris facilisis pharetra quam quis varius. Nulla facilisi. Vivamus mollis porttitor convallis. Etiam aliquam, eros vitae interdum rutrum, neque turpis laoreet lacus, vel egestas arcu nisl volutpat purus. Nullam vel viverra neque.
%FINSUPPR%
Mauris euismod, augue eu sagittis commodo, massa nulla volutpat justo, at condimentum sapien arcu et ligula. Aliquam semper, enim in posuere rhoncus, libero velit semper sapien, nec convallis sapien massa mollis nisl. Ut tortor sem, consequat ac nulla at, varius tempor turpis. Morbi aliquam, ligula a sodales imperdiet, urna odio iaculis purus, non interdum sapien turpis at erat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi vitae auctor nibh. In et lectus risus. Sed vitae orci erat. Cras mollis orci eget sem lobortis, vel consectetur tortor sollicitudin.

Dans sed, on peut afficher tous les caractères contenus entre deux correspondances avec la commande p. Il suffit de mettre un point d'exclamation :

sed '/%SUPPR%/,/%FINSUPPR%/!p' fichier

Explication de la commande : Pour toutes les données comprises entre %SUPPR% et %FINSUPPR% on n'imprime (p comme print) pas (négation avec le "!"). Résultat :
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ut volutpat dui. Integer nec neque eu arcu mollis tincidunt eget et odio. Proin facilisis rutrum felis eget volutpat. Quisque et elit mi. Nunc metus libero, tempor vel interdum et, lacinia vel nisl. Nam consequat non lorem vel scelerisque. Vestibulum rutrum velit at varius semper. Nullam auctor nulla urna, eu auctor justo dictum nec. Sed luctus sem in magna dignissim, vel commodo lacus accumsan. Sed gravida nunc nec velit sagittis accumsan. Aliquam aliquet euismod venenatis. Praesent sit amet ullamcorper nisi.

# -- ici il y avait un paragraphe, mais ça c'était avant.

Mauris euismod, augue eu sagittis commodo, massa nulla volutpat justo, at condimentum sapien arcu et ligula. Aliquam semper, enim in posuere rhoncus, libero velit semper sapien, nec convallis sapien massa mollis nisl. Ut tortor sem, consequat ac nulla at, varius tempor turpis. Morbi aliquam, ligula a sodales imperdiet, urna odio iaculis purus, non interdum sapien turpis at erat. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Morbi vitae auctor nibh. In et lectus risus. Sed vitae orci erat. Cras mollis orci eget sem lobortis, vel consectetur tortor sollicitudin.

jeudi 20 décembre 2012

Appel gc (Garbage Collector) et autres fonctions depuis JMX

Voici un court article de trucs et astuces pour JMX.

Sommaire :



Utilisation des composants Graphiques

Par exemple, on peut vouloir appeler le garbage collector sur une machine distante, en se connectant à JConsole c'est assez simple :

Appel GC depuis JConsole
 Ou en utilisant explicitement des Beans :

Appel au Garbage Collector depuis JConsole

Le même exemple avec Java VisualVM qui est fourni par défaut dans les JDK récents au même titre que JConsole :

Appel au Garbage Collecter en utilisant Java VisualVM


Effectuer une connexion distante

 Je remets ici comment lancer un programme Java pour permettre la connexion distante à JConsole, pour plus de détails se référer à mes précédents articles JMX - Partie 1 - Partie 2 et Partie 3 :
java -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9021 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -jar mon_programme.jar
Une fois les paramètres ajoutés à la ligne de lancement, pour se connecter avec JConsole, ou Jvisualvm il suffit de rentrer l'adresse de la machine et le port :
Connexion à la JVM avec JConsole
Voici le même exemple avec JVisualvm qui est plus attrayant :

Connexion avec Java VisualVM
Pour afficher cette boîte de dialogue, il faut d'abord faire un clic-droit sur Local et cliquer sur "Add JMX Connection..."

En utilisant la ligne de commande (Jmxsh)

Jmxsh que je vous invite à télécharger sur google code est une petite bibliothèque permettant d'effectuer des appels JMX, sans être miraculeuse elle est bien pratique. En gardant l'exemple de l'appel au GarbageCollector :
java -jar jmxsh-R5.jar
History file null/.jmxsh_history not writable, command-line history disabled.
jmxsh v1.0, Tue Jan 22 17:23:12 CET 2008

Type 'help' for help.  Give the option '-?' to any command
for usage help.

Starting up in shell mode.
%
Le shell est démarré, maintenant on peut se connecter et invoquer le GarbageCollector
% jmx_connect -h localhost -p 9021
Connected to service:jmx:rmi:///jndi/rmi://localhost:9021/jmxrmi.

% jmx_invoke --mbean java.lang:type=Memory gc
can't read "MBEAN": no such variable
Le message can't read "MBEAN": no such variable ne pose pas de problèmes, après vérification le GC est bien effectué. On peut également automatiser l'appel, en copiant notre appel précédent dans un fichier :
$ echo jmx_invoke --mbean java.lang:type=Memory gc > script
$ java -jar jmxsh-R5.jar -l gc.log -h localhost -p 9021 script
Bien sûr dans le script il y a moyen d'automatiser plus de choses, on peut par exemple imaginer une série d'appel au GarbageCollector sur toutes les machines gérées :
#Contenu du fichier script
jmx_connect -h machine1 -p 9021 
jmx_invoke --mbean java.lang:type=Memory gc
jmx_close
jmx_connect -h machine2 -p 9021 
jmx_invoke --mbean java.lang:type=Memory gc
jmx_close
jmx_connect -h machine3 -p 9021 
jmx_invoke --mbean java.lang:type=Memory gc
jmx_close
jmx_connect -h machine4 -p 9021 
jmx_invoke --mbean java.lang:type=Memory gc
jmx_close
jmx_connect -h machine4 -p 9022 #Plusieurs JVM sur la même machine on change de port 
jmx_invoke --mbean java.lang:type=Memory gc
jmx_close
#Fin du fichier script

$ java -jar jmxsh-R5.jar -l gc.log script

mercredi 12 septembre 2012

Oeufs de pâques : Bitbucket.org

Je viens de trouver un oeuf de pâques chez Bitbucket.org, il est plutôt surprenant par son contenu... Pour le faire apparaître il suffit de saisir de le code Konami . Voici ce que j'ai obtenu :
Le monde petit poney est surprenant sur un site comme ça, mais c'est marrant. Le code Konami est souvent présent sur de nombreux site pour cacher des fonctionnalités. Amusez-vous bien à les chercher, pour vous aider, un site qui liste plusieurs : Konamicodesites.com

mardi 17 juillet 2012

Java : Gérer la JVM avec Java Management Extensions (JMX) Technology - Partie 3 - Création d'un serveur, intéraction avec vos beans

Après avoir lu les précédents articles sur JMX vous devez maintenant tout savoir sur le fonctionnement de JMX et sur l'interrogation d'un serveur de Beans Managables. Si ce n'est pas le cas, je vous invite à relire depuis le début :

Java : Gérer la JVM avec Java Management Extensions (JMX) Technology - Partie 1 - Présentation
Java : Gérer la JVM avec Java Management Extensions (JMX) Technology - Partie 2 - Création d'un client

Cet article va aborder la question d'un serveur JMX, il sera question de créer et d'enregistrer des MBeans ainsi que de gérer les notifications.

Lancement du serveur :

Comme indiqué dans la deuxième partie, on peut lancer le serveur depuis la ligne de commande, c'est la méthode la plus simple, pas de code à gérer, ni de droits au travers de policies Java. Le code suivant est un lancement du serveur JMX sur le port 9021 sans authentification ni cryptage :
java -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9021 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -jar mon_programme.jar
L'autre méthode consiste à lancer un registre RMI et d'y enregistrer notre serveur.
package fr.technorevue.jmx;
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.rmi.registry.LocateRegistry;
import java.rmi.server.ExportException;

import javax.management.MBeanServer;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXConnectorServerFactory;
import javax.management.remote.JMXServiceURL;

/**
* Cette classe a la particularité de pouvoir lancer le connecteur RMI pour
* l'accès distant même s'il n'a pas été initié dans la ligne de commande.
* Il reste toutefois fonctionnel dans le cas contraire.
*/
public class JMXServerStarter {
 private int port;
 private MBeanServer serveur;
 private JMXConnectorServer connection;
 // URL de connexion en prenant en compte le port.
 private final static String CONNEXION_URL = "service:jmx:rmi:///jndi/rmi://localhost:%d/jmxrmi";
 private String connecturl;

 public JMXServerStarter(int port) {
  this.port = port;
  this.connecturl = String.format(CONNEXION_URL, this.port);
 }

 public MBeanServer getServeur() throws IOException {
  if (this.serveur == null){
   startConnection();
  }
  return this.serveur;
 }


 private void startConnection() throws IOException {
  this.serveur = ManagementFactory.getPlatformMBeanServer();// Création du serveur.
  try {
   LocateRegistry.createRegistry(this.port); // La création du registre RMI 
   JMXServiceURL url = new JMXServiceURL(this.connecturl);
   // On crée le connecteur et on "démarre" le serveur.
   connection = JMXConnectorServerFactory
     .newJMXConnectorServer(url, null, this.serveur);
   connection.start();
   //Enregistrement du serveur si aucune erreur ne se produit.
  } catch (ExportException ex){}  // Si la création échoue, souvent le registre est déjà lancé.
 }
 
 /**
  * Le serveur doit être arrêté pour l'application accepte de se quitter
  * @throws IOException 
  */
 public void killServer() throws IOException{
  this.serveur = null;
  if(this.connection != null) this.connection.stop();
 }

}
Dans une classe à part on lance l'application avec le serveur :
package fr.technorevue.jmx;
import java.io.IOException;

import javax.management.MBeanServer;

public class Application {
 public static void main(String[] args) {
  JMXServerStarter starter = new JMXServerStarter(9876); // On démarre sur
                // le port 9876.
  try {
   MBeanServer serveur = starter.getServeur();
   System.out.println("Serveur démarré sur le port 9876");
   // Ici on réalisera les inscriptions de beans.
  } catch (IOException ioe) {
   System.err
     .println("Echec de l'obtention du serveur de beans. Connexion impossible "
       + ioe.getLocalizedMessage());
   ioe.printStackTrace();
  }
  // Ici on a le code de l'application, remplacé par un sleep de 10
  // secondes.
  try {
   Thread.sleep(10000);
  } catch (InterruptedException e) {
  }
  System.out.println("Sortie de l'application");
  try {
   starter.killServer();
  } catch (IOException ioe) {
   System.err.println("Erreur lors de l'arrêt sur serveur, était-il lancé? : "+ioe.getLocalizedMessage());
   ioe.printStackTrace();
  }
 }
}
La classe de démarrage du serveur est prévue pour lancer le registre RMI s'il n'a pas été lancé dans la ligne de commande. Pour une meilleure gestion de la sécurité Java, le mieux reste d'initier ce genre de paramètres en utilisant la ligne de commande.

Les MBeans, écriture et enregistrement

Maintenant que nous pouvons interagir avec notre serveur, nous allons écrire nos propres objets manageables et les enregistrer dans le serveur.
Cette opération nous permettra plus tard de gérer à distance nos objets enregistrés.

Le MBean est un objet répondant à un standard, le type du Bean géré par le serveur est déterminé par son nom.
Les beans qui nous intéressent ici sont les MXBeans, leur nom se terminera donc par MXBEan. Ils ont la particularité de fonctionner avec des données "Ouvertes" qui sont des containers génériques de données, on retrouve les types simples, les tableaux et les Maps (en gros). Ce point a été abordé dans la deuxième partie :
Java : Gérer la JVM avec Java Management Extensions (JMX) Technology - Partie 2 - Création d'un client
Pour reconnaître le type du Bean, c'est toujours la convention de nommage qui détermine le type du Bean, ainsi pas besoin d'héritage tout est dans le nom.
package fr.technorevue.jmx;

import java.util.List;
/**
 * Interface complete permettant de voir le fonctionnement de l'enregistrement JMX.
 * L'exemple ci-dessous affiche les méthodes permettant de gérer un serveur d'application.
 * @author Fabien TORDI
 */
public interface ServerManagerMXBean {
    //La constante de nommage du Bean dans le serveur.
 //Il s'appellera ServerManager et apparaîtra dans le domaine fr.technorevue.
 //Dans JConsole c'est affiché comme un dossier.
    public final static String SERVER_MANAGER_MXBEAN_NAME="fr.technorevue:name=ServerManager";
    /**
     * Démarre le serveur.
     */
    public void start();
    /**
     * Redémarre le serveur.
     */
    public void restart();
    /**
     * Arrête le serveur.
     */
    public void stop();
    
    /**
     * Donne le status du serveur.
     * @return Le status
     */
    public String getStatus();
    
    /**
     * Liste les applications du serveur.
     * @return
     */
    public List<String> getApplications();
    
    /**
     * Retourne la liste des applications du serveur, chargées ou non.
     * @param loaded si true retourne les applications chargées, si false les applications non chargées.
     * @return La liste des applications.
     */
    public List<String> listApplications(boolean loaded);

    /**
     * Charge l'application portant le nom passé en paramètres
     * @param application L'application à charger.
     * @return true si l'application est chargée, false sinon.
     */
    public boolean deploy(String application);
    
    
    /**
     * Décharge l'application portant le nom passé en paramètres
     * @param application L'application à charger.
     * @return true si l'application est déchargée, false sinon.
     */
    public boolean undeploy(String application);
    
    /**
     * Modifie le port d'écoute du serveur.
     * @param port Le port d'écoute du serveur.
     */
    public void setPort(int port);
    
    /**
     * Donne le port d'écoute du serveur.
     * @return port Le port d'écoute du serveur.
     */
    public int getPort();
}
Ce code est une interface, permettant d'enregistrer des attributs ainsi que des opérations exécutables par le biais de JMX. Les méthode commençant par get et par set se retrouvent dans les Attributes sans le get ni le set. Si un setter existe, l'attribut sera identifié comme inscriptible. Voici une implémentation sommaire de notre interface.
package fr.technorevue.jmx;

import java.util.ArrayList;
import java.util.List;

/**
 * Implémentation du server manager.
 * @author Fabien TORDI 
 */
public class ServerManager implements ServerManagerMXBean {
 private int port = 12345;
 /*
  * (non-Javadoc)
  * @see fr.technorevue.jmx.ServerManagerMXBean#start()
  */
 @Override
 public void start() {
  System.out.println("Démarrage");
  //Start code (ne nous concerne pas)
 }

 /*
  * (non-Javadoc)
  * @see fr.technorevue.jmx.ServerManagerMXBean#restart()
  */
 @Override
 public void restart() {
  System.out.println("Redémarrage");
  stop();
  start();
 }

 /*
  * (non-Javadoc)
  * @see fr.technorevue.jmx.ServerManagerMXBean#stop()
  */
 @Override
 public void stop() {
  System.out.println("On s'arrête");
  //Stop code...
 }

 /*
  * (non-Javadoc)
  * @see fr.technorevue.jmx.ServerManagerMXBean#getStatus()
  */
 @Override
 public String getStatus() {
  return "Fonctionne.";
 }
 
 /*
  * (non-Javadoc)
  * @see fr.technorevue.jmx.ServerManagerMXBean#listApplications()
  */
 @Override
 public List<String> getApplications() {
  System.out.println("Listing de toutes les applications .");
  List<String> apps = new ArrayList<String>();
  apps.add("App1");
  apps.add("App2");
  apps.add("App3");
  return apps;
 }

 /*
  * (non-Javadoc)
  * @see fr.technorevue.jmx.ServerManagerMXBean#listApplications(boolean)
  */
 @Override
 public List<String> listApplications(boolean loaded) {
  System.out.println("Listing de toutes applications chargées ? "+loaded);
  List<String> apps = new ArrayList<String>();
  if(loaded){
   apps.add("App1");
  }else{
   apps.add("App2");
   apps.add("App3");
  }
  return apps;
 }

 /*
  * (non-Javadoc)
  * @see fr.technorevue.jmx.ServerManagerMXBean#deploy(java.lang.String)
  */
 @Override
 public boolean deploy(String application) {
  System.out.println("Déploiement de l'application "+application);
  return true;
 }

 @Override
 public boolean undeploy(String application) {
  System.out.println("Arrêt de l'application "+application);
  return true;
 }

 @Override
 public void setPort(int port) {
  System.out.println("On redéfini le port d'écoute du serveur sur : "+port);
  this.port = port;
 }
 
 @Override
 public int getPort(){
  return this.port;
 }
}
On enregistre notre MBean dans notre serveur, le code suivant est la mise à jour du point d'entrée de notre application :
package fr.technorevue.jmx;
import java.io.IOException;

import javax.management.InstanceAlreadyExistsException;
import javax.management.MBeanRegistrationException;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotCompliantMBeanException;
import javax.management.ObjectName;

public class Application {
 public static void main(String[] args) {
  JMXServerStarter starter = new JMXServerStarter(9876); // On démarre sur
                // le port 9876.
  try {
   MBeanServer serveur = starter.getServeur();
   System.out.println("Serveur démarré sur le port 9876");
// Ici on enregistre notre Bean dans le serveur, avec un ObjectName pour le définir.
   serveur.registerMBean(new ServerManager(), new ObjectName(ServerManagerMXBean.SERVER_MANAGER_MXBEAN_NAME));
                         //Après toutes les erreurs que le serveur est susceptible de renvoyer lors de l'enregistrement...
  } catch (IOException ioe) {
   System.err
     .println("Echec de l'obtention du serveur de beans. Connexion impossible "
       + ioe.getLocalizedMessage());
   ioe.printStackTrace();
  } catch (InstanceAlreadyExistsException e) {
   System.err.println("Un bean existe déjà avec ce nom");
   e.printStackTrace();
  } catch (MBeanRegistrationException e) {
   System.err.println("Impossible d'enregistrer ce bean\n"+e.getLocalizedMessage());
   e.printStackTrace();
  } catch (NotCompliantMBeanException e) {
   System.err.println("MBean non correct\n"+e.getLocalizedMessage());
   e.printStackTrace();
  } catch (MalformedObjectNameException e) {
   System.err.println("Le nom du MBean est mauvais\n"+e.getLocalizedMessage());
   e.printStackTrace();
  } catch (NullPointerException e) {
   System.err.println("Tentative d'enregistrement d'un MBean null\n"+e.getLocalizedMessage());
   e.printStackTrace();
  }
  // Ici on a le code de l'application, remplacé par un sleep de 10
  // secondes.
  try {
   Thread.sleep(120000);
  } catch (InterruptedException e) {
  }
  System.out.println("Sortie de l'application");
  try {
   starter.killServer();
  } catch (IOException ioe) {
   System.err.println("Erreur lors de l'arrêt sur serveur, était-il lancé? : "+ioe.getLocalizedMessage());
   ioe.printStackTrace();
  }
 }
}
Voici un exemple de ce qui est visible dans JConsole lors de la connexion au serveur JMX de l'application, notre Bean apparaît, les getters et les setters sont regroupés dans les attributs et les opérations moins standard se retrouvent dans Opérations :


Exemple du résultat de l'appel à l'opération listApplications : 

Les notifications

Pour les notifications, il n'y a pas de méthode magique comme pour la decouverte des méthodes d'un Bean, l'objet qui veut envoyer des notifications, il doit soit implémenter NotificationEmitter soit étendre de NotificationBroadcasterSupport. Dans tous les cas, il doit créer un objet Notification et appeler NotificationBroadcasterSupport.sendNotification
Je n'ai pas creusé les notifications aussi bien que le reste alors je vais vous livrer un exemple tiré du site officiel de Java, adapté au code d'exemple. Dans notre code, on va envoyer une notification à chaque fois que quelqu'un modifie le port de l'application. On peut aisément imaginer un listener de notification, dès qu'il reçoit une information comme quoi le port a changé, il redémarre le serveur pour qu'il puisse prendre ne compte la modification. Nous n'irons pas jusque là. Tout d'abord le lien vers ma source : Lesson: Notifications (The Java™ Tutorials > Java Management Extensions (JMX))

D'ailleurs pour cette suite d'article je me suis beaucoup appuyé sur la documentation officielle pour comprendre le fonctionnement de JMX. Voici les changements apportés au bean pour lui permettre d'envoyer des notifications :
public class ServerManager extends NotificationBroadcasterSupport implements ServerManagerMXBean {
 private int port = 12345;
 private long sequenceNumber = 0; //Le sequence Number permet de gérer l'ordre d'envoi des notifications.
 ...
        //Ici la méthode passe synchronized, on ne sait jamais.
 @Override
 public synchronized void setPort(int port) {
  if(port != this.port){
   System.out.println("On redéfini le port d'écoute du serveur sur : "+port);
   Notification n = new AttributeChangeNotification(this,
     sequenceNumber++, System.currentTimeMillis(),
                    "Le port à changé", "Port", "int",
                    this.port, port); //La notification...
   this.port = port;
sendNotification(n);
  }
 }

 // On met à jour les informations de notification.
 @Override
 public MBeanNotificationInfo[] getNotificationInfo() {
  String[] types = new String[] { AttributeChangeNotification.ATTRIBUTE_CHANGE };

  String name = AttributeChangeNotification.class.getName();
  String description = "Un attribut de ce MBean à changé";
  MBeanNotificationInfo info = new MBeanNotificationInfo(types, name,
    description);
  return new MBeanNotificationInfo[] { info };
 }
}
Voilà les notifications sont prêtes pour notre Bean, il ne reste plus qu'à voir si ça fonctionne, on relance la JConsole comme toujours. Un champ Notifications supplémentaire apparaît sous notre Bean. En cliquant dessus on peut s'abonner à celui-ci par le bouton Subscribe c'est ce qui a été fait ci-dessous :

Conclusion

Tout au long de ces articles nous avons vu la puissance de JMX, un outil permettant de faire de la gestion/supervision de machine virtuelle Java simplement.
Nous avons vu comment surveiller l'utilisation des ressources JVM, en local ou a distance grâce à JConsole, je vous conseille également JVisualVM qui est plus développée et qui est livrée avec Java 7.
Nous avons également vu comment écrire un client permettant d'interroger les informations de la JVM exposées par JMX et par ce biais nous avons appris à lire toutes les informations mises à dispositions par JMX.
Dans cet article nous avons vu comment exposer nous même des informations en passant JMX et comment écrire des méthodes de gestion distantes d'une application. Le redémarrage d'un serveur d'application est un exemple parmi toutes les possibilités. La plupart du temps ce sont des fonctionnalités d'audits qui sont activées à distance.
Ce que nous n'avons pas vu et qui fera peut être l'objet d'un quatrième article et tout ce qui concerne la sécurité, la gestion de l'authentification et le problème de timeout des connexions qui n'est pas réglable. J'ai trouvé un excellent exemple sur le sujet que je n'ai pas encore partagé... L'occasion de tout traiter dans le prochain volet.

jeudi 1 décembre 2011

Java : Gérer la JVM avec Java Management Extensions (JMX) Technology - Partie 2 - Création d'un client

Avoir un client JMX peut être pratique mais surtout si on a un programme distant. Si tout se passe en local l'intérêt est vraiment moindre. Pour permettre au programme Java d'être à l'écoute de connexions distantes, il faut rajouter les paramètres suivants :
  • -Dcom.sun.management.jmxremote 
  • -Dcom.sun.management.jmxremote.port=PORT
Ces deux paramètres bien que suffisants, demandent un nom d'utilisateur/mot de passe inscrit dans un fichier de paramètres de la JVM.
Pour désactiver la sécurité voici les paramètres à rajouter :
  • -Dcom.sun.management.jmxremote.authenticate=false
  • -Dcom.sun.management.jmxremote.ssl=false
Bien sûr dans un environnement de production, il peut être utile de bien configurer sa JVM pour qu'elle ne soit pas ouverte à tous les vents. La documentation officielle explique ça très bien, pour peu que l'anglais ne soit pas un problème.

Voici un petit exemple d'une ligne de lancement Java avec les propriétés permettant d'autoriser les appels distants.
java -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9021 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -jar mon_programme.jar

Création de l'agent

Pour se connecter et commencer à dialoguer avec un serveur JMX il faut passer par trois étapes.
  • La création d'un objet URL contenant l'URL de connexion
  • L'appel de la factory pour récupérer la connexion.
  • La création de la couche de communication aux MBeans.
Dans notre cas ça donne donc :
import java.io.IOException;
import java.net.MalformedURLException;
import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL; 

public MBeanServerConnection connect(String server,int port) throws MalformedURLException,IOException,Exception {
    String connectionString = "service:jmx:rmi:///jndi/rmi://"+server+":"+port+"/jmxrmi";
    JMXServiceURL url= new JMXServiceURL(connectionString);
    JMXConnectorFactory jmxc= JMXConnectorFactory.connect(url);
    return jmxc.getMBeanServerConnection();
}

Toutes les interactions faites avec le serveur JMX passent par l'objet MBeanServerConnection. La chaîne de connexion décrite ci-dessus est celle par défaut, mais selon la configuration elle peut varier.


Organisation des objets



Les objets n'ont pas de hiérarchie précise, le niveau que l'on peut obtenir à partir de la connexion est ce que JMX appelle les domaines. Le reste des niveaux que l'on retrouve dans JConsole est déduit par la console d'après des règles qui n'ont rien d'obligatoire.
Les objets sont enregistrés avec un nom. Leur nom défini leur hiérarchie virtuelle. Un objet est nommé de la manière suivante : "domain : propriété1=val, propriété2=val ...".
Ainsi pour gérer la mémoire on va travailler avec l'objet ayant pour nom :
"java.lang:type=Memory"
Si on veut plus de détail et travailler avec l' EdenSpace l'objet aura pour nom :
" java.lang:type=MemoryPool,name=PS Eden Space"

Tous ces objets sont ce qu'on appelle des MBeans pour ManagedBeans on peut retrouver leur nom dans JConsole :
Exemple de description d'un objet dans Jconsole
Dans la partie gauche on voit l'arborescence, on voit que dans java.lang, dans la sous-catégorie MemoryPool on a sélectionné PS Eden Space. Maintenant dans la partie droite, on voit les informations, les MBeanInfo, c'est une collection d'objet que l'on peut extraire du MBean. Dans ce tableau, on a l'information primordiale pour retrouver l'objet, c'est la valeur de ObjectName ( la première ligne du tableau) il s'agit de la chaîne donnée plus haut.
Pour récupérer cet objet auprès du serveur et ainsi travailler avec, on peut forger l'objet à la main :

    ObjectName eden_space= new ObjectName("java.lang:type=MemoryPool,name=PS Eden Space");

A partir de là on peut demander au serveur ses attributs, les opérations que l'on peut exécuter dessus ou s'abonner à des notifications.

L'autre manière de récupérer les objets consiste à interroger directement le serveur en lui demandant ce qu'il possède comme objet.
Les exemples que l'on trouve sur internet cherchent d'abord les domaines avant d'aller chercher les objets. On peut très bien aller chercher tous les objets sans tenir compte du domaine. L'ObjectName est utilisé lors de ces requêtes et les caractères wildcard "*" sont utilisés pour compléter les noms. Ainsi pour chercher tous les éléments enregistrés sur le serveur on peut écrire :


   MBeanServerConnection mbsc = this.connect("serveur",9021);
   ObjectName tous= ObjectName.WILDCARD; // équivalent à new ObjectName("*:*");
   Set<ObjectName> objets = mbsc.queryNames(tous,null);
L'exemple avec d'abord la recherche de domaine :
  MBeanServerConnection mbsc = this.connect("serveur",9021);
   String domains[] = mbsc.getDomains();
   Arrays.sort(domains);
   for(String domain : domains){
     Set<ObjectName> names = new TreeSet(mbsc.queryNames(new ObjectName(domain + ":*"), null));
   }


Maintenant que la façon de retrouver les objets est connue, on peut accéder à toutes les ressources gérées par la machine virtuelle. Elles sont regroupées en trois catégories :
  1. Les notifications
  2. Les attributs
  3. Les méthodes que l'on peut appeler

Les Notifications

Pour les notifications, il suffit de savoir si un objet est un broadcaster . Si c'est bien le cas on peut écouter les notifications qu'il va envoyer. Voici le test pour savoir si un objet est capable d'envoyer des notifications :
private boolean isBroadcaster(ObjectName name,MBeanServerConnection mbsc) {
    try {
        return mbsc.isInstanceOf(name, "javax.management.NotificationBroadcaster");
    } catch (Exception e) {
        System.out.println("Error calling isBroadcaster: " +e.getMessage());
    }
    return false;
}

Ensuite pour s'y abonner, il faut enregistrer un objet NotificationListener sur le serveur et les écoutes sont démarrées. Voici un code très basique pour le NotificationListener :
public class MyListener implements NotificationListener {
   
    @Override
    public void handleNotification(Notification notification, Object handback) {
        System.out.println(notification.getTimeStamp());
        System.out.println(notification.getType());
        System.out.println(notification.getSequenceNumber());
        System.out.println(notification.getSource());
        System.out.println(notification.getMessage());
    }
};
Le code permettant de s'abonner aux notifications:
if(isBroadcaster(objectName,mbsc)){
    mbsc.addNotificationListener(objectName, new MyListener(), null, null); 
}


Les codes sont simplistes et ne révèlent pas toutes les possibilités offertes avec ces fonctions. Par exemple les deux null que l'on voit dans l'appel à addNotificationListener , le premier s'agit un filtre NotificationFilter l'autre de l'objet handback qui sera passé en paramètres de handleNotification. Le filtre est une interface simple qui dit si oui ou non le message doit être filtré.

Les attributs

Plusieurs étapes sont nécessaires pour lister les attributs d'un objet, la première est récupérer les informations auprès du serveur, l'objet qui en résulte est un MBeanInfo pour le récupérer il suffit d'appeler la méthode getMBeanInfo de la connexion au serveur. Depuis cet objet on peut récupérer la liste des constructeurs, la liste des attributs, la liste des notifications envoyées et la liste des opérations. Dans cette section ce qui nous intéresse c'est la liste des attributs, voici le code pour l'obtenir :
try {
    mbi = mbsc.getMBeanInfo(name);
    MBeanAttributeInfo[] attributes = mbi.getAttributes();
} catch (Exception e) {e.printStackTrace();}


Avec ce code on récupère un tableau d'objets MBeanAttributeInfo qui contiennent les informations nécessaires pour déterminer quel type de données se cache derrière les attributs appelés. Les données sont de type OpenType et sont séparées en deux grandes catégories: les données simples et les données composites. Les données simples sont toutes de type SimpleType, les données composite peuvent-être de type CompositeType ou ArrayType ou encore TabularType SimpleType représente un type d'objet proche des types primitifs, (Integer,BigDecimal,String,Void,Date...) la liste n'est pas exaustive. ArrayType est un tableau a n dimensions ( récupérables par la méthode getDimension() ) contenant tous les types autorisés par OpenType ( voir la constante ALLOWED_CLASSNAMES_LIST ) comme les tableaux ils ne peuvent contenir qu'un de ces types à la fois. Le type CompositeType est une sorte de table de hashage d'OpenType et peut contenir tous les types d'OpenType, avec ce type, on peut avoir accès à un nombre illimité de structure et de sous-structure. Les TabularType sont la représentation OpenType des Map Java. La donnée memoryUsageAfter de l'attribut lastGCinfo du MBean GarbageCollector est de ce type. Ces types sont des descripteurs, ce ne sont pas eux qui transportent les données. L'équivalent données, on la même convention de nommage en replaçant Type par Data. Pour récupérer la liste des attributs on peut boucler sur les MBeanAttributeInfo[], un objet Descriptor donne le type d'openType dont il s'agit. Le code qui suit n'affiche qu'un sous niveau d'attributs ( dans le cas d'un ArrayType on considère une seule dimension ) :
for (MBeanAttributeInfo info : attributes) {
    Descriptor d = info.getDescriptor();
    Object val = d.getFieldValue("openType");
    String nom = info.getName();
    if (val instanceof CompositeType) {
        CompositeType ct = (CompositeType)val;
        for (String key : type.keySet()) {
            System.out.println(nom + "/" + key); //"On affiche la clé de la ressource trouvée"
 }
    }else if (val instanceof ArrayType){
        @SuppressWarnings("unchecked")
 ArrayType<OpenType>?<[]> t = (ArrayType<OpenType>?<[]>) val;
 OpenType ot = t.getElementOpenType();
        // Ici on obtient une récursion, ArrayType pouvant contenir n'importe quel OpenType,
        //le mieux à faire et de repasser dans le if/else if/.. dans lequel on se trouve.
    }else if (val == null) { //Correction d'un problème d'incompatiblité Java 1.5
        String type = info.getType();
        if(type != null && type.contains("CompositeData")){ // Exemple avec CompositeData, le problème ne se pose pas avec les types simple
            try {
         CompositeData cd = (CompositeData) mbsc.getAttribute(objectName, nom);
                //On pourrait appliquer cette méthode pour toutes les versions de Java mais elle est très coûteuse en temps.
  if(cd != null){
      CompositeType ct = cd.getCompositeType();
      //Maintenant qu'on a le CompositeType on le traite comme au dessus
                    for (String key : type.keySet()) {
                        System.out.println(nom + "/" + key); //"On affiche la clé de la ressource trouvée"
             }
  }
     } catch (Exception e) {
        e.printStackTrace();
        } 
    }else{
        System.out.println(nom)
    }
}

Ce code d'exemple liste tous les attributs d'un objet inscrit dans le serveur JMX de la JVM sans s'occuper de son type, il ne fait qu'afficher le nom des ressources.
Pour le client qui récupérera les données, le principe est le même on intervient sur la connexion au serveur on lui demande un attribut précis et lui renvoie un OpenData contenant la réponse. Voici un code d'exemple qui écrit dans la sortie standard la réponse et qui décompose les types composites :
   public void printVal(MBeanServerConnexion mbsc,ObjetName objet,String attributs){
        String[] attribs = attribut.split("/");
        Objet val = mbsc.getAttribute(objet,attribs[0]);
        if(val instanceof CompositeData){
            if(attributes.length >1){
                String value = ""+((CompositeData) val).get(attribs[1]);
                System.out.println(attribs[0]+"/"+attribs[1]+" : "+value);
            }
        }else if(attr instanceof CompositeData[]){
            CompositeData[] datas = (CompositeData[])val;
            String value="";
            boolean first =true;
            for(CompositeData da : datas){
                if(attributes.length >1){
                    if(first)first=false;
                    else value+=",";
                    value += ((CompositeData) da).get(attributes[1]);
                }
            }
            System.out.println(attribs[0]+"/"+attribs[1]+" : "+value);
        }else if(attr instanceof Object[]){
            Object[] datas = (Object[])val;
            String value="";
            boolean first =true;
            for(Object d: datas){
                if(first)first=false;
                else value+=",";
                value += d;
            }
            System.out.println(attribs[0]+" : "+value);
        }else{
            System.out.println(attribs[0]+" : "+val);
        }
    }

Ce code d'exemple, outre le fait que de nombreuses parties pourraient faire partie d'autres fonctions pour factoriser montre comment on récupère un attribut d'un objet et comment le découper lorsqu'il s'agit d'un CompositeData. Il faut appeler la valeur sous la clé, si cette valeur est un CompositeData, on peut redescendre d'un niveau et ainsi de suite. Dans l'absolu il n'y a pas de limite dans les niveaux, cet exemple n'est pas exhaustif dans la mesure où le principe est d'écrire dans la sortie standard et non de faire un graphique ou un quelconque autre usage.
Il est bon de savoir que des objets sont dédiés aux fonctions de la JMX. Ces objets sont récupérables en utilisant les Proxy. Voici l'accès par proxy comme indiqué dans la documentation Oracle :
   MBeanServerConnection mbsc;

   // Connect to a running JVM (or itself) and get MBeanServerConnection
   // that has the JVM MBeans registered in it
   ...

   // Get a MBean proxy for RuntimeMXBean interface
   RuntimeMXBean proxy = 
       ManagementFactory.newPlatformMXBeanProxy(mbsc,
                                                ManagementFactory.RUNTIME_MXBEAN_NAME,
                                                RuntimeMXBean.class);
   // Get standard attribute "VmVendor" 
   String vendor = proxy.getVmVendor();

Les Opérations

Les opérations dans JMX sont invoquées un peu de la même manière que ce que l'on ferait en Java classique avec l'introspection de l'API Reflection. En effet il faut appeler le nom de la méthode et donner sa signature. La différence c'est que tout se fait un seul appel, les paramètres aussi sont passés lors de l'appel. Pour récupérer la liste des Opérations disponibles, une méthode dans MBeanInfo permet de le faire, il s'agit de la méthode, getOperations() , comme indiqué dans l'exemple suivant :
try {
    MBeanInfo mbi = mbsc.getMBeanInfo(name);
    MBeanOperationInfo[] operations = mbi.getOperations();
} catch (Exception e) {e.printStackTrace();}

L'objet MBeanOperationInfo renseigne sur le champ d'action de la méthode, sa valeur de retour et sa signature. Commençons par le champ d'action, il s'agit d'un entier comparable a plusieurs constantes de la classe indiquant si la méthode est en lecture, ecriture, les deux ou si c'est impossible à déterminer :
    MBeanOperationInfo[] operations = mbi.getOperations();
    for ( MBeanOperationInfo operation : operations ) {
        int impact = operation.getImpact()
        if(impact == MBeanOperationInfo.INFO) {
            System.out.println(operation.getName() + " est en lecture seule ");
        }else if (impact == MBeanOperationInfo.ACTION){
            System.out.println(operation.getName() + " va engendrer une modification ");
        }else if (impact == MBeanOperationInfo.ACTION_INFO){
            System.out.println(operation.getName() + " va engendrer une modification et renvoie une information ");
        }else if (impact == MBeanOperationInfo.UNKNOWN){
            System.out.println(operation.getName() + " va faire quelque chose mais quoi? ");
        }
        // Comme il s'agit d'entier on peut tester si la méthode retournera une information peu importe si elle est en écriture en utilsant un masque.
        if(impact &  MBeanOperationInfo.INFO ==  MBeanOperationInfo.INFO){
           System.out.println(operation.getName() + " Va renvoyer une information ");
        }
    }

La valeur de retour de la méthode peut être obtenue de différentes façon, selon qu'il s'agisse un MXBean ou d'un MBean. La méthode getReturnType() est celle qui va renvoyer une valeur quoiqu'il arrive mais la valeur retournée n'est pas de type OpenType. En effet elle retourne le nom de la classe de retour.
    MBeanOperationInfo[] operations = mbi.getOperations();
    for ( MBeanOperationInfo operation : operations ) {
        System.out.println(operation.getName() + " : "+operation.getReturnType());
        //peut retourner une information comme celle-ci findMappings : [Ljava.lang.String;
        //C'est à dire que la valeur de retour sera un tableau ( le [L ) de chaine de caractères
    }
L'autre manière pour récupérer le type de retour est d'utiliser le descripteur. Comme dans le cas des attributs, les descripteurs possèdent deux champs importants. Le champ openType et le champ originalType, dans notre cas on va traiter le champ openType qui fonctionne comme pour les attributs et donc on peut traiter les données de la même manière.
    MBeanOperationInfo[] operations = mbi.getOperations();
    for ( MBeanOperationInfo operation : operations ) {
        Descriptor desc = operation.getDescriptor();
        Object val = desc.getField("openType");
        if(val ==null){// ça n'est pas un openType.
            //Utilisation de operation.getReturnType();
        }else{
            OpenType<?> type = (OpenType<?>) val;
            //Traitement standard.
            //Si rien n'est renvoyé ( void ) il s'agit d'un SimpleType de void ( SimpleType<Void> ) 
        }
    }
Pour la signature le de méthode, le même procédé est applicable, on peut récupérer la liste des paramètres avec leur type simple ou leur OpenType lorsqu'il existe.
    MBeanOperationInfo operation;// Récupéré des autres méthodes.
    MBeanParameterInfo[] params = operation.getSignature();
    System.out.print(operation.getName() +":");
    for(MBeanParameterInfo param : params){
        System.out.print(param.getName()+" - "+param.getType()+ " - "+param.getDescriptor().getField("openType") + ",");
    }
    System.out.println();
Maintenant que l'on a la méthode, son type de retour et surtout sa signature on peut se mettre à invoquer des méthodes. Il suffit pour cela d'appeler la méthode invoke de l'objet faisant le pont en le client et le serveur ( le MBeanServerConnection ). Pour cela il faut appeler la méthode d'un objet en utilisant le nom de l'objet, le nom de la méthode, en lui passant des paramètres et sa signature. Un exemple simple, une méthode qui ne prend aucun paramètre et qui ne renvoie rien, la méthode gc() de l'objet Memory
     MBeanServerConnection mbsc = ...;
     mbsc.invoke(new ObjectName("java.lang:name=Memory"),"gc",new Object[]{},new String[]{});

Une petite explication s'impose, les paramètres sont passés en tant que tableau d'objet, ici vide vu qu'il n'y aucun paramètre. Par contre la signature est en mode texte, ainsi bien que les OpenType soient gérés, il semble obligatoire d'utiliser la signature en mode simple ( avec le retour de param.getType() lorsque l'on tente de connaître la signature de la méthode ). Ainsi si l'on veut invoquer une méthode prenant en paramètre un tableau de chaîne de caractère et un long il faudra créer un table de String contenant les signatures de ces deux types.
     String[] signature = new String[]{"[Ljava.lang.String","long"};
     Object[] params = new Object{ signature, 32L}; // Par commodité je passe la signature car c'est un tableau de chaine de caractère.
     Object retour =  mbsc.invoke(new ObjectName("mondomaine:name=MonObjet"),"mamethode",params,signature));
Vous avez maintenant toutes les cartes en main pour créer un agent JMX. La prochaine partie parlera du côté serveur avec la possibilité de créer ses propres objets et de les appeler à la manière de RMI mais en beaucoup plus souple ( bien qu'il s'agisse bien souvent de RMI pour la couche transport). Elle dévoilera un petit exemple de bout en bout de mon cru. Le sujet est en cours d'élaboration, la patience est de mise !

vendredi 18 novembre 2011

Java : Gérer la JVM avec Java Management Extensions (JMX) Technology - Partie 1 - Présentation

JMX permet la gestion de resources entre deux machines virtuelles Java. La JVM implémente par défaut JMX pour permettre la supervision et la gestion de ses arcanes internes. On peut surveiller par exemple les différents espaces mémoire utilisés par un programme Java au moyen de JMX.
Un programme de gestion est livré avec le JDK et est une excellente vitrine technologique, il s'agit de JConsole.

JConsole se lance en tapant la commande jconsole dans une fenêtre d'execution ( windows+R sous windows, souvent Alt+F2 sous linux ). Si java est dans le path système, sinon l'executable se trouve avec les autres binaires java ( dans JAVA_HOME/bin si la variable est définie).

Fenêtre de démarrage de JConsole

Dans cet exemple je vais utiliser JConsole pour gérer JConsole. Biensûr n'importe quel programme Java peut-être géré ( sur la machine locale), et pour des raisons de sécurité, il faut activer la gestion par RMI ou par un autre protocole pour les accès distant. On peut également protéger l'accès par nom d'utilisateur mot de passe.

Onglet  Overview, la page d'accueil de JConsole


Une fois connecté au programme que l'on veut gérer, JConsole montre une fenêtre qui récapitule ce qui se passe sur l'instance sur laquelle on est connecté.
Onglet Memory, vue de la mémoire et actions

 L'onglet Memory affiche une vue détaillée de la mémoire de la JVM mais ce qui est important est la présence du bouton Perform GC, JMX permet donc, non seulement d'interroger une machine distante mais également lui donner l'ordre d'executer certaines actions.
Les autres onglets sont des vues détaillées des autres fonctions.
L'onglet MBeans par contre a un intérêt tout particulier car il dévoile le fonctionnement JMX et il a la particularité d'être dynamique. Si d'aventure dans un programme on rajoute des MBeans ( pour Managed Beans) JConsole sera capable de les voir et d'interragir avec.
Vue Arborescente et dynamique de la mémoire - Fonction gc

Ici on retrouve ce qu'on avait dans la vue "Memory", c'est moins lisible mais c'est un exemple d'affichage de tout ce que la JVM dévoile au grand jour. Ici, bien que le résultat soit le même par rapport à Perform GC dans l'onglet Memory, l'Opération gc() est listée en examinant le serveur.

lundi 7 février 2011

Migration de SVN vers Mercurial : Partie 3 Méthode Avancée, Utilisation des outils Mercurial

Activation de l'extension


Si Mercurial a la bonne idée de proposer des outils de conversion d'un système à un autre, ceux-ci ne sont pas activés par défaut.
Sur le site de Mercurial on peut lire que l'on peut configurer le logiciel par utilisateur en créant des fichiers de configuration dans le répertoire utilisateur.
Sous linux le fichier s'appelle .hgrc et se crée dans le dossier home de l'utilsateur. Habituellement ~/.hgrc permet de retrouver le fichier.
nano ~/.hgrc
Sous Windows Mercurial cherche le fichier Mercurial.ini là où pointe la variable %userprofile%
notepad %userprofile%/mercurial.ini

Je vous conseille ( comme celà est fait sur le site Mercurial) de renseigner un nom d'utilisateur. Dans le fichier de conf il suffit de créer la section [ui]. Pour activer l'extension permettant la conversion il faut ajouter hgext.convert=
Si l'extension n'est pas à l'endroit par défaut il faut taper hgext.convert=chemin/vers/lextension.py

[ui]
username=Fabien <fabien@techno-revue.fr>

[extensions]
hgext.convert=

TortoiseHg génère l'extension sans le préfixe hgext donc, si rajouter hgext ne fonctionne pas il faut essayer de l'enlever.
Pour générer ces paramètres avec TortoiseHg il faut faire afficher le menu contextuel ( clic-droit) pour voir apparaitre TortoiseHg. Dans le sous-menu une entrée global settings vous permet de définir les paramètres par défaut.


Pour activer l'extension convert dans TortoiseHg rien de plus simple, une fois dans les Global Settings il suffit d'aller dans la catégorie Extensions ( l'avant dernière ) et de cocher convert comme ci-dessous:

Paramétrages des données 

Mise en concordance des utilisateurs

Il faut pour se faire créer un fichier contenant les auteurs SVN et leur nouveau nom Mercurial ( s'ils changent bien entendu).
dans votre fichier ( que j'ai nommé auteurs.txt ) vous pouvez trouver des lignes comme celle-ci.
nomsvn=nommercurial <adresse@email.com>
ce qui donne dans mon cas
fab=Fabien <fabien@techno-revue.fr>
clem=Clément <clement@techno-revue.fr>
flow=Florian <florian@techno-revue.fr>

Gestion de l'inclusion ou de l'exclusion des fichiers


Vous pouvez également créer un fichier de mappage de fichiers.
On va l'appeler filemap.txt
Vous pouvez vous en servir pour exclure des fichiers. Par exemple des vidéos dont vous ne vous servirez plus
exclude *.avi
Le fichier n'est pas nécessaire si vous voulez tout inclure.

Gestion des branches et des tags


Là encore cette section n'est pas forcément utile, si vous n'utilisez qu'un tronc principal dans votre dépôt SVN sans même le mettre dans un sous-dossier trunk ou similaire.
Dans tous les autres cas vous allez sûrement apprécier ces petites fonctionnalités.
La plupart du temps sans SVN vous organisez votre dépôt en créant trois sous-dossiers nommés branches,tags et trunk.
Les options de conversion permettent de paramétrer chacun d'eux de la manière suivante :
--config convert.svn.branches=repo/branches ( si vous avez mis vos branches dans toto le chemin est repo/toto )
--config convert.svn.tags=repo/tags ( pareil si vous avez mis vos tags dans stable/libelle la config sera --config convert.svn.tags=repo/stable/libelle

--config convert.svn.trunk=repo/trunk ( votre répertoire de travail principal).

Une autre option pratique est --branchsort qui vous permet de trier les branches par révision pour gérer la notion de parent-enfant de branche sous-branche. Vous avez autrement la possibilité de mettre --datesort pour trier par ordre des révisions.

Paramètres supplémentaires


Si vous avez beaucoup de révision SVN et que ça ne vous intéresse pas de tout réimporter dans Mercurial vous pouvez également définir le paramètre --config convert.svn.startrev

Par exemple vous avez 8000 révisions et vous voulez garder les 2000 dernières.
--config convert.svn.startrev=6000
Je n'ai pas creusé l'option mais il y a également une option --branchmap qui permet à l'aide d'un fichier d'indiquer quelle branche SVN correspondra à quelle branche Mercurial

Migration effective


Maintenant que la migration est prête avec toutes les réglages et les options qui vont bien, il faut lancer le processus.
La première étape consiste à créer un dépôt mercurial, avec la commande suivante :
mkdir projet
cd projet
hg init
En gros il faut créer un dossier pour son projet et faire un hg init dedans pour créer le dossier .hg qui gérera tout votre dépôt.

Ensuite il n'y a plus qu'à lancer la commande de conversion.
hg convert --config convert.svn.trunk=repo/trunk --config convert.svn.tags=repo/tags --config convert.svn.branches=repo/branches --config convert.svn.startrev=6000 --filemap=chemin/vers/filemap.txt --authormap=chemin/vers/auteurs.txt --source-type=svn --dest-type=hg svn://MonRepoSvn chemin/vers/mon/projet

Une fois que les données sont bien moulinées, il faut aller dans le dossier de votre projet Mercurial et faire un commit puis un update et enfin un push vers un éventuel dossier distant.
hg commit
hg update
hg push https://neibaf@utils.techno-revue.fr/monrepo --insecure

Notez chez moi l'utilisation de l'option --insecure C'est parceque utils.techno-revue.fr est un alias pour bitbucket.org et le certificat SSL est celui de bitbucket.org. Du coup les outils mercurial affichent un message comme quoi le certificat n'a pas le bon nom de domaine et arrête la transaction.
Pour forcer ça il faut utiliser l'option --insecure.

Voilà, maintenant tout le monde est sur Mercurial!