Aller au contenu

Session 20 — JMeter UI : debug NAS, métriques Prometheus, IHM avancée

Durée : ~12 heures
Contexte : Session marathon de déploiement, debug, et enrichissement de l'IHM JMeter sur le NAS Synology. Résolution de 25+ bugs de déploiement, intégration du plugin Prometheus johrstrom, puis ajout des fonctionnalités avancées de l'IHM (graphiques, gestionnaire scénarios, éditeur JMX inline, logs en direct).


🎯 Ce qui a été produit

1. Déploiement sur NAS Synology — problèmes et résolutions

Problème seccomp : apk add docker-cli dans le Dockerfile plante sur le NAS (kernel DSM 7 sans seccomp). Solution : suppression de docker-cli, pilotage Docker exclusivement via l'API socket Unix (/var/run/docker.sock) en Node.js pur.

Architecture retenue — sans docker-cli :

Node.js jmeter-ui
  └── socket /var/run/docker.sock
        ├── POST /containers/exec    → lancer JMeter (docker exec dans container permanent)
        ├── GET  /containers/json    → détecter fin de tir
        └── GET  /containers/{id}/logs → logs perfshop-jmeter-ui

Mode volume vs build : Sur le NAS, Dockerfile inutilisable (npm install planterait aussi). Solution dans docker-compose NAS : image: node:20-alpine + code monté en volume + npm install lancé à chaque démarrage dans la command:.


2. Container JMeter permanent (exit seccomp → docker exec)

Abandon du container éphémère créé par docker run à chaque tir — trop de problèmes (réseau introuvable, image absente, args mal formés). Migration vers un container permanent perfshop-jmeter avec tail -f /dev/null, et exécution des tirs via docker exec.

Avant Après
Container éphémère créé à chaque tir Container permanent, exec au lancement
Démarrage JVM ~5s par tir JVM déjà up, démarrage quasi-instantané
Problèmes réseau Docker Réseau fixe dans compose
HOST_WORKDIR pour bind-mounts Volumes fixés dans compose

Variables variabilisées : JMETER_IMAGE, JMETER_CONTAINER_NAME, DOCKER_NETWORK, JMETER_TARGET_INTERNAL, JMETER_TARGET_EXTERNAL, JMETER_TARGET_FRONTEND — toutes issues du .env, jamais hardcodées.


3. Plugin Prometheus johrstrom — intégration complète

17 bugs résolus pour faire fonctionner le pipeline complet : JMeter → PrometheusListener :9270 → Prometheus scrape → IHM métriques live

Points clés résolus :

  • prometheus.ip=0.0.0.0 obligatoire (défaut 127.0.0.1 → inaccessible depuis Prometheus)
  • PrometheusListener doit être dans le hashTree du ThreadGroup (pas au niveau TestPlan)
  • Les stringProp name des labels doivent utiliser les hashcodes Java ("102727412", "3059181") et non des noms arbitraires
  • prometheus.yml : job jmeter ciblant perfshop-jmeter:9270 + --web.enable-lifecycle dans Prometheus pour rechargement à chaud
  • Assertions JMX : test_type=8 (Equals sur response_code) obligatoire — test_type=2 cherchait dans le body, pas le code HTTP

Métriques disponibles confirmées sur le NAS :

jmeter_response_time{quantile="0.5|0.95|0.99", label="GET /api/products"}
jmeter_transactions_total{label="..."}
jmeter_success_ratio_failure / _success / _total
jmeter_threads{state="active|started|finished"}
jmeter_response_time_count / _sum


4. Corrections des 7 scénarios JMX

Fichier Corrections
simple-api-products.jmx ConstantTimer 5s → GaussianRandomTimer ~800ms ±300ms
baseline-nominal.jmx Port 9080→8080, PrometheusListener dans ThreadGroup
browse-product-f4.jmx Assertion test_type 2→8, PrometheusListener dans TG
checkout-stress-f1f2.jmx 2 assertions corrigées, PrometheusListener dans TG
load-catalogue-f3.jmx Assertion OR test_type 6→40, PrometheusListener dans TG
full-chaos-master.jmx PrometheusListener dans TG
parcours-complet-e2e.jmx 8 assertions corrigées, PrometheusListener dans TG

Règle définitive pour les assertions : - Code unique → test_type=8 + Assertion.test_field=Assertion.response_code - Codes multiples (OR) → test_type=40 + Assertion.test_field=Assertion.response_code

Organisation en sous-dossiers (analogie LoadRunner) :

