k6 peut tester la charge d'un broker MQTT grâce à l'extension xk6-mqtt : vous écrivez le test en JavaScript avec un Client de style MQTT.js, k6 l'exécute avec des centaines ou des milliers d'utilisateurs virtuels, et les thresholds transforment le résultat en un contrôle réussi/échoué exécutable en CI. C'est idéal quand vous voulez des tests de charge sous forme de code, versionnés à côté de votre application.
Ce guide explique comment obtenir un binaire k6 avec le support MQTT, le modèle d'event loop qui piège la plupart des premiers scripts, un script de charge avec publishers, subscribers et une métrique de latence de bout en bout, ainsi que des thresholds qui font échouer l'exécution quand le broker ralentit.
Obtenir k6 avec le support MQTT
MQTT ne fait pas partie du binaire de base de k6. Il provient de l'extension xk6-mqtt maintenue par Grafana, importée via k6/x/mqtt. Deux façons de l'utiliser :
Résolution automatique des extensions (k6 v1.2.0 et suivantes). Importez le module dans votre script et lancez-le normalement ; k6 détecte l'import et provisionne l'extension :
k6 version # nécessite v1.2.0 ou plus récent
k6 run mqtt-smoke.jsBinaire personnalisé avec xk6. Utile en CI sans accès réseau ou pour figer la version exacte de l'extension :
go install go.k6.io/xk6/cmd/xk6@latest
xk6 build --with github.com/grafana/xk6-mqtt
./k6 run mqtt-smoke.jsD'abord un smoke test
Commencez avec un seul utilisateur virtuel qui se connecte, s'abonne, publie un message et se déconnecte à sa réception. Si cela échoue, un test de charge échouera simplement plus vite.
import { Client } from "k6/x/mqtt";
const BROKER = __ENV.MQTT_URL || "mqtt://broker.connectmq.com:1883";
export default function () {
const client = new Client({
username: __ENV.MQTT_USER,
password: __ENV.MQTT_PASS,
});
const topic = "testmqtt/k6/smoke";
client.on("connect", () => {
client.subscribe(topic, { qos: 1 });
client.publish(topic, "bonjour depuis k6", { qos: 1 });
});
client.on("message", (t, payload) => {
console.log(`reçu sur ${t} : ${new TextDecoder().decode(payload)}`);
client.end();
});
client.on("error", (err) => {
console.error(`${err.method}: ${err.message}`);
client.end();
});
client.connect(BROKER);
}MQTT_USER=username MQTT_PASS=password k6 run mqtt-smoke.jsGardez le client MQTT en ligne abonné à testmqtt/k6/# pendant l'exécution : vous verrez le message arriver, ce qui confirme le topic et les identifiants indépendamment de k6. L'endpoint partagé broker.connectmq.com convient pour ce smoke test ; lancez le script de charge ci-dessous contre votre propre instance privée de broker en définissant MQTT_URL et les identifiants.
Le modèle d'event loop
C'est ce qui diffère des tests HTTP. Avec xk6-mqtt, une itération ne se termine pas quand la fonction default retourne. Le VU continue d'exécuter son event loop — en traitant les callbacks connect, message et des timers — jusqu'à la fermeture de la connexion, soit parce que vous appelez client.end(), soit parce que le broker la coupe.
Conséquences pour vos scripts :
- Une itération = une session MQTT. Décidez de la durée d'une session et terminez-la explicitement avec
setTimeout(() => client.end(), …). - Utilisez
setIntervaldans le handlerconnectpour publier à rythme fixe.sleep()bloquerait l'event loop. - Gérez toujours
erroret appelez-yclient.end(), sinon un VU en échec peut rester bloqué jusqu'au graceful stop du scénario. - Donnez à chaque connexion un
client_idunique (ou laissez-le vide pour que le broker en attribue un). Les doublons provoquent des session takeovers qui ressemblent à des déconnexions aléatoires.
Un script de charge avec latence de bout en bout
Les métriques intégrées comptent connexions, publications et messages reçus, et mesurent la durée de connexion, mais elles n'indiquent pas le temps mis par un message pour aller du publisher au subscriber. Ajoutez-le vous-même : placez Date.now() dans le payload et enregistrez la différence dans une Trend personnalisée. Publisher et subscriber tournent dans le même VU et partagent donc la même horloge.
import { Client } from "k6/x/mqtt";
import { Trend, Counter } from "k6/metrics";
import exec from "k6/execution";
const BROKER = __ENV.MQTT_URL || "mqtt://broker.connectmq.com:1883";
const SESSION_MS = 30000; // une itération = une session de 30 s
const INTERVAL_MS = 100; // 10 messages par seconde et par VU
const e2eLatency = new Trend("mqtt_e2e_latency", true);
const mqttErrors = new Counter("mqtt_errors");
export const options = {
scenarios: {
telemetry: {
executor: "ramping-vus",
startVUs: 0,
stages: [
{ duration: "30s", target: 100 },
{ duration: "3m", target: 100 },
{ duration: "15s", target: 0 },
],
gracefulRampDown: "35s",
},
},
thresholds: {
mqtt_e2e_latency: ["p(95)<100", "p(99)<250"],
mqtt_connect_duration: ["p(95)<1000"],
mqtt_errors: ["count<1"],
},
};
export default function () {
const vu = exec.vu.idInTest;
const topic = `testmqtt/k6/${vu}/data`;
const client = new Client({
client_id: `k6-${vu}-${exec.vu.iterationInScenario}`,
username: __ENV.MQTT_USER,
password: __ENV.MQTT_PASS,
});
client.on("connect", () => {
client.subscribe(topic, { qos: 1 });
let seq = 0;
const timer = setInterval(() => {
const body = JSON.stringify({ seq: seq++, t: Date.now(), pad: "x".repeat(200) });
client.publish(topic, body, { qos: 1 });
}, INTERVAL_MS);
setTimeout(() => {
clearInterval(timer);
client.end();
}, SESSION_MS);
});
client.on("message", (t, payload) => {
const msg = JSON.parse(new TextDecoder().decode(payload));
e2eLatency.add(Date.now() - msg.t);
});
client.on("error", (err) => {
mqttErrors.add(1, { method: err.method });
client.end();
});
client.connect(BROKER, { keepalive: 30 });
}Avec 100 VUs × 10 messages par seconde, l'objectif est de 1 000 messages publiés et 1 000 livrés par seconde. Le payload fait environ 250 octets ; ajustez pad pour correspondre à vos appareils et vérifiez la taille réelle sur le réseau avec le calculateur de taille de paquet.
gracefulRampDown est réglé un peu au-dessus de la durée de session pour que les VUs terminent leur session en cours pendant la descente au lieu d'être coupés en plein flux, ce qui apparaîtrait comme des messages manquants.
Des thresholds qui font échouer l'exécution
Les thresholds font sortir k6 avec un code non nul quand une condition est violée — exactement ce qu'il faut dans un pipeline :
| Threshold | Signification |
|---|---|
mqtt_e2e_latency: p(95)<100 | 95 % des messages livrés en moins de 100 ms |
mqtt_e2e_latency: p(99)<250 | La latence de queue reste sous 250 ms |
mqtt_connect_duration: p(95)<1000 | Les connexions (TLS compris) aboutissent en moins de 1 s |
mqtt_errors: count<1 | Toute erreur MQTT fait échouer le test |
Ces valeurs sont des exemples. Fixez-les à partir de votre propre référence : lancez le test sur un broker au repos, notez p95/p99 et gardez de la marge. Comparez aussi les compteurs intégrés mqtt_messages_sent et mqtt_messages_received dans le résumé final — en QoS 1 avec un subscriber par topic, ils doivent être proches ; un écart croissant signifie des messages mis en file ou perdus. Pour aller plus loin, voir interpréter les résultats de benchmark.
Choisir un executor
Comme une itération correspond à une session MQTT, l'executor détermine combien de connexions sont ouvertes et à quelle fréquence de nouvelles arrivent :
| Executor | Modélise | Remarque |
|---|---|---|
constant-vus | Une flotte fixe d'appareils toujours connectés | Connexions simultanées = VUs |
ramping-vus | Une flotte qui se connecte par paliers | Utilisé dans le script ci-dessus ; montez lentement pour voir où la latence décroche |
constant-arrival-rate | De nouvelles sessions qui démarrent à rythme fixe | Fixez preAllocatedVUs ≥ débit × durée de session, sinon k6 manquera de VUs |
per-vu-iterations | Chaque VU exécute exactement N sessions | Pratique pour les tests de reconnexion : sessions courtes, nombreuses itérations |
Pour un test de tempête de reconnexions, réduisez SESSION_MS à quelques secondes et utilisez constant-arrival-rate : chaque itération effectue alors un cycle complet CONNECT, SUBSCRIBE et DISCONNECT, et mqtt_connect_duration montre comment le temps de connexion se dégrade quand le débit d'arrivée augmente. Voir les tempêtes de reconnexion pour savoir quoi surveiller.
TLS et WebSockets
Changez le schéma de l'URL pour tester d'autres transports avec le même script :
k6 run -e MQTT_URL=mqtts://broker.connectmq.com:8883 mqtt-load.js
k6 run -e MQTT_URL=wss://broker.connectmq.com:8084/mqtt mqtt-load.jsTLS utilise la configuration TLS standard de k6 (par exemple tlsAuth pour les certificats client). Comparer le même scénario en TCP et en TLS est le moyen le plus rapide de mesurer le coût du handshake — voir tests de performance TLS MQTT.
Exécuter k6 en CI
- Gardez
mqtt-smoke.jscomme contrôle rapide sur chaque pull request et lancezmqtt-load.jschaque nuit ou avant les releases. - Passez les identifiants comme secrets via
-e MQTT_USER=… -e MQTT_PASS=…ou des variables d'environnement ; ne les écrivez jamais en dur. - Exportez les résultats avec
--summary-export=summary.jsonou une sortie comme--out json=results.jsonpour conserver un historique. - Exécutez le générateur de charge dans la même région que l'endpoint broker de vos appareils et surveillez son CPU ; un générateur saturé gonfle la latence.
Voir les tests MQTT dans GitHub Actions pour un workflow complet.
Voir le trafic k6 dans le client en ligne
k6 avec xk6-mqtt transforme un test de charge MQTT en un script court et relisible, avec des critères de réussite clairs. Maîtrisez le modèle d'event loop, mesurez vous-même la latence de bout en bout et laissez les thresholds vous signaler quand une modification du broker a tout ralenti.
Questions fréquentes
k6 prend-il en charge MQTT nativement ?
MQTT est fourni par l'extension xk6-mqtt, importée via k6/x/mqtt. Les versions récentes de k6 (v1.2.0 et suivantes) résolvent automatiquement les extensions officielles importées par un script ; sinon, compilez un binaire avec xk6 build --with github.com/grafana/xk6-mqtt.
Pourquoi les anciens exemples MQTT pour k6 utilisent-ils une autre API ?
Plusieurs projets communautaires s'appelaient aussi xk6-mqtt, avec des API basées sur le constructeur. L'extension actuelle de Grafana utilise un Client de style MQTT.js avec connect, subscribe, publish et des handlers on(event). Ne mélangez pas les exemples des deux.
Comment mesurer la latence MQTT dans k6 ?
Placez Date.now() dans chaque payload, abonnez-vous au même topic dans le même VU et ajoutez la différence à une métrique Trend personnalisée à la réception du message. Posez ensuite un threshold p(95) ou p(99) sur cette Trend.



