Depuis la version 18.10 de Centreon, nous avons intégré la bibliothèque JavaScript React afin d’offrir une interface graphique plus dynamique et plus intuitive. Nous avons tout d’abord adapté les Top Counters en haut de l’application ainsi que la page Extension Manager dans laquelle vous pouvez gérer vos modules ainsi que vos Widgets. Depuis, nous avons implémenté d’autres pages en React comme les pages pour les Activités Métier et les Vues Métier, puis les pages du module d’Auto Découverte. Depuis la version 20.04, les pages des détails des hôtes et des détails des services ont été regroupées en une seule page Statut des Ressources.
Chers utilisateurs, avec cet article, nous souhaitons vous partager notre expérience de développement et d’optimisation front end en vous expliquant en détail le travail que nous avons mené sur Centreon Web et les raisons qui ont motivées nos choix technologiques.
Pourquoi React ?
Parce que cette bibliothèque JavaScript permet de développer des Single Page Application (SPA) afin de disposer d’un rendu dynamique des pages lors des interactions de l’utilisateur. De plus, avec React, chaque partie de votre page est un composant qui peut afficher un ou plusieurs éléments et dans lequel on peut intégrer une logique métier afin de modifier son affichage. Les composants sont organisés de façon hiérarchique, de sorte que chaque composant puisse avoir un ou plusieurs composants enfants. React est facile à utiliser et à apprendre, il est flexible et dispose d’une énorme communauté ainsi que d’un écosystème développé.
Ce qui a motivé l’optimisation
Au fur et à mesure que nous développons Centreon Web, nous ajoutons de nouvelles fonctionnalités et en améliorons certaines. Ce fut tout particulièrement le cas pour les pages de Statut des Ressources et de Découverte d’hôtes dans nos dernières versions. Nous avons ajouté de nombreuses fonctionnalités et nous en avons améliorées grâce à vos retours.
Cependant, ajouter des fonctionnalités à Centreon Web peut rendre l’application plus gourmande en termes de consommation mémoire jusqu’à la ralentir, d’où le rôle essentiel de l’optimisation. Il est également important pour une application web de garder une certaine fluidité et d’avoir un retour rapide de l’affichage afin de capter l’attention de l’utilisateur. Selon la loi du seuil de Doherty, il faut fournir à l’utilisateur un retour d’affichage d’au maximum quatre cents millisecondes après que ce dernier a interagi.
Le but de notre optimisation était donc de rendre l’utilisation plus fluide et l’actualisation de nos composants React la plus rapide possible, que ce soit au moment où un utilisateur interagit avec l’application ou quand l’application web reçoit des données de l’API.
Pour cela, nous avons listé les pages optimisées, à savoir les pages Statut des Ressources, Découverte d’hôtes, Activités Métier et Vues Métier.
Nous avons attribué des scénarios d’utilisation simple pour chaque page et pouvant impacter les performances dans l’application web.
Page | Scenarii |
Statut des Ressources |
|
Découverte d’hôtes |
|
Activités Métier |
|
Vues Métier |
|
Enfin, nous avons évalué les composants qui “consomment le plus” lors de l’actualisation. Pour cela, nous avons utilisé le React Devtools, une extension disponible sur Chrome et Firefox qui indique si l’application est développée avec React et qui ajoute deux onglets dans la console du navigateur.
Onglets du React Devtools
Le premier onglet Components (Composants en français) présente une arborescence des composants React actuellement affichés dans Centreon Web. Le second onglet Profiler (Profileur en Français) permet de voir dans votre application web les composants les plus “gourmands” pour répertorier les composants à optimiser, tout comme l’onglet Performance. Le résultat de cette analyse est un graphique de flamme qui représente les composants concernés.
Exemple de graphique de flamme du Profileur lorsqu’on rafraîchit la liste des ressources
Comment lire ce graphique ? Chaque bloc représente un composant React et tous les composants sont hiérarchisés. La couleur grise veut dire que le composant n’a pas été actualisé, alors que les composants de couleur ont été actualisés. Plus la couleur tend vers l’orange, plus le composant met du temps à s’actualiser. Le profileur nous indique également la durée d’actualisation de chaque composant, ainsi que celle des composants enfants rattachés.
Important : avec React, si un composant s’actualise, il actualise également ses composants enfants de façon récursive. L’inconvénient de ce principe est que des composants peuvent être actualisés alors qu’ils n’ont pas subi de modifications graphiques. Par conséquent, il est préférable de ne pas les actualiser afin de s’épargner des mises à jour inutiles et coûteuses en temps.
La gestion des états à l’origine de l’actualisation des composants
En React, il existe deux types de gestions des états (ou states en anglais). Le premier consiste à gérer les états à l’intérieur des composants. Pour cela, on peut utiliser le hook useState fourni par l’API de React. Ce hook va nous permettre d’afficher un état et de modifier l’affichage d’un composant ou le comportement de l’application.
Exemple d’utilisation du hook React useState
Par ailleurs, un état peut être de différents types : nombre, chaîne de caractères, booléenne, un objet ou même un tableau.
Il est également possible de passer des états d’un composant vers ses composants enfants via des propriétés.
Exemple d’utilisation du passage de propriétés
Le second type de gestion d’états consiste à les gérer de façon globale. Cela est plutôt pratique quand on a une architecture de composants relativement complexe et quand on souhaite manipuler un état dans plusieurs composants. Cela évite de créer un passage de propriétés à travers différents composants qui peut dégrader la lisibilité du code.
Il existe beaucoup de bibliothèques permettant de faire de la gestion d’états (state management) globale, notamment Redux mais nous utilisons le Context API de React qui est beaucoup plus facile à mettre en place (le Context permet de faire passer des données à travers la hiérarchie des composants).
En effet, il suffit de trois étapes pour créer et d’utiliser un React Context. Dans un premier temps, il faut créer le React Context :
Création d’un Context
Puis l’intégrer dans un composant, de préférence le plus haut dans la hiérarchie.
Composant implémentant un Context
Et enfin, il faut utiliser le Context pour manipuler les états dans les composants enfants.
Composant utilisant un Context
Depuis l’arrivée de la page Statut des Ressources, le Context est utilisé dans chacune des pages React afin de manipuler les filtres, stocker la liste des Ressources pour la page Statut des Ressources, stocker les paramètres de l’utilisateur, déclencher une requête vers l’API pour mettre à jour la liste des ressources, etc. Cela a permis de gagner en lisibilité dans le code.
Malheureusement, lorsqu’une donnée du Context est modifiée, tous les composants utilisant ce Context sont mis à jour même si certains de ces composants n’utilisent pas cette donnée en question. Ce qui implique des actualisations inutiles de composants et par conséquent, une plus grande lenteur de l’application pour répondre aux interactions des utilisateurs.
La solution ? La mémoïsation !
Pour surmonter cette situation, nous avons introduit la mémoïsation. C’est une technique d’optimisation qui stocke le résultat d’une fonction coûteuse en cache et utilise ce résultat stocké quand les entrées n’ont pas été mises à jour.
Avec React, nous pouvons appliquer la mémoïsation sur le résultat d’une fonction mais, plus important encore, nous pouvons mémoïser des composants en fonction de leurs propriétés. Pour mémoïser facilement un composant, il suffit d’utiliser la fonction memo fournie par l’API React.
Mémoïsation du composant DisplayNumber
Dans l’exemple, ci-dessus, nous avons mémoïsé le composant DisplayNumber de façon à ce qu’il soit actualisé uniquement si la propriété number est actualisée.
Dans ce cas, nous avons voulu industrialiser la mémoïsation afin de l’appliquer facilement à nos composants. Pour cela, nous avons créé un hook customisé que l’on appelle useMemoComponent qui va utiliser deux informations. La première est le composant à mémoïser et la seconde, les propriétés qui vont servir à la mémoïsation.
Hook custom permettant à un composant d’être mémoïsé
Ici, nous utilisons le hook useMemo, fourni par React, qui permet de mémoriser le résultat d’une fonction “consommatrice”. Dans le cas présent, nous créons une fonction qui retourne un composant. Enfin, nous avons implémenté un hook customisé useDeepCompare, afin de comparer l’intégralité des propriétés au cours de la mémoïsation. Tout comme la fonction memo, si une seule des propriétés passées en paramètre a changé, l’affichage du composant est actualisé. Dans le cas contraire, le résultat stocké en cache est utilisé.
Prenons un exemple dans la page de Statut des Ressources, nous souhaitons mémoïser le composant qui permet de rafraîchir la liste des ressources et d’activer ou non l’auto rafraîchissement.
En effet, nous souhaitons que ce composant ne soit pas actualisé sans raison mais uniquement si l’une des 3 propriétés de memoProps est modifiée :
- sending : la requête de rafraîchissement de la liste des ressources a été envoyée ou non,
- enabledAutorefresh : si l’auto-rafraîchissement est activé ou non,
- selectedResourceId : si la ressource sélectionnée a été modifiée afin de rafraîchir également les détails de cette dernière.
Composant utilisant la mémoïsation
Ici, le composant RefreshActionsContent affiche nos deux boutons et memoProps est un tableau de valeurs qui sert à mémoïser le composant. Si une des valeurs du tableau est modifiée, le composant est actualisé.
A savoir qu’il est relativement important de mémoïser au maximum les composants utilisant un Context afin d’éviter les actualisations inutiles. Cela est pertinent dans notre cas, puisque les données de notre exemple proviennent du Context.
Si le profileur de React Devtools est relancé, vous pourrez constater que, quand la liste des ressources est mise à jour, le composant RefreshActionsContent n’a pas été actualisé et une petite annotation indique que le composant est mémoïsé.
Exemple d’un composant mémoïsé
Nous avons donc mémoïsé les composants utilisant le Context et les composants consommateurs de ressources lors de l’actualisation, comme les graphes. Voici le résultat avant / après.
Sans mémoïsation
Avec mémoïsation
Comme vous pouvez le constater, il y a beaucoup moins de composants actualisés pour la même action. Seuls les composants qui ont besoin d’être actualisés le sont.
Dans le même temps, il y a un autre bénéfice : la durée de l’actualisation effectuée est considérablement diminuée. Sans la mémoïsation, la durée est de 68,7 millisecondes alors qu’avec la mémoïsation, elle passe à 24 millisecondes.
Page | Pourcentage de rendu gagné sur l’ensemble des scenari |
Statut des Ressources | 35% |
Découverte d’hôtes | 25% |
Activités Métier, Vues Métier | 60% |
Cela signifie que notre application est plus efficiente dans son actualisation lorsque l’utilisateur interagit avec l’application.
Pour conclure
Il existe plusieurs manières d’optimiser une application web comme la refactorisation, la réduction de la taille et/ou l’optimisation du bundle avec Webpack ou d’autres outils de paquetage.
Malgré une mise en place plus complexe, nous avons fait le choix d’utiliser la mémoïsation car c’est un outil qui nous a semblé essentiel dans le cadre de l’optimisation d’une application web aussi évoluée et riche fonctionnellement que Centreon. Cette amélioration des temps d’actualisation est disponible dans la version 21.04 de Centreon avec bien d’autres fonctionnalités.
Bien entendu, n’hésitez pas à utiliser ces outils pour optimiser vos propres applications web !
Titulaire d’une licence en développement et qualité des logiciels, Tom a toujours évolué dans le monde de la supervision IT open source. Fin 2019, il rejoint Centreon en tant que front-end engineer pour se consacrer à l’interface graphique de la plateforme. Dans ce contexte, il développe de nouvelles interfaces (resources status, auto-discovery, etc.) et s’assure que la plateforme Centreon soit la plus performante afin de supporter la meilleure expérience utilisateur possible.