From abc99812cff522421769921fb175bf32397ac884 Mon Sep 17 00:00:00 2001 From: uzma Date: Tue, 14 Mar 2023 11:28:54 +0000 Subject: [PATCH 1/2] [BAEL-5894] Add Java code for Gatling perf test --- testing-modules/gatling-java/Jenkinsfile | 20 +++ testing-modules/gatling-java/README.md | 7 + testing-modules/gatling-java/pom.xml | 79 +++++++++++ .../src/main/java/org/baeldung/Address.java | 13 ++ .../main/java/org/baeldung/Application.java | 12 ++ .../src/main/java/org/baeldung/Employee.java | 16 +++ .../java/org/baeldung/EmployeeController.java | 99 ++++++++++++++ .../org/baeldung/EmployeeCreationRequest.java | 29 ++++ .../EmployeeRegistrationSimulation.java | 87 ++++++++++++ .../src/test/resources/gatling.conf | 127 ++++++++++++++++++ .../src/test/resources/logback.xml | 22 +++ testing-modules/pom.xml | 1 + 12 files changed, 512 insertions(+) create mode 100644 testing-modules/gatling-java/Jenkinsfile create mode 100644 testing-modules/gatling-java/README.md create mode 100644 testing-modules/gatling-java/pom.xml create mode 100644 testing-modules/gatling-java/src/main/java/org/baeldung/Address.java create mode 100644 testing-modules/gatling-java/src/main/java/org/baeldung/Application.java create mode 100644 testing-modules/gatling-java/src/main/java/org/baeldung/Employee.java create mode 100644 testing-modules/gatling-java/src/main/java/org/baeldung/EmployeeController.java create mode 100644 testing-modules/gatling-java/src/main/java/org/baeldung/EmployeeCreationRequest.java create mode 100644 testing-modules/gatling-java/src/test/java/org/baeldung/EmployeeRegistrationSimulation.java create mode 100644 testing-modules/gatling-java/src/test/resources/gatling.conf create mode 100644 testing-modules/gatling-java/src/test/resources/logback.xml diff --git a/testing-modules/gatling-java/Jenkinsfile b/testing-modules/gatling-java/Jenkinsfile new file mode 100644 index 0000000000..0786788406 --- /dev/null +++ b/testing-modules/gatling-java/Jenkinsfile @@ -0,0 +1,20 @@ +pipeline { + agent any + stages { + stage("Build Maven") { + steps { + sh 'mvn -B clean package' + } + } + stage("Run Gatling") { + steps { + sh 'mvn gatling:test' + } + post { + always { + gatlingArchive() + } + } + } + } +} \ No newline at end of file diff --git a/testing-modules/gatling-java/README.md b/testing-modules/gatling-java/README.md new file mode 100644 index 0000000000..71848ecfdc --- /dev/null +++ b/testing-modules/gatling-java/README.md @@ -0,0 +1,7 @@ +### Relevant Articles: +Load testing Rest End point using Gatling + + + +### Running a simualtion + To run the simulation from command prompt use mvn gatling:test \ No newline at end of file diff --git a/testing-modules/gatling-java/pom.xml b/testing-modules/gatling-java/pom.xml new file mode 100644 index 0000000000..090a99f3d7 --- /dev/null +++ b/testing-modules/gatling-java/pom.xml @@ -0,0 +1,79 @@ + + + 4.0.0 + org.baeldung + gatling + 1.0-SNAPSHOT + gatling + + + com.baeldung + testing-modules + 1.0.0-SNAPSHOT + + + + io.gatling + gatling-app + ${gatling.version} + + + io.gatling.highcharts + gatling-charts-highcharts + ${gatling.version} + + + + org.springframework.boot + spring-boot-starter-web + ${spring.version} + + + org.projectlombok + lombok + 1.18.24 + provided + + + com.github.javafaker + javafaker + ${faker.version} + + + + + + + + + net.alchim31.maven + scala-maven-plugin + ${scala-maven-plugin.version} + + + + + + io.gatling + gatling-maven-plugin + ${gatling-maven-plugin.version} + + org.baeldung.EmployeeRegistrationSimulation + + + + + + + 1.8 + 1.8 + UTF-8 + 3.9.0 + 4.2.9 + 1.0.2 + 2.7.5 + + + \ No newline at end of file diff --git a/testing-modules/gatling-java/src/main/java/org/baeldung/Address.java b/testing-modules/gatling-java/src/main/java/org/baeldung/Address.java new file mode 100644 index 0000000000..2a8e6c60dc --- /dev/null +++ b/testing-modules/gatling-java/src/main/java/org/baeldung/Address.java @@ -0,0 +1,13 @@ +package org.baeldung; + +import lombok.Builder; +import lombok.Data; + +@Builder +@Data +public class Address { + private String postCode; + private String Street; + private String houseNo; + private String city; +} diff --git a/testing-modules/gatling-java/src/main/java/org/baeldung/Application.java b/testing-modules/gatling-java/src/main/java/org/baeldung/Application.java new file mode 100644 index 0000000000..fce18fe70c --- /dev/null +++ b/testing-modules/gatling-java/src/main/java/org/baeldung/Application.java @@ -0,0 +1,12 @@ +package org.baeldung; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class Application { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} \ No newline at end of file diff --git a/testing-modules/gatling-java/src/main/java/org/baeldung/Employee.java b/testing-modules/gatling-java/src/main/java/org/baeldung/Employee.java new file mode 100644 index 0000000000..c52130b175 --- /dev/null +++ b/testing-modules/gatling-java/src/main/java/org/baeldung/Employee.java @@ -0,0 +1,16 @@ +package org.baeldung; + +import java.util.Set; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class Employee { + private String empName; + private Address address; + private String id; + private Set projects; + +} diff --git a/testing-modules/gatling-java/src/main/java/org/baeldung/EmployeeController.java b/testing-modules/gatling-java/src/main/java/org/baeldung/EmployeeController.java new file mode 100644 index 0000000000..ce5d558d8e --- /dev/null +++ b/testing-modules/gatling-java/src/main/java/org/baeldung/EmployeeController.java @@ -0,0 +1,99 @@ +package org.baeldung; + +import java.net.URI; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.util.UriComponentsBuilder; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RestController +@RequestMapping("/api/employees") +public class EmployeeController { + + @GetMapping(produces = { MediaType.APPLICATION_JSON_VALUE }) + public List getAllEmployees() { + return createEmployees(); + } + + @GetMapping("/{id}") + public Employee getEmployeeWithId(@PathVariable("id") Long id) { + log.info("Getting employee with ID '{}'", id); + + List allEmployees = createEmployees(); + return allEmployees.get(ThreadLocalRandom.current() + .nextInt(0, allEmployees.size())); + } + + @PostMapping(consumes = { MediaType.APPLICATION_JSON_VALUE }) + public ResponseEntity addEmployee(@RequestBody EmployeeCreationRequest request, UriComponentsBuilder uriComponentsBuilder) { + + log.info("Creating new employee with employeeName: {}", request.getEmpName()); + + URI location = uriComponentsBuilder.path("/api/employees/{id}") + .buildAndExpand("99") + .toUri(); + return ResponseEntity.created(location) + .build(); + } + + private List createEmployees() { + + Set projects = new HashSet(); + projects.add("proj1"); + projects.add("proj2"); + + Employee employee1 = Employee.builder() + .id(UUID.randomUUID() + .toString()) + .address(Address.builder() + .houseNo("1") + .city("London") + .postCode("HP17") + .build()) + .projects(projects) + .empName("Andy") + .build(); + + Employee employee2 = Employee.builder() + .id(UUID.randomUUID() + .toString()) + .address(Address.builder() + .houseNo("2") + .city("Cardiff") + .postCode("CF12") + .build()) + .projects(projects) + .empName("Bob") + .build(); + + Employee employee3 = Employee.builder() + .id(UUID.randomUUID() + .toString()) + .address(Address.builder() + .houseNo("4") + .city("Burmingham") + .postCode("BA4") + .build()) + .projects(projects) + .empName("Clive") + .build(); + + return Arrays.asList(employee1, employee2, employee3); + + } +} \ No newline at end of file diff --git a/testing-modules/gatling-java/src/main/java/org/baeldung/EmployeeCreationRequest.java b/testing-modules/gatling-java/src/main/java/org/baeldung/EmployeeCreationRequest.java new file mode 100644 index 0000000000..72c5e1ec27 --- /dev/null +++ b/testing-modules/gatling-java/src/main/java/org/baeldung/EmployeeCreationRequest.java @@ -0,0 +1,29 @@ +package org.baeldung; + +public class EmployeeCreationRequest { + + private String empName; + + private String empNumber; + + public String getEmpName() { + return empName; + } + + public void setEmpName(String empName) { + this.empName = empName; + } + + @Override + public String toString() { + return "org.baeldung.EmployeeCreationRequest{" + "employeename='" + empName + '\'' + '}'; + } + + public String getEmpNumber() { + return empNumber; + } + + public void setEmpNumber(String empNumber) { + this.empNumber = empNumber; + } +} \ No newline at end of file diff --git a/testing-modules/gatling-java/src/test/java/org/baeldung/EmployeeRegistrationSimulation.java b/testing-modules/gatling-java/src/test/java/org/baeldung/EmployeeRegistrationSimulation.java new file mode 100644 index 0000000000..de59273bbd --- /dev/null +++ b/testing-modules/gatling-java/src/test/java/org/baeldung/EmployeeRegistrationSimulation.java @@ -0,0 +1,87 @@ +package org.baeldung; + +import static io.gatling.javaapi.core.CoreDsl.StringBody; +import static io.gatling.javaapi.core.CoreDsl.global; +import static io.gatling.javaapi.core.CoreDsl.rampUsersPerSec; +import static io.gatling.javaapi.http.HttpDsl.header; +import static io.gatling.javaapi.http.HttpDsl.http; +import static io.gatling.javaapi.http.HttpDsl.status; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.stream.Stream; + +import com.github.javafaker.Faker; + +import io.gatling.javaapi.core.CoreDsl; +import io.gatling.javaapi.core.OpenInjectionStep.RampRate.RampRateOpenInjectionStep; +import io.gatling.javaapi.core.ScenarioBuilder; +import io.gatling.javaapi.core.Simulation; +import io.gatling.javaapi.http.HttpDsl; +import io.gatling.javaapi.http.HttpProtocolBuilder; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class EmployeeRegistrationSimulation extends Simulation { + + private static final HttpProtocolBuilder HTTP_PROTOCOL_BUILDER = setupProtocolForSimulation(); + + private static final Iterator> FEED_DATA = setupTestFeedData(); + + private static final ScenarioBuilder POST_SCENARIO_BUILDER = buildPostScenario(); + + public EmployeeRegistrationSimulation() { + + setUp(POST_SCENARIO_BUILDER.injectOpen(postEndpointInjectionProfile()) + .protocols(HTTP_PROTOCOL_BUILDER)).assertions(global().responseTime() + .max() + .lte(10000), global().successfulRequests() + .percent() + .gt(90d)); + } + + private RampRateOpenInjectionStep postEndpointInjectionProfile() { + int totalDesiredUserCount = 200; + double userRampUpPerInterval = 50; + double rampUpIntervalSeconds = 30; + + int totalRampUptimeSeconds = 120; + int steadyStateDurationSeconds = 300; + return rampUsersPerSec(userRampUpPerInterval / (rampUpIntervalSeconds / 60)).to(totalDesiredUserCount) + .during(Duration.ofSeconds(totalRampUptimeSeconds + steadyStateDurationSeconds)); + } + + private static HttpProtocolBuilder setupProtocolForSimulation() { + return HttpDsl.http.baseUrl("http://localhost:8080") + .acceptHeader("application/json") + .maxConnectionsPerHost(10) + .userAgentHeader("Gatling/Performance Test"); + } + + private static Iterator> setupTestFeedData() { + Faker faker = new Faker(); + Iterator> iterator; + iterator = Stream.generate(() -> { + Map stringObjectMap = new HashMap<>(); + stringObjectMap.put("empName", faker.name() + .fullName()); + return stringObjectMap; + }) + .iterator(); + return iterator; + } + + private static ScenarioBuilder buildPostScenario() { + return CoreDsl.scenario("Load Test Creating User") + .feed(FEED_DATA) + .exec(http("create-employee-request").post("/api/employees") + .header("Content-Type", "application/json") + .body(StringBody("{ \"empName\": \"${empName}\" }")) + .check(status().is(201)) + .check(header("Location").saveAs("location"))) + .exec(http("get-employee-request").get(session -> session.getString("location")) + .check(status().is(200))); + } +} diff --git a/testing-modules/gatling-java/src/test/resources/gatling.conf b/testing-modules/gatling-java/src/test/resources/gatling.conf new file mode 100644 index 0000000000..6ebfd6e820 --- /dev/null +++ b/testing-modules/gatling-java/src/test/resources/gatling.conf @@ -0,0 +1,127 @@ + ######################### +# Gatling Configuration # +######################### + +# This file contains all the settings configurable for Gatling with their default values + +gatling { + core { + #outputDirectoryBaseName = "" # The prefix for each simulation result folder (then suffixed by the report generation timestamp) + #runDescription = "" # The description for this simulation run, displayed in each report + #encoding = "utf-8" # Encoding to use throughout Gatling for file and string manipulation + #simulationClass = "" # The FQCN of the simulation to run (when used in conjunction with noReports, the simulation for which assertions will be validated) + #mute = false # When set to true, don't ask for simulation name nor run description (currently only used by Gatling SBT plugin) + #elFileBodiesCacheMaxCapacity = 200 # Cache size for request body EL templates, set to 0 to disable + #rawFileBodiesCacheMaxCapacity = 200 # Cache size for request body Raw templates, set to 0 to disable + #rawFileBodiesInMemoryMaxSize = 1000 # Below this limit, raw file bodies will be cached in memory + + extract { + regex { + #cacheMaxCapacity = 200 # Cache size for the compiled regexes, set to 0 to disable caching + } + xpath { + #cacheMaxCapacity = 200 # Cache size for the compiled XPath queries, set to 0 to disable caching + } + jsonPath { + #cacheMaxCapacity = 200 # Cache size for the compiled jsonPath queries, set to 0 to disable caching + #preferJackson = false # When set to true, prefer Jackson over Boon for JSON-related operations + } + css { + #cacheMaxCapacity = 200 # Cache size for the compiled CSS selectors queries, set to 0 to disable caching + } + } + + directory { + #data = user-files/data # Folder where user's data (e.g. files used by Feeders) is located + #bodies = user-files/bodies # Folder where bodies are located + #simulations = user-files/simulations # Folder where the bundle's simulations are located + #reportsOnly = "" # If set, name of report folder to look for in order to generate its report + #binaries = "" # If set, name of the folder where compiles classes are located: Defaults to GATLING_HOME/target. + #results = results # Name of the folder where all reports folder are located + } + } + charting { + #noReports = false # When set to true, don't generate HTML reports + #maxPlotPerSeries = 1000 # Number of points per graph in Gatling reports + #useGroupDurationMetric = false # Switch group timings from cumulated response time to group duration. + indicators { + #lowerBound = 800 # Lower bound for the requests' response time to track in the reports and the console summary + #higherBound = 1200 # Higher bound for the requests' response time to track in the reports and the console summary + #percentile1 = 50 # Value for the 1st percentile to track in the reports, the console summary and Graphite + #percentile2 = 75 # Value for the 2nd percentile to track in the reports, the console summary and Graphite + #percentile3 = 95 # Value for the 3rd percentile to track in the reports, the console summary and Graphite + #percentile4 = 99 # Value for the 4th percentile to track in the reports, the console summary and Graphite + } + } + http { + #fetchedCssCacheMaxCapacity = 200 # Cache size for CSS parsed content, set to 0 to disable + #fetchedHtmlCacheMaxCapacity = 200 # Cache size for HTML parsed content, set to 0 to disable + #perUserCacheMaxCapacity = 200 # Per virtual user cache size, set to 0 to disable + #warmUpUrl = "http://gatling.io" # The URL to use to warm-up the HTTP stack (blank means disabled) + #enableGA = true # Very light Google Analytics, please support + ssl { + keyStore { + #type = "" # Type of SSLContext's KeyManagers store + #file = "" # Location of SSLContext's KeyManagers store + #password = "" # Password for SSLContext's KeyManagers store + #algorithm = "" # Algorithm used SSLContext's KeyManagers store + } + trustStore { + #type = "" # Type of SSLContext's TrustManagers store + #file = "" # Location of SSLContext's TrustManagers store + #password = "" # Password for SSLContext's TrustManagers store + #algorithm = "" # Algorithm used by SSLContext's TrustManagers store + } + } + ahc { + #keepAlive = true # Allow pooling HTTP connections (keep-alive header automatically added) + #connectTimeout = 60000 # Timeout when establishing a connection + #pooledConnectionIdleTimeout = 60000 # Timeout when a connection stays unused in the pool + #readTimeout = 60000 # Timeout when a used connection stays idle + #maxRetry = 2 # Number of times that a request should be tried again + #requestTimeout = 60000 # Timeout of the requests + #acceptAnyCertificate = true # When set to true, doesn't validate SSL certificates + #httpClientCodecMaxInitialLineLength = 4096 # Maximum length of the initial line of the response (e.g. "HTTP/1.0 200 OK") + #httpClientCodecMaxHeaderSize = 8192 # Maximum size, in bytes, of each request's headers + #httpClientCodecMaxChunkSize = 8192 # Maximum length of the content or each chunk + #webSocketMaxFrameSize = 10240000 # Maximum frame payload size + #sslEnabledProtocols = [TLSv1.2, TLSv1.1, TLSv1] # Array of enabled protocols for HTTPS, if empty use the JDK defaults + #sslEnabledCipherSuites = [] # Array of enabled cipher suites for HTTPS, if empty use the JDK defaults + #sslSessionCacheSize = 0 # SSLSession cache size, set to 0 to use JDK's default + #sslSessionTimeout = 0 # SSLSession timeout in seconds, set to 0 to use JDK's default (24h) + #useOpenSsl = false # if OpenSSL should be used instead of JSSE (requires tcnative jar) + #useNativeTransport = false # if native transport should be used instead of Java NIO (requires netty-transport-native-epoll, currently Linux only) + #usePooledMemory = true # if Gatling should use pooled memory + #tcpNoDelay = true + #soReuseAddress = false + #soLinger = -1 + #soSndBuf = -1 + #soRcvBuf = -1 + } + dns { + #queryTimeout = 5000 # Timeout of each DNS query in millis + #maxQueriesPerResolve = 3 # Maximum allowed number of DNS queries for a given name resolution + } + } + data { + #writers = [console, file] # The list of DataWriters to which Gatling write simulation data (currently supported : console, file, graphite, jdbc) + console { + #light = false # When set to true, displays a light version without detailed request stats + } + file { + #bufferSize = 8192 # FileDataWriter's internal data buffer size, in bytes + } + leak { + #noActivityTimeout = 30 # Period, in seconds, for which Gatling may have no activity before considering a leak may be happening + } + graphite { + #light = false # only send the all* stats + #host = "localhost" # The host where the Carbon server is located + #port = 2003 # The port to which the Carbon server listens to (2003 is default for plaintext, 2004 is default for pickle) + #protocol = "tcp" # The protocol used to send data to Carbon (currently supported : "tcp", "udp") + #rootPathPrefix = "gatling" # The common prefix of all metrics sent to Graphite + #bufferSize = 8192 # GraphiteDataWriter's internal data buffer size, in bytes + #writeInterval = 1 # GraphiteDataWriter's write interval, in seconds + } + } +} diff --git a/testing-modules/gatling-java/src/test/resources/logback.xml b/testing-modules/gatling-java/src/test/resources/logback.xml new file mode 100644 index 0000000000..b9ba6255a0 --- /dev/null +++ b/testing-modules/gatling-java/src/test/resources/logback.xml @@ -0,0 +1,22 @@ + + + + + + %d{HH:mm:ss.SSS} [%-5level] %logger{15} - %msg%n%rEx + false + + + + + + + + + + + + + + + diff --git a/testing-modules/pom.xml b/testing-modules/pom.xml index 64546b5064..80e0c3bc29 100644 --- a/testing-modules/pom.xml +++ b/testing-modules/pom.xml @@ -51,6 +51,7 @@ xmlunit-2 zerocode mockito-2 + gatling-java \ No newline at end of file From 734709cd476f6fba28e71a17902f1abab2f6a909 Mon Sep 17 00:00:00 2001 From: uzma Date: Fri, 17 Mar 2023 11:49:32 +0000 Subject: [PATCH 2/2] [BAEL-5894] fix artifact name in parent pom --- testing-modules/gatling-java/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing-modules/gatling-java/pom.xml b/testing-modules/gatling-java/pom.xml index 090a99f3d7..c759928cc5 100644 --- a/testing-modules/gatling-java/pom.xml +++ b/testing-modules/gatling-java/pom.xml @@ -4,9 +4,9 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.baeldung - gatling + gatling-java 1.0-SNAPSHOT - gatling + gatling-java com.baeldung