BT

Diffuser les Connaissances et l'Innovation dans le Développement Logiciel d'Entreprise

Contribuez

Sujets

Sélectionner votre région

Accueil InfoQ Actualités Tests D'événements Du JDK Flight Recorder Avec JfrUnit

Tests D'événements Du JDK Flight Recorder Avec JfrUnit

Gunnar Morling, ingénieur logiciel open source chez Red Hat, introduit JfrUnit, un nouvel utilitaire de test qui peut être utilisé pour détecter les régressions de performances avec JUnit ou le framework Spock. L'interprétation des résultats des tests de performances, tels que les temps de réponse, peut être difficile car il peut y avoir des régressions causées par d'autres facteurs que l'application elle-même, tels que d'autres processus ou le réseau. JfrUnit peut être utilisé pour tester les performances de l'application en mesurant l'allocation de mémoire, les E/S, les requêtes de base de données, ou d'autres éléments spécifiques à l'application.

Le JDK Flight Recorder (JFR) collecte les événements d'une application en cours d'exécution qui peuvent être utilisés pour diagnostiquer ou profiler une application. Ces événements peuvent être presque n'importe quoi, de l'allocation de mémoire à la récupération de mémoire. Il est possible d'utiliser l'outil directement à partir de la ligne de commande, mais il est souvent utilisé avec JDK Mission Control qui fournit une interface graphique et divers plugins qui peuvent être utilisés en conjonction avec JFR. JfrUnit permet de créer des assertions qui vérifient les événements JFR à partir de l'application.

JfrUnit prend en charge OpenJDK 16 et la dépendance est disponible sur Maven Central :

<dependency>
  <groupId>org.moditect.jfrunit</groupId>
  <artifactId>jfrunit</artifactId>
  <version>1.0.0.Alpha1</version>
  <scope>test</scope>
</dependency>

L'implémentation d'un test JUnit commence par l'ajout de l'annotation @JfrEventTest à la classe de test unitaire, sauf si le test est marqué avec l'annotation @QuarkusTest car le framework de test Quarkus interagit automatiquement avec l'enregistrement JFR. Les tests utilisent l'annotation @EnableEvent pour collecter des événements spécifiques, par exemple, des événements de récupération de mémoire. Après avoir exécuté la logique du programme, la méthode jfrEvents.awaitEvents() attend tous les événements JFR de la JVM ou de l'application, avant que des assertions ne soient utilisées pour vérifier que l'événement s'est produit :

@JfrEventTest
public class GarbageCollectionTest {
    public JfrEvents jfrEvents = new JfrEvents();

    @Test
    @EnableEvent("jdk.GarbageCollection")
    public void testGarbageCollectionEvent() throws Exception {
        System.gc();

        jfrEvents.awaitEvents();

        assertThat(jfrEvents).contains(event("jdk.GarbageCollection"));
    }
}

Alternativement, le framework Spock peut être utilisé pour écrire le même test :

class GarbageCollectionSpec extends Specification {
    JfrEvents jfrEvents = new JfrEvents()

    @EnableEvent('jdk.GarbageCollection')
    def 'Contains a garbage collection Jfr event'() {
        when:
        System.gc()

        then:
        jfrEvents['jdk.GarbageCollection']
    }
}

En plus de vérifier si un événement s'est produit, il est également possible de vérifier les détails d'un événement, comme la durée d'une méthode Thread.sleep() :

@Test
@EnableEvent("jdk.ThreadSleep")
public void testThreadSleepEvent() throws Exception {
    Thread.sleep(42);

    jfrEvents.awaitEvents();

    assertThat(jfrEvents)
            .contains(event("jdk.ThreadSleep")
            .with("time", Duration.ofMillis(42)));
}

JfrUnit permet de créer des scénarios encore plus complexes. Considérez l'exemple suivant qui collecte les événements d'allocation de mémoire et les additionne avant de vérifier que l'allocation de mémoire se situe entre certaines valeurs :

@Test
@EnableEvent("jdk.ObjectAllocationInNewTLAB")
@EnableEvent("jdk.ObjectAllocationOutsideTLAB")
public void testAllocationEvent() throws Exception {
    String threadName = Thread.currentThread().getName();

    // Application logic which creates objects

    jfrEvents.awaitEvents();
    long sum = jfrEvents.filter(this::isObjectAllocationEvent)
            .filter(event -> event.getThread().getJavaName().equals(threadName))
            .mapToLong(this::getAllocationSize)
            .sum();

    assertThat(sum).isLessThan(43_000_000);
    assertThat(sum).isGreaterThan(42_000_000);
}

private boolean isObjectAllocationEvent(RecordedEvent re) {
    String name = re.getEventType().getName();
    return name.equals("jdk.ObjectAllocationInNewTLAB") ||
            name.equals("jdk.ObjectAllocationOutsideTLAB");
}

private long getAllocationSize(RecordedEvent recordedEvent) {
    return recordedEvent.getEventType().getName()
            .equals("jdk.ObjectAllocationInNewTLAB") ?
            recordedEvent.getLong("tlabSize") :
            recordedEvent.getLong("allocationSize");
}

L'activation de plusieurs événements est également possible en utilisant le caractère générique "*", par exemple, @EnableEvent("jdk.ObjectAllocation*") peut être utilisé pour activer tous les événements ObjectAllocation.

Pour réinitialiser les événements collectés, la méthode jfrEvents.reset() peut être utilisée pour s'assurer que seuls les événements après le reset() sont collectées. Par exemple, lors de l'exécution de plusieurs itérations et de la vérification des résultats par itération :

for (int i = 0; i < ITERATIONS; i++) {
    // Application logic

    jfrEvents.awaitEvents();


    // Assertions

    jfrEvents.reset();
}

Les frameworks tels que Hibernate n'émettent pas d'événements eux-mêmes, mais dans ces cas, l'agent JMC peut être utilisé pour créer des événements. Avec l'agent JMC, des événements de requêtes SQL peuvent être générés, qui peuvent ensuite être utilisés pour vérifier le nombre de requêtes SQL envoyées à la base de données. Ceci est démontré dans la session Tests de régression de performances continus avec JfrUnit et l'exemple est disponible sur Exemples pour JfrUnit.

 

Evaluer cet article

Pertinence
Style

Contenu Éducatif

BT