diff --git a/.github/workflows/doc-build.yml b/.github/workflows/doc-build.yml
index 555944bd55789..475721a14fd4c 100644
--- a/.github/workflows/doc-build.yml
+++ b/.github/workflows/doc-build.yml
@@ -52,7 +52,8 @@ jobs:
key: q2maven-doc-${{ steps.get-date.outputs.date }}
- name: Build
run: |
- ./mvnw -Dquickly-ci -B --settings .github/mvn-settings.xml install
+ ./mvnw -Dquickly-ci -B -DskipDocs=false --settings .github/mvn-settings.xml install
+
- name: Build Docs
run: |
./mvnw -e -B --settings .github/mvn-settings.xml clean org.asciidoctor:asciidoctor-maven-plugin:process-asciidoc -pl docs -Ddocumentation-pdf
diff --git a/docs/pom.xml b/docs/pom.xml
index f7ede35460abd..d34e6b4a56e9d 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/
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 7b330a696f2b0..f7d6d8046196d 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 42f12a3739e5f..40d91c2aa48d5 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
@@ -144,10 +144,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.11, 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 referenced using the following:
++
+`\{code-examples}/telemetry-micrometer-tutorial-ExampleResource.java`.
+
=== Variables for use in documents
-The following variables externalize key information that can change over time, and so references
-to such information should be done by using the variable inside of {} curly brackets. The
-complete list of externalized variables for use is given in the following table:
+The following variables externalize key information that can change over time. References
+to such information should use the variable inside of curly brackets, `{}`.
+
+The complete list of externalized variables for use is given in the following table:
.Variables
[cols=" 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..389d302676bbb
--- /dev/null
+++ b/integration-tests/micrometer-prometheus/src/main/java/documentation/example/telemetry/micrometer/tutorial/ExampleResource.java
@@ -0,0 +1,134 @@
+// 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;
- });
- }
-}