Ouidou https://blog.ouidou.fr/ Conseil et expertise en technologies Wed, 26 Aug 2026 10:17:56 +0000 fr-FR hourly 1 https://wordpress.org/?v=7.0.4 Partenariat Ouidou HeroCoders : un renfort pour votre écosystème Atlassian https://blog.ouidou.fr/actualites/partenariat-ouidou-herocoders-un-renfort-pour-votre-ecosysteme-atlassian Wed, 26 Aug 2026 07:43:48 +0000 https://blog.ouidou.fr/?p=18579 L’article Partenariat Ouidou HeroCoders : un renfort pour votre écosystème Atlassian est apparu en premier sur Ouidou.

]]>

L’article Partenariat Ouidou HeroCoders : un renfort pour votre écosystème Atlassian est apparu en premier sur Ouidou.

]]>
Ouidou au Salon de la Data & IA 2026 à Nantes https://blog.ouidou.fr/actualites/ouidou-au-salon-de-la-data-ia-2026-a-nantes Tue, 28 Jul 2026 14:26:22 +0000 https://blog.ouidou.fr/?p=18550 L’article Ouidou au Salon de la Data & IA 2026 à Nantes est apparu en premier sur Ouidou.

]]>

L’article Ouidou au Salon de la Data & IA 2026 à Nantes est apparu en premier sur Ouidou.

]]>
Replay webinaire Ouidou x Sonar https://blog.ouidou.fr/actualites/replay-webinaire-ouidou-x-sonar Thu, 02 Jul 2026 10:42:16 +0000 https://blog.ouidou.fr/?p=18527 L’article Replay webinaire Ouidou x Sonar est apparu en premier sur Ouidou.

]]>

L’article Replay webinaire Ouidou x Sonar est apparu en premier sur Ouidou.

]]>
Diagnostiquer, comprendre et optimiser les performances d’une application React https://blog.ouidou.fr/articles-techniques/performances-react-optimisation Mon, 04 May 2026 10:12:04 +0000 https://blog.ouidou.fr/?p=18295 Écrit par Thomas M. Diagnostiquer, comprendre et optimiser les performances d’une application React Une application React “lente” ne se résume pas à “React est lourd”. Dans […]

L’article Diagnostiquer, comprendre et optimiser les performances d’une application React est apparu en premier sur Ouidou.

]]>
Écrit par Thomas M.

Diagnostiquer, comprendre et optimiser les performances d’une application React

Une application React “lente” ne se résume pas à “React est lourd”. Dans la majorité des cas, les problèmes viennent de re-renders inutiles, d’un DOM trop volumineux ou d’un bundle surdimensionné. L’objectif de cet article est de donner une démarche concrète et des techniques pratiques pour diagnostiquer et optimiser les performances de vos composants.

Comprendre d’où viennent les lenteurs

Chaque mise à jour en React passe par le cycle render → reconciliation → commit : React exécute le composant, compare le virtual DOM avec le précédent puis applique les changements au DOM. Un re-render peut venir d’un setState, d’un changement de props, de context, ou simplement d’un parent qui se rafraîchit.

Les ralentissements proviennent souvent de re-renders inutiles, notamment lorsque :

  • Un composant se re-render alors que ses props n’ont pas changé,
  • Des callbacks ou objets sont recréés à chaque render (() => …, style={{…}}),
  • Un state ou un contexte trop global force une grande partie de l’arbre à se mettre à jour.

Identifier ces situations permet de cibler précisément ce qu’il faut optimiser avant d’appliquer React.memo, useCallback ou useMemo.

Profiler l’application pour mesurer avant d’optimiser

  • Le React DevTools Profiler est l’outil principal pour analyser les re-renders.
    Il permet d’enregistrer une interaction et d’inspecter :
    • Le Flamegraph : visualise les composants les plus coûteux (barres larges ou colorées).
    • La vue Ranked : classe les composants du plus lent au plus rapide.
    • “Why did this render?” : indique la raison exacte d’un re-render (props changed, parent rendered, etc.).

Exemple classique : une simple frappe dans un champ de recherche rafraîchit un Header statique → signe d’un re-render inutile.

  • Quand l’UI freeze malgré des renders React rapides, utilisez l’onglet Performance de Chrome pour repérer des Long Tasks (>50 ms) sur le thread principal. On y détecte souvent des :
    • Calculs JavaScript lourds,
    • Recalculs de layout,
    • Phases de garbage collection.

Croiser les données des deux outils permet de savoir si le problème vient réellement de React ou d’un coût externe (algorithme, librairie, manipulation du DOM).

Limiter les re-renders avec React.memo, useMemo, useCallback

React.memo : limiter le render d’un composant

React.memo mémorise le render d’un composant fonctionnel basé sur une comparaison superficielle de ses props.

const Header = React.memo(function Header({ user }) {
  return <h1>Bonjour {user.name}</h1>;
});

Si user (référence) ne change pas, Header ne se re-render pas, même si son parent s’actualise.

Attention : si vous passez un objet ou une fonction recréés à chaque render (onClick={() => …}, style={{ ... }}), la comparaison échoue et React.memo ne sert plus à rien.

React.memo effectue une comparaison superficielle (shallow compare) des props à chaque render du parent.
Cette comparaison a elle-même un coût, proportionnel au nombre de props. Sur des composants simples ou peu coûteux à rendre, ce coût peut être supérieur au gain, rendant React.memo inutile voire contre-productif.

useMemo : Mémoïser les calculs et stabiliser les objets

Deux grands cas d’usage :

  1. Calculs coûteux
const filteredTodos = useMemo(
  () => todos.filter(t => t.text.includes(filter)),
  [todos, filter]
);

Le filtre n’est recalculé que si todos ou filter change réellement.

  1. Stabiliser des props non primitives
const style = useMemo(
  () => ({ backgroundColor: theme === 'dark' ? 'black' : 'white' }),
  [theme]
);
// sans useMemo, un nouveau `{}` à chaque render casse React.memo
<MemoizedChild style={style} />;

useCallback : Stabiliser les fonctions passées en props

useCallback est l’équivalent de useMemo pour les fonctions :

const handleClick = useCallback(() => {
  alert('Clic !');
}, []); 
<MemoizedButton onClick={handleClick} />;

Indispensable dès que :

  • Vous passez un callback à un composant mémoïsé,
  • Vous utilisez une fonction dans les dépendances d’un useEffect.

Colocaliser l’état et structurer l’architecture

Une des optimisations les plus simples mais tout autant efficace est de déplacer l’état aussi bas que possible.

Dans cet exemple, le composant App re-render tout à chaque saisie.

function App() {
  const [text, setText] = useState('');
  return (
    <>
      <input value={text} onChange={e => setText(e.target.value)} />
      <HeavyChart />
      <ExpensiveTable />
    </>
  );
}

Déplacer l’état dans un composant → la saisie ne re-render que le petit composant.

function SearchInput() {
  const [text, setText] = useState('');
  return <input value={text} onChange={e => setText(e.target.value)} />;
}
function App() {
  return (
    <>
      <SearchInput />
      <HeavyChart />
      <ExpensiveTable />
    </>
  );
}

Dans vos formulaires, listes et dashboards :

  • Séparez les états qui changent souvent (input, filtres) de ceux qui changent rarement,
  • Découpez en petits composants pour limiter la zone impactée par chaque mise à jour.

Virtualiser Les listes longues

Le problème du DOM trop volumineux

Render une liste de 10 000 éléments avec un simple .map() crée 10 000 nœuds DOM. Conséquences :

  • Temps de render initial énorme,
  • Scroll saccadé,
  • Re-renders catastrophiques,
  • Risque de crash de l’onglet.

Virtualisation

La virtualisation permet de ne render que les éléments visibles à l’écran + un petit buffer. Bibliothèques courantes :

  • TanStack Virtual (@tanstack/react-virtual),
  • react-window.

Exemple simplifié avec TanStack Virtual :

import { useVirtualizer } from '@tanstack/react-virtual';
function VirtualizedList({ items }) {
  const parentRef = useRef(null);
  const rowVirtualizer = useVirtualizer({
    count: items.length,
    getScrollElement: () => parentRef.current,
    estimateSize: () => 35,
  });
  return (
    <div ref={parentRef} style={{ height: 400, overflow: 'auto' }}>
      <div
        style={{
          height: rowVirtualizer.getTotalSize(),
          position: 'relative',
          width: '100%',
        }}
      >
        {rowVirtualizer.getVirtualItems().map(virtualRow => (
          <div
            key={virtualRow.key}
            style={{
              position: 'absolute',
              top: 0,
              left: 0,
              width: '100%',
              height: virtualRow.size,
              transform: `translateY(${virtualRow.start}px)`,
            }}
          >
            {items[virtualRow.index]}
          </div>
        ))}
      </div>
    </div>
  );
}

