[BAEL-5631] Quarkus vs Spring boot improvements (#12520)

* Initial impl

* Update framework versions testing from jm to wrk

* Add hyperfoil

* Add hyperfoil read me
This commit is contained in:
Thiago dos Santos Hora 2022-07-30 14:26:16 +02:00 committed by GitHub
parent d0ba47e75d
commit a313c855d3
30 changed files with 1258 additions and 302 deletions

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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
1 Holtsville
2 Adjuntas
3 Aguada
4 Aguadilla
5 Maricao
6 Anasco
7 Angeles
8 Arecibo
9 Bajadero
10 Barceloneta
11 Boqueron
12 Cabo Rojo
13 Penuelas
14 Camuy
15 Castaner
16 Rosario
17 Sabana Grande
18 Ciales
19 Utuado
20 Dorado
21 Ensenada
22 Florida
23 Garrochales
24 Guanica
25 Guayanilla
26 Hatillo
27 Hormigueros
28 Isabela
29 Jayuya
30 Lajas
31 Lares
32 Las Marias
33 Manati
34 Moca
35 Rincon
36 Quebradillas
37 Mayaguez
38 San German
39 San Sebastian
40 Morovis
41 Sabana Hoyos
42 San Antonio
43 Vega Alta
44 Vega Baja
45 Yauco
46 Aguas Buenas
47 Aguirre
48 Aibonito
49 Maunabo
50 Arroyo
51 Mercedita
52 Ponce
53 Naguabo
54 Naranjito
55 Orocovis
56 Palmer
57 Patillas
58 Caguas
59 Canovanas
60 Ceiba
61 Cayey
62 Fajardo
63 Cidra
64 Puerto Real
65 Punta Santiago
66 Roosevelt Roads
67 Rio Blanco
68 Rio Grande
69 Salinas
70 San Lorenzo
71 Santa Isabel
72 Vieques
73 Villalba
74 Yabucoa
75 Coamo
76 Las Piedras
77 Loiza
78 Luquillo
79 Culebra
80 Juncos
81 Gurabo
82 Coto Laurel
83 Comerio
84 Corozal
85 Guayama
86 La Plata
87 Humacao
88 Barranquitas
89 Juana Diaz
90 St Thomas
91 Christiansted
92 St John
93 Frederiksted
94 Kingshill
95 San Juan
96 Fort Buchanan
97 Toa Baja
98 Sabana Seca
99 Toa Alta
100 Bayamon
101 Catano
102 Guaynabo
103 Trujillo Alto
104 Saint Just
105 Carolina
106 Agawam
107 Amherst
108 Barre
109 Belchertown
110 Blandford
111 Bondsville
112 Brimfield
113 Chester
114 Chesterfield
115 Chicopee
116 Cummington
117 Easthampton
118 East Longmeadow
119 East Otis
120 Feeding Hills
121 Gilbertville
122 Goshen
123 Granby
124 Granville
125 Hadley
126 Hampden
127 Hardwick
128 Hatfield
129 Haydenville
130 Holyoke
131 Huntington
132 Leeds
133 Leverett
134 Ludlow
135 Monson
136 North Amherst

View File

@ -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

View File

@ -1,150 +1,143 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<artifactId>quarkus-project</artifactId>
<version>0.1-SNAPSHOT</version>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>quarkus-vs-springboot</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>${quarkus.platform.artifact-id}</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.baeldung</groupId>
<artifactId>quarkus-vs-springboot</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>quarkus-project</artifactId>
<version>0.1-SNAPSHOT</version>
<properties>
<compiler-plugin.version>3.10.1</compiler-plugin.version>
<maven.compiler.parameters>true</maven.compiler.parameters>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>2.9.2.Final</quarkus.platform.version>
<surefire-plugin.version>3.0.0-M6</surefire-plugin.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-reactive-panache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-pg-client</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-container-image-docker</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>${quarkus.platform.artifact-id}</artifactId>
<version>${quarkus.platform.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
<build>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-reactive-panache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-mysql-client</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-container-image-docker</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version>
<configuration>
<parameters>${maven.compiler.parameters}</parameters>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<useSystemClassLoader>false</useSystemClassLoader>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<parameters>${maven.compiler.parameters}</parameters>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<useSystemClassLoader>false</useSystemClassLoader>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
</systemPropertyVariables>
</configuration>
</plugin>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<quarkus.native.additional-build-args>-H:+AllowVMInspection</quarkus.native.additional-build-args>
<quarkus.package.type>native</quarkus.package.type>
</properties>
</profile>
</profiles>
<properties>
<compiler-plugin.version>3.8.1</compiler-plugin.version>
<maven.compiler.parameters>true</maven.compiler.parameters>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>2.2.2.Final</quarkus.platform.version>
<surefire-plugin.version>3.0.0-M4</surefire-plugin.version>
</properties>
</project>
</build>
<properties>
<quarkus.native.additional-build-args>-H:+AllowVMInspection</quarkus.native.additional-build-args>
<quarkus.package.type>native</quarkus.package.type>
</properties>
</profile>
</profiles>
</project>

View File

@ -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/

View File

@ -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'

View File

@ -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<ZipCode, String> {
return find("city = ?1", city).stream();
}
@ReactiveTransactional
public Uni<ZipCode> save(ZipCode zipCode) {
return zipCode.persistAndFlush();
return zipCode.persist();
}
}

View File

@ -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<ZipCode> findById(@PathParam("zipcode") String zipcode) {
return zipRepo.findById(zipcode);
return getById(zipcode);
}
@GET
@ -32,12 +31,17 @@ public class ZipCodeResource {
}
@POST
@Transactional
public Uni<ZipCode> 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<ZipCode> getById(String zipCode) {
return zipRepo.findById(zipCode);
}
private Uni<ZipCode> createZipCode(ZipCode zipCode) {

View File

@ -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

View File

@ -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

View File

@ -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/.

View File

@ -10,8 +10,8 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.0</version>
<relativePath />
<version>2.6.9</version>
<relativePath/>
</parent>
<dependencies>
@ -29,14 +29,9 @@
<version>${spring-native.version}</version>
</dependency>
<dependency>
<groupId>io.r2dbc</groupId>
<artifactId>r2dbc-postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
<groupId>com.github.jasync-sql</groupId>
<artifactId>jasync-r2dbc-mysql</artifactId>
<version>2.0.8</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -48,131 +43,210 @@
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>r2dbc</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mysql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-bom</artifactId>
<version>1.17.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>${repackage.classifier}</classifier>
<classifier>exec</classifier>
<layers>
<enabled>true</enabled>
</layers>
<image>
<builder>paketobuildpacks/builder:tiny</builder>
<env>
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
<BP_NATIVE_IMAGE>false</BP_NATIVE_IMAGE>
<BPL_JFR_ENABLED>true</BPL_JFR_ENABLED>
</env>
</image>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<version>${spring-native.version}</version>
<executions>
<execution>
<id>test-generate</id>
<goals>
<goal>test-generate</goal>
</goals>
</execution>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<includes>
<include>**/*IT</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-releases</id>
<name>Spring Releases</name>
<id>spring-release</id>
<name>Spring release</name>
<url>https://repo.spring.io/release</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<!-- This can be removed once a stable version of org.springframework.experimental:spring-aot-maven-plugin,
compatible with latest JDK, is released. -->
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-releases</id>
<name>Spring Releases</name>
<id>spring-release</id>
<name>Spring release</name>
<url>https://repo.spring.io/release</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
<!-- This can be removed once a stable version of org.springframework.experimental:spring-aot-maven-plugin,
compatible with latest JDK, is released. -->
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/libs-milestone-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
<profiles>
<profile>
<id>native</id>
<properties>
<repackage.classifier>exec</repackage.classifier>
<native-buildtools.version>0.9.3</native-buildtools.version>
</properties>
<dependencies>
<dependency>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>junit-platform-native</artifactId>
<version>${native-buildtools.version}</version>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layers>
<enabled>true</enabled>
</layers>
<image>
<builder>paketobuildpacks/builder:tiny</builder>
<env>
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
<BPL_JFR_ENABLED>true</BPL_JFR_ENABLED>
</env>
</image>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<executions>
<execution>
<id>test-generate</id>
<goals>
<goal>test-generate</goal>
</goals>
</execution>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>local-native</id>
<properties>
<repackage.classifier>exec</repackage.classifier>
<native-buildtools.version>0.9.11</native-buildtools.version>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<executions>
<execution>
<id>test-generate</id>
<goals>
<goal>test-generate</goal>
</goals>
</execution>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.graalvm.buildtools</groupId>
<artifactId>native-maven-plugin</artifactId>
<version>${native-buildtools.version}</version>
<extensions>true</extensions>
<configuration>
<buildArgs combine.children="append">
<buildArgs>-H:+AllowVMInspection</buildArgs>
</buildArgs>
</configuration>
<executions>
<execution>
<id>test-native</id>
<phase>test</phase>
<goals>
<goal>test</goal>
</goals>
</execution>
<execution>
<id>build-native</id>
<phase>package</phase>
<goals>
<goal>build</goal>
</goals>
<phase>package</phase>
</execution>
<execution>
<id>test-native</id>
<goals>
<goal>test</goal>
</goals>
<phase>test</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M6</version>
<configuration>
<argLine>-DspringAot=true
-agentlib:native-image-agent=access-filter-file=src/test/resources/access-filter.json,config-merge-dir=target/classes/META-INF/native-image</argLine>
@ -185,9 +259,8 @@
<properties>
<java.version>11</java.version>
<repackage.classifier />
<spring-native.version>0.11.0-RC1</spring-native.version>
<log4j2.version>2.17.1</log4j2.version>
<spring-native.version>0.12.1</spring-native.version>
<surefire-plugin.version>3.0.0-M6</surefire-plugin.version>
</properties>
</project>
</project>

View File

@ -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" ]

View File

@ -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
db:
condition: service_healthy
deploy:
resources:
limits:
cpus: '3.00'

View File

@ -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);
}
}

View File

@ -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<ZipCode> 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<ZipCode> 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<ZipCode> getById(String zipCode) {
return zipRepo.findById(zipCode);
}
private boolean isKeyDuplicated(Throwable ex) {
return ex instanceof DataIntegrityViolationException || ex instanceof UncategorizedR2dbcException;
}
private Function<? super Throwable, ? extends Mono<ZipCode>> recoverWith(ZipCode zipCode) {
return throwable -> zipRepo.findById(zipCode.getZip());
}
private Supplier<Mono<? extends ZipCode>> createZipCode(ZipCode zipCode) {

View File

@ -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
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

View File

@ -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

View File

@ -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
1 Holtsville
2 Adjuntas
3 Aguada
4 Aguadilla
5 Maricao
6 Anasco
7 Angeles
8 Arecibo
9 Bajadero
10 Barceloneta
11 Boqueron
12 Cabo Rojo
13 Penuelas
14 Camuy
15 Castaner
16 Rosario
17 Sabana Grande
18 Ciales
19 Utuado
20 Dorado
21 Ensenada
22 Florida
23 Garrochales
24 Guanica
25 Guayanilla
26 Hatillo
27 Hormigueros
28 Isabela
29 Jayuya
30 Lajas
31 Lares
32 Las Marias
33 Manati
34 Moca
35 Rincon
36 Quebradillas
37 Mayaguez
38 San German
39 San Sebastian
40 Morovis
41 Sabana Hoyos
42 San Antonio
43 Vega Alta
44 Vega Baja
45 Yauco
46 Aguas Buenas
47 Aguirre
48 Aibonito
49 Maunabo
50 Arroyo
51 Mercedita
52 Ponce
53 Naguabo
54 Naranjito
55 Orocovis
56 Palmer
57 Patillas
58 Caguas
59 Canovanas
60 Ceiba
61 Cayey
62 Fajardo
63 Cidra
64 Puerto Real
65 Punta Santiago
66 Roosevelt Roads
67 Rio Blanco
68 Rio Grande
69 Salinas
70 San Lorenzo
71 Santa Isabel
72 Vieques
73 Villalba
74 Yabucoa
75 Coamo
76 Las Piedras
77 Loiza
78 Luquillo
79 Culebra
80 Juncos
81 Gurabo
82 Coto Laurel
83 Comerio
84 Corozal
85 Guayama
86 La Plata
87 Humacao
88 Barranquitas
89 Juana Diaz
90 St Thomas
91 Christiansted
92 St John
93 Frederiksted
94 Kingshill
95 San Juan
96 Fort Buchanan
97 Toa Baja
98 Sabana Seca
99 Toa Alta
100 Bayamon
101 Catano
102 Guaynabo
103 Trujillo Alto
104 Saint Just
105 Carolina
106 Agawam
107 Amherst
108 Barre
109 Belchertown
110 Blandford
111 Bondsville
112 Brimfield
113 Chester
114 Chesterfield
115 Chicopee
116 Cummington
117 Easthampton
118 East Longmeadow
119 East Otis
120 Feeding Hills
121 Gilbertville
122 Goshen
123 Granby
124 Granville
125 Hadley
126 Hampden
127 Hardwick
128 Hatfield
129 Haydenville
130 Holyoke
131 Huntington
132 Leeds
133 Leverett
134 Ludlow
135 Monson
136 North Amherst

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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