├── .github └── workflows │ └── maven.yml ├── .gitignore ├── .gitmodules ├── LICENSE.txt ├── README.md ├── docker-compose.yaml ├── example-service ├── .dockerignore ├── .mvn │ └── wrapper │ │ ├── MavenWrapperDownloader.java │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── docker │ │ ├── Dockerfile.jvm │ │ └── Dockerfile.native │ ├── java │ │ └── dev │ │ │ └── morling │ │ │ └── demos │ │ │ ├── jfr │ │ │ ├── FlightRecorderFilter.java │ │ │ ├── JaxRsInvocationEvent.java │ │ │ ├── Metrics.java │ │ │ └── PathFilterControl.java │ │ │ └── quarkus │ │ │ ├── Todo.java │ │ │ ├── TodoForm.java │ │ │ └── TodoResource.java │ └── resources │ │ ├── META-INF │ │ └── resources │ │ │ └── index.html │ │ ├── application.properties │ │ ├── init.sql │ │ └── templates │ │ ├── base.html │ │ ├── error.html │ │ ├── todo-form.html │ │ ├── todo.html │ │ └── todos.html │ └── test │ └── java │ └── dev │ └── morling │ └── demos │ └── quarkus │ ├── TodoResourceTest.java │ └── testutil │ ├── MatchesJson.java │ └── PostgresResource.java ├── grafana-all-dashboards.yml ├── grafana-datasource.yml ├── grafana-todo-dashboard.json ├── init.sql ├── jfr_datasource.png ├── jfr_grafana.png ├── jfr_jax_rs_events.png ├── prometheus.yml └── servers.json /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2021 The original authors 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | name: Build 18 | 19 | on: 20 | push: 21 | branches: [ master ] 22 | pull_request: 23 | branches: [ master ] 24 | 25 | jobs: 26 | build: 27 | runs-on: ubuntu-latest 28 | 29 | steps: 30 | - name: 'Check out repository' 31 | uses: actions/checkout@v2 32 | with: 33 | submodules: 'true' 34 | 35 | - name: 'Set up Java' 36 | uses: actions/setup-java@v2 37 | with: 38 | java-version: 17 39 | distribution: 'zulu' 40 | 41 | - name: 'Cache Maven packages' 42 | uses: actions/cache@v2 43 | with: 44 | path: ~/.m2 45 | key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} 46 | restore-keys: ${{ runner.os }}-m2 47 | 48 | - name: 'Build example service' 49 | run: mvn -B clean verify -f example-service/pom.xml 50 | 51 | - name: 'Build Grafana datasource' 52 | run: mvn -B clean verify -f jfr-datasource/pom.xml -DskipTests=true 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Eclipse 2 | .project 3 | .classpath 4 | .factorypath 5 | .settings/ 6 | bin/ 7 | 8 | # IntelliJ 9 | .idea 10 | *.ipr 11 | *.iml 12 | *.iws 13 | 14 | # NetBeans 15 | nb-configuration.xml 16 | 17 | # Visual Studio Code 18 | .vscode 19 | 20 | # OSX 21 | .DS_Store 22 | 23 | # Vim 24 | *.swp 25 | *.swo 26 | 27 | # patch 28 | *.orig 29 | *.rej 30 | 31 | # Maven 32 | target/ 33 | pom.xml.tag 34 | pom.xml.releaseBackup 35 | pom.xml.versionsBackup 36 | release.properties 37 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "path_to_submodule"] 2 | path = jfr-datasource 3 | url = https://github.com/gunnarmorling/jfr-datasource.git 4 | 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JDK Flight Recorder Custom Events 2 | 3 | Example code accompanying the [blog post](https://www.morling.dev/blog/rest-api-monitoring-with-custom-jdk-flight-recorder-events/) "Monitoring REST APIs with JDK Flight Recorder Custom Events". 4 | It shows how to use custom event types with JDK Flight Recorder and Mission Control to gain insight into runtime performance of a JAX-RS based REST API. 5 | It also demonstrates how to export Flight Recorder events in realtime via MicroProfile Metrics, using the JDK Flight Recorder event streaming API ([JEP 349](https://openjdk.java.net/jeps/349)) added in Java 14. 6 | 7 | ![Custom Flight Recorder Events in JDK Mission Control](jfr_jax_rs_events.png) 8 | 9 | ![Metrics for Flight Recorder Events in Grafana](jfr_grafana.png) 10 | 11 | Update Feb. 8th: the example has been expanded to show the usage of the [JFR datasource](https://github.com/rh-jmc-team/jfr-datasource) for Grafana. 12 | 13 | ## Checking Out the Sources 14 | 15 | Clone the repository by running 16 | 17 | ``` 18 | git clone git@github.com:gunnarmorling/jfr-custom-events.git --recurse-submodules 19 | ``` 20 | 21 | The `--recurse-submodules` ensures that the _jfr-datasource_ sub-module will be cloned as well. 22 | If you have cloned the repository omitting that option, you can run `git submodule update --init --recursive` for updating the sub-module. 23 | 24 | ## Build 25 | 26 | Make sure to have Java 17 installed. 27 | Run the following to build this project: 28 | 29 | ```shell 30 | # Example service 31 | mvn clean package -f example-service/pom.xml 32 | 33 | # JFR datasource for Grafana 34 | mvn clean package -f jfr-datasource/pom.xml 35 | 36 | docker-compose up --build 37 | ``` 38 | 39 | Open the web application at http://localhost:8080/. 40 | You then can connect to the running application on port 1898 using Mission Control, 41 | start Flight Recorder and observe "JAX-RS" events in the recording. 42 | You also can observe the exported metrics via Grafana at http://localhost:3000/. 43 | 44 | ### Dev Mode 45 | 46 | When working on the example service, it can be started in the Quarkus dev mode instead of packaging it as a container image for faster feedback; 47 | 48 | ```shell 49 | docker-compose up --build --scale example-service=0 50 | 51 | mvn compile quarkus:dev -f example-service/pom.xml 52 | ``` 53 | 54 | When starting a recording, make sure to use the process for _target/flight-recorder-demo-dev.jar_. 55 | 56 | ## Loading JFR Recordings into Grafana 57 | 58 | The JFR datasource has been setup in Grafana. 59 | To examine a JFR recording, load it into the datasource: 60 | 61 | ```shell 62 | curl -F "file=@/path/to/my/recording.jfr" localhost:8081/load 63 | ``` 64 | 65 | Then open the "JFR Events" dashboard in Grafana and zoom into the time range of the recording. 66 | 67 | ![JFR Datasource in Grafana](jfr_datasource.png) 68 | 69 | # License 70 | 71 | This code base is available ander the Apache License, version 2. 72 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | todo-db: 4 | image: postgres:13 5 | ports: 6 | - "5432:5432" 7 | environment: 8 | POSTGRES_USER: todouser 9 | POSTGRES_PASSWORD: todopw 10 | POSTGRES_DB: tododb 11 | volumes: 12 | - ./init.sql:/docker-entrypoint-initdb.d/init.sql 13 | networks: 14 | - my-network 15 | 16 | pgadmin: 17 | container_name: pgadmin_container 18 | image: dpage/pgadmin4 19 | environment: 20 | PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-pgadmin4@pgadmin.org} 21 | PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin} 22 | volumes: 23 | - ./servers.json:/pgadmin4/servers.json 24 | ports: 25 | - "${PGADMIN_PORT:-5050}:80" 26 | networks: 27 | - my-network 28 | 29 | example-service: 30 | image: dev.morling/flight-recorder-example-service:1.0 31 | build: 32 | context: ./example-service 33 | dockerfile: src/main/docker/Dockerfile.${QUARKUS_MODE:-jvm} 34 | ports: 35 | - 8080:8080 36 | - 1898:1898 37 | environment: 38 | QUARKUS_DATASOURCE_URL: jdbc:postgresql://todo-db:5432/tododb 39 | links: 40 | - todo-db 41 | networks: 42 | - my-network 43 | 44 | prometheus: 45 | image: prom/prometheus:latest 46 | ports: 47 | - 9090:9090 48 | links: 49 | - example-service 50 | volumes: 51 | - ./prometheus.yml:/etc/prometheus/prometheus.yml 52 | networks: 53 | - my-network 54 | 55 | grafana: 56 | image: grafana/grafana:latest 57 | ports: 58 | - 3000:3000 59 | links: 60 | - prometheus 61 | - jfr-data-source 62 | volumes: 63 | - ./grafana-datasource.yml:/etc/grafana/provisioning/datasources/datasource.yml 64 | - ./grafana-all-dashboards.yml:/etc/grafana/provisioning/dashboards/all.yml 65 | - ./grafana-todo-dashboard.json:/var/lib/grafana/dashboards/grafana-todo-dashboard.json 66 | - ./jfr-datasource/dashboards/dashboard.json:/var/lib/grafana/dashboards/grafana-jfr-dashboard.json 67 | environment: 68 | GF_INSTALL_PLUGINS: grafana-simple-json-datasource 69 | networks: 70 | - my-network 71 | 72 | jfr-data-source: 73 | image: dev.morling/jfr-data-source:1.0 74 | build: 75 | context: ./jfr-datasource 76 | dockerfile: docker/Dockerfile.jvm 77 | ports: 78 | - 8081:8080 79 | networks: 80 | - my-network 81 | 82 | networks: 83 | my-network: 84 | name: flight-recorder-network 85 | -------------------------------------------------------------------------------- /example-service/.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !target/*-runner 3 | !target/*-runner.jar 4 | !target/lib/* 5 | !target/quarkus-app/* -------------------------------------------------------------------------------- /example-service/.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import java.io.File; 18 | import java.io.FileInputStream; 19 | import java.io.FileOutputStream; 20 | import java.io.IOException; 21 | import java.net.Authenticator; 22 | import java.net.PasswordAuthentication; 23 | import java.net.URL; 24 | import java.nio.channels.Channels; 25 | import java.nio.channels.ReadableByteChannel; 26 | import java.util.Properties; 27 | 28 | public class MavenWrapperDownloader { 29 | 30 | private static final String WRAPPER_VERSION = "0.5.5"; 31 | /** 32 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 33 | */ 34 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 35 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 36 | 37 | /** 38 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 39 | * use instead of the default one. 40 | */ 41 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 42 | ".mvn/wrapper/maven-wrapper.properties"; 43 | 44 | /** 45 | * Path where the maven-wrapper.jar will be saved to. 46 | */ 47 | private static final String MAVEN_WRAPPER_JAR_PATH = 48 | ".mvn/wrapper/maven-wrapper.jar"; 49 | 50 | /** 51 | * Name of the property which should be used to override the default download url for the wrapper. 52 | */ 53 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 54 | 55 | public static void main(String args[]) { 56 | System.out.println("- Downloader started"); 57 | File baseDirectory = new File(args[0]); 58 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 59 | 60 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 61 | // wrapperUrl parameter. 62 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 63 | String url = DEFAULT_DOWNLOAD_URL; 64 | if(mavenWrapperPropertyFile.exists()) { 65 | FileInputStream mavenWrapperPropertyFileInputStream = null; 66 | try { 67 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 68 | Properties mavenWrapperProperties = new Properties(); 69 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 70 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 71 | } catch (IOException e) { 72 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 73 | } finally { 74 | try { 75 | if(mavenWrapperPropertyFileInputStream != null) { 76 | mavenWrapperPropertyFileInputStream.close(); 77 | } 78 | } catch (IOException e) { 79 | // Ignore ... 80 | } 81 | } 82 | } 83 | System.out.println("- Downloading from: " + url); 84 | 85 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 86 | if(!outputFile.getParentFile().exists()) { 87 | if(!outputFile.getParentFile().mkdirs()) { 88 | System.out.println( 89 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 90 | } 91 | } 92 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 93 | try { 94 | downloadFileFromURL(url, outputFile); 95 | System.out.println("Done"); 96 | System.exit(0); 97 | } catch (Throwable e) { 98 | System.out.println("- Error downloading"); 99 | e.printStackTrace(); 100 | System.exit(1); 101 | } 102 | } 103 | 104 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 105 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 106 | String username = System.getenv("MVNW_USERNAME"); 107 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 108 | Authenticator.setDefault(new Authenticator() { 109 | @Override 110 | protected PasswordAuthentication getPasswordAuthentication() { 111 | return new PasswordAuthentication(username, password); 112 | } 113 | }); 114 | } 115 | URL website = new URL(urlString); 116 | ReadableByteChannel rbc; 117 | rbc = Channels.newChannel(website.openStream()); 118 | FileOutputStream fos = new FileOutputStream(destination); 119 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 120 | fos.close(); 121 | rbc.close(); 122 | } 123 | 124 | } 125 | -------------------------------------------------------------------------------- /example-service/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gunnarmorling/jfr-custom-events/a907ddb1bdc9f7e43e951d8f637284f49daceeca/example-service/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /example-service/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.1/apache-maven-3.6.1-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar 3 | -------------------------------------------------------------------------------- /example-service/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 | # http://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 | # Maven2 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 /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | fi 118 | 119 | if [ -z "$JAVA_HOME" ]; then 120 | javaExecutable="`which javac`" 121 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 122 | # readlink(1) is not available as standard on Solaris 10. 123 | readLink=`which readlink` 124 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 125 | if $darwin ; then 126 | javaHome="`dirname \"$javaExecutable\"`" 127 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 128 | else 129 | javaExecutable="`readlink -f \"$javaExecutable\"`" 130 | fi 131 | javaHome="`dirname \"$javaExecutable\"`" 132 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 133 | JAVA_HOME="$javaHome" 134 | export JAVA_HOME 135 | fi 136 | fi 137 | fi 138 | 139 | if [ -z "$JAVACMD" ] ; then 140 | if [ -n "$JAVA_HOME" ] ; then 141 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 142 | # IBM's JDK on AIX uses strange locations for the executables 143 | JAVACMD="$JAVA_HOME/jre/sh/java" 144 | else 145 | JAVACMD="$JAVA_HOME/bin/java" 146 | fi 147 | else 148 | JAVACMD="`which java`" 149 | fi 150 | fi 151 | 152 | if [ ! -x "$JAVACMD" ] ; then 153 | echo "Error: JAVA_HOME is not defined correctly." >&2 154 | echo " We cannot execute $JAVACMD" >&2 155 | exit 1 156 | fi 157 | 158 | if [ -z "$JAVA_HOME" ] ; then 159 | echo "Warning: JAVA_HOME environment variable is not set." 160 | fi 161 | 162 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 163 | 164 | # traverses directory structure from process work directory to filesystem root 165 | # first directory with .mvn subdirectory is considered project base directory 166 | find_maven_basedir() { 167 | 168 | if [ -z "$1" ] 169 | then 170 | echo "Path not specified to find_maven_basedir" 171 | return 1 172 | fi 173 | 174 | basedir="$1" 175 | wdir="$1" 176 | while [ "$wdir" != '/' ] ; do 177 | if [ -d "$wdir"/.mvn ] ; then 178 | basedir=$wdir 179 | break 180 | fi 181 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 182 | if [ -d "${wdir}" ]; then 183 | wdir=`cd "$wdir/.."; pwd` 184 | fi 185 | # end of workaround 186 | done 187 | echo "${basedir}" 188 | } 189 | 190 | # concatenates all lines of a file 191 | concat_lines() { 192 | if [ -f "$1" ]; then 193 | echo "$(tr -s '\n' ' ' < "$1")" 194 | fi 195 | } 196 | 197 | BASE_DIR=`find_maven_basedir "$(pwd)"` 198 | if [ -z "$BASE_DIR" ]; then 199 | exit 1; 200 | fi 201 | 202 | ########################################################################################## 203 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 204 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 205 | ########################################################################################## 206 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 207 | if [ "$MVNW_VERBOSE" = true ]; then 208 | echo "Found .mvn/wrapper/maven-wrapper.jar" 209 | fi 210 | else 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 213 | fi 214 | if [ -n "$MVNW_REPOURL" ]; then 215 | jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" 216 | else 217 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" 218 | fi 219 | while IFS="=" read key value; do 220 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 221 | esac 222 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 223 | if [ "$MVNW_VERBOSE" = true ]; then 224 | echo "Downloading from: $jarUrl" 225 | fi 226 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 227 | if $cygwin; then 228 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 229 | fi 230 | 231 | if command -v wget > /dev/null; then 232 | if [ "$MVNW_VERBOSE" = true ]; then 233 | echo "Found wget ... using wget" 234 | fi 235 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 236 | wget "$jarUrl" -O "$wrapperJarPath" 237 | else 238 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" 239 | fi 240 | elif command -v curl > /dev/null; then 241 | if [ "$MVNW_VERBOSE" = true ]; then 242 | echo "Found curl ... using curl" 243 | fi 244 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 245 | curl -o "$wrapperJarPath" "$jarUrl" -f 246 | else 247 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 248 | fi 249 | 250 | else 251 | if [ "$MVNW_VERBOSE" = true ]; then 252 | echo "Falling back to using Java to download" 253 | fi 254 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 255 | # For Cygwin, switch paths to Windows format before running javac 256 | if $cygwin; then 257 | javaClass=`cygpath --path --windows "$javaClass"` 258 | fi 259 | if [ -e "$javaClass" ]; then 260 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 261 | if [ "$MVNW_VERBOSE" = true ]; then 262 | echo " - Compiling MavenWrapperDownloader.java ..." 263 | fi 264 | # Compiling the Java class 265 | ("$JAVA_HOME/bin/javac" "$javaClass") 266 | fi 267 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 268 | # Running the downloader 269 | if [ "$MVNW_VERBOSE" = true ]; then 270 | echo " - Running MavenWrapperDownloader.java ..." 271 | fi 272 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 273 | fi 274 | fi 275 | fi 276 | fi 277 | ########################################################################################## 278 | # End of extension 279 | ########################################################################################## 280 | 281 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 282 | if [ "$MVNW_VERBOSE" = true ]; then 283 | echo $MAVEN_PROJECTBASEDIR 284 | fi 285 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 286 | 287 | # For Cygwin, switch paths to Windows format before running java 288 | if $cygwin; then 289 | [ -n "$M2_HOME" ] && 290 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 291 | [ -n "$JAVA_HOME" ] && 292 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 293 | [ -n "$CLASSPATH" ] && 294 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 295 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 296 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 297 | fi 298 | 299 | # Provide a "standardized" way to retrieve the CLI args that will 300 | # work with both Windows and non-Windows executions. 301 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 302 | export MAVEN_CMD_LINE_ARGS 303 | 304 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 305 | 306 | exec "$JAVACMD" \ 307 | $MAVEN_OPTS \ 308 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 309 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 310 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 311 | -------------------------------------------------------------------------------- /example-service/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 http://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 Maven2 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 key stroke 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 "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\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/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" 124 | 125 | FOR /F "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%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.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% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 162 | if ERRORLEVEL 1 goto error 163 | goto end 164 | 165 | :error 166 | set ERROR_CODE=1 167 | 168 | :end 169 | @endlocal & set ERROR_CODE=%ERROR_CODE% 170 | 171 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 172 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 173 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 174 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 175 | :skipRcPost 176 | 177 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 178 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 179 | 180 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 181 | 182 | exit /B %ERROR_CODE% 183 | -------------------------------------------------------------------------------- /example-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | dev.morling.demos 6 | flight-recorder-demo 7 | 1.0.0-SNAPSHOT 8 | 9 | 3.8.1 10 | true 11 | 17 12 | 17 13 | UTF-8 14 | UTF-8 15 | quarkus-bom 16 | io.quarkus.platform 17 | 2.4.2.Final 18 | 3.0.0-M5 19 | 20 | 21 | 22 | 23 | ${quarkus.platform.group-id} 24 | ${quarkus.platform.artifact-id} 25 | ${quarkus.platform.version} 26 | pom 27 | import 28 | 29 | 30 | 31 | 32 | 33 | io.quarkus 34 | quarkus-resteasy 35 | 36 | 37 | io.quarkus 38 | quarkus-resteasy-qute 39 | 40 | 41 | io.quarkus 42 | quarkus-resteasy-jsonb 43 | 44 | 45 | org.jboss.resteasy 46 | resteasy-multipart-provider 47 | 48 | 49 | io.quarkus 50 | quarkus-jdbc-postgresql 51 | 52 | 53 | io.quarkus 54 | quarkus-hibernate-orm-panache 55 | 56 | 57 | io.quarkus 58 | quarkus-smallrye-metrics 59 | 60 | 61 | 62 | io.quarkus 63 | quarkus-junit5 64 | test 65 | 66 | 67 | io.rest-assured 68 | rest-assured 69 | test 70 | 71 | 72 | org.assertj 73 | assertj-core 74 | test 75 | 76 | 77 | org.testcontainers 78 | junit-jupiter 79 | 1.16.0 80 | 81 | 82 | org.testcontainers 83 | postgresql 84 | 1.16.0 85 | 86 | 87 | org.skyscreamer 88 | jsonassert 89 | 1.5.0 90 | test 91 | 92 | 93 | 94 | 95 | 96 | ${quarkus.platform.group-id} 97 | quarkus-maven-plugin 98 | ${quarkus.platform.version} 99 | true 100 | 101 | 102 | 103 | build 104 | generate-code 105 | generate-code-tests 106 | 107 | 108 | 109 | 110 | 111 | maven-compiler-plugin 112 | ${compiler-plugin.version} 113 | 114 | ${maven.compiler.parameters} 115 | 116 | 117 | 118 | maven-surefire-plugin 119 | ${surefire-plugin.version} 120 | 121 | 122 | org.jboss.logmanager.LogManager 123 | ${maven.home} 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | native 132 | 133 | 134 | native 135 | 136 | 137 | 138 | 139 | 140 | maven-failsafe-plugin 141 | ${surefire-plugin.version} 142 | 143 | 144 | 145 | integration-test 146 | verify 147 | 148 | 149 | 150 | ${project.build.directory}/${project.build.finalName}-runner 151 | org.jboss.logmanager.LogManager 152 | ${maven.home} 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | native 162 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /example-service/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 docker image run: 5 | # 6 | # mvn package 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.jvm -t quarkus/flight-recorder-demo-jvm . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/flight-recorder-demo-jvm 15 | # 16 | ### 17 | # FROM fabric8/java-alpine-openjdk8-jre:1.6.5 18 | # ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" 19 | # ENV AB_ENABLED=jmx_exporter 20 | 21 | # Be prepared for running in OpenShift too 22 | # RUN adduser -G root --no-create-home --disabled-password 1001 \ 23 | # && chown -R 1001 /deployments \ 24 | # && chmod -R "g+rwX" /deployments \ 25 | # && chown -R 1001:root /deployments 26 | 27 | #FROM fedora:32 28 | # 29 | #RUN mkdir /tmp/jdk \ 30 | # && cd /tmp/jdk \ 31 | # && curl -O https://download.java.net/java/GA/jdk15/779bf45e88a44cbd9ea6621d33e33db1/36/GPL/openjdk-15_linux-x64_bin.tar.gz \ 32 | # && tar -xvf openjdk-15_linux-x64_bin.tar.gz 33 | # 34 | #COPY target/lib/* /deployments/lib/ 35 | #COPY target/*-runner.jar /deployments/app.jar 36 | #EXPOSE 8080 37 | # 38 | ## run with user 1001 39 | ## USER 1001 40 | # 41 | #ENTRYPOINT [ "/tmp/jdk/jdk-15/bin/java", "-Dcom.sun.management.jmxremote", "-Dcom.sun.management.jmxremote.port=1898", "-Dcom.sun.management.jmxremote.rmi.port=1898", "-Djava.rmi.server.hostname=0.0.0.0", "-Dcom.sun.management.jmxremote.ssl=false", "-Dcom.sun.management.jmxremote.authenticate=false", "-Dcom.sun.management.jmxremote.local.only=false", "-jar", "/deployments/app.jar", "-Dquarkus.http.host=0.0.0.0", "-Djava.util.logging.manager=org.jboss.logmanager.LogManager" ] 42 | 43 | 44 | FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4 45 | 46 | ARG JAVA_PACKAGE=java-17-openjdk-headless 47 | ARG RUN_JAVA_VERSION=1.3.8 48 | ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' 49 | # Install java and the run-java script 50 | # Also set up permissions for user `1001` 51 | #RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \ 52 | RUN microdnf install curl tar gzip ca-certificates \ 53 | && mkdir /tmp/jdk \ 54 | && cd /tmp/jdk \ 55 | && curl --output openjdk-17_linux-x64_bin.tar.gz https://download.java.net/java/GA/jdk17/0d483333a00540d886896bac774ff48b/35/GPL/openjdk-17_linux-x64_bin.tar.gz \ 56 | && tar xzf openjdk-17_linux-x64_bin.tar.gz \ 57 | && rm -f openjdk-17_linux-x64_bin.tar.gz \ 58 | && microdnf update \ 59 | && microdnf clean all \ 60 | && mkdir /deployments \ 61 | && chown 1001 /deployments \ 62 | && chmod "g+rwX" /deployments \ 63 | && chown 1001:root /deployments \ 64 | && curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \ 65 | && chown 1001 /deployments/run-java.sh \ 66 | && chmod 540 /deployments/run-java.sh \ 67 | && echo "securerandom.source=file:/dev/urandom" >> /tmp/jdk/jdk-17/conf/security/java.security 68 | 69 | # Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size. 70 | ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" 71 | # We make four distinct layers so if there are application changes the library layers can be re-used 72 | #COPY --chown=1001 target/quarkus-app/lib/ /deployments/lib/ 73 | COPY --chown=1001 target/*.jar /deployments/app.jar 74 | #COPY --chown=1001 target/quarkus-app/app/ /deployments/app/ 75 | #COPY --chown=1001 target/quarkus-app/quarkus/ /deployments/quarkus/ 76 | 77 | EXPOSE 8080 78 | USER 1001 79 | 80 | #ENTRYPOINT [ "/deployments/run-java.sh" ] 81 | ENTRYPOINT [ "/tmp/jdk/jdk-17/bin/java", "-Dquarkus.datasource.jdbc.url=jdbc:postgresql://todo-db:5432/tododb", "-Dcom.sun.management.jmxremote", "-Dcom.sun.management.jmxremote.port=1898", "-Dcom.sun.management.jmxremote.rmi.port=1898", "-Djava.rmi.server.hostname=0.0.0.0", "-Dcom.sun.management.jmxremote.ssl=false", "-Dcom.sun.management.jmxremote.authenticate=false", "-Dcom.sun.management.jmxremote.local.only=false", "-jar", "/deployments/app.jar", "-Dquarkus.http.host=0.0.0.0", "-Djava.util.logging.manager=org.jboss.logmanager.LogManager" ] -------------------------------------------------------------------------------- /example-service/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 docker image run: 5 | # 6 | # mvn package -Pnative -Dquarkus.native.container-build=true 7 | # 8 | # Then, build the image with: 9 | # 10 | # docker build -f src/main/docker/Dockerfile.native -t quarkus/flight-recorder-demo . 11 | # 12 | # Then run the container using: 13 | # 14 | # docker run -i --rm -p 8080:8080 quarkus/flight-recorder-demo 15 | # 16 | ### 17 | FROM registry.access.redhat.com/ubi8/ubi-minimal 18 | WORKDIR /work/ 19 | COPY target/*-runner /work/application 20 | RUN chmod 775 /work 21 | EXPOSE 8080 22 | CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] -------------------------------------------------------------------------------- /example-service/src/main/java/dev/morling/demos/jfr/FlightRecorderFilter.java: -------------------------------------------------------------------------------- 1 | package dev.morling.demos.jfr; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.ws.rs.container.ContainerRequestContext; 6 | import javax.ws.rs.container.ContainerRequestFilter; 7 | import javax.ws.rs.container.ContainerResponseContext; 8 | import javax.ws.rs.container.ContainerResponseFilter; 9 | import javax.ws.rs.ext.Provider; 10 | 11 | import org.jboss.resteasy.core.ResourceMethodInvoker; 12 | 13 | @Provider 14 | public class FlightRecorderFilter implements ContainerRequestFilter, ContainerResponseFilter { 15 | 16 | @Override 17 | public void filter(ContainerRequestContext requestContext) throws IOException { 18 | JaxRsInvocationEvent event = new JaxRsInvocationEvent(); 19 | 20 | if (!event.isEnabled()) { 21 | return; 22 | } 23 | 24 | event.begin(); 25 | 26 | requestContext.setProperty(JaxRsInvocationEvent.NAME, event); 27 | } 28 | 29 | @Override 30 | public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) 31 | throws IOException { 32 | JaxRsInvocationEvent event = (JaxRsInvocationEvent) requestContext.getProperty(JaxRsInvocationEvent.NAME); 33 | 34 | if (event == null || !event.isEnabled()) { 35 | return; 36 | } 37 | 38 | event.end(); 39 | event.path = String.valueOf(requestContext.getUriInfo().getPath()); 40 | 41 | if (event.shouldCommit()) { 42 | event.method = requestContext.getMethod(); 43 | event.mediaType = String.valueOf(requestContext.getMediaType()); 44 | event.length = requestContext.getLength(); 45 | event.queryParameters = requestContext.getUriInfo().getQueryParameters().toString(); 46 | event.headers = requestContext.getHeaders().toString(); 47 | event.javaMethod = getJavaMethod(requestContext); 48 | event.responseLength = responseContext.getLength(); 49 | event.responseHeaders = responseContext.getHeaders().toString(); 50 | event.status = responseContext.getStatus(); 51 | 52 | event.commit(); 53 | } 54 | } 55 | 56 | private String getJavaMethod(ContainerRequestContext requestContext) { 57 | String propName = "org.jboss.resteasy.core.ResourceMethodInvoker"; 58 | ResourceMethodInvoker invoker = (ResourceMethodInvoker)requestContext.getProperty(propName); 59 | return invoker.getMethod().toString(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /example-service/src/main/java/dev/morling/demos/jfr/JaxRsInvocationEvent.java: -------------------------------------------------------------------------------- 1 | package dev.morling.demos.jfr; 2 | 3 | import jdk.jfr.Category; 4 | import jdk.jfr.DataAmount; 5 | import jdk.jfr.Description; 6 | import jdk.jfr.Event; 7 | import jdk.jfr.Label; 8 | import jdk.jfr.Name; 9 | import jdk.jfr.SettingDefinition; 10 | import jdk.jfr.StackTrace; 11 | 12 | @Name(JaxRsInvocationEvent.NAME) 13 | @Label("JAX-RS Invocation") 14 | @Category("JAX-RS") 15 | @Description("Invocation of a JAX-RS resource method") 16 | @StackTrace(false) 17 | class JaxRsInvocationEvent extends Event { 18 | 19 | static final String NAME = "dev.morling.jfr.JaxRsInvocation"; 20 | 21 | @Label("Resource Method") 22 | public String method; 23 | 24 | @Label("Media Type") 25 | public String mediaType; 26 | 27 | @Label("Java Method") 28 | public String javaMethod; 29 | 30 | @Label("Path") 31 | public String path; 32 | 33 | @Label("Query Parameters") 34 | public String queryParameters; 35 | 36 | @Label("Headers") 37 | public String headers; 38 | 39 | @Label("Length") 40 | @DataAmount 41 | public int length; 42 | 43 | @Label("Response Headers") 44 | public String responseHeaders; 45 | 46 | @Label("Response Length") 47 | public int responseLength; 48 | 49 | @Label("Response Status") 50 | public int status; 51 | 52 | @Label("Path Filter") 53 | @SettingDefinition 54 | protected boolean pathFilter(PathFilterControl pathFilter) { 55 | return pathFilter.matches(path); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /example-service/src/main/java/dev/morling/demos/jfr/Metrics.java: -------------------------------------------------------------------------------- 1 | package dev.morling.demos.jfr; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | import javax.enterprise.context.ApplicationScoped; 6 | import javax.enterprise.event.Observes; 7 | import javax.inject.Inject; 8 | 9 | import org.eclipse.microprofile.metrics.Metadata; 10 | import org.eclipse.microprofile.metrics.MetricRegistry; 11 | import org.eclipse.microprofile.metrics.MetricType; 12 | 13 | import io.quarkus.runtime.ShutdownEvent; 14 | import io.quarkus.runtime.StartupEvent; 15 | import jdk.jfr.FlightRecorder; 16 | import jdk.jfr.consumer.RecordingStream; 17 | 18 | @ApplicationScoped 19 | public class Metrics { 20 | 21 | private RecordingStream recordingStream; 22 | 23 | @Inject 24 | MetricRegistry metricsRegistry; 25 | 26 | public void registerEvent(@Observes StartupEvent se) { 27 | FlightRecorder.register(JaxRsInvocationEvent.class); 28 | System.out.println("#### Registered"); 29 | } 30 | 31 | public void onStartup(@Observes StartupEvent se) { 32 | recordingStream = new RecordingStream(); 33 | recordingStream.enable(JaxRsInvocationEvent.NAME); 34 | 35 | recordingStream.onEvent(JaxRsInvocationEvent.NAME, event -> { 36 | 37 | String path = event.getString("path").replaceAll("(\\/)([0-9]+)(\\/?)", "$1{param}$3"); 38 | String method = event.getString("method"); 39 | String name = path + "-" + method; 40 | 41 | Metadata metadata = metricsRegistry.getMetadata().get(name); 42 | if (metadata == null) { 43 | metricsRegistry.timer(Metadata.builder() 44 | .withName(name) 45 | .withType(MetricType.TIMER) 46 | .withDescription("Metrics for " + path + " (" + method + ")") 47 | .build()).update(event.getDuration()); 48 | } 49 | else { 50 | metricsRegistry.timer(name).update(event.getDuration()); 51 | } 52 | }); 53 | recordingStream.startAsync(); 54 | } 55 | 56 | public void stop(@Observes ShutdownEvent se) { 57 | recordingStream.close(); 58 | try { 59 | recordingStream.awaitTermination(); 60 | } 61 | catch (InterruptedException e) { 62 | throw new RuntimeException(e); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /example-service/src/main/java/dev/morling/demos/jfr/PathFilterControl.java: -------------------------------------------------------------------------------- 1 | package dev.morling.demos.jfr; 2 | 3 | import java.util.Set; 4 | import java.util.regex.Pattern; 5 | 6 | import jdk.jfr.SettingControl; 7 | 8 | public class PathFilterControl extends SettingControl { 9 | 10 | private Pattern pattern = Pattern.compile(".*"); 11 | 12 | @Override 13 | public void setValue(String value) { 14 | System.out.println("SetValue #### " + value); 15 | this.pattern = Pattern.compile(value); 16 | } 17 | 18 | @Override 19 | public String combine(Set values) { 20 | System.out.println("Combine ##### " + values); 21 | return String.join("|", values); 22 | } 23 | 24 | @Override 25 | public String getValue() { 26 | System.out.println("GetValue ##### " + pattern); 27 | return pattern.toString(); 28 | } 29 | 30 | public boolean matches(String s) { 31 | System.out.println("Matches #### " + pattern + " " + s); 32 | return pattern.matcher(s).matches(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example-service/src/main/java/dev/morling/demos/quarkus/Todo.java: -------------------------------------------------------------------------------- 1 | package dev.morling.demos.quarkus; 2 | 3 | import io.quarkus.hibernate.orm.panache.PanacheEntity; 4 | 5 | import javax.persistence.Entity; 6 | 7 | @Entity 8 | public class Todo extends PanacheEntity { 9 | 10 | public String title; 11 | public int priority; 12 | public boolean completed; 13 | } 14 | -------------------------------------------------------------------------------- /example-service/src/main/java/dev/morling/demos/quarkus/TodoForm.java: -------------------------------------------------------------------------------- 1 | package dev.morling.demos.quarkus; 2 | 3 | import javax.ws.rs.FormParam; 4 | 5 | public class TodoForm { 6 | 7 | public @FormParam("title") String title; 8 | public @FormParam("completed") String completed; 9 | public @FormParam("priority") String priority; 10 | 11 | public Todo convertIntoTodo() { 12 | Todo todo = new Todo(); 13 | todo.title = title; 14 | todo.completed = "on".equals(completed); 15 | todo.priority = Integer.parseInt(priority); 16 | return todo; 17 | } 18 | 19 | public Todo updateTodo(Todo toUpdate) { 20 | toUpdate.title = title; 21 | toUpdate.completed = "on".equals(completed); 22 | toUpdate.priority = Integer.parseInt(priority); 23 | return toUpdate; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /example-service/src/main/java/dev/morling/demos/quarkus/TodoResource.java: -------------------------------------------------------------------------------- 1 | package dev.morling.demos.quarkus; 2 | 3 | import java.net.URI; 4 | import java.util.List; 5 | import java.util.stream.Collectors; 6 | import java.util.stream.IntStream; 7 | 8 | import javax.inject.Inject; 9 | import javax.transaction.Transactional; 10 | import javax.ws.rs.Consumes; 11 | import javax.ws.rs.GET; 12 | import javax.ws.rs.POST; 13 | import javax.ws.rs.Path; 14 | import javax.ws.rs.PathParam; 15 | import javax.ws.rs.Produces; 16 | import javax.ws.rs.QueryParam; 17 | import javax.ws.rs.core.MediaType; 18 | import javax.ws.rs.core.Response; 19 | 20 | import org.jboss.resteasy.annotations.providers.multipart.MultipartForm; 21 | 22 | import io.quarkus.panache.common.Sort; 23 | import io.quarkus.panache.common.Sort.Direction; 24 | import io.quarkus.qute.Template; 25 | import io.quarkus.qute.TemplateInstance; 26 | 27 | @Path("/todo") 28 | public class TodoResource { 29 | 30 | @Inject 31 | Template error; 32 | 33 | @Inject 34 | Template todo; 35 | 36 | @Inject 37 | Template todos; 38 | 39 | final List priorities = IntStream.range(1, 6).boxed().collect(Collectors.toList()); 40 | 41 | @GET 42 | @Consumes(MediaType.TEXT_HTML) 43 | @Produces(MediaType.TEXT_HTML) 44 | public TemplateInstance listTodos(@QueryParam("filter") String filter) { 45 | return todos.data("todos", find(filter)) 46 | .data("todo", new Todo()) 47 | .data("update", false) 48 | .data("priorities", priorities) 49 | .data("filter", filter) 50 | .data("filtered", filter != null && !filter.isEmpty()); 51 | } 52 | 53 | @GET 54 | @Produces(MediaType.APPLICATION_JSON) 55 | @Consumes(MediaType.APPLICATION_JSON) 56 | public List listTodosJson(@QueryParam("filter") String filter) { 57 | return find(filter); 58 | } 59 | 60 | private List find(String filter) { 61 | Sort sort = Sort.ascending("completed") 62 | .and("priority", Direction.Descending) 63 | .and("title", Direction.Ascending); 64 | 65 | if (filter != null && !filter.isEmpty()) { 66 | return Todo.find("LOWER(title) LIKE LOWER(?1)", sort, "%" + filter + "%").list(); 67 | } 68 | else { 69 | return Todo.findAll(sort).list(); 70 | } 71 | } 72 | 73 | @POST 74 | @Consumes(MediaType.MULTIPART_FORM_DATA) 75 | @Transactional 76 | @Path("/new") 77 | public Response addTodo(@MultipartForm TodoForm todoForm) { 78 | Todo todo = todoForm.convertIntoTodo(); 79 | todo.persist(); 80 | 81 | return Response.status(301) 82 | .location(URI.create("/todo")) 83 | .build(); 84 | } 85 | 86 | @POST 87 | @Consumes(MediaType.APPLICATION_JSON) 88 | @Produces(MediaType.APPLICATION_JSON) 89 | @Transactional 90 | public Response addTodo(Todo todo) { 91 | todo.persist(); 92 | 93 | return Response.created(URI.create("/todo/" + todo.id)) 94 | .entity(todo) 95 | .build(); 96 | } 97 | 98 | @GET 99 | @Produces(MediaType.TEXT_HTML) 100 | @Path("/{id}/edit") 101 | public TemplateInstance updateForm(@PathParam("id") long id) { 102 | Todo loaded = Todo.findById(id); 103 | 104 | if (loaded == null) { 105 | return error.data("error", "Todo with id " + id + " does not exist."); 106 | } 107 | 108 | return todo.data("todo", loaded) 109 | .data("priorities", priorities) 110 | .data("update", true); 111 | } 112 | 113 | @POST 114 | @Consumes(MediaType.MULTIPART_FORM_DATA) 115 | @Transactional 116 | @Path("/{id}/edit") 117 | public Object updateTodo( 118 | @PathParam("id") long id, 119 | @MultipartForm TodoForm todoForm) { 120 | 121 | Todo loaded = Todo.findById(id); 122 | 123 | if (loaded == null) { 124 | return error.data("error", "Todo with id " + id + " has been deleted after loading this form."); 125 | } 126 | 127 | loaded = todoForm.updateTodo(loaded); 128 | 129 | return Response.status(301) 130 | .location(URI.create("/todo")) 131 | .build(); 132 | } 133 | 134 | @POST 135 | @Transactional 136 | @Path("/{id}/delete") 137 | public Response deleteTodo(@PathParam("id") long id) { 138 | Todo.delete("id", id); 139 | 140 | return Response.status(301) 141 | .location(URI.create("/todo")) 142 | .build(); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /example-service/src/main/resources/META-INF/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | quarkus.datasource.db-kind=postgresql 2 | quarkus.datasource.username=todouser 3 | quarkus.datasource.password=todopw 4 | quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/tododb 5 | 6 | quarkus.hibernate-orm.log.sql=true 7 | quarkus.hibernate-orm.database.generation=drop-and-create 8 | # quarkus.hibernate-orm.database.generation=update 9 | quarkus.hibernate-orm.database.default-schema=todo 10 | 11 | quarkus.package.type=uber-jar 12 | 13 | quarkus.native.enable-vm-inspection=true 14 | -------------------------------------------------------------------------------- /example-service/src/main/resources/init.sql: -------------------------------------------------------------------------------- 1 | create schema todo; 2 | 3 | -------------------------------------------------------------------------------- /example-service/src/main/resources/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | {#insert title}Default Title{/} 15 | 16 | 26 | 27 | 28 |
29 |