Pour les hauteurs dynamiques, on peut mesurer chaque élément après le render et laisser la lib ajuster la position et la hauteur totale.

Optimiser le chargement et le bundle

Réduire le temps du premier chargement est tout aussi important que d’accélérer les renders.

Code splitting avec React.lazy et Suspense

Une première approche consiste à utiliser le code splitting grâce à React.lazy et Suspense. Cela permet de ne charger certaines parties qu’au moment où elles sont nécessaires, par exemple une page lourde ou une fonctionnalité secondaire.

const DashboardPage = React.lazy(() => import('./pages/DashboardPage'));
<Suspense fallback={<div>Chargement...</div>}>
  <Routes>
    <Route path="/dashboard" element={<DashboardPage />} />
  </Routes>
</Suspense>

React.lazy est particulièrement utile au niveau des routes, mais aussi pour des composants rarement affichés (éditeur riche, graphiques, modales complexes).

Lazy loading des librairies lourdes

Il est également important de pratiquer le lazy loading des librairies lourdes, en utilisant les imports dynamiques (import()). Cela évite de charger des outils coûteux tant que l’utilisateur n’en a pas besoin.

Analyse de bundle

Pour comprendre ce qui alourdit réellement l’application, il faut analyser le bundle à l’aide d’outils comme webpack-bundle-analyzer ou rollup-plugin-visualizer. Cela permet d’identifier facilement :

  • Les dépendances trop volumineuses,
  • Les doublons,
  • Ou les imports non ciblés (ex. importer toute une bibliothèque alors qu’une seule fonction est utilisée).

Optimisation des images

Enfin, les images peuvent fortement dégrader le chargement si elles sont mal optimisées. Utiliser des formats modernes comme WebP ou AVIF, combiner cela avec loading="lazy" et ajuster la taille des fichiers permet d’obtenir un gain de performance immédiat.

Lorsque les optimisations classiques ne suffisent pas, il existe des méthodes plus avancées en fonction des cas d’utilisations.

Appliquer des techniques avancées

Quand les optimisations “classiques” ne suffisent pas :

  • useTransition et useDeferredValue : marquer certaines mises à jour comme non urgentes pour éviter les freezes lors de filtres complexes ou de gros changements d’écran. (React 18+)
  • Render concurrent : permet à React d’interrompre un render long pour traiter une interaction plus urgente (frappe clavier, clic).
  • Web Workers : déporter les calculs CPU lourds (algos, parsing) hors du thread principal.
  • <canvas> / WebGL : pour les interfaces à très haute fréquence de rafraîchissement (graphiques temps réel, visualisations interactives).

Conclusion

Optimiser les performances d’une application React, ce n’est pas tout envelopper dans React.memo.

  • Mesurer avec React DevTools et le Performance Profiler.
  • Identifier les re-renders inutiles et les composants réellement coûteux.
  • Optimiser localement avec React.memo, useMemo, useCallback et une bonne colocation de l’état.
  • Traiter les cas extrêmes (listes longues, bundles volumineux) avec la virtualisation et le code splitting.
  • Répéter le cycle mesure → optimisation → mesure.

En gardant cette démarche scientifique, vous évitez l’optimisation prématurée tout en construisant des applications React rapides, fluides et maintenables.

L’article Diagnostiquer, comprendre et optimiser les performances d’une application React est apparu en premier sur Ouidou.

]]>
Ouicommit Paris – IA : de l’expérimentation à l’industrialisation https://blog.ouidou.fr/actualites/ia-de-lexperimentation-a-lindustrialisation Wed, 15 Apr 2026 12:53:33 +0000 https://blog.ouidou.fr/?p=18138 L’article Ouicommit Paris – IA : de l’expérimentation à l’industrialisation est apparu en premier sur Ouidou.

]]>
L’article Ouicommit Paris – IA : de l’expérimentation à l’industrialisation est apparu en premier sur Ouidou.

]]>
Ouidou et Scaleway : un partenariat stratégique pour accélérer le cloud souverain en Europe https://blog.ouidou.fr/actualites/ouidou-et-scaleway-un-partenariat-strategique-pour-accelerer-le-cloud-souverain-en-europe Wed, 08 Apr 2026 16:55:09 +0000 https://blog.ouidou.fr/?p=18209 L’article Ouidou et Scaleway : un partenariat stratégique pour accélérer le cloud souverain en Europe est apparu en premier sur Ouidou.

]]>
L’article Ouidou et Scaleway : un partenariat stratégique pour accélérer le cloud souverain en Europe est apparu en premier sur Ouidou.

]]>
JTE : un moteur de templates moderne, rapide et sécurisé pour Java https://blog.ouidou.fr/articles-techniques/jte-java-template-engine Mon, 09 Mar 2026 09:34:01 +0000 https://blog.ouidou.fr/?p=18057 Écrit par Thusitha T. Introduction Les moteurs de templates occupent une place clé dans la création de vues côté serveur, l’envoi d’emails personnalisés ou encore la […]

L’article JTE : un moteur de templates moderne, rapide et sécurisé pour Java est apparu en premier sur Ouidou.

]]>
Écrit par Thusitha T.

Introduction

Les moteurs de templates occupent une place clé dans la création de vues côté serveur, l’envoi d’emails personnalisés ou encore la génération de fichiers HTML à partir de données Java. Bien que des outils tels que Thymeleaf, FreeMarker ou Velocity soient populaires, un nouvel acteur a réussi à se démarquer grâce à sa performance et sa facilité d’utilisation : JTE (Java Template Engine).
Cet article vous propose une introduction à JTE, incluant ses principes opérationnels et ses bénéfices techniques, tout en fournissant un aperçu de son intégration au sein d’un projet Java modern.

Qu’est-ce que JTE ?

JTE (Java Template Engine) est un moteur de templates compilé, créé en 2020, qui vise à offrir une alternative plus rapide, sécurisée et typée aux moteurs de templates traditionnels utilisés dans les applications Java.
Contrairement à des solutions interprétées comme Thymeleaf ou FreeMarker, JTE compile les templates en classes Java au moment du build. Cela lui permet d’obtenir des performances proches de celles du code natif, tout en garantissant la détection d’erreurs à la compilation.

Objectif principal

JTE vise à fournir un moteur de templates modern pour Java, approprié aux contextes de haute performance (API, microservices, applications server-side rendering, etc.), tout en maintenant une simplicité d’apprentissage.

Les atouts majeurs

Haute Performance

L’un des atouts les plus marquants de JTE est dans sa rapidité d’exécution.

En effet, JTE supprime la phase d’interprétation. Les fichiers .jte sont transformés en classes Java lors de la compilation. Ainsi, les modèles sont donc directement exécutés comme du code Java optimisé par la JVM.

Des comparaisons indépendantes ont démontré que JTE peut être plus rapide que Thymeleaf ou FreeMarker dans différents scénarios.

Sécurité renforcée

En appliquant une échappement automatique du HTML et des autres formats (XML, JSON, etc.), JTE réduit drastiquement les risques d’injections XSS (Cross-Site Scripting).
De plus, comme les templates sont compilés, il n’y a aucune exécution dynamique de code, contrairement à d’autres moteurs plus permissifs.

Typage fort et vérification à la compilation

Etant donné que les templates JTE sont vérifiés à la compilation, si un champ ou une méthode n’existe plus dans un objet passé au template, l’erreur est détectée avant le déploiement, et non à l’exécution.

Intégration fluide

JTE ne nécessite pas de configuration complexe.
Un simple ajout de dépendance Maven ou Gradle suffit pour l’utiliser dans un projet Java.
Il s’intègre facilement avec des frameworks comme Spring Boot.

Installation

<dependency>
    <groupId>gg.jte</groupId>
    <artifactId>jte-spring-boot-starter-3</artifactId>
    <version>3.2.1</version>
</dependency>
<dependency>
    <groupId>gg.jte</groupId>
    <artifactId>jte</artifactId>
    <version>3.2.1</version>
</dependency>

