Blog

Tutorial: Inzicht in performance met Gatling en Jenkins

Maandagochtend 08:00, je loopt je werk binnen, opent je mail en daar is er weer een. Het zoveelste mailtje met het bericht dat een van je applicaties erg traag is sinds enkele weken. Je hebt een vermoeden dat het aan de release van 2 maanden geleden ligt, toen er wat aangepast is in de database configuratie. Maar hoe ga je dit controleren?

Met een stopwatch alle services checken op verschillende tijden? Met behulp van Gatling en Jenkins kan je dit geautomatiseerd laten doen. Op deze manier heb je altijd alle responstijden van je services paraat en heb je dus ook je performance inzichtelijk. 

In deze tutorial gebruik je Jenkins Pipelines in combinatie met een Jenkinsfile, hoe je dit snel en makkelijk kan doen lees je hier.

Ook gebruik je de performancetest tool Gatling, binnen Jenkins kan je hiervan gebruik maken via de Gatling Jenkins plugin. Download deze plugin en ook de Scala SDK, aangezien Scala de taal is waarin geschreven gaat worden.

Maak een Maven project aan met onderstaande configuratie:

<dependencies>
    <dependency>
        <groupId>io.gatling.highcharts</groupId>
        <artifactId>gatling-charts-highcharts</artifactId>
        <version>3.3.1</version>
        <scope>test</scope>
    </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>io.gatling</groupId>
            <artifactId>gatling-maven-plugin</artifactId>
            <version>3.0.5</version>
        </plugin>
    </plugins>
</build>

Neem de volgende structuur over in je project: src/test/scala/gatling en maak hieronder DemoSimulation.scala aan. Zet onderstaande in je Scala testfile:

package gatling

import io.gatling.core.Predef._
import io.gatling.http.Predef._
import scala.concurrent.duration._ 

class DemoSimulation extends Simulation {

  val httpProtocol = http
    .baseUrl("http://computer-database.gatling.io/computers")
    .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
    .acceptLanguageHeader("en-US,en;q=0.5")
    .acceptEncodingHeader("gzip, deflate")
    .userAgentHeader("Mozilla/5.0 (Macintosh;Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0")

}

 

Je testfile bevat nu een basis http-protocol waarin je de URL en headers meegeeft die je wilt gebruiken om mee te testen. In ons geval pak je de testomgeving van Gatling zelf, de http://computer-database.gatling.io. Hierin kan je naar hartenlust al je tests uitvoeren, zonder dat je je zorgen hoeft te maken over boze beheerders en crashende omgevingen. Vervolgens gebruik je dezelfde headers in de testfile die je ook in een gebruikelijke aanroep doet.

Headers kunnen overigens een behoorlijke invloed hebben op je performance. Het kan lonend zijn om hier eens goed over na te denken, voor tips zie http://www.keycdn.com/blog/http-headers.

Na het zetten van het http-protocol ga je kijken naar de inhoud van de berichten.

In het voorbeeld ga je een user en een admin aanmaken die vervolgens verschillende acties uitvoeren. Users en admins zijn 2 verschillende rollen waarbij admins vaak hetzelfde mogen als de users, maar als extra ook beheerfuncties mogen uitvoeren.

Maak onder je http-protocol een object aan met daarin een val (value), deze val bevat een vastgestelde waarde. Voor de user geef je aan dat je een http-request wilt doen met de naam “user requests” naar “/365” opgevolgd met een pauze van 5 milisecondes. Deze actie voer je uit in de val “read”. Voor de admin is het soortgelijk, zie het voorbeeld hieronder:

object Browse {
  val read = exec(http("user requests").get("/365")).pause(5)
}

object Remove {
  val delete = exec(http("admin requests").get("/1")).pause(5)

Je hebt nu 1 actie gemaakt voor beide functionaliteiten, maar voor performancetesten wil je vaak repetitief verschillende onderdelen na elkaar uitvoeren.

In Gatling heten deze onderdelen Scenario’s. Een scenario stelt bepaald gebruikersgedrag voor, een workflow die de virtuele gebruikers gaan volgen.

In het voorbeeld ga je overlap creëren tussen deze scenario’s:

val users = scenario("Users").exec(Browse.read)
val admins = scenario("Admins").exec(Browse.read, Remove.delete)

Je hebt nu 2 scenario’s aangemaakt, waarbij het scenario users de Browse uitvoert. Het scenario admins doet dit ook, maar daarnaast wordt de Remove ook uitgevoerd. Op deze manier kan je verschillend gedrag aan verschillende scenario’s toevoegen, zodat je bij iedere rol een andere flow kan aangeven.

Alle losse onderdelen van de workflow zijn nu aanwezig. Wat nog ontbreekt is de setup voor het bepalen van de load op je applicatie. Dit is altijd een combinatie van verschillende factoren, bijvoorbeeld interval, totaaltijd, aantal gelijktijdige users, totaal aantal users, et cetera. Deze opzet hangt compleet af van het normale aantal gebruikers en het doel van je applicatie. Het inlogscherm van bijvoorbeeld bol.com krijgt immers andere gebruikersaantallen te verwerken dan de webshop van je tante.

Binnen performancetesten zijn er 2 modellen waarop je je test kan opzetten:

  • Gesloten systeem: waar je het aantal gelijktijdige users controleert.
  • Open systeem: waar je de starttijd van de users controleert.

In een gesloten systeem is het aantal gelijktijdige users gelimiteerd, denk hierbij aan een ticketsysteem wanneer je een kaartje wilt kopen voor een concert.

Een open systeem heeft geen controle over het aantal users, bij problemen kan je de site gewoon proberen te benaderen. De meeste websites werken op deze manier.

Meer informatie over deze modellen vind je hier.

Je gebruikt in de test een open systeem, die kan je op de volgende manier opzetten:

setUp(
  users.inject(rampUsers(10) during (5 seconds)),
  admins.inject(rampUsers(2) during (5 seconds))
).protocols(httpProtocol)

In de setup komt alles samen wat er gaat gebeuren in je test. Beide scenario’s worden geïnjecteerd met een aantal users gedurende een bepaalde tijd. Vervolgens selecteer je het http-protocol dat je in het begin hebt geconfigureerd. 

Je gebruikt een opzet genaamd rampUsers, hierbij worden 10 users lineair opgeschaald gedurende 5 seconden (dus 1 extra user per 500 ms).

Gatling heeft een handig overzicht voor alle opties die je kan gebruiken binnen je test.

De test is nu klaar om te draaien, dit ga je doen met stages in de JenkinsFile.

stage('Performance test') {
    steps {
        sh 'mvn gatling:test -Dgatling.simulationClass=gatling.DemoSimulation'
    }
}

stage('Visualizing data') {
    steps {
         gatlingArchive()
    }
}

In de eerste stage voer je een shell commando uit met daarin de naam van je testfile zodat alle tests gedraaid worden. Met de tweede stage worden er via gatlingArchive verschillende rapporten gegenereerd. Deze geven een mooi beeld weer van de opbouw van de load en de bijbehorende responstijden.

 

 

Tot slot

Met deze informatie kan je in een middag tijd al een hoop inzicht krijgen in het gedrag van je services. Het mooie aan deze opzet is ook dat je de test periodiek kan draaien om te controleren of je applicatie anders reageert en zo ja, sinds wanneer dit is.

Je moet altijd overwegen of een performancetest nodig is voor jouw specifieke geval, aangezien je lang niet altijd applicaties voor een miljoenenpubliek maakt.

Je kan de volledige configuratie en testopzet checken op github.

Door Koos Drost – Java consultant bij Conspect – 28 januari 2020