Tests de performance - 02 - k6 : ton premier test de charge HTTP

Installer k6 et écrire ton premier test de charge. Virtual users, duration, checks et premiers résultats.

02 - k6 : ton premier test de charge HTTP

Ce que tu vas apprendre

  • Ce qu'est k6 et pourquoi c'est mon outil de référencé
  • Installer k6 et lancer ton premier test de charge
  • Lire et comprendre les résultats : latence, throughput, checks

Prerequisites

Un terminal. Une URL a tester (ton API locale ou un site en staging). Avoir lu l'introduction de la serie.


Pourquoi k6

J'ai essaye pas mal d'outils de test de charge avant de me fixer sur k6. JMeter avec son interface Swing des annees 2000, Gatling avec son DSL Scala que personne ne connaît, Artillery avec ses fichiers YAML interminables. k6 a regle le problème : c'est un binaire Go, les scripts sont en JavaScript, et les résultats sont lisibles directement dans le terminal.

k6 est open source, développé par Grafana Labs. Le binaire fait 30 MB, il n'a aucune dépendance, et il tourne sur n'importe quoi. Les scripts sont en JavaScript (ES6), donc si tu fais du TypeScript au quotidien sur paltemps.fr ou ailleurs, tu es deja chez toi.

Installation

Trois options :

bash# macOS
brew install k6

# Linux (Debian/Ubuntu)
sudo gpg -k
sudo gpg --no-default-keyring --keyring /usr/share/keyrings/k6-archive-keyring.gpg \
  --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys C5AD17C747E3415A3642D57D77C6C491D6AC1D69
echo "deb [signed-by=/usr/share/keyrings/k6-archive-keyring.gpg] https://dl.k6.io/deb stable main" | \
  sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update && sudo apt-get install k6

# Docker (pas d'install)
docker run --rm -i grafana/k6 run - <script.js

Verifie que ca marche :

bashk6 version

Ton premier script

Cree un fichier load-test.js :

javascriptimport http from "k6/http";
import { check, sleep } from "k6";

export const options = {
  vus: 10,
  duration: "30s",
};

export default function () {
  const res = http.get("https://blog.paltemps.fr/");
  check(res, {
    "status 200": (r) => r.status === 200,
    "response < 500ms": (r) => r.timings.duration < 500,
  });
  sleep(1);
}

Decomposons :

options.vus: 10 : 10 virtual users (VU) simultanes. Chaque VU exécuté la fonction default en boucle pendant toute la duree du test.

options.duration: "30s" : le test dure 30 secondes.

export default function() : c'est ce que chaque VU exécuté a chaque itération. Une requête GET, deux verifications, puis une pause d'1 seconde.

check() : les assertions du test. Ici on vérifié que le status est 200 et que la réponse arrive en moins de 500ms. Les checks ne font pas échouer le test (contrairement a un assert classique), ils sont reportes dans les résultats.

sleep(1) : simule le "think time" d'un vrai utilisateur. Sans sleep, chaque VU bombarderait le serveur aussi vite que possible. Un vrai utilisateur attend, lit la page, clique. Le sleep rend le test realiste.

Lancer le test

bashk6 run load-test.js

k6 affiche une barre de progression pendant l'exécution, puis un rapport complet.

Lire les résultats