Les fichiers .jte placés dans src/main/resources/templates seront automatiquement détectés et rendus via les contrôleurs Spring (@Controller + ModelAndView).

Structure de base

src/
 ├─ main/
 │   ├─ java/
 │   ├─ resources/
 │   │   └─ templates/
 │   │       └─ hello.jte

Exemple de template hello.jte

@import java.time.LocalDateTime
<html>
  <body>
    <h1>Hello ${name}!</h1>
    <p>Nous sommes le ${LocalDateTime.now()}.</p>
  </body>
</html>

Contrôleur

import org.springframework.web.servlet.ModelAndView;
@GetMapping("/hello")
public ModelAndView hello() {
  return new ModelAndView("hello", Map.of("name", "Jean"))
}

Classe Java générée du template

package jte;
import gg.jte.TemplateOutput;
import gg.jte.html.HtmlTemplateOutput;
import gg.jte.runtime.Template;
import java.time.LocalDateTime;
import java.util.Map;
public final class JtehelloGenerated implements Template {
  @Override
  public void render(Map<String, Object> params, TemplateOutput output) {
    String name = (String) params.get("name");
    output.writeContent("<html>\n  <body>\n    <h1>Hello ");
    output.writeUserContent(name);
    output.writeContent("!</h1>\n    <p>Nous sommes le ");
    output.writeUserContent(LocalDateTime.now());
    output.writeContent(".</p>\n  </body>\n</html>");
  }
  @Override
  public static void render(Map<String, Object> params, HtmlTemplateOutput output) {
    new JtehelloGenerated().render(params, output);
  }
}

Explications

Le fichier .jte est transformé en une classe Java qui implémente l’interface gg.jte.runtime.Template et tout le contenu statique HTML est injecté via output.writeContent(...).

Les variables quant à elles (${name}, ${LocalDateTime.now()}) deviennent des appels Java typés et sûrs.

La méthode render est appelée par le moteur JTE lors de l’exécution.

Résultat affiché

<html>
  <body>
    <h1>Hello Jean!</h1>
    <p>Nous sommes le 2025-10-23T14:35:10.123.</p>
  </body>
</html>

Exemples d’utilisation de JTE

Affichage conditionnel

@if(isAdmin)
  <p>Bienvenue, administrateur ${userName}.</p>
@else if(isGuest)
  <p>Bienvenue, invité.</p>
@else
  <p>Bienvenue, utilisateur.</p>
@end

Etant donné que le template est compilé en if / else Java, aucune erreur n’est possible à l’exécution.

Boucle for (foreach)

<ul>
@for(Product product : products)
  <li>${product.getName()} — ${product.getPrice()} €</li>
@end
</ul>

Fragment réutilisable

Supposons que nous avons le fragment fragments/header.jte :

@param String title
<header>
  <h1>${title}</h1>
</header>

En appelant ce dernier dans le template suivant :

@template.views.fragments.header(
  title = "Accueil"
)
<main>
  <p>Bienvenue sur notre site !</p>
</main>

Nous aurons le résultat suivant :

<header>
  <h1>Accueil</h1>
</header>
<main>
  <p>Bienvenue sur notre site !</p>
</main>

Limites et points de vigilance avec JTE

Même si JTE se distingue par ses performances et sa sécurité, il est important de connaitre ses limites avant de l’adopter dans un projet.

Recompilation après chaque modification

Vu que JTE travaille avec des templates compilés, lorsqu’on modifie un .jte, les modifications seront prises en compte seulement après recompilation.

Communauté encore jeune

Comparé à Thymeleaf ou FreeMarker, JTE dispose d’une communauté plus restreinte.
La documentation officielle est claire mais moins riche en exemples et moins d’intégrations tierces existent pour l’instant (tags Spring Security, intégrations JSP, etc.).

Moins d’écosystème autour des extensions

En misant sur la simplicité et la performance, JTE a un écosystème plus fermé que par exemple Thymeleaf qui propose des dialectes et des extensions personnalisables. Autrement dit, si le projet a besoin de tags ou de macros complexes, JTE sera moins adapté.

Extensions et plugins JTE encore limités

JTE propose plusieurs plugins officiels pour l’intégration dans les outils de build, mais ils restent moins matures que ceux des moteurs plus anciens :

PluginDescriptionLimitation actuelle
jte-maven-pluginCompile les templates .jte lors du mvn compilePeu documenté, nécessite une configuration manuelle des chemins
jte-gradle-pluginCompilation automatique dans GradleIncompatible avec certains environnements Gradle anciens (< 7)
jte-intellij-pluginColoration syntaxique et autocomplétionSupport partiel, pas encore complet sur les @include et @param
jte-spring-boot-starterIntégration MVC avec Spring BootFonctionne bien mais peu d’exemples complexes dans la doc

Il n’y a exemple aucun plugin sur Eclipse ou VS Code permettant d’avoir la coloration syntaxique et l’autocomplétion.

Pas de Virtual DOM ni de rendu réactif

JTE est un moteur de rendu côté serveur pur :
il ne propose aucun Virtual DOM, ni mécanisme de réconciliation des changements de données comme le font des bibliothèques front-end (React, Vue, Svelte…).

Chaque rendu JTE recrée le HTML complet à partir du serveur, sans mise à jour partielle du DOM côté client.

Cela signifie que toute modification d’état nécessite un nouvel appel serveur et un rechargement complet de la vue.

Conclusion

JTE s’impose aujourd’hui comme une alternative aux moteurs historiques de templates Java.
Son approche compilée, son typage fort et sa sécurité intégrée en font un choix pertinent pour les développeurs cherchant à améliorer les performances et la fiabilité de leurs applications Java.

Autrement dit, il peut être l’idéal pour les applications où le contenu est statique au build (templates de mails, vues serveur, génération de rapports HTML). Par contre, s’il s’agit d’un projet avec des templates très dynamique, très intéractifs ou orientés utilisateur, un framework front moderne reste plus adapté.

Liens utiles

L’article JTE : un moteur de templates moderne, rapide et sécurisé pour Java est apparu en premier sur Ouidou.

]]>
La Sobriété Numérique, de la quantification des émissions carbone des applications à la mise en œuvre des corrections https://blog.ouidou.fr/articles-techniques/la-sobriete-numerique Mon, 16 Feb 2026 08:58:11 +0000 https://blog.ouidou.fr/?p=17878 Écrit par Hudson T. Qu’est que c’est La Sobriété Numérique Selon l’ADEME, dans une étude réalisée en 2020, 2,5 % de l’empreinte carbone de la France […]

L’article La Sobriété Numérique, de la quantification des émissions carbone des applications à la mise en œuvre des corrections est apparu en premier sur Ouidou.

]]>
Écrit par Hudson T.

Qu’est que c’est La Sobriété Numérique

Selon l’ADEME, dans une étude réalisée en 2020, 2,5 % de l’empreinte carbone de la France est liée au numérique, avec 10 % de la consommation annuelle d’énergie attribuée aux services numériques. Pour atteindre la transition écologique définie par la France, la mesure des émissions de carbone et la mise en œuvre de mesures d’atténuation des risques sont essentielles pour un avenir durable.

Dans cette perspective, nous appliquons les principes de la sobriété numérique, qui consistent à concevoir des services numériques plus économes, afin de réduire les impacts environnementaux et la consommation d’énergie des technologies, par exemple lors de la navigation sur internet et de l’utilisation d’applications web sur les ordinateurs et les téléphones portables.

La Sobriété Numérique dès la conception de l’application

La conception d’une application web implique plusieurs facteurs importants, en particulier si l’on considère l’empreinte carbone associée à l’utilisation de ces applications. Pour réduire la consommation d’énergie, il est nécessaire d’analyser des aspects tels que :

  • Cible : évaluer la taille et les habitudes du public qui utilisera la plateforme et sa fréquence d’utilisation, car le nombre d’accès est directement lié aux émissions générées.
  • Technologies utilisées : choisissez des technologies web modernes mais moins gourmandes en énergie, qui évitent le chargement continu des pages web et la répétition des requêtes et des connexions.
  • Requêtes HTTP : réduire le nombre de requêtes effectuées lors de l’utilisation de l’application, en minimisant les appels redondants.
  • Taille des fichiers : optez pour des fichiers plus petits et optimisés, tels que des images compressées, du HTML, CSS et JavaScript minifiés.
  • Infrastructure d’hébergement : vérifiez la répartition géographique des serveurs afin de réduire la latence et d’améliorer le chargement. Privilégiez les hébergeurs qui utilisent des énergies renouvelables.

