├── .dockerignore ├── .github ├── dependabot.yml └── workflows │ └── maven.yml ├── .gitignore ├── .mvn └── wrapper │ ├── .gitignore │ ├── MavenWrapperDownloader.java │ └── maven-wrapper.properties ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml ├── scripts ├── .gitignore ├── ap-loader-all.jar └── benchmark.sh └── src └── main ├── docker ├── Dockerfile.jvm ├── Dockerfile.legacy-jar ├── Dockerfile.native └── Dockerfile.native-micro ├── java └── profiling │ └── workshop │ ├── allocation │ ├── FastRandomSequenceResource.java │ ├── RandomGeneratorService.java │ ├── RandomSequenceResource.java │ └── StringBuilderPool.java │ ├── greeting │ └── GreetingResource.java │ ├── listtraversal │ ├── Person.java │ ├── PersonTraversalResource.java │ └── PersonsTraversalService.java │ ├── logging │ ├── AgedPersonRepository.java │ ├── LoggingService.java │ └── PersonByAgeResource.java │ └── time │ ├── CachedTimeResource.java │ ├── TimeResource.java │ └── TimeService.java └── resources ├── META-INF └── resources │ └── index.html └── application.properties /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !target/*-runner 3 | !target/*-runner.jar 4 | !target/lib/* 5 | !target/quarkus-app/* -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: maven 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | time: "23:00" 13 | timezone: Europe/London 14 | open-pull-requests-limit: 10 15 | labels: 16 | - dependency 17 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Java CI with Maven 10 | 11 | on: 12 | push: 13 | branches: [ "master" ] 14 | pull_request: 15 | branches: [ "master" ] 16 | 17 | jobs: 18 | build: 19 | 20 | runs-on: ubuntu-latest 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Set up JDK 17 25 | uses: actions/setup-java@v4 26 | with: 27 | java-version: '17' 28 | distribution: 'temurin' 29 | cache: maven 30 | - name: Build with Maven 31 | run: mvn --batch-mode --update-snapshots verify 32 | 33 | # Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive 34 | - name: Update dependency graph 35 | uses: advanced-security/maven-dependency-submission-action@571e99aab1055c2e71a1e2309b9691de18d6b7d6 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #Maven 2 | target/ 3 | pom.xml.tag 4 | pom.xml.releaseBackup 5 | pom.xml.versionsBackup 6 | release.properties 7 | .flattened-pom.xml 8 | 9 | # Eclipse 10 | .project 11 | .classpath 12 | .settings/ 13 | bin/ 14 | 15 | # IntelliJ 16 | .idea 17 | *.ipr 18 | *.iml 19 | *.iws 20 | 21 | # NetBeans 22 | nb-configuration.xml 23 | 24 | # Visual Studio Code 25 | .vscode 26 | .factorypath 27 | 28 | # OSX 29 | .DS_Store 30 | 31 | # Vim 32 | *.swp 33 | *.swo 34 | 35 | # patch 36 | *.orig 37 | *.rej 38 | 39 | # Local environment 40 | .env 41 | 42 | # Ignore hyperfoil 43 | scripts/hyperfoil* 44 | 45 | # Ignore html results 46 | scripts/*.html 47 | 48 | # Ignore jfr results 49 | scripts/*.jfr -------------------------------------------------------------------------------- /.mvn/wrapper/.gitignore: -------------------------------------------------------------------------------- 1 | maven-wrapper.jar 2 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one 3 | * or more contributor license agreements. See the NOTICE file 4 | * distributed with this work for additional information 5 | * regarding copyright ownership. The ASF licenses this file 6 | * to you under the Apache License, Version 2.0 (the 7 | * "License"); you may not use this file except in compliance 8 | * with the License. You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, 13 | * software distributed under the License is distributed on an 14 | * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | * KIND, either express or implied. See the License for the 16 | * specific language governing permissions and limitations 17 | * under the License. 18 | */ 19 | 20 | import java.net.*; 21 | import java.io.*; 22 | import java.nio.channels.*; 23 | import java.util.Properties; 24 | 25 | public class MavenWrapperDownloader 26 | { 27 | private static final String WRAPPER_VERSION = "3.1.1"; 28 | 29 | /** 30 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 31 | */ 32 | private static final String DEFAULT_DOWNLOAD_URL = 33 | "https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/" + WRAPPER_VERSION 34 | + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 35 | 36 | /** 37 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to use instead of the 38 | * default one. 39 | */ 40 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = ".mvn/wrapper/maven-wrapper.properties"; 41 | 42 | /** 43 | * Path where the maven-wrapper.jar will be saved to. 44 | */ 45 | private static final String MAVEN_WRAPPER_JAR_PATH = ".mvn/wrapper/maven-wrapper.jar"; 46 | 47 | /** 48 | * Name of the property which should be used to override the default download url for the wrapper. 49 | */ 50 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 51 | 52 | public static void main( String args[] ) 53 | { 54 | System.out.println( "- Downloader started" ); 55 | File baseDirectory = new File( args[0] ); 56 | System.out.println( "- Using base directory: " + baseDirectory.getAbsolutePath() ); 57 | 58 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 59 | // wrapperUrl parameter. 60 | File mavenWrapperPropertyFile = new File( baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH ); 61 | String url = DEFAULT_DOWNLOAD_URL; 62 | if ( mavenWrapperPropertyFile.exists() ) 63 | { 64 | FileInputStream mavenWrapperPropertyFileInputStream = null; 65 | try 66 | { 67 | mavenWrapperPropertyFileInputStream = new FileInputStream( mavenWrapperPropertyFile ); 68 | Properties mavenWrapperProperties = new Properties(); 69 | mavenWrapperProperties.load( mavenWrapperPropertyFileInputStream ); 70 | url = mavenWrapperProperties.getProperty( PROPERTY_NAME_WRAPPER_URL, url ); 71 | } 72 | catch ( IOException e ) 73 | { 74 | System.out.println( "- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'" ); 75 | } 76 | finally 77 | { 78 | try 79 | { 80 | if ( mavenWrapperPropertyFileInputStream != null ) 81 | { 82 | mavenWrapperPropertyFileInputStream.close(); 83 | } 84 | } 85 | catch ( IOException e ) 86 | { 87 | // Ignore ... 88 | } 89 | } 90 | } 91 | System.out.println( "- Downloading from: " + url ); 92 | 93 | File outputFile = new File( baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH ); 94 | if ( !outputFile.getParentFile().exists() ) 95 | { 96 | if ( !outputFile.getParentFile().mkdirs() ) 97 | { 98 | System.out.println( "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() 99 | + "'" ); 100 | } 101 | } 102 | System.out.println( "- Downloading to: " + outputFile.getAbsolutePath() ); 103 | try 104 | { 105 | downloadFileFromURL( url, outputFile ); 106 | System.out.println( "Done" ); 107 | System.exit( 0 ); 108 | } 109 | catch ( Throwable e ) 110 | { 111 | System.out.println( "- Error downloading" ); 112 | e.printStackTrace(); 113 | System.exit( 1 ); 114 | } 115 | } 116 | 117 | private static void downloadFileFromURL( String urlString, File destination ) 118 | throws Exception 119 | { 120 | if ( System.getenv( "MVNW_USERNAME" ) != null && System.getenv( "MVNW_PASSWORD" ) != null ) 121 | { 122 | String username = System.getenv( "MVNW_USERNAME" ); 123 | char[] password = System.getenv( "MVNW_PASSWORD" ).toCharArray(); 124 | Authenticator.setDefault( new Authenticator() 125 | { 126 | @Override 127 | protected PasswordAuthentication getPasswordAuthentication() 128 | { 129 | return new PasswordAuthentication( username, password ); 130 | } 131 | } ); 132 | } 133 | URL website = new URL( urlString ); 134 | ReadableByteChannel rbc; 135 | rbc = Channels.newChannel( website.openStream() ); 136 | FileOutputStream fos = new FileOutputStream( destination ); 137 | fos.getChannel().transferFrom( rbc, 0, Long.MAX_VALUE ); 138 | fos.close(); 139 | rbc.close(); 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hello-world-app Project 2 | 3 | This project uses Quarkus, the Supersonic Subatomic Java Framework. 4 | 5 | If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ . 6 | 7 | ## Packaging and running the application 8 | 9 | The application can be packaged using: 10 | ```shell script 11 | ./mvnw package 12 | ``` 13 | It produces the `quarkus-run.jar` file in the `target/quarkus-app/` directory. 14 | Be aware that it’s not an _über-jar_ as the dependencies are copied into the `target/quarkus-app/lib/` directory. 15 | 16 | The application is now runnable using `java -jar target/quarkus-app/quarkus-run.jar`. 17 | 18 | If you want to build an _über-jar_, execute the following command: 19 | ```shell script 20 | ./mvnw package -Dquarkus.package.type=uber-jar 21 | ``` 22 | 23 | The application, packaged as an _über-jar_, is now runnable using `java -jar target/*-runner.jar`. 24 | 25 | ## Creating a native executable 26 | 27 | You can create a native executable using: 28 | ```shell script 29 | ./mvnw package -Pnative 30 | ``` 31 | 32 | Or, if you don't have GraalVM installed, you can run the native executable build in a container using: 33 | ```shell script 34 | ./mvnw package -Pnative -Dquarkus.native.container-build=true 35 | ``` 36 | 37 | You can then execute your native executable with: `./target/hello-world-app-1.0.0-SNAPSHOT-runner` 38 | 39 | If you want to learn more about building native executables, please consult https://quarkus.io/guides/maven-tooling. 40 | 41 | ## Related Guides 42 | 43 | - Mutiny ([guide](https://quarkus.io/guides/mutiny-primer)): Write reactive applications with the modern Reactive Programming library Mutiny 44 | - RESTEasy Reactive ([guide](https://quarkus.io/guides/resteasy-reactive)): A JAX-RS implementation utilizing build time processing and Vert.x. This extension is not compatible with the quarkus-resteasy extension, or any of the extensions that depend on it. 45 | 46 | ## Provided Code 47 | 48 | ### RESTEasy Reactive 49 | 50 | Easily start your Reactive RESTful Web Services 51 | 52 | [Related guide section...](https://quarkus.io/guides/getting-started-reactive#reactive-jax-rs-resources) 53 | 54 | # How to load generate HTTP workload 55 | 56 | How to install the chosen load generator: https://hyperfoil.io/quickstart/quickstart1.html 57 | 58 | Why Hyperfoil? https://www.slideshare.net/InfoQ/how-not-to-measure-latency-60111840 59 | 60 | ## Preparation Steps 61 | 62 | Download and unzip the generator: 63 | ```bash 64 | wget https://github.com/Hyperfoil/Hyperfoil/releases/download/hyperfoil-all-0.24/hyperfoil-0.24.zip \ 65 | && unzip hyperfoil-0.24.zip \ 66 | && cd hyperfoil-0.24 67 | ``` 68 | 69 | From within the hyperfoil's `/bin` folder and assuming the Quarkus hello world endpoint to be up and running: 70 | ```bash 71 | [hyperfoil@in-vm]$ wrk -t 1 -c 10 -d 10s http://localhost:8080/hello 72 | ``` 73 | While the benchmark is completed, it will output something like: 74 | ```bash 75 | Running 10s test @ http://localhost:8080/hello 76 | 1 threads and 10 connections 77 | Thread Stats Avg Stdev Max +/- Stdev 78 | Latency 192.64μs 317.09μs 66.06ms 99.77% 79 | Req/Sec 45764.73 17108.08 58857.00 90.91 80 | 503412 requests in 10.002s, 70.57MB read 81 | Requests/sec: 50331.13 82 | Transfer/sec: 7.06MB 83 | ``` 84 | Is it possible to start a CLI interactive sessionn with hyperfoil and both report in html 85 | or perform quick diff between `wrk` runs. 86 | 87 | ## Which profiler? Async-profiler OBVIOUSLY! 88 | 89 | Although [async-profiler](https://github.com/jvm-profiling-tools/async-profiler) is awesome, but using it can be made simpler 90 | with [ap-loader](https://github.com/jvm-profiling-tools/ap-loader). 91 | 92 | Download it: 93 | ```bash 94 | wget https://github.com/jvm-profiling-tools/ap-loader/releases/download/2.9/ap-loader-all.jar 95 | ``` 96 | 97 | Let's have fun with the various scripts in the `/scripts` folder of the project! 98 | 99 | ## Cheat Codes 100 | 101 | The benchmarking scripts allow to capture more information by using the proper output format. 102 | 103 | e.g. 104 | 105 | ```bash 106 | $ ./benchmark.sh -u persons/agesum -r 4000 -e cache-misses -f jfr 107 | ``` 108 | the resulting output file should be named as `_cache_misses.jfr`. 109 | It then can be opened (if you dare!! ) with [JMC 8.x](https://www.oracle.com/java/technologies/javase/products-jmc8-downloads.html) 110 | or can be used the built-in `jfr2flame` converter to produce flamegraphs with 111 | more information e.g. line of code to be blamed, if available 112 | 113 | ```bash 114 | $ java -jar ap-loader-all.jar converter jfr2flame _cache_misses.jfr --threads --lines out.html 115 | ``` 116 | More informations about converter are part of the command's help or can be found on the async profiler homepage. 117 | 118 | ## How to compare benchmarking results? 119 | Hyperfoil allow to easily compare between two runs, but sadly, given that 120 | we've used the CLI versions of `wrk`/`wrk2` (load generation CLI commands) we don't have an easy way to find identifier 121 | for such runs. 122 | 123 | Leveraging on our knowledge of the benchmarking script, it performs 2 runs: 124 | - a warmup run with all-out throughput 125 | - a second run with the configured throughput (if any) 126 | 127 | This means that for each benchmarking session it produces 2 runs on in Hyperfoil 128 | and the ones to be compared must be taken considering the warming up ones too. 129 | 130 | Assuming two benchmarks runs already completed: 131 | ```bash 132 | # run the hyperfoil CLI 133 | $ hyperfoil-0.23/bin/cli.sh 134 | # start a local session on it 135 | [hyperfoil]$ start-local 136 | Starting controller in default directory (/tmp/hyperfoil) 137 | Controller started, listening on 127.0.0.1:33161 138 | Connecting to the controller... 139 | Connected to 127.0.0.1:33161! 140 | # now on we can interact with the local env 141 | [hyperfoil@in-vm]$ runs 142 | RUN_ID BENCHMARK STARTED TERMINATED DESCRIPTION 143 | ! 0000 wrk 2023/01/17 04:53:15.985 2023/01/17 04:53:31.989 144 | ! 0001 wrk 2023/01/17 04:53:35.261 2023/01/17 04:53:51.267 145 | # that could be a loooooooong list by the end of the workshop :P 146 | # ... 147 | ! 0067 wrk 2023/01/18 12:37:06.368 2023/01/18 12:37:32.380 148 | ! 0068 wrk2 2023/01/18 12:37:36.843 2023/01/18 12:38:02.852 149 | ! 0069 wrk 2023/01/18 12:39:00.641 2023/01/18 12:39:26.650 150 | ! 006A wrk2 2023/01/18 12:39:31.091 2023/01/18 12:39:57.099 151 | ``` 152 | As said before, given that each benchmark script run perform 2 hyperfoil runs, the last 2 153 | warmed up runs are `0068` and `006A`. 154 | 155 | Let's see what `compare` says: 156 | ```bash 157 | [hyperfoil@in-vm]$ compare 0068 006A 158 | Comparing runs 0068 and 006A 159 | PHASE METRIC REQUESTS MEAN p50 p90 p99 p99.9 p99.99 160 | calibration request +8(+0.03%) -5.85 ms(-524.95%) +0 ns(+0.00%) -36.20 ms(-7214.30%) -28.57 ms(-153.52%) -28.57 ms(-153.52%) -28.57 ms(-153.52%) 161 | test request -8(-0.01%) -1.45 ms(-257.71%) +0 ns(+0.00%) -1.01 ms(-200.41%) -29.51 ms(-5882.05%) -16.52 ms(-122.33%) -16.52 ms(-122.33%) 162 | ``` 163 | Is it clear that under the same load (whatever it is), the 2 perform very differently, with `006A` getting `1 ms` `p90` lower 164 | latencies, that's an improvement of `-200.41%` (!!!). 165 | 166 | To manually study the dynamics of each run, is it possible to produce an html report for each with: 167 | ```bash 168 | [hyperfoil@in-vm]$ report 0068 --destination=linkedlist.html 169 | ``` 170 | You can then open the `html` file an check the rate progress, latency distributions and errors. 171 | 172 | -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /usr/local/etc/mavenrc ] ; then 40 | . /usr/local/etc/mavenrc 41 | fi 42 | 43 | if [ -f /etc/mavenrc ] ; then 44 | . /etc/mavenrc 45 | fi 46 | 47 | if [ -f "$HOME/.mavenrc" ] ; then 48 | . "$HOME/.mavenrc" 49 | fi 50 | 51 | fi 52 | 53 | # OS specific support. $var _must_ be set to either true or false. 54 | cygwin=false; 55 | darwin=false; 56 | mingw=false 57 | case "`uname`" in 58 | CYGWIN*) cygwin=true ;; 59 | MINGW*) mingw=true;; 60 | Darwin*) darwin=true 61 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 62 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 63 | if [ -z "$JAVA_HOME" ]; then 64 | if [ -x "/usr/libexec/java_home" ]; then 65 | export JAVA_HOME="`/usr/libexec/java_home`" 66 | else 67 | export JAVA_HOME="/Library/Java/Home" 68 | fi 69 | fi 70 | ;; 71 | esac 72 | 73 | if [ -z "$JAVA_HOME" ] ; then 74 | if [ -r /etc/gentoo-release ] ; then 75 | JAVA_HOME=`java-config --jre-home` 76 | fi 77 | fi 78 | 79 | if [ -z "$M2_HOME" ] ; then 80 | ## resolve links - $0 may be a link to maven's home 81 | PRG="$0" 82 | 83 | # need this for relative symlinks 84 | while [ -h "$PRG" ] ; do 85 | ls=`ls -ld "$PRG"` 86 | link=`expr "$ls" : '.*-> \(.*\)$'` 87 | if expr "$link" : '/.*' > /dev/null; then 88 | PRG="$link" 89 | else 90 | PRG="`dirname "$PRG"`/$link" 91 | fi 92 | done 93 | 94 | saveddir=`pwd` 95 | 96 | M2_HOME=`dirname "$PRG"`/.. 97 | 98 | # make it fully qualified 99 | M2_HOME=`cd "$M2_HOME" && pwd` 100 | 101 | cd "$saveddir" 102 | # echo Using m2 at $M2_HOME 103 | fi 104 | 105 | # For Cygwin, ensure paths are in UNIX format before anything is touched 106 | if $cygwin ; then 107 | [ -n "$M2_HOME" ] && 108 | M2_HOME=`cygpath --unix "$M2_HOME"` 109 | [ -n "$JAVA_HOME" ] && 110 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 111 | [ -n "$CLASSPATH" ] && 112 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 113 | fi 114 | 115 | # For Mingw, ensure paths are in UNIX format before anything is touched 116 | if $mingw ; then 117 | [ -n "$M2_HOME" ] && 118 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 119 | [ -n "$JAVA_HOME" ] && 120 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 121 | fi 122 | 123 | if [ -z "$JAVA_HOME" ]; then 124 | javaExecutable="`which javac`" 125 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 126 | # readlink(1) is not available as standard on Solaris 10. 127 | readLink=`which readlink` 128 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 129 | if $darwin ; then 130 | javaHome="`dirname \"$javaExecutable\"`" 131 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 132 | else 133 | javaExecutable="`readlink -f \"$javaExecutable\"`" 134 | fi 135 | javaHome="`dirname \"$javaExecutable\"`" 136 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 137 | JAVA_HOME="$javaHome" 138 | export JAVA_HOME 139 | fi 140 | fi 141 | fi 142 | 143 | if [ -z "$JAVACMD" ] ; then 144 | if [ -n "$JAVA_HOME" ] ; then 145 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 146 | # IBM's JDK on AIX uses strange locations for the executables 147 | JAVACMD="$JAVA_HOME/jre/sh/java" 148 | else 149 | JAVACMD="$JAVA_HOME/bin/java" 150 | fi 151 | else 152 | JAVACMD="`\\unset -f command; \\command -v java`" 153 | fi 154 | fi 155 | 156 | if [ ! -x "$JAVACMD" ] ; then 157 | echo "Error: JAVA_HOME is not defined correctly." >&2 158 | echo " We cannot execute $JAVACMD" >&2 159 | exit 1 160 | fi 161 | 162 | if [ -z "$JAVA_HOME" ] ; then 163 | echo "Warning: JAVA_HOME environment variable is not set." 164 | fi 165 | 166 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 167 | 168 | # traverses directory structure from process work directory to filesystem root 169 | # first directory with .mvn subdirectory is considered project base directory 170 | find_maven_basedir() { 171 | 172 | if [ -z "$1" ] 173 | then 174 | echo "Path not specified to find_maven_basedir" 175 | return 1 176 | fi 177 | 178 | basedir="$1" 179 | wdir="$1" 180 | while [ "$wdir" != '/' ] ; do 181 | if [ -d "$wdir"/.mvn ] ; then 182 | basedir=$wdir 183 | break 184 | fi 185 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 186 | if [ -d "${wdir}" ]; then 187 | wdir=`cd "$wdir/.."; pwd` 188 | fi 189 | # end of workaround 190 | done 191 | echo "${basedir}" 192 | } 193 | 194 | # concatenates all lines of a file 195 | concat_lines() { 196 | if [ -f "$1" ]; then 197 | echo "$(tr -s '\n' ' ' < "$1")" 198 | fi 199 | } 200 | 201 | BASE_DIR=`find_maven_basedir "$(pwd)"` 202 | if [ -z "$BASE_DIR" ]; then 203 | exit 1; 204 | fi 205 | 206 | ########################################################################################## 207 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 208 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 209 | ########################################################################################## 210 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Found .mvn/wrapper/maven-wrapper.jar" 213 | fi 214 | else 215 | if [ "$MVNW_VERBOSE" = true ]; then 216 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 217 | fi 218 | if [ -n "$MVNW_REPOURL" ]; then 219 | jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" 220 | else 221 | jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" 222 | fi 223 | while IFS="=" read key value; do 224 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 225 | esac 226 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 227 | if [ "$MVNW_VERBOSE" = true ]; then 228 | echo "Downloading from: $jarUrl" 229 | fi 230 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 231 | if $cygwin; then 232 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 233 | fi 234 | 235 | if command -v wget > /dev/null; then 236 | if [ "$MVNW_VERBOSE" = true ]; then 237 | echo "Found wget ... using wget" 238 | fi 239 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 240 | wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 241 | else 242 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" 243 | fi 244 | elif command -v curl > /dev/null; then 245 | if [ "$MVNW_VERBOSE" = true ]; then 246 | echo "Found curl ... using curl" 247 | fi 248 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 249 | curl -o "$wrapperJarPath" "$jarUrl" -f 250 | else 251 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 252 | fi 253 | 254 | else 255 | if [ "$MVNW_VERBOSE" = true ]; then 256 | echo "Falling back to using Java to download" 257 | fi 258 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 259 | # For Cygwin, switch paths to Windows format before running javac 260 | if $cygwin; then 261 | javaClass=`cygpath --path --windows "$javaClass"` 262 | fi 263 | if [ -e "$javaClass" ]; then 264 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 265 | if [ "$MVNW_VERBOSE" = true ]; then 266 | echo " - Compiling MavenWrapperDownloader.java ..." 267 | fi 268 | # Compiling the Java class 269 | ("$JAVA_HOME/bin/javac" "$javaClass") 270 | fi 271 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 272 | # Running the downloader 273 | if [ "$MVNW_VERBOSE" = true ]; then 274 | echo " - Running MavenWrapperDownloader.java ..." 275 | fi 276 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 277 | fi 278 | fi 279 | fi 280 | fi 281 | ########################################################################################## 282 | # End of extension 283 | ########################################################################################## 284 | 285 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 286 | if [ "$MVNW_VERBOSE" = true ]; then 287 | echo $MAVEN_PROJECTBASEDIR 288 | fi 289 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 290 | 291 | # For Cygwin, switch paths to Windows format before running java 292 | if $cygwin; then 293 | [ -n "$M2_HOME" ] && 294 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 295 | [ -n "$JAVA_HOME" ] && 296 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 297 | [ -n "$CLASSPATH" ] && 298 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 299 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 300 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 301 | fi 302 | 303 | # Provide a "standardized" way to retrieve the CLI args that will 304 | # work with both Windows and non-Windows executions. 305 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 306 | export MAVEN_CMD_LINE_ARGS 307 | 308 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 309 | 310 | exec "$JAVACMD" \ 311 | $MAVEN_OPTS \ 312 | $MAVEN_DEBUG_OPTS \ 313 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 314 | "-Dmaven.home=${M2_HOME}" \ 315 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 316 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 317 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 50 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" 124 | 125 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% ^ 162 | %JVM_CONFIG_MAVEN_PROPS% ^ 163 | %MAVEN_OPTS% ^ 164 | %MAVEN_DEBUG_OPTS% ^ 165 | -classpath %WRAPPER_JAR% ^ 166 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 167 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 168 | if ERRORLEVEL 1 goto error 169 | goto end 170 | 171 | :error 172 | set ERROR_CODE=1 173 | 174 | :end 175 | @endlocal & set ERROR_CODE=%ERROR_CODE% 176 | 177 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 178 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 179 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 180 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 181 | :skipRcPost 182 | 183 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 184 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 185 | 186 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 187 | 188 | cmd /C exit /B %ERROR_CODE% 189 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | profiling.workshop 6 | hello-world-app 7 | 1.0.0-SNAPSHOT 8 | 9 | 3.13.0 10 | 17 11 | UTF-8 12 | UTF-8 13 | quarkus-bom 14 | io.quarkus.platform 15 | 3.15.1 16 | true 17 | 3.5.0 18 | 19 | 20 | 21 | 22 | ${quarkus.platform.group-id} 23 | ${quarkus.platform.artifact-id} 24 | ${quarkus.platform.version} 25 | pom 26 | import 27 | 28 | 29 | 30 | 31 | 32 | io.quarkus 33 | quarkus-rest 34 | 35 | 36 | io.quarkus 37 | quarkus-rest-jackson 38 | 39 | 40 | io.quarkus 41 | quarkus-mutiny 42 | 43 | 44 | io.quarkus 45 | quarkus-arc 46 | 47 | 48 | io.quarkus 49 | quarkus-scheduler 50 | 51 | 52 | io.quarkus 53 | quarkus-junit5 54 | test 55 | 56 | 57 | io.rest-assured 58 | rest-assured 59 | test 60 | 61 | 62 | 63 | 64 | 65 | ${quarkus.platform.group-id} 66 | quarkus-maven-plugin 67 | ${quarkus.platform.version} 68 | true 69 | 70 | 71 | 72 | build 73 | generate-code 74 | generate-code-tests 75 | 76 | 77 | 78 | 79 | 80 | maven-compiler-plugin 81 | ${compiler-plugin.version} 82 | 83 | 84 | -parameters 85 | 86 | 87 | 88 | 89 | maven-surefire-plugin 90 | ${surefire-plugin.version} 91 | 92 | 93 | org.jboss.logmanager.LogManager 94 | ${maven.home} 95 | 96 | 97 | 98 | 99 | maven-failsafe-plugin 100 | ${surefire-plugin.version} 101 | 102 | 103 | 104 | integration-test 105 | verify 106 | 107 | 108 | 109 | ${project.build.directory}/${project.build.finalName}-runner 110 | org.jboss.logmanager.LogManager 111 | ${maven.home} 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | native 122 | 123 | 124 | native 125 | 126 | 127 | 128 | false 129 | native 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /scripts/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated files 2 | *.html 3 | *.jfr 4 | *.log -------------------------------------------------------------------------------- /scripts/ap-loader-all.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/franz1981/quarkus-profiling-workshop/68ab1577186e768b8017e11b1c4257ce747f6e5b/scripts/ap-loader-all.jar -------------------------------------------------------------------------------- /scripts/benchmark.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | HYPERFOIL_HOME=${HYPERFOIL_HOME:-./hyperfoil} 4 | 5 | URL=hello 6 | 7 | DURATION=40 8 | 9 | EVENT=cpu 10 | 11 | # this can be html or jfr 12 | FORMAT=html 13 | 14 | JFR=false 15 | 16 | THREADS=2 17 | 18 | RATE=0 19 | 20 | CONNECTIONS=10 21 | 22 | JFR_ARGS= 23 | 24 | PERF=false 25 | 26 | WRK_PROFILING=false 27 | 28 | die () { 29 | echo "$*" 30 | exit 1 31 | } 32 | 33 | Help() 34 | { 35 | # Display Help 36 | echo "Syntax: benchmark [OPTIONS]" 37 | echo "options:" 38 | echo "h Display this guide." 39 | echo "" 40 | echo "u Final part of the URL to benchmark." 41 | echo " e.g. benchmark -u time would benchmark http://localhost:8080/time" 42 | echo " default is hello" 43 | echo "" 44 | echo "e event to profile, if supported e.g. -e cpu " 45 | echo " check https://github.com/jvm-profiling-tools/async-profiler#profiler-options for the complete list" 46 | echo " default is cpu" 47 | echo "" 48 | echo "f output format, if supported by the profiler. e.g. async-profiler support html,jfr,collapsed" 49 | echo " default is html" 50 | echo "" 51 | echo "d duration of the load generation phase, in seconds" 52 | echo " default is 20" 53 | echo "" 54 | echo "j if specified, it uses JFR profiling. async-profiler otherwise." 55 | echo "" 56 | echo "t number of I/O threads of the quarkus application." 57 | echo "" 58 | echo " default is 2" 59 | echo "" 60 | echo "r rate of the load generation phase, in requests/sec." 61 | echo " default not specified (0)" 62 | echo "" 63 | echo "c number of connections used by the load generator." 64 | echo " default is 10" 65 | echo "" 66 | echo "p if specified, run perf stat together with the selected profiler. Only GNU Linux." 67 | echo "" 68 | echo "w profile the load generator, Hyperfoil in this case." 69 | echo " default is false" 70 | } 71 | 72 | while getopts "hu:e:f:d:jt:r:c:pw" option; do 73 | case $option in 74 | h) Help 75 | exit;; 76 | u) URL=${OPTARG} 77 | ;; 78 | e) EVENT=${OPTARG} 79 | ;; 80 | f) FORMAT=${OPTARG} 81 | ;; 82 | d) DURATION=${OPTARG} 83 | ;; 84 | j) JFR=true 85 | ;; 86 | t) THREADS=${OPTARG} 87 | ;; 88 | r) RATE=${OPTARG} 89 | ;; 90 | c) CONNECTIONS=${OPTARG} 91 | ;; 92 | p) PERF=true 93 | ;; 94 | w) WRK_PROFILING=true 95 | ;; 96 | esac 97 | done 98 | 99 | if ! [[ -f benchmark.sh ]]; then 100 | die "error: should be run from the scripts/ directory" 101 | fi 102 | 103 | WARMUP=$((${DURATION}*2/5)) 104 | 105 | PROFILING=$((${DURATION}/2)) 106 | 107 | FULL_URL=http://localhost:8080/${URL} 108 | 109 | echo "----- Benchmarking endpoint ${FULL_URL}" 110 | 111 | # set sysctl kernel variables only if necessary 112 | if [[ "$OSTYPE" == "linux-gnu" ]]; then 113 | current_value=$(sysctl -n kernel.perf_event_paranoid) 114 | if [ "$current_value" -ne 1 ]; then 115 | sudo sysctl kernel.perf_event_paranoid=1 116 | sudo sysctl kernel.kptr_restrict=0 117 | fi 118 | fi 119 | 120 | if [ "${JFR}" = true ]; then 121 | JFR_ARGS=-XX:+FlightRecorder 122 | fi 123 | 124 | trap 'echo "cleaning up quarkus process";kill ${quarkus_pid}' SIGINT SIGTERM SIGKILL 125 | 126 | # let's run it with a single thread, is simpler! 127 | # TODO cmd can be extracted and become a run-quarkus.sh script per-se 128 | java ${JFR_ARGS} -Dquarkus.vertx.event-loops-pool-size=${THREADS} -XX:+UnlockDiagnosticVMOptions -XX:+DebugNonSafepoints -jar ../target/quarkus-app/quarkus-run.jar & 129 | quarkus_pid=$! 130 | 131 | sleep 2 132 | 133 | echo "----- Quarkus running at pid $quarkus_pid using ${THREADS} I/O threads" 134 | 135 | if [ "${RATE}" != "0" ] 136 | then 137 | echo "----- Start fixed rate test at ${RATE} requests/sec and profiling" 138 | ${HYPERFOIL_HOME}/bin/wrk2.sh -R ${RATE} -c ${CONNECTIONS} -t ${THREADS} -d ${DURATION}s ${FULL_URL} & 139 | else 140 | echo "----- Start all-out test and profiling" 141 | ${HYPERFOIL_HOME}/bin/wrk.sh -c ${CONNECTIONS} -t ${THREADS} -d ${DURATION}s ${FULL_URL} & 142 | fi 143 | 144 | wrk_pid=$! 145 | 146 | echo "----- Waiting $WARMUP seconds before profiling for $PROFILING seconds" 147 | 148 | sleep $WARMUP 149 | 150 | NOW=$(date "+%y%m%d_%H_%M_%S") 151 | 152 | if [ "${JFR}" = true ] 153 | then 154 | jcmd $quarkus_pid JFR.start duration=${PROFILING}s filename=${NOW}.jfr dumponexit=true settings=profile 155 | else 156 | if [ "${WRK_PROFILING}" = true ]; then 157 | JFR_ARGS=-XX:+FlightRecorder 158 | wrk_jvm_pid=`jps | grep Wrk | awk '{print $1}'` 159 | echo "----- Starting async-profiler on load generator process ($wrk_jvm_pid)" 160 | java -jar ap-loader-all.jar profiler -e ${EVENT} -t -d ${PROFILING} -f wrk_${NOW}_${EVENT}.${FORMAT} $wrk_jvm_pid & 161 | fi 162 | echo "----- Starting async-profiler on quarkus application ($quarkus_pid)" 163 | java -jar ap-loader-all.jar profiler -e ${EVENT} -t -d ${PROFILING} -f ${NOW}_${EVENT}.${FORMAT} $quarkus_pid & 164 | fi 165 | 166 | ap_pid=$! 167 | 168 | if [ "${PERF}" = true ]; then 169 | if [ "${WRK_PROFILING}" = true ]; then 170 | echo "----- Collecting perf stat on $wrk_jvm_pid" 171 | perf stat -d -p "$wrk_jvm_pid" & 172 | wrk_stat_pid=$! 173 | fi 174 | echo "----- Collecting perf stat on $quarkus_pid" 175 | perf stat -d -p $quarkus_pid & 176 | stat_pid=$! 177 | fi 178 | 179 | echo "----- Showing stats for $WARMUP seconds" 180 | 181 | if [[ "$OSTYPE" == "linux-gnu" ]]; then 182 | pidstat -p $quarkus_pid 1 & 183 | pidstat_pid=$! 184 | sleep $WARMUP 185 | kill -SIGTERM "$pidstat_pid" 186 | else 187 | # Print stats header 188 | ps -p $quarkus_pid -o %cpu,rss,vsz | head -1 189 | sleep 1; 190 | # Print stats 191 | for (( i=1; i<$WARMUP; i++ )); do ps -p $quarkus_pid -o %cpu,rss,vsz | tail -1;sleep 1;done; 192 | fi 193 | 194 | echo "----- Stopped stats, waiting load to complete" 195 | 196 | wait $ap_pid 197 | 198 | if [ "${PERF}" = true ]; then 199 | if [ "${WRK_PROFILING}" = true ]; then 200 | kill -SIGINT "$wrk_stat_pid" 201 | fi 202 | kill -SIGINT "$stat_pid" 203 | fi 204 | 205 | wait $wrk_pid 206 | 207 | echo "----- Profiling and workload completed: killing server" 208 | 209 | kill -SIGTERM $quarkus_pid 210 | -------------------------------------------------------------------------------- /src/main/docker/Dockerfile.jvm: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode 3 | # 4 | # Before building the container image run: 5 | # 6 | # ./mvnw package 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.jvm -t quarkus/hello-world-app-jvm . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/hello-world-app-jvm 15 | # 16 | # If you want to include the debug port into your docker image 17 | # you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005 18 | # 19 | # Then run the container using : 20 | # 21 | # docker run -i --rm -p 8080:8080 quarkus/hello-world-app-jvm 22 | # 23 | # This image uses the `run-java.sh` script to run the application. 24 | # This scripts computes the command line to execute your Java application, and 25 | # includes memory/GC tuning. 26 | # You can configure the behavior using the following environment properties: 27 | # - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") 28 | # - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options 29 | # in JAVA_OPTS (example: "-Dsome.property=foo") 30 | # - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is 31 | # used to calculate a default maximal heap memory based on a containers restriction. 32 | # If used in a container without any memory constraints for the container then this 33 | # option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio 34 | # of the container available memory as set here. The default is `50` which means 50% 35 | # of the available memory is used as an upper boundary. You can skip this mechanism by 36 | # setting this value to `0` in which case no `-Xmx` option is added. 37 | # - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This 38 | # is used to calculate a default initial heap memory based on the maximum heap memory. 39 | # If used in a container without any memory constraints for the container then this 40 | # option has no effect. If there is a memory constraint then `-Xms` is set to a ratio 41 | # of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` 42 | # is used as the initial heap size. You can skip this mechanism by setting this value 43 | # to `0` in which case no `-Xms` option is added (example: "25") 44 | # - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. 45 | # This is used to calculate the maximum value of the initial heap memory. If used in 46 | # a container without any memory constraints for the container then this option has 47 | # no effect. If there is a memory constraint then `-Xms` is limited to the value set 48 | # here. The default is 4096MB which means the calculated value of `-Xms` never will 49 | # be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") 50 | # - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output 51 | # when things are happening. This option, if set to true, will set 52 | # `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). 53 | # - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: 54 | # true"). 55 | # - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). 56 | # - CONTAINER_CORE_LIMIT: A calculated core limit as described in 57 | # https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") 58 | # - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). 59 | # - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. 60 | # (example: "20") 61 | # - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. 62 | # (example: "40") 63 | # - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. 64 | # (example: "4") 65 | # - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus 66 | # previous GC times. (example: "90") 67 | # - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") 68 | # - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") 69 | # - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should 70 | # contain the necessary JRE command-line options to specify the required GC, which 71 | # will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). 72 | # - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") 73 | # - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") 74 | # - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be 75 | # accessed directly. (example: "foo.example.com,bar.example.com") 76 | # 77 | ### 78 | FROM registry.access.redhat.com/ubi8/openjdk-17:1.14 79 | 80 | ENV LANGUAGE='en_US:en' 81 | 82 | 83 | # We make four distinct layers so if there are application changes the library layers can be re-used 84 | COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/ 85 | COPY --chown=185 target/quarkus-app/*.jar /deployments/ 86 | COPY --chown=185 target/quarkus-app/app/ /deployments/app/ 87 | COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/ 88 | 89 | EXPOSE 8080 90 | USER 185 91 | ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" 92 | ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" 93 | 94 | -------------------------------------------------------------------------------- /src/main/docker/Dockerfile.legacy-jar: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode 3 | # 4 | # Before building the container image run: 5 | # 6 | # ./mvnw package -Dquarkus.package.type=legacy-jar 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/hello-world-app-legacy-jar . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/hello-world-app-legacy-jar 15 | # 16 | # If you want to include the debug port into your docker image 17 | # you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005 18 | # 19 | # Then run the container using : 20 | # 21 | # docker run -i --rm -p 8080:8080 quarkus/hello-world-app-legacy-jar 22 | # 23 | # This image uses the `run-java.sh` script to run the application. 24 | # This scripts computes the command line to execute your Java application, and 25 | # includes memory/GC tuning. 26 | # You can configure the behavior using the following environment properties: 27 | # - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") 28 | # - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options 29 | # in JAVA_OPTS (example: "-Dsome.property=foo") 30 | # - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is 31 | # used to calculate a default maximal heap memory based on a containers restriction. 32 | # If used in a container without any memory constraints for the container then this 33 | # option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio 34 | # of the container available memory as set here. The default is `50` which means 50% 35 | # of the available memory is used as an upper boundary. You can skip this mechanism by 36 | # setting this value to `0` in which case no `-Xmx` option is added. 37 | # - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This 38 | # is used to calculate a default initial heap memory based on the maximum heap memory. 39 | # If used in a container without any memory constraints for the container then this 40 | # option has no effect. If there is a memory constraint then `-Xms` is set to a ratio 41 | # of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx` 42 | # is used as the initial heap size. You can skip this mechanism by setting this value 43 | # to `0` in which case no `-Xms` option is added (example: "25") 44 | # - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS. 45 | # This is used to calculate the maximum value of the initial heap memory. If used in 46 | # a container without any memory constraints for the container then this option has 47 | # no effect. If there is a memory constraint then `-Xms` is limited to the value set 48 | # here. The default is 4096MB which means the calculated value of `-Xms` never will 49 | # be greater than 4096MB. The value of this variable is expressed in MB (example: "4096") 50 | # - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output 51 | # when things are happening. This option, if set to true, will set 52 | # `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true"). 53 | # - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example: 54 | # true"). 55 | # - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787"). 56 | # - CONTAINER_CORE_LIMIT: A calculated core limit as described in 57 | # https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2") 58 | # - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024"). 59 | # - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion. 60 | # (example: "20") 61 | # - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking. 62 | # (example: "40") 63 | # - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection. 64 | # (example: "4") 65 | # - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus 66 | # previous GC times. (example: "90") 67 | # - GC_METASPACE_SIZE: The initial metaspace size. (example: "20") 68 | # - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100") 69 | # - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should 70 | # contain the necessary JRE command-line options to specify the required GC, which 71 | # will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC). 72 | # - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080") 73 | # - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080") 74 | # - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be 75 | # accessed directly. (example: "foo.example.com,bar.example.com") 76 | # 77 | ### 78 | FROM registry.access.redhat.com/ubi8/openjdk-17:1.14 79 | 80 | ENV LANGUAGE='en_US:en' 81 | 82 | 83 | COPY target/lib/* /deployments/lib/ 84 | COPY target/*-runner.jar /deployments/quarkus-run.jar 85 | 86 | EXPOSE 8080 87 | USER 185 88 | ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" 89 | ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" 90 | -------------------------------------------------------------------------------- /src/main/docker/Dockerfile.native: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. 3 | # 4 | # Before building the container image run: 5 | # 6 | # ./mvnw package -Pnative 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.native -t quarkus/hello-world-app . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/hello-world-app 15 | # 16 | ### 17 | FROM registry.access.redhat.com/ubi8/ubi-minimal:8.6 18 | WORKDIR /work/ 19 | RUN chown 1001 /work \ 20 | && chmod "g+rwX" /work \ 21 | && chown 1001:root /work 22 | COPY --chown=1001:root target/*-runner /work/application 23 | 24 | EXPOSE 8080 25 | USER 1001 26 | 27 | CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] 28 | -------------------------------------------------------------------------------- /src/main/docker/Dockerfile.native-micro: -------------------------------------------------------------------------------- 1 | #### 2 | # This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode. 3 | # It uses a micro base image, tuned for Quarkus native executables. 4 | # It reduces the size of the resulting container image. 5 | # Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image. 6 | # 7 | # Before building the container image run: 8 | # 9 | # ./mvnw package -Pnative 10 | # 11 | # Then, build the image with: 12 | # 13 | # docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/hello-world-app . 14 | # 15 | # Then run the container using: 16 | # 17 | # docker run -i --rm -p 8080:8080 quarkus/hello-world-app 18 | # 19 | ### 20 | FROM quay.io/quarkus/quarkus-micro-image:1.0 21 | WORKDIR /work/ 22 | RUN chown 1001 /work \ 23 | && chmod "g+rwX" /work \ 24 | && chown 1001:root /work 25 | COPY --chown=1001:root target/*-runner /work/application 26 | 27 | EXPOSE 8080 28 | USER 1001 29 | 30 | CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] 31 | -------------------------------------------------------------------------------- /src/main/java/profiling/workshop/allocation/FastRandomSequenceResource.java: -------------------------------------------------------------------------------- 1 | package profiling.workshop.allocation; 2 | 3 | import io.smallrye.common.annotation.NonBlocking; 4 | 5 | import jakarta.inject.Inject; 6 | import jakarta.ws.rs.GET; 7 | import jakarta.ws.rs.Path; 8 | import jakarta.ws.rs.PathParam; 9 | import jakarta.ws.rs.Produces; 10 | import jakarta.ws.rs.core.MediaType; 11 | import java.util.stream.Collectors; 12 | 13 | 14 | /** 15 | * Use me with http://localhost:8080/fast-rnd/10 16 | */ 17 | @Path("/fast-rnd/{count}") 18 | public class FastRandomSequenceResource { 19 | 20 | @Inject 21 | public RandomGeneratorService rndService; 22 | @Inject 23 | public StringBuilderPool pool; 24 | 25 | @GET 26 | @Produces(MediaType.TEXT_PLAIN) 27 | @NonBlocking 28 | public String fastCommaSeparatedRandomInts(@PathParam("count") int count) { 29 | if (count == 0) { 30 | return ""; 31 | } 32 | var tmp = pool.acquire(); 33 | tmp.append(rndService.next()); 34 | for (int i = 1; i < count; i++) { 35 | tmp.append(',').append(rndService.next()); 36 | } 37 | return tmp.toString(); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/profiling/workshop/allocation/RandomGeneratorService.java: -------------------------------------------------------------------------------- 1 | package profiling.workshop.allocation; 2 | 3 | import jakarta.inject.Singleton; 4 | import java.util.concurrent.ThreadLocalRandom; 5 | import java.util.stream.IntStream; 6 | 7 | @Singleton 8 | public class RandomGeneratorService { 9 | 10 | public int next() { 11 | return ThreadLocalRandom.current().nextInt(); 12 | } 13 | 14 | public IntStream generate(int count) { 15 | return IntStream.generate(this::next).limit(count); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/profiling/workshop/allocation/RandomSequenceResource.java: -------------------------------------------------------------------------------- 1 | package profiling.workshop.allocation; 2 | 3 | import io.smallrye.common.annotation.NonBlocking; 4 | 5 | import jakarta.inject.Inject; 6 | import jakarta.ws.rs.GET; 7 | import jakarta.ws.rs.Path; 8 | import jakarta.ws.rs.PathParam; 9 | import jakarta.ws.rs.Produces; 10 | import jakarta.ws.rs.core.MediaType; 11 | import java.util.stream.Collectors; 12 | 13 | 14 | /** 15 | * Use me with http://localhost:8080/rnd/10 16 | */ 17 | @Path("/rnd/{count}") 18 | public class RandomSequenceResource { 19 | 20 | @Inject 21 | public RandomGeneratorService rndService; 22 | 23 | @GET 24 | @Produces(MediaType.TEXT_PLAIN) 25 | @NonBlocking 26 | public String commaSeparatedRandomInts(@PathParam("count") int count) { 27 | return rndService.generate(count).boxed().map(String::valueOf).collect(Collectors.joining(",")); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/profiling/workshop/allocation/StringBuilderPool.java: -------------------------------------------------------------------------------- 1 | package profiling.workshop.allocation; 2 | 3 | import io.vertx.core.Context; 4 | 5 | import jakarta.inject.Singleton; 6 | 7 | @Singleton 8 | public class StringBuilderPool { 9 | 10 | private static final int DEFAULT_INITIAL_CAPACITY = 128 * 1024; 11 | private static final ThreadLocal BUILDER_POOL = ThreadLocal.withInitial(() -> new StringBuilder(DEFAULT_INITIAL_CAPACITY)); 12 | 13 | public StringBuilder acquire() { 14 | if (!Context.isOnEventLoopThread()) { 15 | throw new IllegalStateException("this cannot be used outside the event loop thread"); 16 | } 17 | var pooled = BUILDER_POOL.get(); 18 | if (pooled == null) { 19 | return new StringBuilder(DEFAULT_INITIAL_CAPACITY); 20 | } else { 21 | pooled.setLength(0); 22 | } 23 | BUILDER_POOL.set(null); 24 | return pooled; 25 | } 26 | 27 | public void release(StringBuilder used) { 28 | if (!Context.isOnEventLoopThread()) { 29 | throw new IllegalStateException("this cannot be used outside the event loop thread"); 30 | } 31 | BUILDER_POOL.set(used); 32 | } 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/profiling/workshop/greeting/GreetingResource.java: -------------------------------------------------------------------------------- 1 | package profiling.workshop.greeting; 2 | 3 | import jakarta.ws.rs.GET; 4 | import jakarta.ws.rs.Path; 5 | import jakarta.ws.rs.Produces; 6 | import jakarta.ws.rs.core.MediaType; 7 | 8 | @Path("/hello") 9 | public class GreetingResource { 10 | 11 | @GET 12 | @Produces(MediaType.TEXT_PLAIN) 13 | public String hello() { 14 | return "Hello from RESTEasy Reactive"; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/profiling/workshop/listtraversal/Person.java: -------------------------------------------------------------------------------- 1 | package profiling.workshop.listtraversal; 2 | 3 | import java.util.UUID; 4 | import java.util.concurrent.ThreadLocalRandom; 5 | 6 | public record Person(String name, int age, byte[] avatarImage) { 7 | 8 | public static Person randomPerson() { 9 | String name = UUID.randomUUID().toString(); 10 | int age = ThreadLocalRandom.current().nextInt(0, 120); 11 | byte[] avatar = new byte[1000]; 12 | ThreadLocalRandom.current().nextBytes(avatar); 13 | return new Person(name, age, avatar); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/profiling/workshop/listtraversal/PersonTraversalResource.java: -------------------------------------------------------------------------------- 1 | package profiling.workshop.listtraversal; 2 | 3 | import jakarta.inject.Inject; 4 | import jakarta.ws.rs.GET; 5 | import jakarta.ws.rs.Path; 6 | import jakarta.ws.rs.Produces; 7 | import jakarta.ws.rs.core.MediaType; 8 | 9 | import io.smallrye.common.annotation.NonBlocking; 10 | 11 | @Path("/persons/agesum") 12 | public class PersonTraversalResource { 13 | 14 | @Inject 15 | PersonsTraversalService service; 16 | 17 | @GET 18 | @Produces(MediaType.TEXT_PLAIN) 19 | @NonBlocking 20 | public int sum() { 21 | return service.getAgeSum(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/profiling/workshop/listtraversal/PersonsTraversalService.java: -------------------------------------------------------------------------------- 1 | package profiling.workshop.listtraversal; 2 | 3 | import java.util.LinkedList; 4 | import java.util.List; 5 | import java.util.stream.Stream; 6 | import jakarta.annotation.PostConstruct; 7 | import jakarta.inject.Singleton; 8 | 9 | @Singleton 10 | public class PersonsTraversalService { 11 | 12 | private static final int LIST_SIZE = 10_000; 13 | 14 | private List persons; 15 | 16 | @PostConstruct 17 | public void initLists() { 18 | persons = new LinkedList<>(); 19 | Stream.iterate(0, i -> i < LIST_SIZE, i -> i+1) 20 | .forEach(i -> persons.add(Person.randomPerson())); 21 | } 22 | 23 | public int getAgeSum() { 24 | return persons.stream().mapToInt(Person::age).sum(); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/profiling/workshop/logging/AgedPersonRepository.java: -------------------------------------------------------------------------------- 1 | package profiling.workshop.logging; 2 | 3 | import jakarta.annotation.PostConstruct; 4 | import jakarta.inject.Inject; 5 | import jakarta.inject.Singleton; 6 | import java.time.Year; 7 | import java.util.Collection; 8 | import java.util.List; 9 | import java.util.UUID; 10 | import java.util.stream.Collectors; 11 | 12 | @Singleton 13 | public class AgedPersonRepository { 14 | 15 | @Inject 16 | LoggingService log; 17 | 18 | public record Person(long id, String name, short birthYear) { 19 | 20 | int age() { 21 | return Year.now().getValue() - birthYear; 22 | } 23 | 24 | } 25 | 26 | private static final int MAX_AGE = 80; 27 | private List persons; 28 | 29 | @PostConstruct 30 | public void init() { 31 | final Person[] persons = new Person[MAX_AGE]; 32 | final short oldestBirthYear = (short) (Year.now().getValue() - MAX_AGE); 33 | for (int i = 0; i < MAX_AGE; i++) { 34 | final short birthYear = (short) (oldestBirthYear + i); 35 | persons[i] = new Person(i + 1, UUID.randomUUID().toString(), birthYear); 36 | } 37 | this.persons = List.of(persons); 38 | } 39 | 40 | public Collection withAgeEqualsTo(int age) { 41 | final List sameAgePersons = persons.stream() 42 | .peek( person -> log.finest("Filtering person %s with age %d", person, person.age()) ) 43 | .filter( person -> person.age() == age ) 44 | .collect(Collectors.toList()); 45 | 46 | sameAgePersons.forEach(p -> log.finest("Found person %s with age %d", p, p.age())); 47 | return sameAgePersons; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/profiling/workshop/logging/LoggingService.java: -------------------------------------------------------------------------------- 1 | package profiling.workshop.logging; 2 | 3 | import jakarta.inject.Singleton; 4 | 5 | @Singleton 6 | public class LoggingService { 7 | 8 | private volatile boolean finest; 9 | 10 | public boolean isFinest() { 11 | return finest; 12 | } 13 | 14 | public void finest(boolean value) { 15 | finest = value; 16 | } 17 | 18 | public void finest(String msg, Object... args) { 19 | final String formatted = String.format(msg, args); 20 | if (finest) { 21 | System.err.println(formatted); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/profiling/workshop/logging/PersonByAgeResource.java: -------------------------------------------------------------------------------- 1 | package profiling.workshop.logging; 2 | 3 | import io.smallrye.common.annotation.NonBlocking; 4 | import profiling.workshop.logging.AgedPersonRepository.Person; 5 | 6 | import jakarta.inject.Inject; 7 | import jakarta.ws.rs.GET; 8 | import jakarta.ws.rs.Path; 9 | import jakarta.ws.rs.PathParam; 10 | import jakarta.ws.rs.Produces; 11 | import jakarta.ws.rs.core.MediaType; 12 | import java.util.Collection; 13 | 14 | /** 15 | * Use me with http://localhost:8080/persons/age/42 16 | */ 17 | @Path("/persons/age/{age}") 18 | public class PersonByAgeResource { 19 | 20 | @Inject 21 | AgedPersonRepository agedPersonRepository; 22 | 23 | @GET 24 | @Produces(MediaType.APPLICATION_JSON) 25 | @NonBlocking 26 | public Collection listByAge(@PathParam("age") int age) { 27 | return agedPersonRepository.withAgeEqualsTo(age); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/profiling/workshop/time/CachedTimeResource.java: -------------------------------------------------------------------------------- 1 | package profiling.workshop.time; 2 | 3 | import io.quarkus.scheduler.Scheduled; 4 | 5 | import jakarta.inject.Inject; 6 | import jakarta.ws.rs.GET; 7 | import jakarta.ws.rs.Path; 8 | import jakarta.ws.rs.Produces; 9 | import jakarta.ws.rs.core.MediaType; 10 | import java.time.ZoneId; 11 | import java.time.format.TextStyle; 12 | import java.util.Locale; 13 | import java.util.concurrent.CompletableFuture; 14 | import java.util.concurrent.CompletionStage; 15 | 16 | @Path("/time/cached") 17 | public class CachedTimeResource { 18 | 19 | @Inject 20 | TimeService timeService; 21 | 22 | private static final String ZONE_ID = ZoneId.systemDefault().getDisplayName(TextStyle.FULL, Locale.ITALY); 23 | private volatile CompletableFuture time = new CompletableFuture<>(); 24 | 25 | @Scheduled(every = "1s") 26 | public void updateTime() { 27 | var tick = new TimeResource.Tick(ZONE_ID, timeService.time()); 28 | if (time.isDone()) { 29 | time = CompletableFuture.completedFuture(tick); 30 | } else { 31 | time.complete(tick); 32 | } 33 | } 34 | 35 | @GET 36 | @Produces(MediaType.APPLICATION_JSON) 37 | public CompletionStage now() { 38 | return time; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/profiling/workshop/time/TimeResource.java: -------------------------------------------------------------------------------- 1 | package profiling.workshop.time; 2 | 3 | import io.quarkus.runtime.annotations.RegisterForReflection; 4 | 5 | import jakarta.inject.Inject; 6 | import jakarta.ws.rs.GET; 7 | import jakarta.ws.rs.Path; 8 | import jakarta.ws.rs.Produces; 9 | import jakarta.ws.rs.core.MediaType; 10 | import java.time.ZoneId; 11 | import java.time.format.TextStyle; 12 | import java.util.Locale; 13 | 14 | @Path("/time") 15 | public class TimeResource { 16 | 17 | @Inject 18 | TimeService service; 19 | 20 | @RegisterForReflection 21 | public record Tick(String zoneId, long utcTime) { } 22 | 23 | @GET 24 | @Produces(MediaType.APPLICATION_JSON) 25 | public Tick now() { 26 | return new Tick(ZoneId.systemDefault().getDisplayName(TextStyle.FULL, Locale.ITALY), service.time()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/profiling/workshop/time/TimeService.java: -------------------------------------------------------------------------------- 1 | package profiling.workshop.time; 2 | 3 | import jakarta.inject.Singleton; 4 | import java.util.OptionalLong; 5 | import java.util.concurrent.Semaphore; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | @Singleton 9 | public class TimeService { 10 | 11 | public TimeService() { 12 | 13 | } 14 | 15 | private final Semaphore maxConcurrentRequests = new Semaphore(4); 16 | 17 | public long time() { 18 | maxConcurrentRequests.acquireUninterruptibly(); 19 | try { 20 | TimeUnit.MILLISECONDS.sleep(10L); 21 | } catch (Throwable ignore) { 22 | // 23 | } 24 | try { 25 | return System.currentTimeMillis(); 26 | } finally { 27 | maxConcurrentRequests.release(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | quarkus.package.jar.decompiler.enabled=true --------------------------------------------------------------------------------