JMeter et Groovy : avoir des requêtes HTTP concurrentes

Maintenant que nous avons vu comment faire des scripts Groovy dans JMeter, utilisons Groovy pour faire des requêtes HTTP parallèles avec JMeter.

Oh ! Des requêtes parallèles !? Le vieux rêve dans JMeter… certainement l’une des premières questions que j’ai posé sur la mailing-list des utilisateurs JMeter (jmeter-user).

Bon, je vous préviens, il ne s’agit pas encore de faire un méga test de charge avec ce genre de requêtes parallèles, mais cela peut aider pour des tests ayant des besoins de lancement simultané de requêtes, ou toute autre idée qui vous vient à l’esprit.

Le premier pré-requis est donc d’avoir un JMeter prêt pour faire du Groovy.

Ensuite, nous allons utiliser HTTPBuilder, qui est une couche API basée sur Apache HttpClient, et accessible en Groovy. HTTPBuilder sait faire pas mal de choses (comme du parsing automatique de requêtes JSON), etc. Ici, nous allons dans ce billet seulement nous intéresser à sa capacité à faire des requêtes HTTP de manière asynchrone.

Le petit nom de ce type de requête dans HTTPBuilder est AsyncHTTPBuilder. C’est une requête HTTP qui, au lieu d’être exécutée en premier plan, est envoyée en arrière-plan via une exécution dans un pool d’exécution (élément Executor dans l’API Java) et l’utilisation d’un type particulier : java.util.concurrent.Future pour le résultat de l’exécution de la requête. Ce ‘future’ permet d’avoir la possibilité de lancer plusieurs requêtes HTTP asynchrones (quasiment en même temps), puis de récolter le résultat de leurs exécutions ensuite.

Pour utiliser le HTTPBuilder, il faut donc aller télécharger son archive (au moins la version 0.5.2 SNAPSHOT, car il y a un bug dans la version 0.5.1 sur le AsyncHTTPBuilder), puis la décompresser dans un répertoire temporaire.

Au niveau des fichiers JAR nécessaires au bon fonctionnement de HTTPBuilder, le plus simple est : 1/ copier le http-builder-0.5.2.jar dans le répertoire JMETER_HOME/lib, 2/ copier l’ensemble de fichiers JAR situés dans le sous-répertoire HTTPBUILDER/dependencies dans le répertoire JMETER_HOME/lib. On relance ensuite JMeter.

Voici l’arbre JMeter pour ce billet :

Dans le Plan de test, on place trois variables JMeter qui seront utilisées par le script Groovy. Ce sont tout simplement le nom du site, son port et une URI.


Au niveau du Groupes d’unités, on laisse le 1-1-1 histoire de faciliter le coté démonstration de ce scénario.

L’échantillon Requête HTTP va permettre de récupérer une page HTML qui contient des liens.


En fils de la Requête HTTP, on place un élément post-processeur Extracteur Expression régulière qui est chargé de récupérer avec une expression régulière les liens de la page HTML

L’échantillon Débogage est bien entendu là à des fins de débogage, histoire de voir si l’extracteur RegExp fait bien son travail.

Ensuite, vient l’élément principal de ce scénario : l’échantillon BSF abritant un script Groovy.

Voici donc ce fameux script, les commentaires sont directement dedans :

import static groovyx.net.http.ContentType.HTML
import groovyx.net.http.AsyncHTTPBuilder
// Récupération des variables JMeter
def myHost =vars.get("MY_HOST")
def myPort = vars.get("MY_PORT")
// Construction du debut de l'URL
def myURL = "http://" + myHost + ":" + myPort
// Recuperation du nombre d'elements, puis generation de la liste des URI
def listUri = []
def nbPage = Integer.parseInt(vars.get("PAGE_matchNr"))
for (def i = 1; i <= nbPage; i++) {
 listUri << "/" + vars.get("PAGE_" + i)
}
// Creation d'un pool d'unites d'execution de requetes HTTP asynchrones
def http = new AsyncHTTPBuilder(
 poolSize : args[0],
 uri : myURL,
 contentType : HTML,
 timeout : 20000 )
