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