Tutoriel sur comment utiliser le framework ContiPerf

Antonio Gomes-Rodrigues

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

- Titre : Tutoriel sur comment utiliser le framework ContiPerf

- Auteur : Antonio Gomes-Rodrigues

- Parution : 19 mars 2012

- Mise à jour : 30 octobre 2015

- Licence : 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.

Sommaire

- I. Présentation de ContiPerf

- II. Intégration dans un IDE

- III. Intégration avec Maven

- IV. Go pour la pratique

- V. Écrire un test ContiPerf

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

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

- VIII. Exemple 3 : throughput au minimum de 400

- IX. Exemple 4 : percentil

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

- XI. Exemple 6

- XII. Performance Test Suites

- XIII. Rapports

- XIV. Conclusion

- XV. remerciements

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.

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

Par exemple avec Netbeans :

III - Intégration avec Maven

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

1. <dependency>

2. <groupId>org.junit </groupId>

3. <artifactId>com.springsource.org.junit</artifactId>

4. <version>4.7.0</version>

5. <scope>test</scope>

6. </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 :

1. @Rule

2. public ContiPerfRule i = new ContiPerfRule();

par :

1. @Rule

2. public ContiPerfRule rule = new ContiPerfRule(

3. new HtmlReportModule(),

4. new CSVSummaryReportModule(),

5. new CSVInvocationReportModule(),

6. 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 class org.springframework.samples.petclinic.Pet

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

1. @Rule

2. public ContiPerfRule i = new ContiPerfRule();

4. @Test

5. @PerfTest(invocations = 100, threads = 100)

6. @Required(max = 15, average = 10)

7. public void testsetName() {

8. Pet monChien = new Pet();

9. monChien.setName("Medor");

10. }

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.

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 :

1. @Rule

2. public ContiPerfRule i = new ContiPerfRule();

4. @Test

5. @PerfTest(invocations = 100, threads = 10)

6. @Required(max = 15, average = 10)

7. public void testsetName() {

8. Pet monChien = new Pet();

9. monChien.setName("Medor");

10. }

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

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.

1. @Rule

2. public ContiPerfRule i = new ContiPerfRule();

4. @Test

5. @PerfTest(duration = 100000, threads = 10)

6. @Required(throughput = 400)

7. public void testsetName() {

8. Pet monChien = new Pet();

9. monChien.setName("Medor");

10. }

Ici le test dure 100s.

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 :

1. @Rule

2. public ContiPerfRule i = new ContiPerfRule();

4. @Test

5. @PerfTest(invocations = 100, threads = 10)

6. @Required(max = 10, percentile99 = 6)

7. public void testsetName() {

8. Pet monChien = new Pet();

9. monChien.setName("Medor");

10. }

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

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

Ajoutons le test de la fonction setType :

1. @Test

2. @PerfTest(invocations = 100, threads = 10)

3. @Required(max = 10, percentile99 = 5)

4. public void testsetType() {

5. PetType bulldog = new PetType();

6. bulldog.setName("bulldog");

7. Pet monChien = new Pet();

8. monChien.setType(bulldog);

9. }

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 :

1. @PerfTest(invocations = 100, threads = 10)

2. public class PetTestsPerf {

4. @Rule

5. public ContiPerfRule i = new ContiPerfRule();

7. public PetTestsPerf() {

8. }

10. @Test

11. @Required(max = 10, percentile99 = 6)

12. public void testsetName() {

13. Pet monChien = new Pet();

14. monChien.setName("Medor");

15. }

17. @Test

18. @Required(max = 10, percentile99 = 5)

19. public void testsetType() {

20. PetType bulldog = new PetType();

21. bulldog.setName("bulldog");

22. Pet monChien = new Pet();

23. monChien.setType(bulldog);

24. }

26. }

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.

1. package org.springframework.samples.petclinic.web;

3. import com.sun.syndication.feed.atom.Entry;

4. import com.sun.syndication.feed.atom.Feed;

5. import java.util.*;

6. import org.databene.contiperf.PerfTest;

7. import org.databene.contiperf.Required;

8. import org.junit.Before;

9. import org.junit.Test;

10. import org.springframework.samples.petclinic.Pet;

11. import org.springframework.samples.petclinic.PetType;

12. import org.springframework.samples.petclinic.Visit;

14. @PerfTest(invocations = 100, threads = 10)

15. public class VisitsAtomViewTestsPerf {

17. @Rule

18. public ContiPerfRule i = new ContiPerfRule();

19. private VisitsAtomView visitView;

20. private Map model;

21. private Feed feed;

23. @Before

24. public void setUp() {

25. visitView = new VisitsAtomView();

26. PetType dog = new PetType();

27. dog.setName("dog");

28. Pet bello = new Pet();

29. bello.setName("Bello");

30. bello.setType(dog);

31. Visit belloVisit = new Visit();

32. belloVisit.setPet(bello);

33. belloVisit.setDate(new Date(2009, 0, 1));

34. belloVisit.setDescription("Bello visit");

35. Pet wodan = new Pet();

36. wodan.setName("Wodan");

37. wodan.setType(dog);

38. Visit wodanVisit = new Visit();

39. wodanVisit.setPet(wodan);

40. wodanVisit.setDate(new Date(2009, 0, 2));

41. wodanVisit.setDescription("Wodan visit");

42. List visits = new ArrayList();

43. visits.add(belloVisit);

44. visits.add(wodanVisit);

46. model = new HashMap();

47. model.put("visits", visits);

48. feed = new Feed();

50. }

52. @Test

53. @Required(max = 30, percentile99 = 7)

54. public void buildFeedMetadata() {

55. visitView.buildFeedMetadata(model, feed, null);

56. }

58. @Test

59. @Required(max = 80, percentile99 = 42)

60. public void buildFeedEntries() throws Exception {

61. List entries = visitView.buildFeedEntries(model, null, null);

62. }

63. }

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 :

@RunWith(ContiPerfSuiteRunner.class)
;

@Suite.SuiteClasses({liste des classes à tester})
.

Dans notre exemple on aura :

1. package org.springframework.samples.petclinic;

3. import org.databene.contiperf.junit.ContiPerfSuiteRunner;

4. import org.junit.After;

5. import org.junit.AfterClass;

6. import org.junit.Before;

7. import org.junit.BeforeClass;

8. import org.junit.runner.RunWith;

9. import org.junit.runners.Suite;

11. @RunWith(ContiPerfSuiteRunner.class)

12. @Suite.SuiteClasses({org.springframework.samples.petclinic.PetTestsPerf.class, org.springframework.samples.petclinic.OwnerTestsPerf.class})

13. public class PetclinicTestSuite {

15. @BeforeClass

16. public static void setUpClass() throws Exception {

17. }

19. @AfterClass

20. public static void tearDownClass() throws Exception {

21. }

23. @Before

24. public void setUp() throws Exception {

25. }

27. @After

28. public void tearDown() throws Exception {

29. }

31. }

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

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

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

summary.csv.

- Fichier contenant le résumé du test ContiPerf

1. serviceId,startTime,duration,invocations,min,average,median,90%,95%,99%,max

2. 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

1. latency,startTimeNanos

2. 67,9452032779934

3. 63,9452036182461

4. 0,9452100152044

5. 0,9452100162800

6. 0,9452100186266

7. 0,9452100197511

8. 0,9452100212177

9. 0,9452100223492

10. 62,9452037698296

11. 62,9452037612671

12. 64,9452035922372

13. 67,9452033027032

14. 59,9452040662290

15. 59,9452040958767

16. 59,9452041381935

17. 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…

1. latency,sampleCount

2. 0,979

3. 1,0

4. 2,2

5. 3,0

6. 4,2

7. 5,0

8. 6,0

9. 7,0

10. 8,0

11. 9,1

12. 10,0

13. 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.