De nombreuses pratiques d’éco-conception sont également directement liées à l’expérience utilisateur (UX) et à l’interface (UI). Au cours du processus de conception, il est essentiel de réduire le nombre d’actions nécessaires à l’utilisateur pour atteindre ses objectifs, en optimisant les flux et les interfaces. Une conception simple et fonctionnelle permet non seulement de minimiser l’impact sur l’environnement, mais aussi de créer des applications plus agréables à utiliser.

Comment mesurer l’empreinte carbone des applications web

Globalement, la navigation sur Internet est directement liée à la consommation d’énergie des applications. Il est donc possible de mesurer l’impact carbone des applications en prenant en compte l’énergie consommée lors de leur utilisation.

Pour ce faire, il existe des outils qui permettent de calculer l’empreinte carbone en tenant compte de la matrice énergétique du pays, du pourcentage d’énergies renouvelables utilisées et de l’empreinte carbone moyenne des sites de la région. Une solution existante est le site Ecoindex et l’extension GreenIt – Analyses qui fournissent un rapport sur la quantité de code inutilisé sur la page, le nombre d’images plus grandes que nécessaire et indiquent même les formats d’image qui pourraient être modifiés pour réduire la consommation d’énergie et les émissions de CO₂ lors du téléchargement. Ils vérifient également si l’hébergeur du site utilise de l’énergie renouvelable sur ses serveurs, en utilisant l’outil de The Green Web Foundation.

Figure 1 : Exemple d’évaluation de l’EcoIndex par l’extension GreenIt.

Comment mettre en œuvre L’Eco-conception lors de la rédaction du code

Les stratégies à utiliser pour chaque projet doivent être adaptées à ses caractéristiques spécifiques. Nous vous présentons quelques exemples concrets de bonnes pratiques illustrant comment certaines décisions techniques peuvent influencer directement la performance et la maintenabilité d’une application.

Dans les applications React

Il est important d’éviter les re-rendus inutiles. Cela peut se faire en utilisant React.memo pour prioriser les rendus uniques. Il faut également être attentif à l’utilisation des hooks : par exemple, privilégier useMemo pour les variables qui ne changent pas entre les rendus ou pour les opérations simples, plutôt que de recourir à useState lorsqu’il n’est pas nécessaire. Une autre bonne pratique consiste à mettre en cache les informations afin de réduire les appels HTTP redondants et inutiles.

Dans les applications Java

Il est recommandé d’envisager l’utilisation de Hibernate pour abstraire la base de données. Afin d’optimiser les performances, il est aussi possible de tirer parti du cache de second niveau proposé par Hibernate, qui diminue significativement le nombre d’accès à la base de données.

Dans toutes les applications, il est tout aussi important de s’assurer que les changements souhaités sont effectivement observés dans le code, et de vérifier les points définis au cours du développement. Il est essentiel que notre CD/CI soit adaptée à cet effet. Il existe différents modèles qui contrôlent les applications et leurs résultats en fonction de l’évolution du code, comme le Template Eco-CI fourni par Green Coding.

Une autre solution simple consiste à utiliser le plugin creedengo dans le logiciel SonarQube, le logiciel d’analyse statique de code utilisé dans la plupart des CI existantes. Le plugin permet d’ajouter des règles d’analyse au champ d’application du logiciel, visant à identifier les problèmes d’éco-conception, permettant de les corriger au fur et à mesure de l’écriture du code, dans de nombreux langages tels que Java, JavaScript, PHP, Python et C#. Bien que très récent et en cours de développement, l’outil est déjà disponible pour la version 9.4.+ de SonarQube. Le plugin vous permet d’ajouter de nouvelles règles à marquer avec la balise creedengo comme indiqué ci-dessous.

Ci-dessous, nous présentons un exemple d’identification d’un problème d’éco-conception dans un code Java, signalé par SonarQube avec le plugin installé. Comme on peut le constater, notre code présente un problème de performance marqué avec le tag creedengo, qui indique une inefficacité liée à l’appel de la fonction size à l’intérieur d’une boucle.

Utilisation de Creedengo dans SonarQube

Figure 2 : Exemple d’analyse SonarQube avec des erreurs de performance remontées par creedengo.

Ce comportement est problématique car la fonction size est évaluée à chaque itération, entraînant une consommation inutile de cycles CPU et, par conséquent, une augmentation de la consommation énergétique.

SonarQube propose une solution : stocker la valeur retournée par la fonction size dans une variable avant la boucle. Cela permet d’éviter de recalculer la taille de la collection à chaque itération, améliorant ainsi l’efficacité du code tout en réduisant son impact énergétique.

Exemple d'analyse de code Java

Figure 3 : Exemple de proposition SonarQube avec creedengo pour améliorer l’efficacité.

Conclusion

La sobriété numérique est cruciale pour réduire l’impact environnemental des services numériques. En adoptant des pratiques d’éco-conception, en optimisant le code et en mesurant les émissions de carbone, il est possible de réduire la consommation énergétique tout en améliorant l’expérience utilisateur. Cela soutient la transition écologique et permet aux entreprises de répondre aux attentes croissantes des consommateurs et des régulateurs en matière de durabilité. Cela renforce également leur image de marque et leur engagement envers l’environnement.

Sources

Outils de mesure mentionnés

Quelques ressources pour aller plus loin…

L’article La Sobriété Numérique, de la quantification des émissions carbone des applications à la mise en œuvre des corrections est apparu en premier sur Ouidou.

]]>
Introduction pratique au Q-learning avec Gymnasium Taxi-v3 https://blog.ouidou.fr/articles-techniques/introduction-pratique-au-q-learning-avec-gymnasium-taxi-v3 Tue, 06 Jan 2026 10:12:18 +0000 https://blog.ouidou.fr/?p=17885 Écrit par Kévin A. Introduction L’apprentissage par renforcement (Reinforcement Learning, ou RL) est une branche de l’intelligence artificielle dans laquelle un agent IA apprend à prendre […]

L’article Introduction pratique au Q-learning avec Gymnasium Taxi-v3 est apparu en premier sur Ouidou.

]]>
Écrit par Kévin A.

Introduction

L’apprentissage par renforcement (Reinforcement Learning, ou RL) est une branche de l’intelligence artificielle dans laquelle un agent IA apprend à prendre des décisions par essai-erreur. Contrairement à l’apprentissage supervisé, il n’existe pas de réponses toutes faites : l’agent explore son environnement et améliore ses choix grâce aux récompenses reçues.

Aujourd’hui nous allons découvrir Gymnasium, une bibliothèque open source proposant de nombreux environnements simulés. Dans cet article, nous allons explorer l’environnement Taxi-v3. Dans cet environnement, le but est simple : déplacer le taxi pour récupérer un passager et le déposer à sa destination le plus efficacement possible.

Nous allons découvrir comment entraîner notre agent à accomplir cette tâche en utilisant le Q-Learning, un algorithme à la fois simple et particulièrement efficace.

Comprendre le Q-Learning et la Q-Table

La Q-Table

Le Q-Learning est un algorithme basé sur une Q-Table, une matrice où chaque ligne correspond à un état possible, et chaque colonne à une action. La valeur dans la case Q(s,a) indique la « qualité » de choisir l’action a dans l’état s.

Au départ, la Q-Table est remplie de zéros. L’agent IA apprend progressivement à mettre à jour cette table en interagissant avec l’environnement.

La formule de mise à jour

Chaque fois que le taxi prend une action et reçoit une récompense, la Q-Table est mise à jour selon :

  • α (alpha): taux d’apprentissage (généralement entre 0 et 1, mais peut être supérieur dans certains cas spéciaux), correspond au paramètre lr dans notre fonction.
  • γ (gamma): facteur d’actualisation qui valorise les récompenses futures, correspond au paramètre gamma dans notre fonction.
  • r : récompense immédiate.
  • s,a : état et action actuels.
  • s′ : nouvel état après action.

Dans notre implémentation, cette formule est appliquée avec les valeurs par défaut de lr = 0.1 et gamma = 0.99, ce qui signifie que l’agent apprend modérément vite et accorde une grande importance aux récompenses futures.

Stratégie d’exploration

Pour éviter que l’agent ne se contente d’une stratégie sous-optimale, on utilise une approche ε-greedy :

