From a313c855d32b0ac6e7ee417d104ee8dab10c6304 Mon Sep 17 00:00:00 2001 From: Thiago dos Santos Hora Date: Sat, 30 Jul 2022 14:26:16 +0200 Subject: [PATCH] [BAEL-5631] Quarkus vs Spring boot improvements (#12520) * Initial impl * Update framework versions testing from jm to wrk * Add hyperfoil * Add hyperfoil read me --- .../quarkus-vs-springboot/README.md | 54 +++- .../hyperfoil/docker_run.sh | 13 + .../hyperfoil/volume/benchmark.hf.yaml | 86 ++++++ .../{ => hyperfoil/volume}/cities.csv | 0 .../quarkus-vs-springboot/jmeter/cities.csv | 136 +++++++++ .../{ => jmeter}/load_test.jmx | 0 .../run_test_jmeter.sh} | 0 .../quarkus-project/build.sh | 8 +- .../quarkus-project/pom.xml | 277 +++++++++--------- .../src/main/docker/Dockerfile.jvm | 3 +- .../src/main/docker/quarkus.yml | 30 +- .../baeldung/quarkus_project/ZipCodeRepo.java | 4 +- .../quarkus_project/ZipCodeResource.java | 16 +- .../src/main/resources/application.properties | 13 +- .../spring-project/build.sh | 11 + .../spring-project/build_jvm_docker.sh | 6 - .../spring-project/pom.xml | 229 ++++++++++----- .../src/main/docker/Dockerfile.jvm | 12 - .../spring-project/src/main/docker/spring.yml | 30 +- .../com/baeldung/spring_project/Startup.java | 6 +- .../baeldung/spring_project/ZipCodeApi.java | 24 +- .../src/main/resources/application.properties | 12 +- .../baeldung/spring_project/StartupIT.java | 13 +- .../quarkus-vs-springboot/wrk/cities.csv | 136 +++++++++ .../quarkus-vs-springboot/wrk/generator.lua | 65 ++++ .../quarkus-vs-springboot/wrk/get_by_city.lua | 79 +++++ .../quarkus-vs-springboot/wrk/get_zipcode.lua | 77 +++++ .../quarkus-vs-springboot/wrk/json.lua | 133 +++++++++ .../wrk/post_zipcode.lua | 73 +++++ .../quarkus-vs-springboot/wrk/run_test_wrk.sh | 14 + 30 files changed, 1258 insertions(+), 302 deletions(-) create mode 100644 quarkus-modules/quarkus-vs-springboot/hyperfoil/docker_run.sh create mode 100644 quarkus-modules/quarkus-vs-springboot/hyperfoil/volume/benchmark.hf.yaml rename quarkus-modules/quarkus-vs-springboot/{ => hyperfoil/volume}/cities.csv (100%) create mode 100644 quarkus-modules/quarkus-vs-springboot/jmeter/cities.csv rename quarkus-modules/quarkus-vs-springboot/{ => jmeter}/load_test.jmx (100%) rename quarkus-modules/quarkus-vs-springboot/{run_test.sh => jmeter/run_test_jmeter.sh} (100%) create mode 100755 quarkus-modules/quarkus-vs-springboot/spring-project/build.sh delete mode 100644 quarkus-modules/quarkus-vs-springboot/spring-project/build_jvm_docker.sh delete mode 100644 quarkus-modules/quarkus-vs-springboot/spring-project/src/main/docker/Dockerfile.jvm create mode 100644 quarkus-modules/quarkus-vs-springboot/wrk/cities.csv create mode 100644 quarkus-modules/quarkus-vs-springboot/wrk/generator.lua create mode 100644 quarkus-modules/quarkus-vs-springboot/wrk/get_by_city.lua create mode 100644 quarkus-modules/quarkus-vs-springboot/wrk/get_zipcode.lua create mode 100644 quarkus-modules/quarkus-vs-springboot/wrk/json.lua create mode 100644 quarkus-modules/quarkus-vs-springboot/wrk/post_zipcode.lua create mode 100755 quarkus-modules/quarkus-vs-springboot/wrk/run_test_wrk.sh diff --git a/quarkus-modules/quarkus-vs-springboot/README.md b/quarkus-modules/quarkus-vs-springboot/README.md index 05eaabb923..13c0b8ab5f 100644 --- a/quarkus-modules/quarkus-vs-springboot/README.md +++ b/quarkus-modules/quarkus-vs-springboot/README.md @@ -6,6 +6,9 @@ To follow this tutorial, you will need the following things: - Maven (Embedded, IDE, or local installation) - Docker (https://www.docker.com/) - Jmeter (https://jmeter.apache.org/) +- wrk (https://github.com/wg/wrk) +- hyperfoil (https://hyperfoil.io/) +- lua (https://www.lua.org/) To create this test, I used some custom features from Jmeter. You can install the Jmeter plugin manager here: https://loadium.com/blog/how-to-install-use-jmeter-plugin. After that, please install the following plugins: @@ -17,31 +20,32 @@ The test file is `load_test.jmx` in case of any change need. You can open it wit $jmeter_home/bin/jmeter -n -t load_test.jmx -l log.csv -e -o ./report ``` -Just remember to change the variable `jmeter_home` with the path to the JMeter folder. The path to the data files is relative, so either keep them in the same folder as the test or use Jmeter GUI to change it. +Just remember to change the variable `jmeter_home` with the path to the JMeter folder. The path to the data files is relative, so either keep them in the same folder as the test or use Jmeter GUI to change it. Rememeber that as mentioned in the article, we cannot consider the response times recorded by Jmeter due to the Coordinated Omission Problem. Open the VisualVM application and select your application to start monitoring before running the test, and of course, start the sample application first. ## Spring Boot To build the application, you only need to run the following command in the Spring project root: ``` -./mvnw package -f pom.xml +./mvnw clean package -f pom.xml ``` Or this one in case you want to build the native one: ``` -./mvnw -DskipTests package -Pnative -f pom.xml +./mvnw clean package -Pnative -f pom.xml ``` In this case, you will need to have the `GRAALVM_HOME` env variable defined. You only need this if you want to build the image locally. Otherwise, you can build it using docker by leveraging the Spring Boot maven plugin. It will pull a docker image of the GraalVM, and with that, it will create the native image of the app. To do that, run: ``` -./mvnw spring-boot:build-image +./mvnw clean package spring-boot:build-image -Pnative -f pom.xml ``` -You can also create a docker image with the JVM version of the app running the script `build_jvm_docker.sh` or: +You can also create a docker image with the JVM version one of the app running the script `build.sh` or: ``` -docker build -f src/main/docker/Dockerfile.jvm -t spring-project:0.1-SNAPSHOT . +./mvnw clean package spring-boot:build-image -f pom.xml + ``` -You can execute the script `start_app.sh` or `start_jvm.sh` to run the application locally. In this case, you will need the Postgres DB. You can run it in docker with the command: +You can execute the script `start_app.sh` or `start_jvm.sh` to run the application locally. In this case, you will need the Mysql DB. You can run it in docker with the command: ``` -docker run -e POSTGRES_PASSWORD=example -p 5432:5432 postgres +docker run --name mysqldb --network=host -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=baeldung -d mysql:5.7.38 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci ``` You can also run both application and DB from docker, using: ``` @@ -67,7 +71,7 @@ And to the JVM version: To start the application locally, use either the scripts `start_app.sh` and `start_jvm.sh` with the docker DB: ``` -docker run -e POSTGRES_PASSWORD=example -p 5432:5432 postgres +docker run --name mysqldb --network=host -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=baeldung -d mysql:5.7.38 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci ``` Or use the script to build the docker image of the application, running: ```bash @@ -94,6 +98,38 @@ docker-compose -f src/main/docker/quarkus.yml up Now you have all you need to reproduce the tests with your machine. +## Wrk +Another option to execute the load test is to use the wrk. This library is capable of generation a pretty high load only using a single core. To install it you only have to checkout the project compile it (using make) and define the `wrk_home` envvar. To run the test use: + +``` +./run_test_wrk.sh +``` +You will need to have installed lua in your machine. + +### Tips +If you want to run the applications in your machine you can use the following command to restrict the CPUs available to the app: + +``` +cpulimit -l 300 -p ## 300 means at most 3 cores. +``` + +This will make sure the load is on the application and not in the DB. +## Hyperfoil + +To the hyperfoil test to get a report regarding the performance of the application, its throughput and response time. You can run the `docker_run.sh` from the hyperfoil folder, or the following: + +``` +docker run -it -v volume:/benchmarks:Z -v tmp/reports:/tmp/reports:Z --network=host quay.io/hyperfoil/hyperfoil cli +``` +And then: +``` +start-local && upload /benchmarks/benchmark.hf.yaml && run benchmark +``` +Optionally, we can extract a html report from it, by running: +``` +report --destination=/tmp/reports +``` + ### Relevant Articles: - [Spring Boot vs Quarkus](https://www.baeldung.com/spring-boot-vs-quarkus) diff --git a/quarkus-modules/quarkus-vs-springboot/hyperfoil/docker_run.sh b/quarkus-modules/quarkus-vs-springboot/hyperfoil/docker_run.sh new file mode 100644 index 0000000000..ee0ee35a29 --- /dev/null +++ b/quarkus-modules/quarkus-vs-springboot/hyperfoil/docker_run.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +docker run -it -v $SCRIPTPATH/volume:/benchmarks:Z -v $SCRIPTPATH/tmp/reports:/tmp/reports:Z --network=host quay.io/hyperfoil/hyperfoil cli + +#start-local && upload /benchmarks/benchmark.hf.yaml && run benchmark + +# step 1 run: start-local +# step 2 run (Run this every time the file is modified): upload /benchmarks/benchmark.hf.yaml +# step 3 run: run benchmark +# step 4 run: stats +# step 5 run: report --destination=/tmp/reports diff --git a/quarkus-modules/quarkus-vs-springboot/hyperfoil/volume/benchmark.hf.yaml b/quarkus-modules/quarkus-vs-springboot/hyperfoil/volume/benchmark.hf.yaml new file mode 100644 index 0000000000..598c61249e --- /dev/null +++ b/quarkus-modules/quarkus-vs-springboot/hyperfoil/volume/benchmark.hf.yaml @@ -0,0 +1,86 @@ +name: benchmark +http: + host: http://localhost:8080 + sharedConnections: 100 +phases: + - main: + constantRate: + startAfter: rampup + usersPerSec: 3300 + maxSessions: 6000 + duration: 5m + forks: + - post_zipcode: &post_zipcode + scenario: + - fetchIndex: + - randomCsvRow: + file: /benchmarks/zip_code_database.csv + removeQuotes: true + columns: + 0: zip + 1: type + 3: city + 6: state + 7: county + 8: timezone + - httpRequest: + sla: + - blockedRatio: 500 + POST: /zipcode + headers: + Content-Type: application/json;charset=UTF-8 + Accept: application/json + body: | + { + "zip" : "${zip}", + "type" : "${type}", + "city" : "${city}", + "state" : "${state}", + "county" : "${county}", + "timezone" : "${timezone}" + } + - get_zipcode: &get_zipcode + scenario: + - fetchIndex: + - randomCsvRow: + file: /benchmarks/zip_code_database.csv + removeQuotes: true + columns: + 0: zipcode + - httpRequest: + sla: + - blockedRatio: 500 + headers: + accept: application/json + GET: /zipcode/${zipcode} + - get_zipcode_by_city: &get_zipcode_by_city + scenario: + - fetchDetails: + - randomCsvRow: + file: /benchmarks/cities.csv + removeQuotes: true + columns: + 0: city + - httpRequest: + sla: + - blockedRatio: 500 + headers: + accept: application/json + GET: /zipcode/by_city?city=${city} + - spike: + constantRate: + startAfter: main + usersPerSec: 4400 + duration: 2m + forks: + - get_zipcode_by_city: *get_zipcode_by_city + - get_zipcode: *get_zipcode + + - rampup: + increasingRate: + initialUsersPerSec: 3 + targetUsersPerSec: 2500 + duration: 1m + forks: + - post_zipcode: *post_zipcode + - get_zipcode: *get_zipcode diff --git a/quarkus-modules/quarkus-vs-springboot/cities.csv b/quarkus-modules/quarkus-vs-springboot/hyperfoil/volume/cities.csv similarity index 100% rename from quarkus-modules/quarkus-vs-springboot/cities.csv rename to quarkus-modules/quarkus-vs-springboot/hyperfoil/volume/cities.csv diff --git a/quarkus-modules/quarkus-vs-springboot/jmeter/cities.csv b/quarkus-modules/quarkus-vs-springboot/jmeter/cities.csv new file mode 100644 index 0000000000..3b7016f3b5 --- /dev/null +++ b/quarkus-modules/quarkus-vs-springboot/jmeter/cities.csv @@ -0,0 +1,136 @@ +Holtsville +Adjuntas +Aguada +Aguadilla +Maricao +Anasco +Angeles +Arecibo +Bajadero +Barceloneta +Boqueron +Cabo Rojo +Penuelas +Camuy +Castaner +Rosario +Sabana Grande +Ciales +Utuado +Dorado +Ensenada +Florida +Garrochales +Guanica +Guayanilla +Hatillo +Hormigueros +Isabela +Jayuya +Lajas +Lares +Las Marias +Manati +Moca +Rincon +Quebradillas +Mayaguez +San German +San Sebastian +Morovis +Sabana Hoyos +San Antonio +Vega Alta +Vega Baja +Yauco +Aguas Buenas +Aguirre +Aibonito +Maunabo +Arroyo +Mercedita +Ponce +Naguabo +Naranjito +Orocovis +Palmer +Patillas +Caguas +Canovanas +Ceiba +Cayey +Fajardo +Cidra +Puerto Real +Punta Santiago +Roosevelt Roads +Rio Blanco +Rio Grande +Salinas +San Lorenzo +Santa Isabel +Vieques +Villalba +Yabucoa +Coamo +Las Piedras +Loiza +Luquillo +Culebra +Juncos +Gurabo +Coto Laurel +Comerio +Corozal +Guayama +La Plata +Humacao +Barranquitas +Juana Diaz +St Thomas +Christiansted +St John +Frederiksted +Kingshill +San Juan +Fort Buchanan +Toa Baja +Sabana Seca +Toa Alta +Bayamon +Catano +Guaynabo +Trujillo Alto +Saint Just +Carolina +Agawam +Amherst +Barre +Belchertown +Blandford +Bondsville +Brimfield +Chester +Chesterfield +Chicopee +Cummington +Easthampton +East Longmeadow +East Otis +Feeding Hills +Gilbertville +Goshen +Granby +Granville +Hadley +Hampden +Hardwick +Hatfield +Haydenville +Holyoke +Huntington +Leeds +Leverett +Ludlow +Monson +North Amherst \ No newline at end of file diff --git a/quarkus-modules/quarkus-vs-springboot/load_test.jmx b/quarkus-modules/quarkus-vs-springboot/jmeter/load_test.jmx similarity index 100% rename from quarkus-modules/quarkus-vs-springboot/load_test.jmx rename to quarkus-modules/quarkus-vs-springboot/jmeter/load_test.jmx diff --git a/quarkus-modules/quarkus-vs-springboot/run_test.sh b/quarkus-modules/quarkus-vs-springboot/jmeter/run_test_jmeter.sh similarity index 100% rename from quarkus-modules/quarkus-vs-springboot/run_test.sh rename to quarkus-modules/quarkus-vs-springboot/jmeter/run_test_jmeter.sh diff --git a/quarkus-modules/quarkus-vs-springboot/quarkus-project/build.sh b/quarkus-modules/quarkus-vs-springboot/quarkus-project/build.sh index 85761adab0..22b6d5c9d4 100644 --- a/quarkus-modules/quarkus-vs-springboot/quarkus-project/build.sh +++ b/quarkus-modules/quarkus-vs-springboot/quarkus-project/build.sh @@ -2,12 +2,14 @@ SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" -./mvnw quarkus:add-extension -Dextensions=container-image-docker +mvn quarkus:add-extension -Dextensions=container-image-docker if [ "$1" = "native" ]; then - ./mvnw package -Pnative -Dquarkus.native.container-build=true -f $SCRIPTPATH/pom.xml && + mvn clean package -Pnative -Dquarkus.native.container-build=true -f $SCRIPTPATH/pom.xml && docker build -f $SCRIPTPATH/src/main/docker/Dockerfile.native -t quarkus-project:0.1-SNAPSHOT $SCRIPTPATH/. +elif [ "$1" = "local-native" ]; then + mvn clean package -DskipTests -Pnative -f $SCRIPTPATH/pom.xml else - ./mvnw package -Dquarkus.container-build=true -f $SCRIPTPATH/pom.xml && + mvn clean package -Dquarkus.container-build=true -f $SCRIPTPATH/pom.xml && docker build -f $SCRIPTPATH/src/main/docker/Dockerfile.jvm -t quarkus-project:0.1-SNAPSHOT $SCRIPTPATH/. fi \ No newline at end of file diff --git a/quarkus-modules/quarkus-vs-springboot/quarkus-project/pom.xml b/quarkus-modules/quarkus-vs-springboot/quarkus-project/pom.xml index eeeb9d3256..8f28fde4a6 100644 --- a/quarkus-modules/quarkus-vs-springboot/quarkus-project/pom.xml +++ b/quarkus-modules/quarkus-vs-springboot/quarkus-project/pom.xml @@ -1,150 +1,143 @@ - - 4.0.0 - quarkus-project - 0.1-SNAPSHOT - - - com.baeldung - quarkus-vs-springboot - 1.0-SNAPSHOT - - - - - - ${quarkus.platform.group-id} - ${quarkus.platform.artifact-id} - ${quarkus.platform.version} - pom - import - - - - + 4.0.0 + + com.baeldung + quarkus-vs-springboot + 1.0-SNAPSHOT + + quarkus-project + 0.1-SNAPSHOT + + 3.10.1 + true + 11 + 11 + UTF-8 + UTF-8 + quarkus-bom + io.quarkus.platform + 2.9.2.Final + 3.0.0-M6 + + - - io.quarkus - quarkus-hibernate-reactive-panache - - - io.quarkus - quarkus-resteasy-reactive - - - io.quarkus - quarkus-resteasy-reactive-jackson - - - io.quarkus - quarkus-reactive-pg-client - - - io.quarkus - quarkus-arc - - - io.quarkus - quarkus-container-image-docker - - - io.quarkus - quarkus-junit5 - test - - - io.rest-assured - rest-assured - test - + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + - - + + + + io.quarkus + quarkus-hibernate-reactive-panache + + + io.quarkus + quarkus-resteasy-reactive + + + io.quarkus + quarkus-resteasy-reactive-jackson + + + io.quarkus + quarkus-reactive-mysql-client + + + io.quarkus + quarkus-arc + + + io.quarkus + quarkus-container-image-docker + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + + ${quarkus.platform.group-id} + quarkus-maven-plugin + ${quarkus.platform.version} + true + + + + build + generate-code + generate-code-tests + + + + + + maven-compiler-plugin + ${compiler-plugin.version} + + ${maven.compiler.parameters} + + + + maven-surefire-plugin + ${surefire-plugin.version} + + false + + org.jboss.logmanager.LogManager + + + + + + + + native + + + native + + + - - ${quarkus.platform.group-id} - quarkus-maven-plugin - ${quarkus.platform.version} - true - - - - build - generate-code - generate-code-tests - - - - - - maven-compiler-plugin - ${compiler-plugin.version} + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + - ${maven.compiler.parameters} + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + - - - maven-surefire-plugin - ${surefire-plugin.version} - - false - - org.jboss.logmanager.LogManager - - - + + + - - - - native - - - native - - - - - - maven-failsafe-plugin - ${surefire-plugin.version} - - - - integration-test - verify - - - - ${project.build.directory}/${project.build.finalName}-runner - org.jboss.logmanager.LogManager - - - - - - - - - -H:+AllowVMInspection - native - - - - - - 3.8.1 - true - 11 - 11 - UTF-8 - UTF-8 - quarkus-bom - io.quarkus.platform - 2.2.2.Final - 3.0.0-M4 - - - \ No newline at end of file + + + -H:+AllowVMInspection + native + + + + diff --git a/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/docker/Dockerfile.jvm b/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/docker/Dockerfile.jvm index e5d6d4d851..63ba538a4a 100644 --- a/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/docker/Dockerfile.jvm +++ b/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/docker/Dockerfile.jvm @@ -41,7 +41,8 @@ RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \ && echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/conf/security/java.security # Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size. -ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.port=5000 -Dcom.sun.management.jmxremote.rmi.port=5001 -Dcom.sun.management.jmxremote.host=0.0.0.0 -Djava.rmi.server.hostname=0.0.0.0" + # We make four distinct layers so if there are application changes the library layers can be re-used COPY --chown=1001 target/quarkus-app/lib/ /deployments/lib/ COPY --chown=1001 target/quarkus-app/*.jar /deployments/ diff --git a/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/docker/quarkus.yml b/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/docker/quarkus.yml index 00bdcf9292..60e35b6cca 100644 --- a/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/docker/quarkus.yml +++ b/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/docker/quarkus.yml @@ -1,23 +1,25 @@ version: '3.1' - services: db: - image: postgres + image: mysql:5.7.38 ports: - - '5432:5432' + - '3306:3306' environment: - POSTGRES_PASSWORD: example + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: baeldung + command: [ 'mysqld', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci' ] + healthcheck: + test: mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD app: image: quarkus-project:0.1-SNAPSHOT - ports: - - '8080:8080' + network_mode: "host" environment: - DB_URL: postgresql://db:5432/postgres - links: - - "db" + DB_URL: mysql://localhost:3306/baeldung?useSSL=true&requireSSL=true + HOST_HOSTNAME: ${EXTERNAL_IP} depends_on: - - "db" -networks: - default: - driver: bridge - + db: + condition: service_healthy + deploy: + resources: + limits: + cpus: '3.00' diff --git a/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/java/com/baeldung/quarkus_project/ZipCodeRepo.java b/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/java/com/baeldung/quarkus_project/ZipCodeRepo.java index 74f46c33ea..f6736a6e9e 100644 --- a/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/java/com/baeldung/quarkus_project/ZipCodeRepo.java +++ b/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/java/com/baeldung/quarkus_project/ZipCodeRepo.java @@ -1,6 +1,7 @@ package com.baeldung.quarkus_project; import io.quarkus.hibernate.reactive.panache.PanacheRepositoryBase; +import io.quarkus.hibernate.reactive.panache.common.runtime.ReactiveTransactional; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; @@ -13,7 +14,8 @@ public class ZipCodeRepo implements PanacheRepositoryBase { return find("city = ?1", city).stream(); } + @ReactiveTransactional public Uni save(ZipCode zipCode) { - return zipCode.persistAndFlush(); + return zipCode.persist(); } } diff --git a/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/java/com/baeldung/quarkus_project/ZipCodeResource.java b/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/java/com/baeldung/quarkus_project/ZipCodeResource.java index b4d41fd855..cb9b0226f3 100644 --- a/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/java/com/baeldung/quarkus_project/ZipCodeResource.java +++ b/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/java/com/baeldung/quarkus_project/ZipCodeResource.java @@ -2,9 +2,8 @@ package com.baeldung.quarkus_project; import io.smallrye.mutiny.Multi; import io.smallrye.mutiny.Uni; -import org.jboss.logging.Logger; -import javax.transaction.Transactional; +import javax.persistence.PersistenceException; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; @@ -22,7 +21,7 @@ public class ZipCodeResource { @GET @Path("/{zipcode}") public Uni findById(@PathParam("zipcode") String zipcode) { - return zipRepo.findById(zipcode); + return getById(zipcode); } @GET @@ -32,12 +31,17 @@ public class ZipCodeResource { } @POST - @Transactional public Uni create(ZipCode zipCode) { - return zipRepo.findById(zipCode.getZip()) + return getById(zipCode.getZip()) .onItem() .ifNull() - .switchTo(createZipCode(zipCode)); + .switchTo(createZipCode(zipCode)) + .onFailure(PersistenceException.class) + .recoverWithUni(() -> getById(zipCode.getZip())); + } + + private Uni getById(String zipCode) { + return zipRepo.findById(zipCode); } private Uni createZipCode(ZipCode zipCode) { diff --git a/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/resources/application.properties b/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/resources/application.properties index 918a129500..7c1bee8da5 100644 --- a/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/resources/application.properties +++ b/quarkus-modules/quarkus-vs-springboot/quarkus-project/src/main/resources/application.properties @@ -1,9 +1,12 @@ -quarkus.datasource.db-kind=postgresql -quarkus.datasource.username=postgres -quarkus.datasource.password=example +quarkus.datasource.db-kind=mysql +quarkus.datasource.username=root +quarkus.datasource.password=root -quarkus.datasource.reactive.url=${DB_URL:postgresql://localhost:5432/postgres} -quarkus.datasource.reactive.max-size=20 +quarkus.datasource.reactive.url=${DB_URL:mysql://localhost:3306/baeldung?useSSL=true&requireSSL=true} +quarkus.datasource.reactive.max-size=95 +quarkus.datasource.reactive.mysql.ssl-mode=required #quarkus.hibernate-orm.log.sql=true quarkus.hibernate-orm.database.generation=drop-and-create +quarkus.native.enable-vm-inspection=true +quarkus.datasource.reactive.trust-all=true \ No newline at end of file diff --git a/quarkus-modules/quarkus-vs-springboot/spring-project/build.sh b/quarkus-modules/quarkus-vs-springboot/spring-project/build.sh new file mode 100755 index 0000000000..d8e131d244 --- /dev/null +++ b/quarkus-modules/quarkus-vs-springboot/spring-project/build.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" + +if [ "$1" = "native" ]; then + mvn clean package -DskipTests spring-boot:build-image -Pnative -f $SCRIPTPATH/pom.xml +elif [ "$1" = "local-native" ]; then + mvn clean package -DskipTests -Plocal-native -f $SCRIPTPATH/pom.xml +else + mvn clean package -DskipTests spring-boot:build-image -f $SCRIPTPATH/pom.xml +fi \ No newline at end of file diff --git a/quarkus-modules/quarkus-vs-springboot/spring-project/build_jvm_docker.sh b/quarkus-modules/quarkus-vs-springboot/spring-project/build_jvm_docker.sh deleted file mode 100644 index c7ee730ec7..0000000000 --- a/quarkus-modules/quarkus-vs-springboot/spring-project/build_jvm_docker.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" - -docker build -f $SCRIPTPATH/src/main/docker/Dockerfile.jvm -t spring-project:0.1-SNAPSHOT $SCRIPTPATH/. - diff --git a/quarkus-modules/quarkus-vs-springboot/spring-project/pom.xml b/quarkus-modules/quarkus-vs-springboot/spring-project/pom.xml index 7f0fa4c8c6..408c223e9f 100644 --- a/quarkus-modules/quarkus-vs-springboot/spring-project/pom.xml +++ b/quarkus-modules/quarkus-vs-springboot/spring-project/pom.xml @@ -10,8 +10,8 @@ org.springframework.boot spring-boot-starter-parent - 2.6.0 - + 2.6.9 + @@ -29,14 +29,9 @@ ${spring-native.version} - io.r2dbc - r2dbc-postgresql - runtime - - - org.postgresql - postgresql - runtime + com.github.jasync-sql + jasync-r2dbc-mysql + 2.0.8 org.springframework.boot @@ -48,131 +43,210 @@ reactor-test test + + + org.testcontainers + testcontainers + test + + + + org.testcontainers + r2dbc + test + + + + org.testcontainers + mysql + test + + + + mysql + mysql-connector-java + test + + + + org.testcontainers + junit-jupiter + test + + + + + + org.testcontainers + testcontainers-bom + 1.17.2 + pom + import + + + + org.springframework.boot spring-boot-maven-plugin - ${repackage.classifier} + exec + + true + paketobuildpacks/builder:tiny - true + false + true - org.springframework.experimental - spring-aot-maven-plugin - ${spring-native.version} - - - test-generate - - test-generate - - - - generate - - generate - - - + maven-surefire-plugin + ${surefire-plugin.version} + + + **/*IT + + - spring-releases - Spring Releases + spring-release + Spring release https://repo.spring.io/release - - false - - - - - spring-milestones - Spring Milestones - https://repo.spring.io/libs-milestone-local - - false - - spring-releases - Spring Releases + spring-release + Spring release https://repo.spring.io/release - - false - - - - - spring-milestones - Spring Milestones - https://repo.spring.io/libs-milestone-local - - false - native - - exec - 0.9.3 - - org.graalvm.buildtools - junit-platform-native - ${native-buildtools.version} + org.junit.platform + junit-platform-launcher + test + + org.springframework.boot + spring-boot-maven-plugin + + + true + + + paketobuildpacks/builder:tiny + + true + true + + + + + + org.springframework.experimental + spring-aot-maven-plugin + + + test-generate + + test-generate + + + + generate + + generate + + + + + + + + + local-native + + exec + 0.9.11 + + + + org.junit.platform + junit-platform-launcher + test + + + + + + org.springframework.experimental + spring-aot-maven-plugin + + + test-generate + + test-generate + + + + generate + + generate + + + + org.graalvm.buildtools native-maven-plugin ${native-buildtools.version} + true -H:+AllowVMInspection - - test-native - test - - test - - build-native - package build + package + + + test-native + + test + + test org.apache.maven.plugins maven-surefire-plugin + 3.0.0-M6 -DspringAot=true -agentlib:native-image-agent=access-filter-file=src/test/resources/access-filter.json,config-merge-dir=target/classes/META-INF/native-image @@ -185,9 +259,8 @@ 11 - - 0.11.0-RC1 - 2.17.1 + 0.12.1 + 3.0.0-M6 - \ No newline at end of file + diff --git a/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/docker/Dockerfile.jvm b/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/docker/Dockerfile.jvm deleted file mode 100644 index ca3f3cca76..0000000000 --- a/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/docker/Dockerfile.jvm +++ /dev/null @@ -1,12 +0,0 @@ -FROM openjdk:11 - -ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' - -COPY --chown=1001 target/spring-project-0.1-SNAPSHOT-exec.jar /spring-app/ - -WORKDIR /spring-app - -EXPOSE 8080 -USER 1001 - -ENTRYPOINT ["java", "-jar", "spring-project-0.1-SNAPSHOT-exec.jar" ] \ No newline at end of file diff --git a/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/docker/spring.yml b/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/docker/spring.yml index 2214e0a898..347b5dfe2f 100644 --- a/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/docker/spring.yml +++ b/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/docker/spring.yml @@ -2,21 +2,25 @@ version: '3.1' services: db: - image: postgres + image: mysql:5.7.38 ports: - - '5432:5432' + - '3306:3306' environment: - POSTGRES_PASSWORD: example + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: baeldung + command: [ 'mysqld', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci' ] + healthcheck: + test: mysqladmin ping -h 127.0.0.1 -u $$MYSQL_USER --password=$$MYSQL_PASSWORD app: - image: spring-project:0.1-SNAPSHOT - ports: - - '8080:8080' + image: docker.io/library/spring-project:0.1-SNAPSHOT + network_mode: "host" environment: - DB_URL: r2dbc:postgresql://db:5432/postgres - links: - - "db" + DB_URL: r2dbc:mysql://localhost:3306/baeldung?useSSL=true&requireSSL=true + HOST_HOSTNAME: ${EXTERNAL_IP} depends_on: - - "db" -networks: - default: - driver: bridge \ No newline at end of file + db: + condition: service_healthy + deploy: + resources: + limits: + cpus: '3.00' diff --git a/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/java/com/baeldung/spring_project/Startup.java b/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/java/com/baeldung/spring_project/Startup.java index 48cf7e8ed1..e8544da8db 100644 --- a/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/java/com/baeldung/spring_project/Startup.java +++ b/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/java/com/baeldung/spring_project/Startup.java @@ -1,21 +1,22 @@ package com.baeldung.spring_project; -import com.baeldung.spring_project.domain.ZIPRepo; import io.r2dbc.spi.ConnectionFactory; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.core.io.ByteArrayResource; +import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories; import org.springframework.r2dbc.connection.R2dbcTransactionManager; import org.springframework.r2dbc.connection.init.ConnectionFactoryInitializer; import org.springframework.r2dbc.connection.init.ResourceDatabasePopulator; import org.springframework.transaction.ReactiveTransactionManager; @SpringBootApplication +@EnableR2dbcRepositories public class Startup { public static void main(String[] args) { - SpringApplication.run(Startup.class, args).getBean(ZIPRepo.class).findById(""); + SpringApplication.run(Startup.class, args); } @Bean @@ -34,4 +35,5 @@ public class Startup { @Bean ReactiveTransactionManager transactionManager(ConnectionFactory connectionFactory) { return new R2dbcTransactionManager(connectionFactory); } + } diff --git a/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/java/com/baeldung/spring_project/ZipCodeApi.java b/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/java/com/baeldung/spring_project/ZipCodeApi.java index 263ce67e21..8d1f07b7b9 100644 --- a/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/java/com/baeldung/spring_project/ZipCodeApi.java +++ b/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/java/com/baeldung/spring_project/ZipCodeApi.java @@ -2,11 +2,14 @@ package com.baeldung.spring_project; import com.baeldung.spring_project.domain.ZIPRepo; import com.baeldung.spring_project.domain.ZipCode; -import org.springframework.transaction.annotation.Transactional; +import org.springframework.dao.DataAccessResourceFailureException; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.r2dbc.UncategorizedR2dbcException; import org.springframework.web.bind.annotation.*; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import java.util.function.Function; import java.util.function.Supplier; @RestController @@ -21,7 +24,7 @@ public class ZipCodeApi { @GetMapping("/{zipcode}") public Mono findById(@PathVariable String zipcode) { - return zipRepo.findById(zipcode); + return getById(zipcode); } @GetMapping("/by_city") @@ -29,10 +32,23 @@ public class ZipCodeApi { return zipRepo.findByCity(city); } - @Transactional @PostMapping public Mono create(@RequestBody ZipCode zipCode) { - return zipRepo.findById(zipCode.getZip()).switchIfEmpty(Mono.defer(createZipCode(zipCode))); + return getById(zipCode.getZip()) + .switchIfEmpty(Mono.defer(createZipCode(zipCode))) + .onErrorResume(this::isKeyDuplicated, this.recoverWith(zipCode)); + } + + private Mono getById(String zipCode) { + return zipRepo.findById(zipCode); + } + + private boolean isKeyDuplicated(Throwable ex) { + return ex instanceof DataIntegrityViolationException || ex instanceof UncategorizedR2dbcException; + } + + private Function> recoverWith(ZipCode zipCode) { + return throwable -> zipRepo.findById(zipCode.getZip()); } private Supplier> createZipCode(ZipCode zipCode) { diff --git a/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/resources/application.properties b/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/resources/application.properties index 1d49b67fda..e303baf6f6 100644 --- a/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/resources/application.properties +++ b/quarkus-modules/quarkus-vs-springboot/spring-project/src/main/resources/application.properties @@ -1,5 +1,7 @@ -spring.r2dbc.url=${DB_URL:r2dbc:postgresql://localhost:5432/postgres} -spring.r2dbc.username=postgres -spring.r2dbc.password=example -spring.r2dbc.pool.enabled=true -spring.r2dbc.pool.maxSize=20 \ No newline at end of file +spring.r2dbc.url=${DB_URL:r2dbc:mysql://localhost:3306/baeldung?useSSL=true&requireSSL=true} +spring.r2dbc.properties.sslMode=required +spring.r2dbc.username=root +spring.r2dbc.password=root +spring.r2dbc.pool.enabled=true +spring.r2dbc.pool.maxSize=95 + diff --git a/quarkus-modules/quarkus-vs-springboot/spring-project/src/test/java/com/baeldung/spring_project/StartupIT.java b/quarkus-modules/quarkus-vs-springboot/spring-project/src/test/java/com/baeldung/spring_project/StartupIT.java index 7487e5aa7f..7715fdc1d2 100644 --- a/quarkus-modules/quarkus-vs-springboot/spring-project/src/test/java/com/baeldung/spring_project/StartupIT.java +++ b/quarkus-modules/quarkus-vs-springboot/spring-project/src/test/java/com/baeldung/spring_project/StartupIT.java @@ -1,9 +1,20 @@ package com.baeldung.spring_project; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; import org.springframework.boot.test.context.SpringBootTest; +import org.testcontainers.junit.jupiter.Testcontainers; -@SpringBootTest +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; + +@SpringBootTest( + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, + properties = { "spring.r2dbc.url=r2dbc:tc:mysql:///baeldung?TC_IMAGE_TAG=5.7.34"} +) +@TestInstance(value = PER_CLASS) +@Testcontainers +@Disabled class StartupIT { @Test diff --git a/quarkus-modules/quarkus-vs-springboot/wrk/cities.csv b/quarkus-modules/quarkus-vs-springboot/wrk/cities.csv new file mode 100644 index 0000000000..3b7016f3b5 --- /dev/null +++ b/quarkus-modules/quarkus-vs-springboot/wrk/cities.csv @@ -0,0 +1,136 @@ +Holtsville +Adjuntas +Aguada +Aguadilla +Maricao +Anasco +Angeles +Arecibo +Bajadero +Barceloneta +Boqueron +Cabo Rojo +Penuelas +Camuy +Castaner +Rosario +Sabana Grande +Ciales +Utuado +Dorado +Ensenada +Florida +Garrochales +Guanica +Guayanilla +Hatillo +Hormigueros +Isabela +Jayuya +Lajas +Lares +Las Marias +Manati +Moca +Rincon +Quebradillas +Mayaguez +San German +San Sebastian +Morovis +Sabana Hoyos +San Antonio +Vega Alta +Vega Baja +Yauco +Aguas Buenas +Aguirre +Aibonito +Maunabo +Arroyo +Mercedita +Ponce +Naguabo +Naranjito +Orocovis +Palmer +Patillas +Caguas +Canovanas +Ceiba +Cayey +Fajardo +Cidra +Puerto Real +Punta Santiago +Roosevelt Roads +Rio Blanco +Rio Grande +Salinas +San Lorenzo +Santa Isabel +Vieques +Villalba +Yabucoa +Coamo +Las Piedras +Loiza +Luquillo +Culebra +Juncos +Gurabo +Coto Laurel +Comerio +Corozal +Guayama +La Plata +Humacao +Barranquitas +Juana Diaz +St Thomas +Christiansted +St John +Frederiksted +Kingshill +San Juan +Fort Buchanan +Toa Baja +Sabana Seca +Toa Alta +Bayamon +Catano +Guaynabo +Trujillo Alto +Saint Just +Carolina +Agawam +Amherst +Barre +Belchertown +Blandford +Bondsville +Brimfield +Chester +Chesterfield +Chicopee +Cummington +Easthampton +East Longmeadow +East Otis +Feeding Hills +Gilbertville +Goshen +Granby +Granville +Hadley +Hampden +Hardwick +Hatfield +Haydenville +Holyoke +Huntington +Leeds +Leverett +Ludlow +Monson +North Amherst \ No newline at end of file diff --git a/quarkus-modules/quarkus-vs-springboot/wrk/generator.lua b/quarkus-modules/quarkus-vs-springboot/wrk/generator.lua new file mode 100644 index 0000000000..a1973072d9 --- /dev/null +++ b/quarkus-modules/quarkus-vs-springboot/wrk/generator.lua @@ -0,0 +1,65 @@ +local require = require +local json = require "json" + +math.randomseed(os.time()) + +-- read csv lines +function ParseCSVLine(line,sep) + local res = {} + local pos = 1 + sep = sep or ',' + while true do + local c = string.sub(line,pos,pos) + if (c == "") then break end + if (c == '"') then + local txt = "" + repeat + local startp,endp = string.find(line,'^%b""',pos) + txt = txt..string.sub(line,startp+1,endp-1) + pos = endp + 1 + c = string.sub(line,pos,pos) + if (c == '"') then txt = txt..'"' end + until (c ~= '"') + table.insert(res,txt) + assert(c == sep or c == "") + pos = pos + 1 + else + local startp,endp = string.find(line,sep,pos) + if (startp) then + table.insert(res,string.sub(line,pos,startp-1)) + pos = endp + 1 + else + table.insert(res,string.sub(line,pos)) + break + end + end + end + return res +end + +loadFile = function() + local filename = "zip_code_database.csv" + + local data = {} + local count = 0 + local sep = "," + + for line in io.lines(filename) do + local values = ParseCSVLine(line,sep) + data[count + 1] = { zip=values[1], type=values[2], city=values[4], state=values[7], county=values[8], timezone=values[9] } + count = count + 1 + end + + return data +end + +generator = function() + local data = loadFile() + return coroutine.create(function() + for k,v in pairs(data) do + coroutine.yield(json.stringify(v)) + end + end) +end + +return generator() \ No newline at end of file diff --git a/quarkus-modules/quarkus-vs-springboot/wrk/get_by_city.lua b/quarkus-modules/quarkus-vs-springboot/wrk/get_by_city.lua new file mode 100644 index 0000000000..b101e3bbda --- /dev/null +++ b/quarkus-modules/quarkus-vs-springboot/wrk/get_by_city.lua @@ -0,0 +1,79 @@ +local require = require +local json = require "json" + +math.randomseed(os.clock()*100000000000) + +function ParseCSVLine(line,sep) + local res = {} + local pos = 1 + sep = sep or ',' + while true do + local c = string.sub(line,pos,pos) + if (c == "") then break end + if (c == '"') then + local txt = "" + repeat + local startp,endp = string.find(line,'^%b""',pos) + txt = txt..string.sub(line,startp+1,endp-1) + pos = endp + 1 + c = string.sub(line,pos,pos) + if (c == '"') then txt = txt..'"' end + until (c ~= '"') + table.insert(res,txt) + assert(c == sep or c == "") + pos = pos + 1 + else + local startp,endp = string.find(line,sep,pos) + if (startp) then + table.insert(res,string.sub(line,pos,startp-1)) + pos = endp + 1 + else + table.insert(res,string.sub(line,pos)) + break + end + end + end + return res +end + +loadFile = function() + local filename = "cities.csv" + + local data = {} + local count = 0 + local sep = "," + + for line in io.lines(filename) do + local values = ParseCSVLine(line,sep) + data[count + 1] = values[1] + count = count + 1 + end + + return data +end + +local data = loadFile() + +local urlencode = function (str) + str = string.gsub (str, "([^0-9a-zA-Z !'()*._~-])", -- locale independent + function (c) return string.format ("%%%02X", string.byte(c)) end) + str = string.gsub (str, " ", "+") + return str +end + +request = function() + url_path = "/zipcode/by_city?city=" .. urlencode(data[math.random(1, 136)]) + + local headers = { ["Content-Type"] = "application/json;charset=UTF-8" } + + return wrk.format("GET", url_path, headers, nil) +end + +done = function(summary, latency, requests) + io.write("--------------GET CITY ZIPCODES----------------\n") + for _, p in pairs({ 50, 90, 99, 99.999 }) do + n = latency:percentile(p) + io.write(string.format("%g%%,%d\n", p, n)) + end + io.write("-----------------------------------------------\n\n") +end \ No newline at end of file diff --git a/quarkus-modules/quarkus-vs-springboot/wrk/get_zipcode.lua b/quarkus-modules/quarkus-vs-springboot/wrk/get_zipcode.lua new file mode 100644 index 0000000000..f2abf607c2 --- /dev/null +++ b/quarkus-modules/quarkus-vs-springboot/wrk/get_zipcode.lua @@ -0,0 +1,77 @@ +local require = require +local json = require "json" + +math.randomseed(os.clock()*100000000000) + +function ParseCSVLine(line,sep) + local res = {} + local pos = 1 + sep = sep or ',' + while true do + local c = string.sub(line,pos,pos) + if (c == "") then break end + if (c == '"') then + local txt = "" + repeat + local startp,endp = string.find(line,'^%b""',pos) + txt = txt..string.sub(line,startp+1,endp-1) + pos = endp + 1 + c = string.sub(line,pos,pos) + if (c == '"') then txt = txt..'"' end + + until (c ~= '"') + table.insert(res,txt) + assert(c == sep or c == "") + pos = pos + 1 + else + local startp,endp = string.find(line,sep,pos) + if (startp) then + table.insert(res,string.sub(line,pos,startp-1)) + pos = endp + 1 + else + table.insert(res,string.sub(line,pos)) + break + end + end + end + return res +end + +loadFile = function() + local filename = "zip_code_database.csv" + + local data = {} + local count = 0 + local sep = "," + + for line in io.lines(filename) do + local values = ParseCSVLine(line,sep) + data[count + 1] = values[1] + count = count + 1 + end + + return data +end + +local data = loadFile() + +request = function() + + local value = data[math.random(1, 12079)] + + url_path = "/zipcode/" .. value + + local headers = { ["Content-Type"] = "application/json;charset=UTF-8" } + + return wrk.format("GET", url_path, headers, nil) +end + + +done = function(summary, latency, requests) + io.write("--------------GET ZIPCODE----------------\n") + for _, p in pairs({ 50, 90, 99, 99.999 }) do + n = latency:percentile(p) + io.write(string.format("%g%%,%d\n", p, n)) + end + io.write("-----------------------------------------\n\n") +end diff --git a/quarkus-modules/quarkus-vs-springboot/wrk/json.lua b/quarkus-modules/quarkus-vs-springboot/wrk/json.lua new file mode 100644 index 0000000000..6f023a9294 --- /dev/null +++ b/quarkus-modules/quarkus-vs-springboot/wrk/json.lua @@ -0,0 +1,133 @@ +local json = {} + +local function kind_of(obj) + if type(obj) ~= 'table' then return type(obj) end + local i = 1 + for _ in pairs(obj) do + if obj[i] ~= nil then i = i + 1 else return 'table' end + end + if i == 1 then return 'table' else return 'array' end +end + +local function escape_str(s) + local in_char = {'\\', '"', '/', '\b', '\f', '\n', '\r', '\t'} + local out_char = {'\\', '"', '/', 'b', 'f', 'n', 'r', 't'} + for i, c in ipairs(in_char) do + s = s:gsub(c, '\\' .. out_char[i]) + end + return s +end + +local function skip_delim(str, pos, delim, err_if_missing) + pos = pos + #str:match('^%s*', pos) + if str:sub(pos, pos) ~= delim then + if err_if_missing then + error('Expected ' .. delim .. ' near position ' .. pos) + end + return pos, false + end + return pos + 1, true +end + +local function parse_str_val(str, pos, val) + val = val or '' + local early_end_error = 'End of input found while parsing string.' + if pos > #str then error(early_end_error) end + local c = str:sub(pos, pos) + if c == '"' then return val, pos + 1 end + if c ~= '\\' then return parse_str_val(str, pos + 1, val .. c) end + local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'} + local nextc = str:sub(pos + 1, pos + 1) + if not nextc then error(early_end_error) end + return parse_str_val(str, pos + 2, val .. (esc_map[nextc] or nextc)) +end + +local function parse_num_val(str, pos) + local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos) + local val = tonumber(num_str) + if not val then error('Error parsing number at position ' .. pos .. '.') end + return val, pos + #num_str +end + +function json.stringify(obj, as_key) + local s = {} + local kind = kind_of(obj) + if kind == 'array' then + if as_key then error('Can\'t encode array as key.') end + s[#s + 1] = '[' + for i, val in ipairs(obj) do + if i > 1 then s[#s + 1] = ', ' end + s[#s + 1] = json.stringify(val) + end + s[#s + 1] = ']' + elseif kind == 'table' then + if as_key then error('Can\'t encode table as key.') end + s[#s + 1] = '{' + for k, v in pairs(obj) do + if #s > 1 then s[#s + 1] = ', ' end + s[#s + 1] = json.stringify(k, true) + s[#s + 1] = ':' + s[#s + 1] = json.stringify(v) + end + s[#s + 1] = '}' + elseif kind == 'string' then + return '"' .. escape_str(obj) .. '"' + elseif kind == 'number' then + if as_key then return '"' .. tostring(obj) .. '"' end + return tostring(obj) + elseif kind == 'boolean' then + return tostring(obj) + elseif kind == 'nil' then + return 'null' + else + error('Unjsonifiable type: ' .. kind .. '.') + end + return table.concat(s) +end + +json.null = {} + +function json.parse(str, pos, end_delim) + pos = pos or 1 + if pos > #str then error('Reached unexpected end of input.') end + local pos = pos + #str:match('^%s*', pos) + local first = str:sub(pos, pos) + if first == '{' then + local obj, key, delim_found = {}, true, true + pos = pos + 1 + while true do + key, pos = json.parse(str, pos, '}') + if key == nil then return obj, pos end + if not delim_found then error('Comma missing between object items.') end + pos = skip_delim(str, pos, ':', true) + obj[key], pos = json.parse(str, pos) + pos, delim_found = skip_delim(str, pos, ',') + end + elseif first == '[' then + local arr, val, delim_found = {}, true, true + pos = pos + 1 + while true do + val, pos = json.parse(str, pos, ']') + if val == nil then return arr, pos end + if not delim_found then error('Comma missing between array items.') end + arr[#arr + 1] = val + pos, delim_found = skip_delim(str, pos, ',') + end + elseif first == '"' then + return parse_str_val(str, pos + 1) + elseif first == '-' or first:match('%d') then + return parse_num_val(str, pos) + elseif first == end_delim then + return nil, pos + 1 + else + local literals = {['true'] = true, ['false'] = false, ['null'] = json.null} + for lit_str, lit_val in pairs(literals) do + local lit_end = pos + #lit_str - 1 + if str:sub(pos, lit_end) == lit_str then return lit_val, lit_end + 1 end + end + local pos_info_str = 'position ' .. pos .. ': ' .. str:sub(pos, pos + 10) + error('Invalid json syntax starting at ' .. pos_info_str) + end +end + +return json diff --git a/quarkus-modules/quarkus-vs-springboot/wrk/post_zipcode.lua b/quarkus-modules/quarkus-vs-springboot/wrk/post_zipcode.lua new file mode 100644 index 0000000000..b8e60da015 --- /dev/null +++ b/quarkus-modules/quarkus-vs-springboot/wrk/post_zipcode.lua @@ -0,0 +1,73 @@ +local require = require +local json = require "json" + +math.randomseed(os.clock()*100000000000) + +function ParseCSVLine(line,sep) + local res = {} + local pos = 1 + sep = sep or ',' + while true do + local c = string.sub(line,pos,pos) + if (c == "") then break end + if (c == '"') then + local txt = "" + repeat + local startp,endp = string.find(line,'^%b""',pos) + txt = txt..string.sub(line,startp+1,endp-1) + pos = endp + 1 + c = string.sub(line,pos,pos) + if (c == '"') then txt = txt..'"' end + + until (c ~= '"') + table.insert(res,txt) + assert(c == sep or c == "") + pos = pos + 1 + else + local startp,endp = string.find(line,sep,pos) + if (startp) then + table.insert(res,string.sub(line,pos,startp-1)) + pos = endp + 1 + else + table.insert(res,string.sub(line,pos)) + break + end + end + end + return res +end + +loadFile = function() + local filename = "zip_code_database.csv" + + local data = {} + local count = 0 + local sep = "," + + for line in io.lines(filename) do + local values = ParseCSVLine(line,sep) + data[count + 1] = { zip=values[1], type=values[2], city=values[4], state=values[7], county=values[8], timezone=values[9] } + count = count + 1 + end + + return data +end + +local data = loadFile() + +request = function() + local url_path = "/zipcode" + local val = data[math.random(1, 12079)] + + local headers = { ["Content-Type"] = "application/json;charset=UTF-8" } + return wrk.format("POST", url_path, headers, json.stringify(val)) +end + +done = function(summary, latency, requests) + io.write("--------------POST ZIPCODE----------------\n") + for _, p in pairs({ 50, 75, 90, 99, 99.999 }) do + n = latency:percentile(p) + io.write(string.format("%g%%,%d\n", p, n)) + end + io.write("------------------------------------------\n\n") +end \ No newline at end of file diff --git a/quarkus-modules/quarkus-vs-springboot/wrk/run_test_wrk.sh b/quarkus-modules/quarkus-vs-springboot/wrk/run_test_wrk.sh new file mode 100755 index 0000000000..0d565d0688 --- /dev/null +++ b/quarkus-modules/quarkus-vs-springboot/wrk/run_test_wrk.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +$wrk_home/wrk -t1 -c5 -d1m -s ./post_zipcode.lua --timeout 2m -H 'Host: localhost' http://localhost:8080 & sleep 60 + +$wrk_home/wrk -t1 -c20 -d5m -s ./post_zipcode.lua --timeout 2m -H 'Host: localhost' http://localhost:8080 & sleep 60 + +$wrk_home/wrk -t1 -c20 -d5m -s ./get_by_city.lua --timeout 2m -H 'Host: localhost' http://localhost:8080 \ & +$wrk_home/wrk -t1 -c20 -d5m -s ./get_zipcode.lua --timeout 2m -H 'Host: localhost' http://localhost:8080 \ & sleep 120 + +$wrk_home/wrk -t2 -c10 -d3m -s ./get_by_city.lua --timeout 2m -H 'Host: localhost' http://localhost:8080 \ & +$wrk_home/wrk -t2 -c10 -d3m -s ./get_zipcode.lua --timeout 2m -H 'Host: localhost' http://localhost:8080 \ & + +wait +