Tests de charge avec Artillery.io

Artillery.io c'est quoi ?

Artillery est un outil permettant de réaliser des tests de montée en charge de sites web, il est open source. Le projet est disponible sur Github (https://github.com/artilleryio/artillery).

Artillery permet de mettre en place des scénarios ayant pour but de décrire le comportement d’un utilisateur sur l’application à tester. Il existe de nombreux projets équivalents : JMeter, Gatling, Locust, etc ...

 

Pourquoi Artillery.io ?

Lors d’un atelier de tests de charge pour un client, j’ai choisi Artillery.io pour sa facilité d’écriture des scripts en YAML. La simplicité de manipulation des headers HTTP pour l’authentification par exemple, et l’extraction de contenus sous forme de variables pour les ré-utiliser dans les appels HTTP suivants.

 

Passage en revue de certaines fonctionnalités d’Artillery

Les deux éléments essentiels d’Artillery sont les “scénarios” et les “phases”.

  • Les scénarios permettent d'écrire les différents appels HTTP à réaliser,
    cela s’appelle le Flow.
  • Les phases permettent de définir la charge/l'intensité que l’on veut envoyer
    au site web à stresser.

D’autres fonctionnalités très pratiques sont également disponibles :

  • les variables permettent de créer des appels HTTP avec des données externes au Flow, par exemple depuis un fichier csv afin de solliciter la cible différemment comme des vrais utilisateurs.
  • les environnements permettent de définir plusieurs cibles et d’avoir des phases (intensités de tir) différentes pour chaque environnement, en ayant le même scénario, par exemple : dev, staging, et prod.
 

Les Flows :

Le Flow est l'enchaînement des appels HTTP à réaliser sur la cible.

Chaque élément du Flow peut-être un verbe HTTP (GET / POST / PUT / PATCH / DELETE),
et les arguments spécifiques à chacun.

Dans les Flows , il y a des facilitateurs pour manipuler les cookies, les forms (urlencode et multipart), Basic Auth HTTP, le suivi ou non des redirections, le support des réponses GZIP.

Exemple d’appel HTTP avec Basic Authen :

.../...
- get:
   url: "/protected/resource"
   auth:
     user: myusername
     pass: mypassword
.../...
 

Extraction des contenus des réponses HTTP : captures

Suite à une réponse d'un appel HTTP, il est possible de vérifier et extraire des infos sur les données retournées par l'appel HTTP, pour extraire des tokens, ou toute autre information des headers ou du body.
Pour cela, nous avons plusieurs solutions pour procéder à l’extraction du body : soit avec des expressions régulières, soit en manipulant directement la structure json retournée si le body est en json. Il est possible d’extraire des données dans une variable pour les réutiliser dans la suite du script.

Il est possible de conditionner la bonne exécution du Flow via l’attente de résultats : Expectations and Assertions.

Exemple de capture et d’expectation :

.../...
scenarios:
  - name: Get pets
    flow:
      - get:
        url: "/pets"
        capture:
          - json: "$.name"
            as: name
        expect:
          - statusCode: 200
          - contentType: json
          - hasProperty: results
          - equals:
              - "Tiki"
              - "{{ name }}"
.../...

Il faut que la réponse HTTP soit code 200 http, que le content type retourné soit json, que la propriété “results” soit présente, et que le contenu de la variable “name” soit égal à “Tiki”.

 

Les scénarios :

Un scénario permet de définir un ou plusieurs Flows (enchaînement d’appels HTTP).
On peut ensuite jouer l’ensemble des scénarios en même temps, en appliquant une pondération par scénario pour contrôler le nombre d’exécutions.
On pourrait imaginer 3 types de scénarios sur un site de e-commerce : les anonymes qui se baladent de produit en produit, ceux qui créent un compte, et ceux qui passent commande ; et définir un poids pour chacun de ces scénarios

  • scénario : anonyme : 6/10
  • scénario : création de compte : 2/10
  • scénario : recherche article panier + commande : 2/10

Automatiquement, Artillery.io va respecter les ratios demandés en terme de nombre d'exécutions de scénarios. Si on ne spécifie pas de poids, il fera à parts égales l’ensemble des scénarios écrits.
Il n’y a pas de limite sur le nombre de scénarios par exécution de charge.

 

Les Phases :

Comme vu plus haut, une fonctionnalité très sympathique d’Artillery.io est la possibilité de définir des phases. Elles permettent de définir la charge souhaitée sur la cible. Chaque phase peut avoir des caractéristiques différentes pour varier l’intensité de la charge durant l’exécution du test de charge.
Artillery.io raisonne en terme d’utilisateurs virtuels. Il va créer durant toute la durée de la charge des nouveaux utilisateurs qui vont exécuter le Flow d’un des scénarios.

Exemple de phase possible, mais vous pouvez imaginer toutes sortes de variantes :
phase 1 : 1 utilisateur pendant 10 secondes
phase 2 : 40 utilisateurs pendant 10 secondes avec une rampe de 10 utilisateurs par seconde
phase 3 : 80 utilisateurs pendant 20 secondes

config: 
phases:
  - duration: 10
    arrivalRate: 1
    name: "phase1"
  - duration: 10
    arrivalRate: 40
    rampTo: 10
    name: "Phase2"
  - duration: 20
    arrivalRate: 80
    name: "Phase3"
 

L’installation et l'exécution :

$ npm install -g artillery
$ artillery run mon_test.yml
$

Il existe aussi un dockerfile :https://github.com/artilleryio/artillery-docker.

 

Résultats et Rapports :

À la fin de l’exécution, Artillery.io propose un récapitulatif des résultats obtenus sous forme de compteurs.

Complete report @ 2019-07-19T08:50:27.966Z
  Scenarios launched:  300
  Scenarios completed: 300
  Requests completed:  600
  RPS sent: 18.86
  Request latency:
    min: 52.1
    max: 11005.7
    median: 408.2
    p95: 1727.4
    p99: 3144
  Scenario counts:
    0: 300 (100%)
  Codes:
    200: 300
    302: 300

Cette synthèse est également disponible dans le fichier .json associé au “run”. Ce fichier contient aussi des informations durant le “run”.

Il est possible de générer un rapport détaillé html humainement lisible, avec la commande :

$ artillery report mon_test.yml.json

Vous pouvez afficher le rapport dans un navigateur le fichier mon_test.yml.json.html :

 

Tout ce dont je n’ai pas parlé :

  • Moult plugins, par exemple enregistrement via un proxy local pour créer facilement vos Flows ; plugin datadog pour envoyer les métriques dans DataDog ; support des websocket/socket.io/http live streaming (hls) ; plugin Fuzzing pour tester vos APIs HTTP ; et plein d’autres mentionnés dans le github de l’outil :https://github.com/artilleryio.
  • Il est possible de piloter et de configurer Artillery.io par variable d’environnement, ce qui en fait un outil très facilement intégrable dans une chaîne de CI pour automatiser les tests de charge.
  • Artillery.io est écrit en nodeJS, il est possible d'étendre vos scripts avec des fonctions JS pour traiter des spécificités propres à vos cibles.
 

Astuce :

Naturellement, il est prévu d'écrire des Flows qui chargent un seul site web. Dans certains cas, il peut être intéressant de passer sur plusieurs cibles durant le script, pour cela vous pouvez écrire l’url complète dans votre Flow et cela remplace la “target”, par exemple :

# exemple de multiple cibles dans le même flowconfig:
target: http://www.cible.com
 phases:
../..
scenarios:
 - flow:
 - get:
    url: "/list"
 - get:
    url: "http://www.cible1.com/pets"
 - get:
    url: "http://www.cible2.com/cattle"
 - get:
    url: "http://www.cible1.com/pets-infos"
 - get:
    url: "http://www.cible2.com/cattle-infos"
../..
 

Les avantages et les inconvénients :

j’ai bien aimé :

  • la simplicité d'écriture des appels HTTP et l’utilisation des variables
  • la simplicité de définition des charges et des variations de charge
  • les statistiques fournies durant l'exécution et la fin de la charge, en format json
  • la génération de rapports HTML d'après le fichier json des statistiques
  • la facilité et la rapidité d'écriture des scénarios en YAML
  • La manipulation du body json pour tester des API très simplement.

j’ai moins aimé :

  • Dans certains cas, les messages d’erreur de parse du YAML ne sont pas très explicites et le YAML n’aide pas …
  • Le mode cluster, pour avoir plusieurs machines qui chargent une infrastructure avec consolidation des résultats, est inclus dans l’offre pro payante par abonnement compatible AWS/Lambda.