jeudi 24 juillet 2014

Optimisation HomeAutomate



      Depuis peu, je remarque des temps de réponses lents lors de l'affichage des pages d'HomeAutomate. Trop c'est trop, j'ai donc décidé d'investiguer sur les raisons de cette lenteur. Il est vrai qu'on ajoute et on ajoute des fonctionnalités et au final cela charge la mule.

      Approche : 

      La question ultime à se poser : 


Est-ce la partie back-end ou le front-end qui provoque ce lag ?

      (1ère partie) Pour le back-end : 


      Inutile de vous rappeler qu'HomeAutomate est écrit en Java, qui dit Java dit mémoire et GC (Garbage Collector : mécanisme qui va en résumé : observer les objets "déférencés" et les supprimer automatiquement de la JVM, l'équivent d'un free en C++). En effet un objet qui n'est plus référencé par un autre objet, ne sert plus.


      Il existe un outil formidable pour analyser/décortiquer ce qui se passe dans une JVM, il s'appelle VisualVM. (Gratuit, vous pouvez le télécharger sur cette adresse : http://visualvm.java.net ).


      Pour utiliser VisualVM, il faut au préalable modifier la ligne de commande de lancement de votre java (que cela soit un batch ou un serveur d'application comme TOMCAT). Il faut ajouter le paramètre suivant dans la ligne de commande (9999 correspondant au port d'écoute JMX, et oui VisualVM utilise JMX)

    -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9999


      Après avoir modifié votre ligne de commande, relancez votre serveur d'application ou votre batch.

      Ouvrez ensuite bin/VisualVM.exe ou bin/visualvm sous Unix (après l'avoir téléchargé et décompressé) :


     Attention, si VisualVm ne se lance pas, vérifiez le fichier de configuration etc/visualvm.conf, votre chemin JDK n'est peut être pas bon, vous pouvez le customiser dans ce fichier de configuration.

     L'outil se lance :





    Première chose à faire avant "tout installer" tous les plugins disponibles, allez sous Tools/Plugin/Available Plugins et installer tout ! L'outil pourra redémarrer.

    Deuxième chose à faire, déclarer votre machine/serveur où tourne votre java, pour cela dans le menu de gauche faites un clic droit sur Remote puis Add Remote Host (Ps si votre programme java tourne en local, vous devriez le voir dans la partie 'Local'  au dessus de Remote.





   Ajoutez alors votre serveur (Rien de spécial), puis cliquez droit sur votre serveur et Add JMX Connection : 


    
   Ajouter le n° de port derrière le serveur et cliquez sur 'Do not require SSL connection' si vous n'avez pas activé la connection sécurisée à la ligne de commande (au dessus)




  Double-cliquez sur la ligne JMX sous votre serveur et vous devriez voir cela (Si la connection échoue, il y a peut être un pb de port, de firewall, de ligne de commande java, ...) :





  On retrouve, plusieurs onglets : 

    - OverView (retrouve les options de lancement et les caractéristiques de la JVM)
    - Monitor (permet de visualiser l'utilisation mémoire au sein de la JVM, la charge CPU de votre application, le nombre de classes instanciées, le nombre de Threads)

    


      A noter, 2 boutons intéressants, Perform GC (qui va permettre de forcer le passage du garbage qui normalement est délégué à la JVM) et Heap Dump (qui va décharger la totalité de la JVM dans un fichier, lisible par la suite). On remarque sur le graphique Orange, l'effet escalier typique d'un fonctionnement du GC, des objets sont instanciés, donc la mémoire augmente, les objets ne sont plus utilisés, le garbage passe et la courbe mémoire redescend. Vous pouvez déjà vérifier que cette courbe reste stable dans le temps, si elle tend à augmenter et ne jamais diminuer vous encourez alors de gros problèmes. 
   
     Dans mon projet, la taille maxi mémoire est de 256Mo, j'estime cela suffisant pour ce que je fais, mais votre projet est il suffisamment taillé en mémoire ? rappel cette allocation s'effectue avec les options -Xms (pour le mini réservé) et -Xmx (pour le maxi réservé). Ex : -Xms128M -Xmx256M forcera la JVM à s'allouer 128M et l'autorisera à grignoter jusqu'à 256M (Malloc en C)

     - Thread (permet d'obtenir des informations si votre projet et multi-threadé).





      On y retrouve les informations sur les Threads et les temps passés de chacun, on peut connaitre leurs états (Running, Sleeping, etc...) Mon projet étant multi-threadé, en détaillant le Thread-3, on peut savoir exactement quelle opération il est en train d'effectuer, des threads trop gourmands en temps CPU peuvent pénaliser la JVM. Dans l'exemple, mon thread alterne entre Sleep et Running (Travail de fond qui se connecte à une Socket)

     - Sampler (permet d'effectuer un échantillonnage de la JVM), cela permet d'identifier toutes les méthodes qui sont appelées, combien de fois, combien de temps et de faire des recoupements très intéressant si vos problèmes ne sont pas des fuites mémoires mais des temps de traitements longs.



     Rien de plus facile, cliquez sur CPU et laissez les mesures s'enregistrer puis cliquez sur Stop. Vous apercevrez alors les méthodes les plus consommatrices (hotspot) en cliquant sur les colonnes Self-time, CPU,  pour trier par ordre décroissant. Ici une méthode fillMacPresent (qui me permet fonctionnellement de savoir quels périphériques réseaux sont présents sur mon réseau interne à l'aide la MAC), cette méthode a duré 794ms pendant l'enregistrement. La case à cocher settings permet de filtrer certains packages pour ne s’intéresser qu'aux nôtres.

    Une fois l'enregistrement terminé, cliquez sur le bouton, Snapshot :


   Une snapshot est alors sauvegardée et visible dans le bandeau de gauche et VisualVM vous ouvre un nouvel onglet sur votre Snapshot.
   



   Recherchez votre méthode qui coûtait cher (ici fillPresentMac), on constate qu'elle est appelée dans le Thread-10, par la méthode doTraitement et qu'apparement ce qui dure le plus c'est le isReachable (le ping que je fait) qui dure 10 secondes ! énorme ! On pourra également comparer des snapshots avec l'option compare en haut du tableau.

   Voici donc une bonne base pour trouver/détecter des méthodes anormalement longues. Attention les 10 secondes correspondent au temps cumulés, cela peut signifier que la méthode a pu être appelée 10 fois et la méthode isReachable dure finalement 1 seconde.

   Note : si le bouton MEMORY est grisé, cela signifie qu'il faut essayer de le lancer en local sur votre machine au lieu du remote.


     Revenons maintenant à d'éventuels problèmes mémoire et comment les cerner, retournons sur l'onglet Monitor et cliquons sur le bouton heapDump, une popup apparait et demandera le chemin où sera stocké le dump (attention sur la machine en remote). Cliquez sur OK, le dump est plus ou moins long suivant la taille de la JVM (256Mo c'est 10 secondes maxi chez moi).

     Allez maintenant dans le menu File puis load ...


     Allez alors ouvrir le dump précédemment créé (Attention, il faut penser à changer les extensions de fichier dans la boite de dialogue): 




    Cette vue va vous permettre d'identifier le nombre d'instance d'objet mais aussi la taille estimée et le contenu de certains objets (pratique) : le bouton find permet de rechercher les x objets les plus gourmands en taille.

   

     A noter également l'onglet classes, qui permet de chercher dans toutes les classes :




    En double cliquant sur une classe/type d'objet, on peut alors inspecter sont contenu photographié lors du dump, trop fort non ? Ici, une treemap estimée avec une taille de 288 099, contenant mes objets Jdb (Journal de bord) :





    Voilà, la partie analyse Back-End est terminée, dans mon cas, il y a effectivement la méthode fillMac qui dure un peu plus longtemps que prévue mais rien de bien méchant dans l'ensemble. 
Et j'avais également laissé un peu trop d'objets dans le JDB, j'ai préféré modifier le paramètre fonctionnel plutôt que d'augmenter les Xmx. Vous noterez qu'une connaissance du projet et son fonctionnement est indispensable.


Mon flair m'oriente maintenant vers la partie Front-End, à suivre....