{#insert title}Default Title{/}

30 | 31 | {#insert contents}No contents!{/} 32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /example-service/src/main/resources/templates/error.html: -------------------------------------------------------------------------------- 1 | {#include base.html} 2 | {#title}Uh Oh{/title} 3 | {#contents} 4 |
5 | 8 | Home 9 |
10 | {/contents} 11 | {/include} 12 | -------------------------------------------------------------------------------- /example-service/src/main/resources/templates/todo-form.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 | 7 |
8 | 9 |
10 | 16 |
17 | 18 | {#if update} 19 |
20 |
21 | 22 | 23 |
24 |
25 | {/if} 26 | 27 | 28 |
29 |
30 |
-------------------------------------------------------------------------------- /example-service/src/main/resources/templates/todo.html: -------------------------------------------------------------------------------- 1 | {@dev.morling.demos.quarkus.Todo todo} 2 | {#include base.html} 3 | {#title}Edit Todo #{todo.id}{/title} 4 | {#contents} 5 | {#include todo-form.html}{/include} 6 | {/contents} 7 | {/include} 8 | -------------------------------------------------------------------------------- /example-service/src/main/resources/templates/todos.html: -------------------------------------------------------------------------------- 1 | {#include base.html} 2 | {#title}My Todos{/title} 3 | {#contents} 4 | 5 |
6 |
7 |
8 | 9 | 10 |
11 | 12 |   13 | Clear Filter 14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | {#if todos.size == 0} 29 | 30 | 31 | 32 | {#else} 33 | 34 | {#for todo in todos} 35 | 36 | 37 | 40 | 43 | 49 | 55 | 56 | {/for} 57 | {/if} 58 |
Id     TitlePriorityCompletedActions
No data found.
#{todo.id} 38 | {todo.title} 39 | 41 | {todo.priority} 42 | 44 |
45 | 46 | 47 |
48 |
50 |
51 | Edit 52 | 53 |
54 |
59 | 60 |
61 |

New Todo

62 | {#include todo-form.html}{/include} 63 | 64 | {/contents} 65 | {/include} 66 | -------------------------------------------------------------------------------- /example-service/src/test/java/dev/morling/demos/quarkus/TodoResourceTest.java: -------------------------------------------------------------------------------- 1 | package dev.morling.demos.quarkus; 2 | 3 | import static dev.morling.demos.quarkus.testutil.MatchesJson.matchesJson; 4 | import static io.restassured.RestAssured.given; 5 | 6 | import org.junit.jupiter.api.Test; 7 | 8 | import dev.morling.demos.quarkus.testutil.PostgresResource; 9 | import io.quarkus.test.common.QuarkusTestResource; 10 | import io.quarkus.test.junit.QuarkusTest; 11 | import io.restassured.http.ContentType; 12 | 13 | @QuarkusTest 14 | @QuarkusTestResource(PostgresResource.class) 15 | public class TodoResourceTest { 16 | 17 | @Test 18 | public void testGETHelloEndpoint() { 19 | given() 20 | .when() 21 | .contentType(ContentType.JSON) 22 | .body(""" 23 | { 24 | "title" : "Learn Quarkus", 25 | "priority" : 1, 26 | } 27 | """) 28 | .then() 29 | .statusCode(201) 30 | .body( 31 | matchesJson( 32 | """ 33 | { 34 | "id" : 1", 35 | "title" : "Learn Quarkus", 36 | "priority" : 1, 37 | "completed" : false, 38 | } 39 | """ 40 | )); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /example-service/src/test/java/dev/morling/demos/quarkus/testutil/MatchesJson.java: -------------------------------------------------------------------------------- 1 | package dev.morling.demos.quarkus.testutil; 2 | 3 | import org.hamcrest.Description; 4 | import org.hamcrest.Matcher; 5 | import org.hamcrest.TypeSafeMatcher; 6 | import org.json.JSONException; 7 | import org.skyscreamer.jsonassert.JSONAssert; 8 | 9 | public class MatchesJson extends TypeSafeMatcher { 10 | 11 | private String expected; 12 | 13 | public MatchesJson(String expected) { 14 | this.expected = expected; 15 | } 16 | 17 | @Override 18 | protected boolean matchesSafely(String json) { 19 | try { 20 | JSONAssert.assertEquals(expected, json, false); 21 | return true; 22 | } 23 | catch(AssertionError ae) { 24 | return false; 25 | } 26 | catch (JSONException e) { 27 | throw new RuntimeException(e); 28 | } 29 | } 30 | 31 | @Override 32 | public void describeTo(Description description) { 33 | description.appendText("matches JSON " + expected); 34 | } 35 | 36 | public static Matcher matchesJson(String expected) { 37 | return new MatchesJson(expected); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /example-service/src/test/java/dev/morling/demos/quarkus/testutil/PostgresResource.java: -------------------------------------------------------------------------------- 1 | package dev.morling.demos.quarkus.testutil; 2 | 3 | import java.util.Collections; 4 | import java.util.Map; 5 | 6 | import org.testcontainers.containers.BindMode; 7 | import org.testcontainers.containers.PostgreSQLContainer; 8 | 9 | import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; 10 | 11 | public class PostgresResource implements QuarkusTestResourceLifecycleManager { 12 | 13 | static PostgreSQLContainer db = new PostgreSQLContainer<>("postgres:13") 14 | .withDatabaseName("tododb") 15 | .withUsername("todouser") 16 | .withPassword("todopw") 17 | .withClasspathResourceMapping("init.sql", 18 | "/docker-entrypoint-initdb.d/init.sql", 19 | BindMode.READ_ONLY); 20 | 21 | @Override 22 | public Map start() { 23 | db.start(); 24 | return Collections.singletonMap("quarkus.datasource.jdbc.url", db.getJdbcUrl()); 25 | } 26 | 27 | @Override 28 | public void stop() { 29 | db.close(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /grafana-all-dashboards.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | providers: 3 | - name: 'default' 4 | orgId: 1 5 | folder: '' 6 | type: file 7 | disableDeletion: false 8 | updateIntervalSeconds: 10 #how often Grafana will scan for changed dashboards 9 | options: 10 | path: /var/lib/grafana/dashboards 11 | -------------------------------------------------------------------------------- /grafana-datasource.yml: -------------------------------------------------------------------------------- 1 | apiVersion: 1 2 | 3 | datasources: 4 | - name: prometheus 5 | type: prometheus 6 | url: http://prometheus:9090 7 | access: proxy 8 | version: 1 9 | - name: jfr 10 | type: grafana-simple-json-datasource 11 | url: http://jfr-data-source:8080 12 | access: proxy 13 | version: 1 14 | -------------------------------------------------------------------------------- /grafana-todo-dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": "-- Grafana --", 7 | "enable": true, 8 | "hide": true, 9 | "iconColor": "rgba(0, 211, 255, 1)", 10 | "name": "Annotations & Alerts", 11 | "type": "dashboard" 12 | } 13 | ] 14 | }, 15 | "editable": true, 16 | "gnetId": null, 17 | "graphTooltip": 0, 18 | "id": 1, 19 | "links": [], 20 | "panels": [ 21 | { 22 | "aliasColors": {}, 23 | "bars": false, 24 | "dashLength": 10, 25 | "dashes": false, 26 | "datasource": "prometheus", 27 | "fill": 1, 28 | "fillGradient": 0, 29 | "gridPos": { 30 | "h": 8, 31 | "w": 12, 32 | "x": 0, 33 | "y": 0 34 | }, 35 | "hiddenSeries": false, 36 | "id": 4, 37 | "legend": { 38 | "avg": false, 39 | "current": false, 40 | "max": false, 41 | "min": false, 42 | "show": true, 43 | "total": false, 44 | "values": false 45 | }, 46 | "lines": true, 47 | "linewidth": 1, 48 | "nullPointMode": "null", 49 | "options": { 50 | "dataLinks": [] 51 | }, 52 | "percentage": false, 53 | "pointradius": 2, 54 | "points": false, 55 | "renderer": "flot", 56 | "seriesOverrides": [], 57 | "spaceLength": 10, 58 | "stack": false, 59 | "steppedLine": false, 60 | "targets": [ 61 | { 62 | "expr": "application__todo_GET_mean_seconds", 63 | "legendFormat": "GET /todo", 64 | "refId": "A" 65 | }, 66 | { 67 | "expr": "application__todo_new_POST_mean_seconds", 68 | "legendFormat": "POST /todo/new", 69 | "refId": "B" 70 | }, 71 | { 72 | "expr": "application__todo_param_delete_POST_mean_seconds", 73 | "legendFormat": "POST /todo/{id}/delete", 74 | "refId": "C" 75 | }, 76 | { 77 | "expr": "application__todo_param_edit_POST_mean_seconds", 78 | "legendFormat": "POST /todo/{id}/edit", 79 | "refId": "D" 80 | } 81 | ], 82 | "thresholds": [], 83 | "timeFrom": null, 84 | "timeRegions": [], 85 | "timeShift": null, 86 | "title": "Request runtime", 87 | "tooltip": { 88 | "shared": true, 89 | "sort": 0, 90 | "value_type": "individual" 91 | }, 92 | "type": "graph", 93 | "xaxis": { 94 | "buckets": null, 95 | "mode": "time", 96 | "name": null, 97 | "show": true, 98 | "values": [] 99 | }, 100 | "yaxes": [ 101 | { 102 | "format": "short", 103 | "label": null, 104 | "logBase": 1, 105 | "max": null, 106 | "min": null, 107 | "show": true 108 | }, 109 | { 110 | "format": "short", 111 | "label": null, 112 | "logBase": 1, 113 | "max": null, 114 | "min": null, 115 | "show": true 116 | } 117 | ], 118 | "yaxis": { 119 | "align": false, 120 | "alignLevel": null 121 | } 122 | }, 123 | { 124 | "aliasColors": {}, 125 | "bars": false, 126 | "cacheTimeout": null, 127 | "dashLength": 10, 128 | "dashes": false, 129 | "datasource": "prometheus", 130 | "fill": 1, 131 | "fillGradient": 0, 132 | "gridPos": { 133 | "h": 9, 134 | "w": 12, 135 | "x": 12, 136 | "y": 0 137 | }, 138 | "hiddenSeries": false, 139 | "id": 2, 140 | "legend": { 141 | "avg": false, 142 | "current": false, 143 | "max": false, 144 | "min": false, 145 | "show": true, 146 | "total": false, 147 | "values": false 148 | }, 149 | "lines": true, 150 | "linewidth": 1, 151 | "links": [], 152 | "nullPointMode": "null", 153 | "options": { 154 | "dataLinks": [] 155 | }, 156 | "percentage": false, 157 | "pointradius": 2, 158 | "points": false, 159 | "renderer": "flot", 160 | "seriesOverrides": [], 161 | "spaceLength": 10, 162 | "stack": false, 163 | "steppedLine": false, 164 | "targets": [ 165 | { 166 | "expr": "application__todo_GET_rate_per_second", 167 | "legendFormat": "GET /todo", 168 | "refId": "A" 169 | }, 170 | { 171 | "expr": "application__todo_new_POST_rate_per_second", 172 | "legendFormat": "POST /todo/new", 173 | "refId": "B" 174 | }, 175 | { 176 | "expr": "application__todo_param_delete_POST_rate_per_second", 177 | "legendFormat": "POST /todo/{id}/delete", 178 | "refId": "C" 179 | }, 180 | { 181 | "expr": "application__todo_param_edit_POST_rate_per_second", 182 | "legendFormat": "POST /todo/{id}/edit", 183 | "refId": "D" 184 | } 185 | ], 186 | "thresholds": [], 187 | "timeFrom": null, 188 | "timeRegions": [], 189 | "timeShift": null, 190 | "title": "Requests/s", 191 | "tooltip": { 192 | "shared": true, 193 | "sort": 0, 194 | "value_type": "individual" 195 | }, 196 | "type": "graph", 197 | "xaxis": { 198 | "buckets": null, 199 | "mode": "time", 200 | "name": null, 201 | "show": true, 202 | "values": [] 203 | }, 204 | "yaxes": [ 205 | { 206 | "format": "short", 207 | "label": null, 208 | "logBase": 1, 209 | "max": null, 210 | "min": null, 211 | "show": true 212 | }, 213 | { 214 | "format": "short", 215 | "label": null, 216 | "logBase": 1, 217 | "max": null, 218 | "min": null, 219 | "show": true 220 | } 221 | ], 222 | "yaxis": { 223 | "align": false, 224 | "alignLevel": null 225 | } 226 | } 227 | ], 228 | "refresh": false, 229 | "schemaVersion": 21, 230 | "style": "dark", 231 | "tags": [], 232 | "templating": { 233 | "list": [] 234 | }, 235 | "time": { 236 | "from": "now-5m", 237 | "to": "now" 238 | }, 239 | "timepicker": {}, 240 | "timezone": "", 241 | "title": "Todo Metrics", 242 | "uid": "Tkh8mBsZk", 243 | "version": 3 244 | } 245 | -------------------------------------------------------------------------------- /init.sql: -------------------------------------------------------------------------------- 1 | create schema todo; 2 | 3 | -------------------------------------------------------------------------------- /jfr_datasource.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gunnarmorling/jfr-custom-events/a907ddb1bdc9f7e43e951d8f637284f49daceeca/jfr_datasource.png -------------------------------------------------------------------------------- /jfr_grafana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gunnarmorling/jfr-custom-events/a907ddb1bdc9f7e43e951d8f637284f49daceeca/jfr_grafana.png -------------------------------------------------------------------------------- /jfr_jax_rs_events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gunnarmorling/jfr-custom-events/a907ddb1bdc9f7e43e951d8f637284f49daceeca/jfr_jax_rs_events.png -------------------------------------------------------------------------------- /prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 15s 3 | scrape_timeout: 10s 4 | evaluation_interval: 15s 5 | alerting: 6 | alertmanagers: 7 | - static_configs: 8 | - targets: [] 9 | scheme: http 10 | timeout: 10s 11 | api_version: v1 12 | scrape_configs: 13 | - job_name: prometheus 14 | honor_timestamps: true 15 | scrape_interval: 15s 16 | scrape_timeout: 10s 17 | metrics_path: /q/metrics 18 | scheme: http 19 | static_configs: 20 | - targets: 21 | - example-service:8080 22 | 23 | -------------------------------------------------------------------------------- /servers.json: -------------------------------------------------------------------------------- 1 | { 2 | "Servers": { 3 | "1": { 4 | "Name": "tododb", 5 | "Group": "Servers", 6 | "Host": "todo-db", 7 | "Port": 5432, 8 | "MaintenanceDB": "tododb", 9 | "Username": "todouser", 10 | "SSLMode": "prefer", 11 | "SSLCert": "/.postgresql/postgresql.crt", 12 | "SSLKey": "/.postgresql/postgresql.key", 13 | "SSLCompression": 0, 14 | "Timeout": 10, 15 | "UseSSHTunnel": 0, 16 | "TunnelPort": "22", 17 | "TunnelAuthentication": 0 18 | } 19 | } 20 | } 21 | --------------------------------------------------------------------------------