L’approche ε-greedy équilibre exploration et exploitation en Q-learning : l’agent explore aléatoirement avec une probabilité ε, sinon il choisit la meilleure action connue. Un ε élevé favorise l’exploration (utile au début), tandis qu’un ε faible privilégie l’exploitation des connaissances acquises.

Le epsilon decay consiste à réduire progressivement la valeur de ε au cours de l’entraînement. Cette diminution graduelle permet de commencer par une exploration intensive pour découvrir l’environnement, puis de converger vers une stratégie stable en privilégiant l’exploitation des connaissances.

Plusieurs approches existent pour implémenter cette décroissance :

  • La décroissance exponentielle utilise la formule ε = ε_initial × decay_rate^épisode et offre une réduction rapide au début qui ralentit progressivement.
  • La décroissance linéaire applique une réduction constante à chaque épisode avec ε = ε_initial – (decay_step × épisode).
  • Une approche particulièrement populaire est la décroissance avec plancher, qui suit ε = max(ε_min, ε × decay_rate) et maintient un niveau minimal d’exploration permanent, évitant que l’agent devienne trop rigide.
  • Enfin, la décroissance inverse propose une formule ε = ε_initial / (1 + decay_rate × épisode) pour une décroissance douce et continue.

Chaque approche offre un compromis différent entre vitesse de convergence et maintien de l’exploration, le choix dépendant de la complexité de l’environnement et des objectifs d’apprentissage.

Description du problème

Dans Taxi-v3, le joueur (notre agent IA) contrôle un taxi dans une grille de 5×5 cases. Sa mission est simple : récupérer un passager à un point donné et le déposer à la bonne destination.

L’environnement nous met à disposition tous les éléments nécessaires pour définir le problème d’apprentissage par renforcement de manière claire et structurée.

  • États : position du taxi, position du passager (4 emplacements possibles ou « dans le taxi »), et destination (4 emplacements possibles). Cela donne 500 états possibles.
  • Actions : 6 choix sont disponibles – se déplacer au nord, sud, est, ouest, prendre un passager, déposer un passager.
  • Récompenses :
    • +20 si le passager est déposé au bon endroit.
    • -10 si le taxi tente une mauvaise action (par exemple déposer sans passager).
    • -1 pour chaque pas, afin d’encourager une solution rapide.

Implémentation en Python

Voici une version simplifiée de l’entraînement avec Q-Learning :

import gymnasium as gym
import numpy as np
# Création de l'environnement Taxi
env = gym.make("Taxi-v3")
# Initialisation de la Q-Table
state_space = env.observation_space.n
action_space = env.action_space.n
Q = np.zeros((state_space, action_space))
# Hyperparamètres
alpha = 0.1     # taux d'apprentissage
gamma = 0.6     # facteur d'actualisation
epsilon = 0.1   # exploration
episodes = 10000
# Entraînement
for episode in range(episodes):
    state, _ = env.reset()
    done = False
    while not done:
        if np.random.uniform(0,1) < epsilon:
            action = env.action_space.sample()  # exploration
        else:
            action = np.argmax(Q[state])        # exploitation
        next_state, reward, done, truncated, info = env.step(action)
        # Mise à jour de la Q-Table
        best_next_action = np.argmax(Q[next_state])
        Q[state, action] = Q[state, action] + alpha * (
            reward + gamma * Q[next_state, best_next_action] - Q[state, action]
        )
        state = next_state

Après un entraînement de 10000 épisodes comme dans notre exemple (quelques minutes) le taxi apprend à résoudre efficacement sa mission.

Résultats et analyse

Évaluation de l’agent

Une fois entraîné, l’agent peut être évalué sur plusieurs parties de test. Les métriques typiques sont :

  • score moyen par épisode,
  • nombre moyen d’étapes nécessaires pour déposer le passager.

En général, après un bon entraînement, l’agent réussit sa mission en moins de 20 étapes.

Visualisation des performances

On peut tracer la progression du score moyen :

import matplotlib.pyplot as plt
# Liste des récompenses obtenues à chaque épisode d'entraînement
plt.plot(rewards_per_episode) 
# Axe horizontal : numéro de l'épisode (de 0 à nombre total d'épisodes)
plt.xlabel("Épisodes") 
# Axe vertical : récompense cumulée par épisode
plt.ylabel("Score moyen") 
# Titre du graphique montrant l'évolution des performances
plt.title("Progression du taxi avec Q-Learning") 
# Affiche le graphique pour visualiser l'apprentissage de l'agent
plt.show() 

Voici par exemple un des graphiques de l’entraînement de notre agent. Ce graphique montre comment notre taxi apprend au fil du temps grâce au Q-Learning.

Plusieurs choses sont affiché sur ce graphique.

l’axe horizontal, chaque point correspond à un épisode d’entraînement, c’est-à-dire une partie que joue notre taxi. Plus on avance vers la droite, plus l’agent a eu d’occasions d’apprendre.

l’axe vertical, on y voit deux choses : En orange, on voit le nombre d’étapes que l’agent met pour terminer une partie. Au départ, ce nombre est très élevé (l’agent échoue souvent et atteint la limite de 200 actions), puis il diminue progressivement à mesure que le taxi apprend à être plus efficace.

En vert, les récompenses obtenues par l’agent, elles vont très loin dans le négatif au départ, puis augmentent et se stabilisent quand le taxi apprend à bien jouer.

La ligne rouge, elle représente l’exploration. Au début, le taxi teste un peu tout au hasard (l’epsilon est élevé). Au fur et à mesure, il explore moins et applique ce qu’il a appris (l’epsilon diminue jusqu’à 0).

Notre graphique illustre l’évolution des récompenses et du nombre de pas, où l’on observe une stabilisation progressive (convergence) des performances lorsque l’agent apprend une stratégie optimale, tandis que l’epsilon décroît vers zéro.

Optimiser les performances de l’algorithme Q-Learning

Une fois l’algorithme fonctionnel, il est intéressant de chercher à accélérer l’apprentissage et les performances finales de notre agent. Cela passe par l’optimisation des hyperparamètres.

Le taux d’apprentissage (lr)

Le taux d’apprentissage détermine l’importance accordée aux nouvelles informations par rapport aux connaissances déjà acquises dans la Q-Table. Dans notre approche, la valeur par défaut est fixée à 0.1 (sur une échelle de 0 à 1). Un taux trop faible entraîne un apprentissage très lent car l’agent intègre difficilement les nouvelles expériences, tandis qu’un taux trop élevé provoque des changements trop brutaux qui effacent les apprentissages précédents.

Le facteur de discount (gamma)

Le facteur de discount détermine quelle importance l’agent accorde aux récompenses futures par rapport à celles qu’il peut obtenir immédiatement. Notre implémentation utilise une valeur par défaut de 0.99 (sur une échelle de 0 à 1). Un gamma faible incite l’agent à rechercher des gains immédiats sans se soucier des conséquences, alors qu’un gamma élevé le pousse à prendre des décisions qui peuvent être moins avantageuses maintenant mais plus bénéfiques à long terme.

La probabilité d’exploration (epsilon)

La probabilité d’exploration équilibre entre l’exploration aléatoire de nouvelles actions et l’exploitation des meilleures actions connues. Notre approche utilise un decay exponentiel avec la formule : epsilon = min_epsilon + (max_epsilon – min_epsilon) * exp(-epsilon_decay * episode).

Nos paramètres sont configurés avec :

  • un epsilon initial de 0.3 pour une exploration modérée au début,
  • un max_epsilon de 1.0 permettant une exploration maximale si nécessaire,
  • un min_epsilon de 0.001 pour une exploitation quasi-totale en fin d’entraînement,
  • et un epsilon_decay de 0.1 pour une décroissance progressive.

Cette configuration permet une transition fluide de l’exploration vers l’exploitation, avec un suivi de l’évolution d’epsilon via epsilon_vec pour analyser le comportement de l’agent.

epsilon = 1.0
epsilon_min = 0.01
epsilon_decay = 0.995
for episode in range(episodes):
    ...
    epsilon = max(epsilon_min, epsilon * epsilon_decay) #  Décroissance avec plancher 

Le nombre d’épisodes

  • Plus il y a d’épisodes, plus l’agent a de chances de converger.
  • Comme nous pouvons le voir sur le graphique d’entraînement, nous atteignons un point de convergence autour de 20 000 épisodes

Comparer les résultats

