Tutoriel sur comment utiliser le framework ContiPerf

Lorsque l'on fait du développement Java, on ne se préoccupe pas toujours des problèmes de performance. Et lorsque la performance est une contrainte, on ne sait pas toujours comment la mesurer et avec quel outil.

Idéalement, cet outil s'intégrerait à notre environnement de développement et se lancerait automatiquement.

En somme, il nous faudrait un framework comme Junit, mais dédié à la performance.

Pour réagir à ce tutoriel, un espace de dialogue vous est proposé sur le forum. Commentez Donner une note à l'article (5)

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Présentation de ContiPerf

Bonne nouvelle, cela existe et porte le nom de ContiPerf.

ContiPerf se présente comme une surcouche à Junit 4 pour les tests de performance, avec toutes ces facilités et tous ces avantages :

  • configuration par annotations ;
  • intégration avec les IDE supportant Junit 4 comme Eclipse, Netbeans… ;
  • intégration avec Apache Maven ;
  • aucune dépendance à part JUnit ;
  • export des résultats au format CSV et HTML ;
  • regroupement des tests en Test Suites.

Attention, ContiPerf ne remplace pas une bonne méthode de test de performance (avoir des résultats reproductibles, avoir les bons jeux de test…) et en particulier les tests de charge.
De plus, il n'est pas encore adapté au microbenchmarking.

Pour plus d'informations, le site officiel est très complet.

Avant de commencer à écrire des tests unitaires de performance, regardons comment intégrer ContiPerf à notre environnement de développement.

II. Intégration dans un IDE

Afin d'intégrer ContiPerf à votre IDE, il suffit d'ajouter la librairie contiperf.jar dans le Classpath.

Image non disponible

Puis afin de lancer le test, on fait comme pour JUnit.

Par exemple avec Netbeans :

Image non disponible
Image non disponible

III. Intégration avec Maven

L'intégration à Maven est tout aussi simple.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
<dependency>
<groupId>org.junit </groupId>
<artifactId>com.springsource.org.junit</artifactId>
<version>4.7.0</version> 
<scope>test</scope>
</dependency>

IV. Go pour la pratique