jmeter/scenarios/
  ├── baseline-nominal.jmx          → Général
  ├── simple-api-products.jmx
  ├── Fonctionnel/                  → 3 scénarios
  ├── Master/                       → 1 scénario
  └── E2E/                          → 1 scénario


5. resolveDockerTarget — localhost → host.docker.internal

Sur Docker Desktop Windows, localhost depuis un container = le container lui-même, pas la machine hôte. Solution dans server.js : substitution automatique.

// server.js — resolveDockerTarget()
const resolvedTarget = rawTarget.replace(/localhost/g, 'host.docker.internal');

Transparent pour l'étudiant : il voit localhost:9080, JMeter reçoit host.docker.internal:9080.


6. IHM avancée — fonctionnalités ajoutées

Sélecteur URL cible enrichi : - 4 modes radio : Interne / Externe / Frontend / Personnalisée - Aperçu dynamique URL utilisée : mis à jour en temps réel - Checkbox "Utiliser le JMX tel quel" — désactive la surcharge -J params pour les étudiants qui ont codé leurs URLs en dur dans leur script

Sélecteur vUsers : radio Slider (1–500) ↔ Saisie libre (illimité) avec grisage croisé

Sélecteur Durée : radio Slider (max 24h) ↔ Saisie libre (valeur + unité min/h/j/sem) avec badge › 24h quand le slider n'est plus représentatif. Source de vérité = _durationMinReal.

Gestionnaire de scénarios (modale) : - Vue arborescente des dossiers + fichiers - Drag & drop entre dossiers - Création de dossier - Upload vers le dossier choisi - Suppression fichiers et dossiers vides

Éditeur JMX inline (modale) : - Parse XML côté serveur → extraction ThreadGroups, timers, assertions, samplers - Bandeau "Variables disponibles" avec ${__P(users,...)} et ${__P(duration,...)} copiables - Champs éditables : threads, ramp-up, durée, boucles, think times - Bouton ↩ Reset par champ + badge orange si valeur diverge de l'original - Sauvegarde → réécriture XML du JMX

Badge container JMeter (header) : - Polling toutes les 60s via socket Docker - 3 états : 🟢 JMeter ✓ / 🔴 JMeter ✗ / ⚪ Vérification - Tooltip : nom container, statut, heure démarrage

Badge paramètres du tir : 👥 110 vUsers · ⏱ 07:00 dans le bandeau "En cours" pour distinguer les paramètres lancés des valeurs courantes de l'IHM.

Persistance runState : /app/results/.runstate.json — survit aux restarts Node. Au redémarrage : récupération du tir en cours si JMeter tourne encore, sinon finished.

Grisage config pendant tir : tous les contrôles de configuration grisés (opacity:0.45, pointer-events:none, disabled) pendant qu'un tir est en cours. Seul le bouton ⏹ reste actif.

Gestion exit codes : | Code | Signification | Badge IHM | |---|---|---| | 0 | Succès complet | 🟣 Terminé | | 1 | Assertions échouées (chaos normal) | 🟡 Terminé (erreurs sample) | | 143 | SIGTERM — fin durée ou bouton Stop | 🟣 Terminé | | Autre | Crash JMeter | 🔴 Erreur |


7. Logs JMeter — double accès

IHM /api/logs : lecture via socket Docker - docker exec perfshop-jmeter tail -n N /jmeter-logs/jmeter.log pour les logs JMeter - docker logs --tail N perfshop-jmeter-ui pour les logs Node.js - Panneau deux colonnes avec sélecteurs target + nombre de lignes + auto-refresh 5s - Bouton 🔄 par tableau pour actualisation individuelle - Gestion 401 : stop auto + reload page login si session expirée

Grafana/Loki : - perfshop-jmeter-ui : collecté par Promtail via docker_sd ✅ - perfshop-jmeter : collecté via fichier statique Promtail depuis ./jmeter/logs/jmeter.log (volume ./jmeter/logs:/jmeter-logs monté sur le container ET sur Promtail) - Commande JMeter : -j /jmeter-logs/jmeter.log pour écrire dans le volume partagé

Dashboard Grafana JMeter — 3 nouveaux panels (IDs 105-107) : - Row "📋 Logs — JMeter & UI" - Panel logs perfshop-jmeter (Loki query {container="perfshop-jmeter"}) - Panel logs perfshop-jmeter-ui (Loki query {container="perfshop-jmeter-ui"})


8. Dashboard Grafana JMeter — mise à jour complète