La meilleure façon de trouver les bons réglages est de tester plusieurs combinaisons et de comparer les courbes de performance. Une approche simple est la recherche par grille (grid search) : tester différentes valeurs de α, γ, ε et observer la convergence.

def grid_search():
    learning_rates = [0.001, 0.01, 0.1]
    gammas = [0.9, 0.95, 0.99]
    epsilons = [0.1, 0.2, 0.3]
    epsilon_decays = [0.001, 0.01, 0.1]
    best_params = None
    best_reward = float('-inf')
    for lr, gamma, epsilon, epsilon_decay in itertools.product(learning_rates, gammas, epsilons, epsilon_decays):
        print(f"Training with lr={lr}, gamma={gamma}, epsilon={epsilon}, epsilon_decay={epsilon_decay}")
        _, mean_reward = train(env=gym.make("Taxi-v3"),
                               episodes=1000,
                               lr=lr,
                               gamma=gamma,
                               epsilon=epsilon,
                               epsilon_decay=epsilon_decay)
        if mean_reward > best_reward:
            best_reward = mean_reward
            best_params = (lr, gamma, epsilon, epsilon_decay)
    print(f"Best parameters: lr={best_params[0]}, gamma={best_params[1]}, epsilon={best_params[2]}, epsilon_decay={best_params[3]}")
    print(f"Best mean reward: {best_reward}")
grid_search()

Cette méthode peut être améliorée en élargissant la plage de valeurs testées pour chaque paramètre et en augmentant la granularité de la recherche.

Limites du Q-Learning

Même si le Q-Learning fonctionne très bien pour un petit environnement comme Taxi, il présente des limites importantes. La scalabilité constitue un problème majeur car la Q-Table grandit de manière exponentielle quand l’espace d’états devient énorme, rendant impossible son stockage en mémoire vive (RAM) pour des environnements complexes.

De plus, l’algorithme souffre d’un manque de généralisation puisqu’il ne peut pas apprendre sur des états qu’il n’a jamais rencontrés auparavant. Enfin, l’exploration ε-greedy reste une approche basique, alors que d’autres méthodes comme l’exploration de Boltzmann ou UCB (Upper Confidence Bound) offrent des stratégies plus intelligentes.

Pour dépasser ces limites, on peut utiliser des réseaux de neurones pour approximer la fonction Q. C’est le principe des Deep Q-Networks (DQN), utilisés dans des environnements beaucoup plus complexes comme les jeux vidéos, la robotique ou d’autres domaines nécessitant une représentation continue des états.

Visualisation de l’agent

Gymnasium permet de visualiser le plateau de jeu avec :

import gymnasium as gym
env = gym.make("Taxi-v3", render_mode="human")
print(env.render())

Nous avons accès à plusieurs valeurs possibles pour le paramètre render_mode. (ansi, rgb_array, rgb_array_list, human).

Pour cet article nous utiliserons le render_mode « human » qui offre la meilleure visualisation graphique interactive en temps réel.

Et voilà notre agent à l’œuvre !

Conclusion

J’espère vous avoir fait découvrir un sujet intéressant à travers cet article. L’environnement Taxi-v3 est un excellent point d’entrée dans l’IA. Il offre une complexité suffisante pour comprendre les mécanismes fondamentaux tout en restant accessible.

Cet exemple pratique nous a donné un aperçu complet de l’apprentissage par renforcement. Nous avons vu la construction de la Q-Table et l’optimisation des hyperparamètres. Nous avons observé comment un agent développe une stratégie optimale par l’expérience seule, sans supervision directe.

Le Q-Learning s’applique concrètement dans plusieurs domaines. Par exemple, l’optimisation de systèmes de recommandation où l’agent apprend à proposer le contenu le plus pertinent. Ou encore la gestion automatique des ressources serveur pour optimiser les performances. Il peut aussi développer des chatbots qui s’améliorent grâce aux retours utilisateurs. Ces applications montrent qu’un algorithme simple peut résoudre des problèmes complexes du monde réel.

Pour approfondir vos connaissances, plusieurs pistes s’offrent à vous. Explorez d’autres environnements Gymnasium plus complexes. Expérimentez avec des variantes comme SARSA. Ou orientez-vous vers le Deep Reinforcement Learning avec les DQN pour des problèmes nécessitant une représentation continue des états.

liens utiles

Le projet :
https://gitlab.tech.ouidou.fr/kevin.aubel/q-learning

Articles similaire:

Q-Learning in Reinforcement Learning – GeeksforGeeks

A Beginner’s Guide to Q-Learning: Understanding with a Simple Gridworld Example

Reinforcement Learning 101

Site avec implementation de Q-learning:
Search | Kaggle

simonescaccia/dqn-Taxi-v3 · Hugging Face

winkin119/q-Taxi-v3 · Hugging Face

L’article Introduction pratique au Q-learning avec Gymnasium Taxi-v3 est apparu en premier sur Ouidou.

]]>
Introduction à K6 https://blog.ouidou.fr/articles-techniques/introduction-a-k6 Tue, 09 Dec 2025 13:14:00 +0000 https://blog.ouidou.fr/?p=17873 Écrit par Mouloud H. Définitions des types de tests Avant de plonger dans l’utilisation de K6, il est important de comprendre les différents types de tests […]

L’article Introduction à K6 est apparu en premier sur Ouidou.

]]>
Écrit par Mouloud H.

Définitions des types de tests

Avant de plonger dans l’utilisation de K6, il est important de comprendre les différents types de tests qu’il permet de réaliser :

  • Smoke testing : Un test rapide visant à valider que le système ou le script fonctionne correctement sous une charge minimale. Souvent appelé « test de santé », il s’agit d’une méthode préliminaire pour vérifier que les fonctionnalités essentielles de l’application fonctionnent correctement avant de procéder à des tests plus approfondis. Cela permet de s’assurer qu’il n’y a pas de problèmes critiques, comme des erreurs empêchant l’application de démarrer ou de répondre correctement à des requêtes simples.
  • Load testing : Ce type de test mesure les performances du système sous une charge normale ou attendue. L’objectif est de vérifier comment le système se comporte dans des conditions standard d’utilisation.
  • Stress testing : Ici, le test pousse le système au-delà de ses limites normales pour évaluer sa stabilité et sa résilience sous une charge extrême.
  • Soak testing : Aussi appelé test d’endurance, il évalue les performances et la fiabilité du système sur une période prolongée, permettant d’identifier les éventuelles fuites de mémoire ou autres problèmes de stabilité à long terme.

Qu’est-ce que K6 ?

K6 est un outil de test de charge open source, conçu pour les développeurs et les équipes DevOps. Il permet de simuler des charges réalistes sur des applications web et des API, afin de tester leurs performances et leur évolutivité. Développé en Go et racheté par Grafana Labs en 2021, K6 offre une solution complète pour détecter les régressions de performance et construire des systèmes robustes.

Pourquoi utiliser K6 ?

  • Simplicité d’utilisation : Facile à installer et à utiliser, avec une prise en main rapide grâce à JavaScript pour la rédaction des scripts.
  • Haute performance : Consomme moins de ressources que les outils basés sur la JVM comme Gatling ou JMeter.
  • Polyvalence : Convient pour différents types de tests, comme le smoke testing, le load testing, le stress testing ou encore le soak testing.
  • Visualisation des données : Compatible avec des outils comme Grafana et Datadog pour des tableaux de bord interactifs et un suivi en temps réel.
  • Open source et gratuit : Accessible à tous sans frais, avec une documentation complète.

Fonctionnalités principales