Maintenant que vous êtes entièrement convaincu (enfin j'espère !), regardons d'un peu plus près comment écrire un test de performance ContiPerf.

Pour ceux qui n'ont pas l'habitude d'utiliser JUnit, je leur conseille de l'étudier avant.

V. Écrire un test ContiPerf

Pour un test, il nous faut :

  • un objet ContiPerfRule et son annotation @Rule ;
  • un protocole de test (nombre d'itération, nombre de thread, durée du test) défini à l'aide de l'annotation @PerfTest ;
  • des critères d'acceptance (moyenne / médian / max / percentiles / durée totale / throughput / temps de réponse) définis à l'aide de l'annotation @Required ;
  • du code métier.

Le tout aura la forme suivante :

Si l'on veut que ContiPerf génère aussi les rapports, il faut remplacer la ligne :

 
Sélectionnez
1.
2.
@Rule 
public ContiPerfRule i = new ContiPerfRule();

par :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
@Rule 
public ContiPerfRule rule = new ContiPerfRule( 
        new HtmlReportModule(), 
        new CSVSummaryReportModule(), 
        new CSVInvocationReportModule(), 
        new CSVLatencyReportModule());

VI. Exemple 1 : maximum 15 ms avec une moyenne inférieure à 10 ms

Nous allons écrire des tests ContiPerf pour l'application Spring PetClinic.

Dans un premier temps, nous allons tester la classe org.springframework.samples.petclinic.Pet

Testons la création (new Pet) de 100 chiens prénommés Medor (setName).

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
@Rule 
public ContiPerfRule i = new ContiPerfRule(); 
 
@Test 
@PerfTest(invocations = 100, threads = 100) 
@Required(max = 15, average = 10) 
public void testsetName() { 
  Pet monChien = new Pet(); 
  monChien.setName("Medor"); 
}

Dans ce test, on crée 100 chiens (invocations = 100) en parallèle (1 par thread) et on veut qu'une création dure au maximum 15 ms (max) avec une moyenne (average) inférieure à 10 ms.

Image non disponible

VII. Exemple 2 : maximum 15 ms avec une moyenne inférieure à 10 ms

Reprenons le même exemple, mais cette fois-ci pour certaines raisons (par exemple un pool), on sait qu'il n'y aura jamais plus de 10 créations de chiens en même temps.

L'exemple devient :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
@Rule 
public ContiPerfRule i = new ContiPerfRule(); 

@Test 
@PerfTest(invocations = 100, threads = 10) 
@Required(max = 15, average = 10) 
public void testsetName() { 
  Pet monChien = new Pet(); 
  monChien.setName("Medor"); 
}

Ici chaque thread va créer 10 chiens (invocations/threads) pour un total de 100 chiens.

Image non disponible

VIII. Exemple 3 : throughput au minimum de 400

Toujours avec le même exemple, mais maintenant on veut que le throughput soit au minimum de 400 invocations par seconde.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
@Rule 
public ContiPerfRule i = new ContiPerfRule(); 
 
@Test 
@PerfTest(duration = 100000, threads = 10) 
@Required(throughput = 400) 
public void testsetName() { 
  Pet monChien = new Pet(); 
  monChien.setName("Medor"); 
}

Ici le test dure 100 s.

Image non disponible

IX. Exemple 4 : percentil

Enfin, pour ceux qui trouvent que l'utilisation du percentil est plus intéressante que la moyenne, il suffit de modifier le test de l'exemple 2 en remplaçant average par percentile99.

Par exemple, si on veut que 99 % des exécutions ne durent pas plus de 6 ms, l'exemple devient :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
@Rule 
public ContiPerfRule i = new ContiPerfRule(); 
 
@Test 
@PerfTest(invocations = 100, threads = 10) 
@Required(max = 10, percentile99 = 6) 
public void testsetName() { 
  Pet monChien = new Pet(); 
  monChien.setName("Medor"); 
}
Image non disponible

X. Exemple 5 : mutualisation des critères d'acceptance

Continuons avec la classe org.springframework.samples.petclinic.Pet.

Ajoutons le test de la fonction setType :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
@Test 
@PerfTest(invocations = 100, threads = 10) 
@Required(max = 10, percentile99 = 5) 
public void testsetType() { 
  PetType bulldog = new PetType(); 
  bulldog.setName("bulldog"); 
  Pet monChien = new Pet(); 
  monChien.setType(bulldog); 
}
Image non disponible

Lorsqu'on a plusieurs tests pour une classe, on peut regrouper le protocole de test (@PerfTest) et les critères d'acceptance (@Required) au niveau de la classe et plus au niveau des fonctions.

Par exemple, pour mettre en commun notre protocole de test pour les deux tests ContiPerf, il suffit d'écrire le test de la manière suivante :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
@PerfTest(invocations = 100, threads = 10) 
public class PetTestsPerf { 

 @Rule 
 public ContiPerfRule i = new ContiPerfRule(); 

 public PetTestsPerf() { 
 } 

 @Test 
 @Required(max = 10, percentile99 = 6) 
 public void testsetName() { 
  Pet monChien = new Pet(); 
  monChien.setName("Medor"); 
 } 

 @Test 
 @Required(max = 10, percentile99 = 5) 
 public void testsetType() { 
  PetType bulldog = new PetType(); 
  bulldog.setName("bulldog"); 
  Pet monChien = new Pet(); 
  monChien.setType(bulldog); 
 } 

}

XI. Exemple 6

Poursuivons avec un test plus complexe de la classe org.springframework.samples.petclinic.web.VisitsAtomView.

Pour cela, nous allons reprendre le test unitaire qui existe déjà et le transformer en test ContiPerf.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
package org.springframework.samples.petclinic.web;

import com.sun.syndication.feed.atom.Entry;
import com.sun.syndication.feed.atom.Feed;
import java.util.*;
import org.databene.contiperf.PerfTest;
import org.databene.contiperf.Required;
import org.junit.Before;
import org.junit.Test;
import org.springframework.samples.petclinic.Pet;
import org.springframework.samples.petclinic.PetType;
import org.springframework.samples.petclinic.Visit;

@PerfTest(invocations = 100, threads = 10)
public class VisitsAtomViewTestsPerf {

  @Rule
  public ContiPerfRule i = new ContiPerfRule();
  private VisitsAtomView visitView;
  private Map model;
  private Feed feed;

  @Before
  public void setUp() {
    visitView = new VisitsAtomView();
    PetType dog = new PetType();
    dog.setName("dog");
    Pet bello = new Pet();
    bello.setName("Bello");
    bello.setType(dog);
    Visit belloVisit = new Visit();
    belloVisit.setPet(bello);
    belloVisit.setDate(new Date(2009, 0, 1));
    belloVisit.setDescription("Bello visit");
    Pet wodan = new Pet();
    wodan.setName("Wodan");
    wodan.setType(dog);
    Visit wodanVisit = new Visit();
    wodanVisit.setPet(wodan);
    wodanVisit.setDate(new Date(2009, 0, 2));
    wodanVisit.setDescription("Wodan visit");
    List visits = new ArrayList();
    visits.add(belloVisit);
    visits.add(wodanVisit);

    model = new HashMap();
    model.put("visits", visits);
    feed = new Feed();

  }

  @Test
  @Required(max = 30, percentile99 = 7)
  public void buildFeedMetadata() {
    visitView.buildFeedMetadata(model, feed, null);
  }

  @Test
  @Required(max = 80, percentile99 = 42)
  public void buildFeedEntries() throws Exception {
    List entries = visitView.buildFeedEntries(model, null, null);
  }
}
Image non disponible

XII. Performance Test Suites

Bien sûr, on peut regrouper plusieurs tests ContiPerf dans une Test Suite.

Pour cela, il faut utiliser ces deux annotations :

 
Sélectionnez
1.
@RunWith(ContiPerfSuiteRunner.class) ;
 
Sélectionnez
1.
 @Suite.SuiteClasses({liste des classes à tester})

Dans notre exemple on aura :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
package org.springframework.samples.petclinic; 

import org.databene.contiperf.junit.ContiPerfSuiteRunner; 
import org.junit.After; 
import org.junit.AfterClass; 
import org.junit.Before; 
import org.junit.BeforeClass; 
import org.junit.runner.RunWith; 
import org.junit.runners.Suite; 

@RunWith(ContiPerfSuiteRunner.class) 
@Suite.SuiteClasses({org.springframework.samples.petclinic.PetTestsPerf.class, org.springframework.samples.petclinic.OwnerTestsPerf.class}) 
public class PetclinicTestSuite { 

  @BeforeClass 
  public static void setUpClass() throws Exception { 
  } 

  @AfterClass 
  public static void tearDownClass() throws Exception { 
  } 

  @Before 
  public void setUp() throws Exception { 
  } 

  @After 
  public void tearDown() throws Exception { 
  } 

}

XIII. Rapports

Penchons-nous un peu plus sur les rapports générés par ContiPerf.

Avec la version 2.0.1, deux formats d'exports sont possibles.

  • Le format HTML
Image non disponible

Pour avoir ce rapport (fichier index.html), il faut utiliser public ContiPerfRule i = new ContiPerfRule(); ou public ContiPerfRule i = new ContiPerfRule(new HtmlReportModule());

  • Le format CSV
Image non disponible

Comme on peut le voir, on génère jusqu'à trois fichiers CSV.

summary.csv.

  • Fichier contenant le résumé du test ContiPerf
 
Sélectionnez
1.
2.
serviceId,startTime,duration,invocations,min,average,median,90%,95%,99%,max 
org.springframework.samples.petclinic.OwnerTestsPerf.testHasPet,1329047753040,78,1000,0,0.8,0,0,0,24,6

Pour l'obtenir, il faut utiliser public ContiPerfRule i = new ContiPerfRule(new CSVSummaryReportModule());

org.springframework.samples.petclinic.OwnerTestsPerf.testHasPet.inv.csv

  • Fichier contenant le temps de réponse de chaque itération
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
latency,startTimeNanos 
67,9452032779934 
63,9452036182461 
0,9452100152044 
0,9452100162800 
0,9452100186266 
0,9452100197511 
0,9452100212177 
0,9452100223492 
62,9452037698296 
62,9452037612671 
64,9452035922372 
67,9452033027032 
59,9452040662290 
59,9452040958767 
59,9452041381935 
61,9452038566633

Pour l'obtenir, il faut utiliser public ContiPerfRule i = new ContiPerfRule(new CSVInvocationReportModule());

org.springframework.samples.petclinic.OwnerTestsPerf.testHasPet.stat.csv

  • Fichier contenant la répartition des temps de réponse

Par exemple, ici on voit qu'il y a eu une réponse avec un temps de réponse de 9 ms, neuf cent soixante-dix-neuf réponses avec un temps de réponse de 0 ms…

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
latency,sampleCount 
0,979 
1,0 
2,2 
3,0 
4,2 
5,0 
6,0 
7,0 
8,0 
9,1 
10,0 
11,0

Pour l'obtenir, il faut utiliser public ContiPerfRule i = new ContiPerfRule(new CSVLatencyReportModule());

Comme on peut le voir, on peut faire des choses intéressantes avec les rapports standards (surtout avec les fichiers CSV). Si cela n'est pas suffisant, ContiPerf étant open source et libre, on peut ajouter facilement enrichir des rapports et des métrics.

Supposons que nous voulions ajouter une metric dans le rapport HTML.

Pour cela, nous devons modifier/utiliser trois fichiers sources :

  • HtmlReportModule : code générant le rapport HTML ;
  • LatencyCounter : code calculant les metrics de base ;
  • PerformanceRequirement : code contenant les critères d'acceptance.

Dans le cas de mon besoin initial, les metrics présentés suffisent largement. Cela dit, je reste curieux de vos remarques et des évolutions que vous aurez pu opérer en fonction des vôtres.

XIV. Conclusion

On retient que ContiPerf est un utilitaire de test léger qui permet à l'utilisateur d'exploiter facilement JUnit 4 en faisant de tests de performance par exemple pour tester les performances en continu.

XV. remerciements

Nous tenons à remercier milkoseck pour sa relecture attentive de cet article et Laethy pour la mise au gabarit.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2015 Antonio Gomes-Rodrigues. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.