diff --git a/docs/pom.xml b/docs/pom.xml
index f7ede35460abd..7ff6a11b1e19c 100644
--- a/docs/pom.xml
+++ b/docs/pom.xml
@@ -23,6 +23,7 @@
https://quarkus.io
https://github.com/quarkusio/quarkus
https://github.com/quarkusio/quarkus-quickstarts
+ ${project.basedir}/../target/asciidoc/examples
Quarkus - Documentation
@@ -2794,6 +2795,24 @@
+
+ copy-tagged-java-source
+ process-classes
+
+ java
+
+
+ ${skipDocs}
+ io.quarkus.docs.generation.CopyJavaExamples
+
+ ${code-example-dir}
+ ${project.basedir}/../integration-tests
+
+
+ ${env.MAVEN_CMD_LINE_ARGS}
+
+
+
generate-doc-manifests
prepare-package
@@ -2830,6 +2849,7 @@
true
${project.basedir}/../target/asciidoc/generated
+ ${code-example-dir}
./images
font
true
@@ -2911,7 +2931,7 @@
https://quarkiverse.github.io/quarkiverse-docs/quarkus-neo4j/dev/index.html
https://quarkiverse.github.io/quarkiverse-docs/quarkus-vault/dev/index.html
https://quarkiverse.github.io/quarkiverse-docs/quarkus-vault/dev/vault-datasource.html
- https://quarkiverse.github.io/quarkiverse-docs/quarkus-micrometer-registry/dev/
+ https://quarkiverse.github.io/quarkiverse-docs/quarkus-micrometer-registry/dev/
@@ -3007,6 +3027,33 @@
+
+ org.codehaus.mojo
+ exec-maven-plugin
+
+ ${skipDocs}
+
+
+
+ copy-tagged-java-source
+ process-classes
+
+ java
+
+
+ ${skipDocs}
+ io.quarkus.docs.generation.CopyJavaExamples
+
+ ${code-example-dir}
+ ${project.basedir}/../integration-tests
+
+
+ ${env.MAVEN_CMD_LINE_ARGS}
+
+
+
+
+
org.asciidoctor
asciidoctor-maven-plugin
diff --git a/docs/src/main/asciidoc/_examples/attributes.adoc b/docs/src/main/asciidoc/_examples/attributes.adoc
index 601d8289973a4..03eff399e165a 100644
--- a/docs/src/main/asciidoc/_examples/attributes.adoc
+++ b/docs/src/main/asciidoc/_examples/attributes.adoc
@@ -3,8 +3,9 @@
:idprefix:
:idseparator: -
:icons: font
+:code-examples: ../../../../../target/asciidoc/examples
:doc-guides: ..
:doc-examples: .
:imagesdir: ./images
:includes: ../includes
-:root: ../../asciidoc
+
diff --git a/docs/src/main/asciidoc/_templates/attributes.adoc b/docs/src/main/asciidoc/_templates/attributes.adoc
index f67f3f84a8d9c..cd4f7aeebf0d1 100644
--- a/docs/src/main/asciidoc/_templates/attributes.adoc
+++ b/docs/src/main/asciidoc/_templates/attributes.adoc
@@ -3,8 +3,8 @@
:idprefix:
:idseparator: -
:icons: font
+:code-examples: ../../../../../target/asciidoc/examples
:doc-guides: ../
:doc-examples: ../_examples
-:imagesdir: ../../asciidoc/images
+:imagesdir: ../images
:includes: ../includes
-:root: ../../asciidoc/
diff --git a/docs/src/main/asciidoc/attributes.adoc b/docs/src/main/asciidoc/attributes.adoc
index 36a0d40806710..efa26032c201f 100644
--- a/docs/src/main/asciidoc/attributes.adoc
+++ b/docs/src/main/asciidoc/attributes.adoc
@@ -4,8 +4,8 @@
:idprefix:
:idseparator: -
:icons: font
-// tag::xref-attributes[]
+:code-examples: ../../../../target/asciidoc/examples
+:doc-guides: ./
:doc-examples: ./_examples
:imagesdir: ./images
:includes: ./includes
-// end::xref-attributes[]
diff --git a/docs/src/main/asciidoc/doc-reference.adoc b/docs/src/main/asciidoc/doc-reference.adoc
index 9c7b7ba9cf1d7..06481c7ca9d29 100644
--- a/docs/src/main/asciidoc/doc-reference.adoc
+++ b/docs/src/main/asciidoc/doc-reference.adoc
@@ -23,10 +23,10 @@ The Asciidoc files can be found in the `src/main/asciidoc` directory within the
Create new documentation files using the appropriate template for the content type:
-Concepts:: Use `src/main/asciidoc/_templates/template-concepts.adoc`
-How-To Guides:: Use `src/main/asciidoc/_templates/template-howto.adoc`
-Reference:: Use `src/main/asciidoc/_templates/template-reference.adoc`
-Tutorials:: Use `src/main/asciidoc/_templates/template-tutorial.adoc`
+Concepts:: Use `docs/src/main/asciidoc/_templates/template-concepts.adoc`
+How-To Guides:: Use `docs/src/main/asciidoc/_templates/template-howto.adoc`
+Reference:: Use `docs/src/main/asciidoc/_templates/template-reference.adoc`
+Tutorials:: Use `docs/src/main/asciidoc/_templates/template-tutorial.adoc`
== Output locations
@@ -138,10 +138,16 @@ Quarkus documentation is built from source in a few different environments.
We use attributes in our cross-references to ensure our docs can be built across these environments.
.Cross-reference source attributes
-[source,asciidoc]
-----
-include::attributes.adoc[tag=xref-attributes]
-----
+[cols="
----
-<1> The cross reference starts with `xref:`, uses a cross-reference source attribute(`\{doc-guides}`), and provides a readable description: `[Quarkus Documentation concepts]`
+<1> The cross reference starts with `xref:`, uses a cross-reference source attribute(`\{doc-guides}`), and provides a readable description: `[Quarkus Documentation concepts]`.
+
+=== Referencing source code
+
+There are many ways to include source examples in documentation.
+
+The simplest way is to write it directly in the file, like this:
+
+[source,asciidoc]
+-----
+[source,java]
+----
+System.out.println("Hello, World!");
+----
+-----
+
+For tutorials, you may want to reference code that you know is being built and tested regularly.
+As of 2.10-ish, the Quarkus documentation module build will look for source files in `integration-tests` modules that are in the `documentaton.examples` package.
+It will copy those files into a flattened structure in the `target/asciidoc/examples` directory (from the project root).
+Copied content can then be referenced using the `\{code-examples}` source attribute
+
+.Micrometer example
+- Integration test source:
++
+`integration-tests/micrometer-prometheus/src/main/java/documentation/example/telemetry/micrometer/tutorial/ExampleResource.java`
+
+- Copy for inclusion in docs (flattened file structure):
++
+`target/asciidoc/examples/telemetry-micrometer-tutorial-ExampleResource.java`.
+- Content is included using the following:
++
+`\{code-examples}/telemetry-micrometer-tutorial-ExampleResource.java`.
diff --git a/docs/src/main/asciidoc/telemetry-micrometer-tutorial.adoc b/docs/src/main/asciidoc/telemetry-micrometer-tutorial.adoc
new file mode 100644
index 0000000000000..b69ae2cb6abf1
--- /dev/null
+++ b/docs/src/main/asciidoc/telemetry-micrometer-tutorial.adoc
@@ -0,0 +1,200 @@
+////
+This guide is maintained in the main Quarkus repository
+and pull requests should be submitted there:
+https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc
+////
+[id="tutorial-micrometer"]
+= Collect metrics using Micrometer
+include::./attributes.adoc[]
+
+Create an application that uses the Micrometer metrics library to collect runtime, extension and application metrics and expose them as a Prometheus (OpenMetrics) endpoint.
+
+== Prerequisites
+
+include::{includes}/prerequisites.adoc[]
+
+== Solution
+
+We recommend that you follow the instructions to create the application step by step, but you can skip right to the solution if you prefer. Either:
+
+* Clone the git repository: `git clone {quickstarts-clone-url}`, or
+* Download an {quickstarts-archive-url}[archive].
+
+The solution is located in the `micrometer-quickstart` {quickstarts-tree-url}/micrometer-quickstart[directory].
+
+:sectnums:
+:sectnumlevels: 3
+== Creating the Maven Project
+
+Create a new project with the following command:
+
+:create-app-artifact-id: micrometer-quickstart
+:create-app-extensions: resteasy-reactive,micrometer-registry-prometheus
+include::{includes}/devtools/create-app.adoc[]
+
+This command generates a Maven project, that imports the `micrometer-registry-prometheus` extension as a dependency.
+This extension will load the core `micrometer` extension as well as additional library dependencies required to support prometheus.
+
+== Writing the application
+
+Let's first add a simple endpoint that calculates prime numbers.
+
+[source,java]
+----
+include::{code-examples}/telemetry-micrometer-tutorial-ExampleResource.java[tags=example;!ignore;!registry;!gauge;!counter;!timer]
+----
+
+Start your application in dev mode:
+
+include::{includes}/devtools/dev.adoc[]
+
+=== Review automatically generated metrics
+
+The Micrometer extension automatically times HTTP server requests.
+
+Let's use `curl` (or a browser) to visit our endpoint a few times:
+
+[source,shell]
+----
+curl http://localhost:8080/example/prime/256
+curl http://localhost:8080/example/prime/7919
+----
+
+The Micrometer Prometheus MeterRegistry extension creates an endpoint we can use to observe collected metrics. Let's take a look at the metrics that have been collected:
+
+[source,shell]
+----
+curl http://localhost:8080/q/metrics
+----
+
+Look for `http_server_requests_seconds_count`, `http_server_requests_seconds_sum`, and
+`http_server_requests_seconds_max` in the output.
+
+Dimensional labels are added for the request uri, the HTTP method
+(GET, POST, etc.), the status code (200, 302, 404, etc.), and a more general outcome field. You should find something like this:
+
+[source,text]
+----
+# HELP http_server_requests_seconds
+# TYPE http_server_requests_seconds summary
+http_server_requests_seconds_count{method="GET",outcome="SUCCESS",status="200",uri="/example/prime/{number}",} 2.0
+http_server_requests_seconds_sum{method="GET",outcome="SUCCESS",status="200",uri="/example/prime/{number}",} 0.017385896
+# HELP http_server_requests_seconds_max
+# TYPE http_server_requests_seconds_max gauge
+http_server_requests_seconds_max{method="GET",outcome="SUCCESS",status="200",uri="/example/prime/{number}",} 0.017385896
+#
+----
+
+NOTE: Metrics appear lazily, you often won't see any data for your endpoint until it is accessed.
+
+== Inject the MeterRegistry
+
+To register meters, you need a reference to the `MeterRegistry` that is configured and maintained by the Micrometer extension.
+
+The `MeterRegistry` can be injected into your application as follows:
+
+[source,java]
+----
+include::{code-examples}/telemetry-micrometer-tutorial-ExampleResource.java[tags=registry;!gauge]
+----
+
+== Add a Counter
+
+Counters are used to measure values that only increase.
+
+Let's add a counter that tracks how often we test a number to see if it is prime.
+We'll add a dimensional label (also called an attribute or a tag) that will allow us to aggregate this counter value in different ways.
+
+[source,java]
+----
+include::{code-examples}/telemetry-micrometer-tutorial-ExampleResource.java[tags=counted;!ignore;!timer]
+----
+
+<1> Find or create a counter called `example.prime.number` that has a `type` label with the specified value.
+<2> Increment that counter.
+
+=== Review collected metrics
+
+If you did not leave Quarkus running in dev mode, start it again:
+
+include::{includes}/devtools/dev.adoc[]
+
+Try the following sequence and look for `example_prime_number_total` in the plain text
+output.
+
+Note that the `_total` suffix is added when Micrometer applies Prometheus naming conventions to
+`example.prime.number`, the originally specified counter name.
+
+[source,shell]
+----
+curl http://localhost:8080/example/prime/-1
+curl http://localhost:8080/example/prime/0
+curl http://localhost:8080/example/prime/1
+curl http://localhost:8080/example/prime/2
+curl http://localhost:8080/example/prime/3
+curl http://localhost:8080/example/prime/15
+curl http://localhost:8080/q/metrics
+----
+
+Notice that there is one measured value for each unique combination of `example_prime_number_total` and `type` value.
+
+Looking at the dimensional data produced by this counter, you can count:
+
+- how often a negative number was checked: `type="not-natural"`
+- how often the number one was checked: `type="one"`
+- how often an even number was checked: `type="even"`
+- how often a prime number was checked: `type="prime"`
+- how often a non-prime number was checked: `type="not-prime"`
+
+You can also count how often a number was checked (generally) by aggregating all of these values together.
+
+== Add a Timer
+
+Timers are a specialized abstraction for measuring duration. Let's add a timer to measure how long it takes to determine if a number is prime.
+
+[source,java]
+----
+include::{code-examples}/telemetry-micrometer-tutorial-ExampleResource.java[tags=timed;!ignore;!default]
+----
+
+<1> Find or create a counter called `example.prime.number` that has a `type` label with the specified value.
+<2> Increment that counter.
+<3> Call a method that wraps the original `testPrimeNumber` method.
+<4> Create a `Timer.Sample` that tracks the start time
+<5> Call the method to be timed and store the boolean result
+<6> Find or create a `Timer` using the specified id and a `prime` label with the result value, and record the duration captured by the `Timer.Sample`.
+
+=== Review collected metrics
+
+If you did not leave Quarkus running in dev mode, start it again:
+
+include::{includes}/devtools/dev.adoc[]
+
+Micrometer will apply Prometheus conventions when emitting metrics for this timer.
+Specifically, measured durations are converted into seconds and this unit is included in the metric name.
+
+Try the following sequence and look for the following entries in the plain text output:
+
+- `example_prime_number_test_seconds_count` -- how many times the method was called
+- `example_prime_number_test_seconds_sum` -- the total duration of all method calls
+- `example_prime_number_test_seconds_max` -- the maximum observed duration within a decaying interval. This value will return to 0 if the method is not invoked frequently.
+
+[source,shell]
+----
+curl http://localhost:8080/example/prime/256
+curl http://localhost:8080/q/metrics
+curl http://localhost:8080/example/prime/7919
+curl http://localhost:8080/q/metrics
+----
+
+Looking at the dimensional data produced by this counter, you can use the sum and the count to calculate how long (on average) it takes to determine if a number is prime. Using the dimensional label, you might be able to understand if there is a significant difference in duration for numbers that are prime when compared with numbers that are not.
+
+:sectnums!:
+== Summary
+
+Congratulations!
+
+You have created a project that uses the Micrometer and Prometheus Meter Registry extensions to collect metrics. You've observed some of the metrics that Quarkus captures automatically, and have added a `Counter` and `Timer` that are unique to the application. You've also added dimensional labels to metrics, and have observed how those labels shape the data emitted by the prometheus endpoint.
+
+
+
diff --git a/docs/src/main/java/io/quarkus/docs/generation/CopyJavaExamples.java b/docs/src/main/java/io/quarkus/docs/generation/CopyJavaExamples.java
new file mode 100755
index 0000000000000..598109a3bdddc
--- /dev/null
+++ b/docs/src/main/java/io/quarkus/docs/generation/CopyJavaExamples.java
@@ -0,0 +1,63 @@
+package io.quarkus.docs.generation;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class CopyJavaExamples {
+
+ public Path outputPath;
+ public List paths;
+
+ // ${project.basedir}/../target/asciidoc/generated/examples ${project.basedir}/../integration-tests
+ public static void main(String[] args) throws Exception {
+ if (args.length < 2) {
+ System.err.println("Must specify target output directory (first) followed by at least one source directory");
+ System.exit(1);
+ }
+ CopyJavaExamples javaExamples = new CopyJavaExamples();
+
+ javaExamples.outputPath = Path.of(args[0]);
+ javaExamples.paths = Arrays.stream(args).skip(1)
+ .map(Path::of)
+ .collect(Collectors.toList());
+
+ try {
+ javaExamples.run();
+ } catch (Exception e) {
+ System.err.println("Exception occurred while trying to copy java examples");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ public void run() throws Exception {
+ if (outputPath != null) {
+ Files.createDirectories(outputPath);
+ }
+
+ for (Path path : paths) {
+ Files.walkFileTree(path, new SimpleFileVisitor<>() {
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ if (file.toString().contains("src/main/java/documentation/example/")) {
+ String name = file.toString();
+ name = name.substring(name.indexOf("example/") + 8)
+ .replace("/", "-");
+
+ Files.copy(file, outputPath.resolve(name), StandardCopyOption.REPLACE_EXISTING);
+ }
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ }
+ }
+
+}
diff --git a/integration-tests/micrometer-prometheus/src/main/java/documentation/example/telemetry/micrometer/tutorial/ExampleResource.java b/integration-tests/micrometer-prometheus/src/main/java/documentation/example/telemetry/micrometer/tutorial/ExampleResource.java
new file mode 100644
index 0000000000000..15b176d1d5bb4
--- /dev/null
+++ b/integration-tests/micrometer-prometheus/src/main/java/documentation/example/telemetry/micrometer/tutorial/ExampleResource.java
@@ -0,0 +1,133 @@
+// tag::example[]
+// tag::ignore[]
+// This example is maintained in quarkus micrometer integration tests
+// integration-tests/micrometer-prometheus/src/main/java/documentation/example/telemetry/micrometer/tutorial/ExampleResource.java
+package documentation.example.telemetry.micrometer.tutorial;
+/*
+// end::ignore[]
+package org.acme.micrometer;
+// tag::ignore[]
+*/
+// end::ignore[]
+
+import java.util.LinkedList;
+import java.util.NoSuchElementException;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Tags;
+import io.micrometer.core.instrument.Timer;
+
+@Path("/example")
+@Produces("text/plain")
+public class ExampleResource {
+ private final LinkedList list = new LinkedList<>();
+ // tag::registry[]
+ private final MeterRegistry registry;
+
+ ExampleResource(MeterRegistry registry) {
+ this.registry = registry;
+ // tag::gauge[]
+ registry.gaugeCollectionSize("example.list.size", Tags.empty(), list);
+ // end::gauge[]
+ }
+ // end::registry[]
+ // tag::gauge[]
+
+ @GET
+ @Path("gauge/{number}")
+ public Long checkListSize(@PathParam("number") long number) {
+ if (number == 2 || number % 2 == 0) {
+ // add even numbers to the list
+ list.add(number);
+ } else {
+ // remove items from the list for odd numbers
+ try {
+ number = list.removeFirst();
+ } catch (NoSuchElementException nse) {
+ number = 0;
+ }
+ }
+ return number;
+ }
+ // end::gauge[]
+
+ // tag::timed[]
+ // tag::counted[]
+ @GET
+ @Path("prime/{number}")
+ public String checkIfPrime(@PathParam("number") long number) {
+ if (number < 1) {
+ // tag::counter[]
+ registry.counter("example.prime.number", "type", "not-natural") // <1>
+ .increment(); // <2>
+ // end::counter[]
+ return "Only natural numbers can be prime numbers.";
+ }
+ if (number == 1) {
+ // tag::counter[]
+ registry.counter("example.prime.number", "type", "one") // <1>
+ .increment(); // <2>
+ // end::counter[]
+ return number + " is not prime.";
+ }
+ if (number == 2 || number % 2 == 0) {
+ // tag::counter[]
+ registry.counter("example.prime.number", "type", "even") // <1>
+ .increment(); // <2>
+ // end::counter[]
+ return number + " is not prime.";
+ }
+
+ // tag::timer[]
+ if (timedTestPrimeNumber(number)) { // <3>
+ // end::timer[]
+ // tag::ignore[]
+ /*
+ }
+ // end::ignore[]
+ // tag::default[]
+ if (testPrimeNumber(number)) {
+ // end::default[]
+ // tag::ignore[]
+ */
+ // end::ignore[]
+ // tag::counter[]
+ registry.counter("example.prime.number", "type", "prime") // <1>
+ .increment(); // <2>
+ // end::counter[]
+ return number + " is prime.";
+ } else {
+ // tag::counter[]
+ registry.counter("example.prime.number", "type", "not-prime") // <1>
+ .increment(); // <2>
+ // end::counter[]
+ return number + " is not prime.";
+ }
+ }
+ // end::counted[]
+ // tag::timer[]
+
+ protected boolean timedTestPrimeNumber(long number){
+ Timer.Sample sample = Timer.start(registry); // <4>
+ boolean result = testPrimeNumber(number); // <5>
+ sample.stop(registry.timer("example.prime.number.test", "prime", result + "")); // <6>
+ return result;
+ }
+ // end::timer[]
+ // end::timed[]
+
+ protected boolean testPrimeNumber(long number) {
+ for (int i = 3; i < Math.floor(Math.sqrt(number)) + 1; i = i + 2) {
+ if (number % i == 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
+// end::example[]
diff --git a/integration-tests/micrometer-prometheus/src/main/java/io/quarkus/it/micrometer/prometheus/ExampleResource.java b/integration-tests/micrometer-prometheus/src/main/java/io/quarkus/it/micrometer/prometheus/ExampleResource.java
deleted file mode 100644
index 627344ea7505d..0000000000000
--- a/integration-tests/micrometer-prometheus/src/main/java/io/quarkus/it/micrometer/prometheus/ExampleResource.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package io.quarkus.it.micrometer.prometheus;
-
-import java.util.LinkedList;
-import java.util.NoSuchElementException;
-
-import javax.ws.rs.GET;
-import javax.ws.rs.Path;
-import javax.ws.rs.PathParam;
-import javax.ws.rs.Produces;
-
-import io.micrometer.core.instrument.MeterRegistry;
-import io.micrometer.core.instrument.Tags;
-import io.micrometer.core.instrument.Timer;
-
-@Path("/example")
-@Produces("text/plain")
-public class ExampleResource {
-
- private final MeterRegistry registry;
-
- LinkedList list = new LinkedList<>();
-
- ExampleResource(MeterRegistry registry) {
- this.registry = registry;
- registry.gaugeCollectionSize("example.list.size", Tags.empty(), list);
- }
-
- @GET
- @Path("gauge/{number}")
- public Long checkListSize(@PathParam("number") long number) {
- if (number == 2 || number % 2 == 0) {
- // add even numbers to the list
- list.add(number);
- } else {
- // remove items from the list for odd numbers
- try {
- number = list.removeFirst();
- } catch (NoSuchElementException nse) {
- number = 0;
- }
- }
- return number;
- }
-
- @GET
- @Path("prime/{number}")
- public String checkIfPrime(@PathParam("number") long number) {
- if (number < 1) {
- registry.counter("example.prime.number", "type", "not-natural").increment();
- return "Only natural numbers can be prime numbers.";
- }
- if (number == 1) {
- registry.counter("example.prime.number", "type", "one").increment();
- return number + " is not prime.";
- }
- if (number == 2 || number % 2 == 0) {
- registry.counter("example.prime.number", "type", "even").increment();
- return number + " is not prime.";
- }
-
- if (testPrimeNumber(number)) {
- registry.counter("example.prime.number", "type", "prime").increment();
- return number + " is prime.";
- } else {
- registry.counter("example.prime.number", "type", "not-prime").increment();
- return number + " is not prime.";
- }
- }
-
- protected boolean testPrimeNumber(long number) {
- Timer timer = registry.timer("example.prime.number.test");
- return timer.record(() -> {
- for (int i = 3; i < Math.floor(Math.sqrt(number)) + 1; i = i + 2) {
- if (number % i == 0) {
- return false;
- }
- }
- return true;
- });
- }
-}