K6 se distingue par plusieurs fonctionnalités majeures, qui en font un outil très polyvalent et puissant pour les tests de performance :

  • Tests de charge : Cette fonctionnalité permet de simuler des milliers d’utilisateurs simultanés pour évaluer la résilience de vos applications. Par exemple, vous pouvez identifier comment votre serveur répond lorsqu’un grand nombre d’utilisateurs accèdent simultanément à une API ou à un site web. C’est essentiel pour anticiper les comportements lors de pics d’activité ou lors d’événements promotionnels importants.
  • Tests de performance : Avec K6, vous pouvez analyser les goulets d’étranglement dans vos systèmes. Cela inclut la détection de lenteurs dans les bases de données, les appels réseau ou encore les API tierces. Grâce à des métriques détaillées, comme le temps moyen de réponse ou les percentiles, vous obtenez une vue complète des performances de votre application sous diverses charges.
  • Tests fonctionnels : Validez les comportements attendus de vos API même sous des charges importantes. Par exemple, vous pouvez vous assurer que les réponses aux requêtes respectent bien les spécifications (codes HTTP, contenu attendu, etc.), garantissant ainsi une qualité de service optimale.
  • Métriques personnalisées : K6 permet de définir vos propres métriques en fonction de vos besoins spécifiques. Par exemple, vous pouvez mesurer le taux d’erreur, le temps de traitement d’une requête ou encore le pourcentage de requêtes réussies. Ces métriques offrent une flexibilité et une précision accrues pour vos analyses.
  • Rapports détaillés : Les rapports générés par K6 incluent des données complètes sur les performances de vos tests. Vous pouvez visualiser les tendances, les erreurs et les temps de réponse sous différentes formes, facilitant ainsi la prise de décision pour optimiser votre application.
  • Gestion des scénarios avancés : K6 offre une configuration flexible des scénarios de test, permettant de simuler des comportements proches de ceux des utilisateurs réels. Cela inclut la possibilité de moduler progressivement la charge, d’ajouter des délais entre les actions des utilisateurs ou de tester différentes étapes d’un processus utilisateur complexe.

Prise en main avec K6

Installation

Pour installer K6, utilisez le gestionnaire de paquets approprié à votre système :

Sous macOS

brew install k6

Sous Linux

sudo apt update && sudo apt install -y k6

Sous Windows

Téléchargez l’exécutable et ajoutez-le à votre PATH.

Création d’un script de test

Un script K6 est écrit en JavaScript ES6. Voici un exemple simple pour tester une API :

import http from 'k6/http';
import { check } from 'k6';
export default function () {
    const res = http.get('http://localhost:3000/slow');
    check(res, {
        'status est 200': (r) => r.status === 200,
    });
}

Présentation du serveur Express

Pour les tests réalisés avec K6, nous avons utilisé un petit serveur Express local qui simule deux types de requêtes : une requête rapide et une requête lente. Ce serveur est conçu pour servir de base à nos tests de charge et de performance.

Voici le code de ce serveur :