Réécriture depuis zéro avec les vraies métriques johrstrom (ancien dashboard utilisait des noms Influx incorrects). 25 panels en 4 sections :

Section Panels
Stats KPI vUsers actifs, TPS, Taux erreur %, P95, Total samples, Temps moyen
Temps de réponse P50/P95/P99/Moy global + par label (sampler)
Débit & Erreurs TPS total + par label, taux succès/erreur %, transactions bargauge
Threads actifs/démarrés/terminés + samples par label
Corrélation backend Heap JVM, erreurs HTTP, latence Spring, CPU process
Logs perfshop-jmeter + perfshop-jmeter-ui (Loki)

Variable de template $rate_window (15s/30s/1m/2m) ajoutée pour ajuster les rate().


9. Rapport HTML JMeter et gestion des rapports IHM

  • Rapports servis statiquement depuis /report/run-XXXXX-report/index.html
  • Liste des rapports avec date/heure lisible extraite du timestamp dans le nom de fichier
  • Bouton 🗑 par rapport avec confirmation → suppression du dossier -report/ + .jtl
  • Validation côté serveur : regex ^run-\d+-report$ (anti path traversal)

10. Guide pédagogique MkDocs

Nouveau fichier mkdocs/docs/guides/jmeter-script-custom.md : - Procédure complète pour créer un scénario JMeter depuis l'IHM JMeter GUI - Comment configurer les variables ${__P(target_host,...)} / ${__P(target_port,8080)} - Comment ajouter le PrometheusListener (position correcte = enfant du ThreadGroup) - Comment uploader et lancer depuis l'IHM PerfShop - Checklist 6 points de contrôle avant upload


📦 Fichiers modifiés

Fichier Nature
jmeter-ui/src/server.js Réécriture complète — socket Docker, exec permanent, logs, éditeur JMX
jmeter-ui/public/index.html IHM avancée — graphiques, gestionnaire, éditeur, logs, grisage
jmeter-ui/Dockerfile Suppression docker-cli (seccomp NAS incompatible)
jmeter-ui/package.json Dépendances nettoyées (suppression cors, multer mis à jour)
docker-compose.yml Container JMeter permanent, volumes logs, mode volume NAS
docker-compose.desktop.yml Alignement avec NAS, resolveDockerTarget, volumes logs
docker-compose.build.yml Alignement complet (Dockerfile build conservé)
.env.example + .env.production Variables JMeter complètes (image, réseau, cibles, RAM)
prometheus/prometheus.yml Job jmeterperfshop-jmeter:9270, --web.enable-lifecycle
grafana/dashboards/dashboard-jmeter.json 25+3 panels, métriques johrstrom correctes
promtail/promtail-config.yml perfshop-jmeter-ui dans docker_sd, job fichier jmeter.log
jmeter/scenarios/*.jmx (7 fichiers) Assertions corrigées, PrometheusListener dans TG
mkdocs/docs/guides/jmeter-script-custom.md Guide créer/uploader un scénario custom
mkdocs/docs/roadmap/roadmap.md Phase 8b JMeter UI documentée, Phase 9.3 = ✅ Livré
mkdocs/docs/vibe-coding/session-17.md Créé
mkdocs/docs/vibe-coding/session-18.md Créé
mkdocs/docs/vibe-coding/session-19.md Créé

🏗️ Anti-patterns documentés (leçons de déploiement)

Anti-pattern Correction
apk add docker-cli sur NAS Socket Docker Unix uniquement
docker run container éphémère docker exec dans container permanent
require() dans module ESM import { spawn } from 'child_process'
Port hardcodé en dur Toujours via variables .env
PrometheusListener au niveau TestPlan Doit être enfant du ThreadGroup
test_type=2 pour assertion HTTP test_type=8 (Equals) ou test_type=40 (OR)
prometheus.ip par défaut Toujours -Jprometheus.ip=0.0.0.0
localhost dans URL cible JMeter resolveDockerTarget()host.docker.internal

🎓 Valeur pédagogique

Concept Ce que l'apprenant voit
Container JMeter permanent Pas de latence JVM, tir quasi-instantané
Métriques Prometheus live P50/P95/P99 en temps réel pendant le tir
Sous-dossiers scénarios Organisation par domaine comme LoadRunner
Éditeur JMX inline Modifier threads/durée/timers sans ouvrir JMeter GUI
Logs en direct Voir les samplers JMeter et logs Node.js sans SSH
Chaos + assertions Exit code 1 = assertions échouées par chaos = comportement normal