diff --git a/README.md b/README.md index 881f2e5..bb4c092 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,20 @@ All applications implement the same functionality and expose the same REST api - `./run-micronaut.sh` - `./run-quarkus.sh` 4. Run Gatling: - - `./gradlew :loadtest:gatlingRun` + - `./gradlew :loadtest:gatlingRun-gatling.simulation.CommonSimulation` + +
+ Chec k wiremock performance + +Run Gradle +```bash +docker-compose up -d wiremock +./gradlew :loadtest:gatlingRun-gatling.simulation.WiremockSimulation +``` + +
+ + # Rest API diff --git a/common-wiremock/src/main/resources/mockserver-config/expectationInitialiser.json b/common-wiremock/src/main/resources/mockserver-config/expectationInitialiser.json new file mode 100644 index 0000000..9ff74f1 --- /dev/null +++ b/common-wiremock/src/main/resources/mockserver-config/expectationInitialiser.json @@ -0,0 +1,74 @@ +[ + { + "httpRequest": { + "method": "GET", + "path": "/exchanges", + "queryStringParameters": { + "currency": "EUR" + } + }, + "httpResponse": { + "statusCode": 200, + "body": { + "base": "EUR", + "rates": { + "USD": 1.0, + "EUR": 1.0, + "GBP": 0.5 + } + }, + "delay": { + "timeUnit": "MILLISECONDS", + "value": 200 + } + } + }, + { + "httpRequest": { + "method": "GET", + "path": "/exchanges", + "queryStringParameters": { + "currency": "GBP" + } + }, + "httpResponse": { + "statusCode": 200, + "body": { + "base": "GBP", + "rates": { + "USD": 2.0, + "EUR": 2.0, + "GBP": 1.0 + } + }, + "delay": { + "timeUnit": "MILLISECONDS", + "value": 200 + } + } + }, + { + "httpRequest": { + "method": "GET", + "path": "/exchanges", + "queryStringParameters": { + "currency": "USD" + } + }, + "httpResponse": { + "statusCode": 200, + "body": { + "base": "USD", + "rates": { + "USD": 1.0, + "EUR": 1.0, + "GBP": 0.5 + } + }, + "delay": { + "timeUnit": "MILLISECONDS", + "value": 200 + } + } + } +] \ No newline at end of file diff --git a/common-wiremock/src/main/resources/stubs/__files/base-eur-response.json b/common-wiremock/src/main/resources/stubs/__files/base-eur-response.json deleted file mode 100644 index 44dbae8..0000000 --- a/common-wiremock/src/main/resources/stubs/__files/base-eur-response.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "base": "EUR", - "rates": { - "USD": 1.0, - "GBP": 0.5 - } -} \ No newline at end of file diff --git a/common-wiremock/src/main/resources/stubs/__files/base-gbp-response.json b/common-wiremock/src/main/resources/stubs/__files/base-gbp-response.json deleted file mode 100644 index 66a8143..0000000 --- a/common-wiremock/src/main/resources/stubs/__files/base-gbp-response.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "base": "GBP", - "rates": { - "USD": 2.0, - "EUR": 2.0 - } -} \ No newline at end of file diff --git a/common-wiremock/src/main/resources/stubs/__files/base-usd-response.json b/common-wiremock/src/main/resources/stubs/__files/base-usd-response.json deleted file mode 100644 index 035c1c4..0000000 --- a/common-wiremock/src/main/resources/stubs/__files/base-usd-response.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "base": "USD", - "rates": { - "EUR": 1.0, - "GBP": 0.5 - } -} \ No newline at end of file diff --git a/common-wiremock/src/main/resources/stubs/mappings/get-eur-200.json b/common-wiremock/src/main/resources/stubs/mappings/get-eur-200.json index dc0c59a..bbbf1f3 100644 --- a/common-wiremock/src/main/resources/stubs/mappings/get-eur-200.json +++ b/common-wiremock/src/main/resources/stubs/mappings/get-eur-200.json @@ -5,9 +5,17 @@ }, "response": { "status": 200, - "bodyFileName": "base-eur-response.json", + "jsonBody": { + "base": "EUR", + "rates": { + "USD": 1.0, + "EUR": 1.0, + "GBP": 0.5 + } + }, "headers": { "Content-Type": "application/json; charset=UTF-8" - } + }, + "fixedDelayMilliseconds": 200 } } \ No newline at end of file diff --git a/common-wiremock/src/main/resources/stubs/mappings/get-gbp-200.json b/common-wiremock/src/main/resources/stubs/mappings/get-gbp-200.json index 3105532..7ab5448 100644 --- a/common-wiremock/src/main/resources/stubs/mappings/get-gbp-200.json +++ b/common-wiremock/src/main/resources/stubs/mappings/get-gbp-200.json @@ -5,9 +5,17 @@ }, "response": { "status": 200, - "bodyFileName": "base-gbp-response.json", + "jsonBody": { + "base": "GBP", + "rates": { + "USD": 2.0, + "EUR": 2.0, + "GBP": 1.0 + } + }, "headers": { "Content-Type": "application/json; charset=UTF-8" - } + }, + "fixedDelayMilliseconds": 200 } } \ No newline at end of file diff --git a/common-wiremock/src/main/resources/stubs/mappings/get-usd-200.json b/common-wiremock/src/main/resources/stubs/mappings/get-usd-200.json index ac99f7a..6bab836 100644 --- a/common-wiremock/src/main/resources/stubs/mappings/get-usd-200.json +++ b/common-wiremock/src/main/resources/stubs/mappings/get-usd-200.json @@ -5,9 +5,17 @@ }, "response": { "status": 200, - "bodyFileName": "base-usd-response.json", + "jsonBody": { + "base": "USD", + "rates": { + "USD": 1.0, + "EUR": 1.0, + "GBP": 0.5 + } + }, "headers": { "Content-Type": "application/json; charset=UTF-8" - } + }, + "fixedDelayMilliseconds": 200 } } \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 145a14c..3af1a31 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,7 @@ -version: '3.8' +version: "3.8" services: db: + container_name: db image: postgres:14.4 restart: always environment: @@ -8,13 +9,25 @@ services: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres ports: - - '5432:5432' + - "5432:5432" wiremock: - image: wiremock/wiremock:2.32.0 + container_name: wiremock + image: wiremock/wiremock:2.35.0 ports: - "8888:8080" - command: - - "--global-response-templating" + command: ["--async-response-enabled=true"] volumes: - ./common-wiremock/src/main/resources/stubs:/home/wiremock + + mockserver: + container_name: mockserver + image: mockserver/mockserver:5.15.0 + ports: + - "8888:1080" + environment: + MOCKSERVER_LOG_LEVEL: WARN + MOCKSERVER_INITIALIZATION_JSON_PATH: /config/expectationInitialiser.json + MOCKSERVER_NIO_EVENT_LOOP_THREAD_COUNT: 20 + volumes: + - ./common-wiremock/src/main/resources/mockserver-config:/config diff --git a/dropwizard-app/src/test/java/bitxon/dropwizard/test/AbstractDropwizardTest.java b/dropwizard-app/src/test/java/bitxon/dropwizard/test/AbstractDropwizardTest.java index 215f653..5ea3cb0 100644 --- a/dropwizard-app/src/test/java/bitxon/dropwizard/test/AbstractDropwizardTest.java +++ b/dropwizard-app/src/test/java/bitxon/dropwizard/test/AbstractDropwizardTest.java @@ -23,7 +23,7 @@ abstract class AbstractDropwizardTest { .withDatabaseName("testdb") .withUsername("postgres") .withPassword("postgres") - .withInitScript("sql/db-test-data.sql"); + .withCopyFileToContainer(MountableFile.forClasspathResource("sql/db-test-data.sql"), "/docker-entrypoint-initdb.d/"); static GenericContainer WIREMOCK = new GenericContainer("wiremock/wiremock:2.35.0") .withExposedPorts(8080) .withCopyFileToContainer(MountableFile.forClasspathResource("stubs"), "/home/wiremock") diff --git a/loadtest/src/gatling/java/gatling/simulation/CommonSimulation.java b/loadtest/src/gatling/java/gatling/simulation/CommonSimulation.java index b85a7e5..28aa825 100644 --- a/loadtest/src/gatling/java/gatling/simulation/CommonSimulation.java +++ b/loadtest/src/gatling/java/gatling/simulation/CommonSimulation.java @@ -137,29 +137,37 @@ private static ChainBuilder postTransfer() { { setUp( - scenarioGetAll.injectOpen( - constantUsersPerSec(4).during(90) - ), - scenarioGetOne.injectOpen( - nothingFor(4), // Wait - atOnceUsers(10), // 10 - rampUsers(10).during(5), // 10 -> 20 - constantUsersPerSec(20).during(15), // 20 - constantUsersPerSec(20).during(15).randomized(), // 20 - rampUsersPerSec(10).to(20).during(10), // 10 -> 20 - rampUsersPerSec(10).to(20).during(10).randomized(), // 10 -> 20 - stressPeakUsers(400).during(20) // 8 - ), - scenarioTransfer.injectOpen( - incrementUsersPerSec(3) - .times(6) - .eachLevelLasting(8) - .separatedByRampsLasting(8) - .startingFrom(8) - ), - scenarioValidation.injectOpen( - constantUsersPerSec(10).during(90) + scenarioTransfer.injectClosed( + constantConcurrentUsers(1000).during(10) ) ).protocols(httpProtocol); } + +// { +// setUp( +// scenarioGetAll.injectOpen( +// constantUsersPerSec(4).during(90) +// ), +// scenarioGetOne.injectOpen( +// nothingFor(4), // Wait +// atOnceUsers(10), // 10 +// rampUsers(10).during(5), // 10 -> 20 +// constantUsersPerSec(20).during(15), // 20 +// constantUsersPerSec(20).during(15).randomized(), // 20 +// rampUsersPerSec(10).to(20).during(10), // 10 -> 20 +// rampUsersPerSec(10).to(20).during(10).randomized(), // 10 -> 20 +// stressPeakUsers(400).during(20) // 8 +// ), +// scenarioTransfer.injectOpen( +// incrementUsersPerSec(3) +// .times(6) +// .eachLevelLasting(8) +// .separatedByRampsLasting(8) +// .startingFrom(8) +// ), +// scenarioValidation.injectOpen( +// constantUsersPerSec(10).during(90) +// ) +// ).protocols(httpProtocol); +// } } diff --git a/loadtest/src/gatling/java/gatling/simulation/WiremockSimulation.java b/loadtest/src/gatling/java/gatling/simulation/WiremockSimulation.java new file mode 100644 index 0000000..6c26f75 --- /dev/null +++ b/loadtest/src/gatling/java/gatling/simulation/WiremockSimulation.java @@ -0,0 +1,55 @@ +package gatling.simulation; + +import io.gatling.javaapi.core.ChainBuilder; +import io.gatling.javaapi.core.FeederBuilder; +import io.gatling.javaapi.core.ScenarioBuilder; +import io.gatling.javaapi.core.Simulation; +import io.gatling.javaapi.http.HttpProtocolBuilder; + +import static io.gatling.javaapi.core.CoreDsl.*; +import static io.gatling.javaapi.http.HttpDsl.http; +import static io.gatling.javaapi.http.HttpDsl.status; + +public class WiremockSimulation extends Simulation { + + static final String BASE_URL = System.getProperty("baseUrl", "http://localhost:8888"); + static final int USERS = Integer.getInteger("users", 800); + static final int REQUESTS_PER_USER = Integer.getInteger("requestsPerUser", 20); + static final int DURATION = Integer.getInteger("duration", 20); + + //----------------------------------------------------------------------------------------------------------------- + + static FeederBuilder feederCurrency = csv("feeders/currency.csv").random(); + + //----------------------------------------------------------------------------------------------------------------- + + private static ChainBuilder getExchangeRate() { + return exec(http("Get Exchange Rate") + .get("/exchanges?currency=#{currency}") + .check(status().is(200)) + ); + } + + //----------------------------------------------------------------------------------------------------------------- + + ScenarioBuilder scenarioGetOne = scenario("Get One - Scenario") + .feed(feederCurrency) + .exec( + repeat(REQUESTS_PER_USER).on(getExchangeRate()) + ); + + //----------------------------------------------------------------------------------------------------------------- + + HttpProtocolBuilder httpProtocol = http.baseUrl(BASE_URL) + .acceptHeader("application/json") + .acceptLanguageHeader("en-US,en;q=0.5"); + + { + setUp( + scenarioGetOne.injectOpen( + rampUsers(USERS).during(DURATION) + ) + ).protocols(httpProtocol); + } + +} diff --git a/loadtest/src/gatling/java/gatling/utils/RandomUtils.java b/loadtest/src/gatling/java/gatling/utils/RandomUtils.java new file mode 100644 index 0000000..33401bb --- /dev/null +++ b/loadtest/src/gatling/java/gatling/utils/RandomUtils.java @@ -0,0 +1,13 @@ +package gatling.utils; + +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +public class RandomUtils { + + + public static String peekRandom(List list) { + var rand = ThreadLocalRandom.current(); + return list.get(rand.nextInt(list.size())); + } +} diff --git a/loadtest/src/gatling/resources/feeders/currency.csv b/loadtest/src/gatling/resources/feeders/currency.csv new file mode 100644 index 0000000..b22f401 --- /dev/null +++ b/loadtest/src/gatling/resources/feeders/currency.csv @@ -0,0 +1,4 @@ +currency +USD +EUR +GBP \ No newline at end of file diff --git a/micronaut-app/src/test/java/bitxon/micronaut/test/AbstractMicronautTest.java b/micronaut-app/src/test/java/bitxon/micronaut/test/AbstractMicronautTest.java index 1e28919..a76b0b1 100644 --- a/micronaut-app/src/test/java/bitxon/micronaut/test/AbstractMicronautTest.java +++ b/micronaut-app/src/test/java/bitxon/micronaut/test/AbstractMicronautTest.java @@ -43,6 +43,7 @@ abstract class AbstractMicronautTest implements TestPropertyProvider { @Override public Map getProperties() { return Map.of( + "micronaut.server.port", "${random.port}", "datasources.default.url", DB.getJdbcUrl(), "datasources.default.username", DB.getUsername(), "datasources.default.password", DB.getPassword(), diff --git a/spring-app/build.gradle b/spring-app/build.gradle index 6e9a1e6..41da5f9 100644 --- a/spring-app/build.gradle +++ b/spring-app/build.gradle @@ -33,6 +33,7 @@ dependencies { compileOnly("org.projectlombok:lombok") runtimeOnly 'org.postgresql:postgresql' + //runtimeOnly 'io.netty:netty-resolver-dns-native-macos:4.1.92.Final:osx-aarch_64' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.boot:spring-boot-testcontainers' diff --git a/test-mockserver.sh b/test-mockserver.sh new file mode 100755 index 0000000..178369e --- /dev/null +++ b/test-mockserver.sh @@ -0,0 +1,15 @@ +#!/bin/bash +docker-compose down +docker-compose up -d mockserver + +#Warm Up +sleep 10 +for run in {1..3}; do + curl --location 'http://localhost:8888/exchanges?currency=USD' + curl --location 'http://localhost:8888/exchanges?currency=EUR' + curl --location 'http://localhost:8888/exchanges?currency=GBP' +done + +#Test +sleep 3 +./gradlew :loadtest:gatlingRun-gatling.simulation.WiremockSimulation diff --git a/test-wiremock.sh b/test-wiremock.sh new file mode 100755 index 0000000..37ad376 --- /dev/null +++ b/test-wiremock.sh @@ -0,0 +1,15 @@ +#!/bin/bash +docker-compose down +docker-compose up -d wiremock + +#Warm Up +sleep 10 +for run in {1..3}; do + curl --location 'http://localhost:8888/exchanges?currency=USD' + curl --location 'http://localhost:8888/exchanges?currency=EUR' + curl --location 'http://localhost:8888/exchanges?currency=GBP' +done + +#Test +sleep 3 +./gradlew :loadtest:gatlingRun-gatling.simulation.WiremockSimulation