const express = require('express');
const app = express();
const port = 3000;
app.get('/fast', (req, res) => {
    setTimeout(() => res.send('Fast response!'), 50);
});
app.get('/slow', (req, res) => {
    setTimeout(() => res.send('Slow response!'), 2000);
});
app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}`);
});

Exécution d’un test

Pour exécuter le script ci-dessus, utilisez la commande suivante :

k6 run script.js

Le résultat obtenu est le suivant :

checks.........................: 100.00% 1 out of 1
     data_received..................: 241 B   120 B/s
     data_sent......................: 84 B    42 B/s
     http_req_blocked...............: avg=1.26ms min=1.26ms med=1.26ms max=1.26ms p(90)=1.26ms p(95)=1.26ms
     http_req_connecting............: avg=210µs  min=210µs  med=210µs  max=210µs  p(90)=210µs  p(95)=210µs 
     http_req_duration..............: avg=2s     min=2s     med=2s     max=2s     p(90)=2s     p(95)=2s    
       { expected_response:true }...: avg=2s     min=2s     med=2s     max=2s     p(90)=2s     p(95)=2s    
     http_req_failed................: 0.00%   0 out of 1
     http_req_receiving.............: avg=224µs  min=224µs  med=224µs  max=224µs  p(90)=224µs  p(95)=224µs 
     http_req_sending...............: avg=121µs  min=121µs  med=121µs  max=121µs  p(90)=121µs  p(95)=121µs 
     http_req_tls_handshaking.......: avg=0s     min=0s     med=0s     max=0s     p(90)=0s     p(95)=0s    
     http_req_waiting...............: avg=2s     min=2s     med=2s     max=2s     p(90)=2s     p(95)=2s    
     http_reqs......................: 1       0.497928/s
     iteration_duration.............: avg=2s     min=2s     med=2s     max=2s     p(90)=2s     p(95)=2s    
     iterations.....................: 1       0.497928/s
     vus............................: 1       min=1      max=1
     vus_max........................: 1       min=1      max=1

Définition des résultats générés

  • checks : Indique le pourcentage et le nombre de vérifications réussies.
  • data_received : Volume total de données reçues par le script.
  • data_sent : Volume total de données envoyées par le script.
  • http_req_blocked : Temps passé à établir une connexion (y compris l’attente si toutes les connexions disponibles sont utilisées).
  • http_req_connecting : Temps passé à établir la connexion TCP.
  • http_req_duration : Durée totale de la requête, incluant le temps d’attente pour une réponse et l’échange de données.
  • http_req_failed : Pourcentage de requêtes HTTP ayant échoué.
  • http_req_receiving : Temps pris pour recevoir les données après qu’une réponse a commencé.
  • http_req_sending : Temps pris pour envoyer les données au serveur.
  • http_req_tls_handshaking : Temps pris pour établir une connexion TLS (si applicable).
  • http_req_waiting : Temps d’attente avant de recevoir la première réponse du serveur.
  • http_reqs : Nombre total de requêtes HTTP effectuées.
  • iteration_duration : Temps total pris pour exécuter une itération complète du script.
  • iterations : Nombre total d’itérations exécutées pendant le test.
  • vus : Nombre d’utilisateurs virtuels actifs pendant le test.
  • vus_max : Nombre maximal d’utilisateurs virtuels pendant le test.

Exemples de tests

Smoke Test

import http from 'k6/http';
import { sleep } from 'k6';
export default function () {
    http.get('http://localhost:3000/fast');
    sleep(1);
}

Résultat attendu

Voici un exemple de résultat obtenu lors de l’exécution d’un Smoke Test :

scenarios: (100.00%) 1 scenario, 1 max VUs, 10m30s max duration (incl. graceful stop):
              * default: 1 iterations for each of 1 VUs (maxDuration: 10m0s, gracefulStop: 30s)
     data_received..................: 241 B 221 B/s
     data_sent......................: 84 B  77 B/s
     http_req_blocked...............: avg=7.09ms  min=7.09ms  med=7.09ms  max=7.09ms  p(90)=7.09ms  p(95)=7.09ms 
     http_req_connecting............: avg=189µs   min=189µs   med=189µs   max=189µs   p(90)=189µs   p(95)=189µs  
     http_req_duration..............: avg=74.24ms min=74.24ms med=74.24ms max=74.24ms p(90)=74.24ms p(95)=74.24ms
       { expected_response:true }...: avg=74.24ms min=74.24ms med=74.24ms max=74.24ms p(90)=74.24ms p(95)=74.24ms
     http_req_failed................: 0.00% 0 out of 1
     http_req_receiving.............: avg=55µs    min=55µs    med=55µs    max=55µs    p(90)=55µs    p(95)=55µs   
     http_req_sending...............: avg=422µs   min=422µs   med=422µs   max=422µs   p(90)=422µs   p(95)=422µs  
     http_req_tls_handshaking.......: avg=0s      min=0s      med=0s      max=0s      p(90)=0s      p(95)=0s     
     http_req_waiting...............: avg=73.76ms min=73.76ms med=73.76ms max=73.76ms p(90)=73.76ms p(95)=73.76ms
     http_reqs......................: 1     0.918662/s
     iteration_duration.............: avg=1.08s   min=1.08s   med=1.08s   max=1.08s   p(90)=1.08s   p(95)=1.08s  
     iterations.....................: 1     0.918662/s
     vus............................: 1     min=1      max=1
     vus_max........................: 1     min=1      max=1

Le rapport montre que l’API répond correctement avec des temps de réponse très courts, ce qui est attendu dans un Smoke Test.

Stress Test

Le Stress Test pousse le système au-delà de ses limites pour évaluer sa stabilité et identifier les points de défaillance. Il permet de déterminer à quel moment le système commence à échouer lorsqu’il est soumis à une charge importante.

Exemple de script

import http from 'k6/http';
import { sleep } from 'k6';
export const options = {
    stages: [
        { duration: '30s', target: 200 },
        { duration: '1m', target: 15000 },
        { duration: '20s', target: 0 },
    ],
};
export default function () {
    http.get('http://localhost:3000/slow');
    sleep(1);
}

Résultat attendu

Voici un exemple de résultat obtenu lors d’un Stress Test :

scenarios: (100.00%) 1 scenario, 15000 max VUs, 2m0s max duration (incl. graceful stop):
              * default: Up to 15000 looping VUs for 2m0s over 3 stages (gracefulRampDown: 20s, gracefulStop: 20s)
    ...
    WARN[0088] Request Failed                                error="Get \"http://localhost:3000/slow\": dial tcp 127.0.0.1:3000: connect: connection reset by peer"
    WARN[0088] Request Failed                                error="Get \"http://localhost:3000/slow\": read tcp 127.0.0.1:59822->127.0.0.1:3000: read: connection reset by peer"
    ...
     data_received..................: 50 MB  445 kB/s
     data_sent......................: 18 MB  155 kB/s
     http_req_blocked...............: avg=1.03ms   min=0s    med=3µs  max=1.92s    p(90)=23µs  p(95)=221µs
     http_req_connecting............: avg=1.01ms   min=0s    med=0s   max=1.92s    p(90)=0s    p(95)=181µs
     http_req_duration..............: avg=2.02s    min=0s    med=2s   max=3.59s    p(90)=2.07s p(95)=2.17s
       { expected_response:true }...: avg=2.02s    min=1.99s med=2s   max=3.59s    p(90)=2.07s p(95)=2.17s
     http_req_failed................: 0.01%  26 out of 208653
     http_req_receiving.............: avg=58.19µs  min=0s    med=18µs max=156.64ms p(90)=64µs  p(95)=129µs
     http_req_sending...............: avg=165.75µs min=0s    med=6µs  max=295.11ms p(90)=35µs  p(95)=98µs 
     http_req_tls_handshaking.......: avg=0s       min=0s    med=0s   max=0s       p(90)=0s    p(95)=0s   
     http_req_waiting...............: avg=2.02s    min=0s    med=2s   max=3.59s    p(90)=2.07s p(95)=2.17s
     http_reqs......................: 208653 1847.535897/s
     iteration_duration.............: avg=3.03s    min=1s    med=3s   max=4.93s    p(90)=3.08s p(95)=3.18s
     iterations.....................: 208653 1847.535897/s
     vus............................: 37     min=3            max=15000
     vus_max........................: 15000  min=15000        max=15000
running (1m52.9s), 00000/15000 VUs, 208653 complete and 0 interrupted iterations

Ce résultat montre que le système est soumis à une charge très élevée, avec des utilisateurs simultanés atteignant 15 000 au pic. Les temps de réponse restent raisonnables bien que légèrement augmentés sous la charge. Quelques erreurs peuvent apparaître (par exemple, 0.01% d’échecs), mais cela est attendu dans des tests de stress extrêmes. Une analyse plus approfondie peut être nécessaire pour optimiser les performances sous ces conditions.

Personnalisation des métriques

K6 permet de créer des métriques personnalisées pour enrichir les rapports. Voici un exemple mesurant le taux d’erreurs :

import http from 'k6/http';
import { Rate } from 'k6/metrics';
const errorRate = new Rate('error_rate');
export default function () {
    const res = http.get('http://localhost:3000/fast');
    errorRate.add(res.status >= 122);
}
data_received..................: 241 B   4.2 kB/s
     data_sent......................: 84 B    1.5 kB/s
     error_rate.....................: 100.00% 1 out of 1
     http_req_blocked...............: avg=1.39ms  min=1.39ms  med=1.39ms  max=1.39ms  p(90)=1.39ms  p(95)=1.39ms 
running (00m00.1s), 0/1 VUs, 1 complete and 0 interrupted iterations

Intégration avec Grafana et Prometheus

Voici une configuration complète pour visualiser vos résultats dans Grafana et Prometheus.

Pré-requis

  • Docker et Docker Compose installés sur votre machine.

Configuration Docker Compose

Créez un fichier docker-compose.yml :

version: '3.7'
services:
  prometheus:
    image: prom/prometheus:latest
    container_name: prometheus
    ports:
      - "9090:9090"
    command:
      - "--config.file=/etc/prometheus/prometheus.yml"
      - "--web.enable-remote-write-receiver"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    networks:
      - monitoring
  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    ports:
      - "3001:3000"
    volumes:
      - grafana-data:/var/lib/grafana
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    networks:
      - monitoring
networks:
  monitoring:
volumes:
  grafana-data:

Configuration Prometheus

Créez un fichier prometheus.yml :

global:
  scrape_interval: 15s
scrape_configs:
  - job_name: 'k6'
    static_configs:
      - targets: ['host.docker.internal:6565']

Exécution

Démarrez les conteneurs :

docker-compose up -d

Lancer un test K6 avec Prometheus Remote Write

Ajoutez cette configuration dans votre script K6 :

import http from 'k6/http';
import { sleep, check } from 'k6';
export const options = {
    stages: [
        { duration: '30s', target: 50 },  // Monter jusqu'à 50 utilisateurs virtuels
        { duration: '1m', target: 10000 }, // Monter jusqu'à 10000 utilisateurs virtuels
        { duration: '2m', target: 200 }, // Monter jusqu'à 200 utilisateurs virtuels
        { duration: '30s', target: 0 },  // Descendre progressivement à 0
    ],
    thresholds: {
        http_req_duration: ['p(95)<2000'], // 95% des requêtes doivent répondre en moins de 2s
        http_req_failed: ['rate<0.01'],   // Moins de 1% des requêtes échouées
    },
    ext: {
        experimental: {
            prometheus_rww: {
                address: 'http://host.docker.internal:9090/api/v1/write',
                pushInterval: '5s',
            },
        },
    },
};
export default function () {
    const fastRes = http.get('http://localhost:3000/fast');
    check(fastRes, {
        'status is 200': (r) => r.status === 200,
        'response time < 100ms': (r) => r.timings.duration < 100,
    });
    const slowRes = http.get('http://localhost:3000/slow');
    check(slowRes, {
        'status is 200': (r) => r.status === 200,
        'response time < 300ms': (r) => r.timings.duration < 300,
    });
    sleep(1);
}

Configurer Grafana

  1. Accédez à Grafana via http://localhost:3001.
  2. Connectez-vous (utilisateur : admin, mot de passe : admin).
  3. Ajoutez Prometheus comme source de données :
    • URL : http://prometheus:9090
  4. Importez un tableau de bord K6 disponible sur Grafana Dashboards (ID : 19665). (k6 Prometheus | Grafana Labs )

Exécution du test :

k6 run --out experimental-prometheus-rw script.js

Résultats des tests et visualisation dans Grafana

Après avoir exécuté le test de charge avec K6, nous avons intégré les données dans Prometheus et visualisé les résultats dans Grafana. Cette configuration permet une analyse approfondie des performances de votre API en temps réel. Voici deux exemples de tableaux de bord obtenus lors des tests et disponible sur localhost:3001:

Exemple de tableau de bord 1

Ce tableau affiche les métriques clés suivantes :

  • Taux de transfert (data sent/received) : Analyse de la quantité de données envoyées et reçues au cours du test.
  • Durée des requêtes HTTP : Répartition des durées de réponse pour les requêtes réussies, ainsi que les statistiques détaillées par URL.
  • Taux de requêtes par seconde : Indication de la charge générée par les utilisateurs virtuels (Virtual Users).

Exemple de tableau de bord 2

Dans ce tableau, nous pouvons observer :

  • Vue d’ensemble des performances : Indication des utilisateurs virtuels actifs (VUs), des requêtes HTTP envoyées, et des taux de succès/échec.
  • Durée des requêtes HTTP : Visualisation des temps de réponse moyens et des percentiles élevés (p95, p99) pour détecter les points d’anomalie.
  • Rythme des requêtes : Statistiques sur les requêtes par seconde au pic de la charge.

Ces graphiques mettent en évidence les performances globales de votre système et permettent de diagnostiquer les éventuels goulots d’étranglement.

Meilleures pratiques pour K6

  • Modularité : Divisez vos scripts pour une meilleure lisibilité et maintenance.
  • Validation : Utilisez des check pour valider les réponses de vos API.
  • Rapports avancés : Intégrez avec Grafana pour visualiser les métriques en temps réel.
  • Automatisation : Intégrez K6 dans vos pipelines CI/CD pour surveiller continuellement les performances.

Conclusion

K6 est un outil indispensable pour évaluer la performance et la résilience des applications modernes. Sa simplicité, ses hautes performances et ses nombreuses options en font un choix idéal pour les développeurs et les équipes DevOps. Essayez-le pour garantir une expérience utilisateur optimale, même sous des charges importantes.

Sources

L’article Introduction à K6 est apparu en premier sur Ouidou.

]]>