// Generation des requetes et lancer de chaque requete
def listResult = []
def reqElt = "" // generation de la liste des requetes
listUri.each { uriPage ->
 listResult << http.get(path:uriPage) { resp, html ->
 println 'Recuperation de ' + uriPage
 return html
 }
 reqElt = reqElt + myURL + uriPage + "\n"
}
SampleResult.setSamplerData(reqElt) // pour rendre visible dans JMeter les requetes transmises
// Constrution des donnees de reponses
// NB: l'appel à it.get() pour chaque element va le faire patienter jusqu'a la fin d'execution de la requête
def responseData = ""
listResult.each {
 def resp = it.get()
 responseData = responseData + "=============\n" + resp + "\n"
 assert resp instanceof groovy.util.slurpersupport.GPathResult
}
SampleResult.setResponseData(responseData) // envoi de la concatenation des resultats à JMeter
println 'done.'
http.shutdown() // Arret du pool d'unites

En complément :

  • le script Groovy prend comme arguments, le nombre d’unités d’exécution pour le pool d’unités. Ici il est à 4 (champ « Paramètres à passer au script/fichier » dans la capture de l’échantillon BSF).
  • On peut jouer avec ce paramètre (unités d’exécution) pour observer ou non le parallélisme des requêtes envoyées.
  • Dans cet exemple, il n’y a pas de paramètres passés lors de l’exécution des requêtes HTTP. C’est bien entendu possible. Voir la doc.

 

Avant de passer à l’exécution du test, on n’oublie pas le récepteur Arbre de résultats, et on peut lancer ensuite.

Voici le résultat pour une exécution avec 1 seule unité

Voici le résultat pour une exécution avec 3 unités

Le temps de réponses est bien meilleur avec 3 unités.

La petite explication c’est que mes pages « /pageX.shtml » ont un petit script d’attente de X secondes (voir annexe ci-dessous). Ainsi, la page 1 attend 1 sec, la page 2 attend 2 secs et la page 3 attends 3 secs.

Donc il est normal d’avoir environ 6 secondes avec 1 seule unité et 3 secondes pour 3 unités.

Voilà pour ce petit script Groovy qui permet d’avoir des requêtes concurrentes.

  • Veuillez noter que Groovy étant basé sur Java, il est bien entendu possible de transformer ce script Groovy en code Java à exécuter dans un échantillon BeanShell ou carrément dans un échantillon Java.
  • Veuillez noter que ce scénario (qui mijotait depuis 3 semaines sur mon ordinateur) m’a donner l’idée de paralléliser le téléchargement des ressources liées à une page HTML dans une requête HTTP (la case à cocher en bas dans l’élément). J’ai donc développer le patch en question, et je devrais le committer la semaine prochaine pour son inclusion dans la prochaine version de JMeter.
  • Veuillez noter que : non ce n’est pas aussi simple de mettre en place des requêtes parallèles pour un même utilisateur virtuel dans JMeter. Notamment à cause des éléments ‘pré’ et ‘post’ exécution d’une requête (pré-processeurs, post-processeur et assertion en autres)

Bon courage dans vos péripéties JMeter.

./

[Annexe : une des pages html demandées]

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html><head>
<meta content="text/html; charset=UTF-8" http-equiv="content-type">
<title>Page 1</html>
</head><body>
P1 AVANT : <!--#exec cmd="date" -->
<!--#exec cmd="sleep 1" -->
P1 APRES : <!--#exec cmd="date" -->
</body></html>

(noter que c’est du Sever Side Include avec Apache)

[Annexe : le résultat dans l’onglet réponse de JMeter, avec 3 unités d’exécutions]

On voit bien que la date de lancement est la même pour les trois pages.

 

 

./