Voici ce que tu vas voir (les chiffres varient selon ton serveur) :

     checks.........................: 95.00% ✓ 570  ✗ 30
     data_received..................: 12 MB  400 kB/s
     data_sent......................: 34 kB  1.1 kB/s
     http_req_blocked...............: avg=2.1ms  min=1µs   med=3µs   max=210ms  p(90)=5µs   p(95)=8µs
     http_req_connecting............: avg=1.8ms  min=0s    med=0s    max=198ms  p(90)=0s    p(95)=0s
     http_req_duration..............: avg=87ms   min=42ms  med=78ms  max=1.2s   p(90)=120ms p(95)=180ms
     http_req_failed................: 0.00%  ✓ 0    ✗ 300
     http_req_receiving.............: avg=1.2ms  min=40µs  med=800µs max=15ms   p(90)=2ms   p(95)=3ms
     http_req_sending...............: avg=30µs   min=8µs   med=25µs  max=1ms    p(90)=50µs  p(95)=80µs
     http_req_tls_handshaking.......: avg=1.5ms  min=0s    med=0s    max=150ms  p(90)=0s    p(95)=0s
     http_req_waiting...............: avg=85ms   min=41ms  med=76ms  max=1.2s   p(90)=118ms p(95)=175ms
     http_reqs......................: 300    10/s
     iteration_duration.............: avg=1.09s  min=1.04s med=1.08s max=2.2s   p(90)=1.12s p(95)=1.18s
     iterations.....................: 300    10/s
     vus............................: 10     min=10  max=10
     vus_max........................: 10     min=10  max=10

Les metriques qui comptent :

http_req_duration : le temps total de la requête HTTP (envoi + attente + reception). C'est LA metrique principale. Regarde le p(95) : 95% des requêtes ont repondu en moins de 180ms. C'est correct pour un blog.

http_reqs : le nombre total de requêtes et le throughput (req/s). Ici, 300 requêtes en 30 secondes = 10 req/s. Normal avec 10 VUs et 1 seconde de sleep.

checks : 95% des checks passent. 30 checks ont echoue. C'est probablement le check "response < 500ms" qui a echoue sur quelques requêtes lentes.

http_req_failed : 0% d'erreurs HTTP. Aucune requête n'a retourne un code 4xx ou 5xx. Bien.

VU vs requêtes : la confusion classique

10 VUs ne signifie pas 10 requêtes par seconde. Ca depend de la duree de chaque itération (requête + sleep + overhead).

Si chaque itération prend 1.1 seconde (1s de sleep + 100ms de requête), alors 10 VUs font environ 9 requêtes par seconde. Si tu enleves le sleep, 10 VUs avec une API qui répond en 50ms font 200 req/s.

La formule : throughput ≈ VUs / duree_iteration_en_secondes.

Pour viser un throughput precis, ajuste le nombre de VUs et le sleep. Ou utilise les scénarios avances (article suivant).

Tester une API avec des headers

La plupart des APIs ont besoin d'authentification :

javascriptimport http from "k6/http";
import { check } from "k6";

export const options = {
  vus: 5,
  duration: "20s",
};

export default function () {
  const params = {
    headers: {
      Authorization: "Bearer mon-token-de-test",
      "Content-Type": "application/json",
    },
  };

  const payload = JSON.stringify({
    query: "test",
    limit: 10,
  });

  const res = http.post("https://api.example.com/search", payload, params);

  check(res, {
    "status 200": (r) => r.status === 200,
    "has results": (r) => JSON.parse(r.body).length > 0,
  });
}

k6 supporte GET, POST, PUT, DELETE, PATCH. Tu peux parser les réponses JSON, extraire des valeurs, et les réutiliser dans les requêtes suivantes. C'est du JavaScript standard.

Le piège du test depuis ta machine

Si tu lances k6 depuis ton laptop sur ton WiFi, la latence réseau s'ajoute au temps de réponse du serveur. Un ping de 30ms vers ton VPS signifie +60ms sur chaque requête (aller-retour). Pour des résultats propres, lance k6 depuis un serveur proche de ta cible (meme datacenter, meme cloud). Ou au minimum, soustrait la latence réseau de base dans ton analyse.

Sur mes tests pour paltemps.fr, je lance k6 directement sur le VPS via SSH. La latence réseau est < 1ms, donc les mesures refletent vraiment le temps de traitement du serveur.


Article précédent : 01 - Benchmark CPU et mémoire avec Bun Article suivant : 03 - Scénarios k6 : ramping, spike et soak

Sources

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.