Create a Micronaut Application to Collect Metrics and Monitor Them on Google Cloud Monitoring
This guide describes how to use the Graal Development Kit for Micronaut (GDK) to create a Micronaut® application that collects standard and custom metrics, then publishes and monitors them on Google Cloud Monitoring, using the Micronaut Google Cloud Platform integration.
The application stores book information in a database and provides endpoints to query books. The application collects metrics for the whole application and measures total computation time for a particular endpoint.
Google Cloud Monitoring provides visibility into the performance, uptime, and overall health of cloud-powered applications. It collects metrics, events, and metadata from Google Cloud services, hosted uptime probes, application instrumentation, and a variety of common application components.
Micronaut GCP provides integration between Micronaut and Google Cloud Platform, including the Operations Suite.
The application uses Micronaut Micrometer to expose application metric data with Micrometer.
Prerequisites #
- JDK 17 or higher. See Setting up Your Desktop.
- A Google Cloud Platform (GCP) account. See Setting up Your Cloud Accounts.
- The Google Cloud CLI. (The CLI is part of the Google Cloud SDK; for more information, see Cloud SDK.)
- A Docker-API compatible container runtime such as Rancher Desktop or Docker installed to run MySQL and to run tests using Testcontainers.
- The GDK CLI. See Setting up Your Desktop. (Optional.)
Note: This guide uses paid services; you may need to enable billing in Google Cloud to complete some steps in this guide.
Follow the steps below to create the application from scratch. However, you can also download the completed example:
A note regarding your development environment
Consider using Visual Studio Code, which provides native support for developing applications with the Graal Development Kit extension.
Note: If you use IntelliJ IDEA, enable annotation processing.
Windows platform: The GDK guides are compatible with Gradle only. Maven support is coming soon.
1. Create the Application #
Create an application using the GDK Launcher.
-
Open the GDK Launcher in advanced mode.
- Create a new project using the following selections.
- Project Type: Application (Default)
- Project Name: gcp-metrics-demo
- Base Package: com.example (Default)
- Clouds: GCP
- Build Tool: Gradle (Groovy) or Maven
- Language: Java (Default)
- Test Framework: JUnit (Default)
- Java Version: 17 (Default)
- Micronaut Version: (Default)
- Cloud Services: Database and Metrics
- Features: Flyway Database Migration, GraalVM Native Image, and Micrometer Annotation
- Sample Code: No
- Click Generate Project, then click Download Zip. The GDK Launcher creates a Java project with the default package
com.example
in a directory named gcp-metrics-demo. The application ZIP file will be downloaded to your default downloads directory. Unzip it, open it in your code editor, and proceed to the next steps.
Alternatively, use the GDK CLI as follows:
gdk create-app com.example.gcp-metrics-demo \
--clouds=gcp \
--services=database,metrics \
--features=graalvm,flyway,micrometer-annotation \
--example-code=false \
--build=gradle \
--jdk=17 \
--lang=java
gdk create-app com.example.gcp-metrics-demo \
--clouds=gcp \
--services=database,metrics \
--features=graalvm,flyway,micrometer-annotation \
--example-code=false \
--build=maven \
--jdk=17 \
--lang=java
For more information, see Using the GDK CLI.
GDK Launcher creates a multi-module project with two subprojects: gcp, and lib. The common application logic is in the lib subproject, and the gcp subproject contain logic specific to the GCP cloud platform. When creating the application with the GDK, the necessary features such as Micronaut Micrometer, and others were added for you. These features will be used in the guide.
1.1. Configure Metrics Collection #
The project generated by the GDK Launcher has Micrometer as a dependency.
Micrometer provides a simple facade over the instrumentation clients for a number of popular monitoring systems.
To configure Micrometer, the following properties were added to the configuration file, gcp/src/main/resources/application.properties, as follows:
micronaut.metrics.enabled=true
micronaut.metrics.binders.files.enabled=true
micronaut.metrics.binders.jdbc.enabled=true
micronaut.metrics.binders.jvm.enabled=true
micronaut.metrics.binders.logback.enabled=true
micronaut.metrics.binders.processor.enabled=true
micronaut.metrics.binders.uptime.enabled=true
micronaut.metrics.export.stackdriver.enabled=true
Several groups of metrics are enabled by default: these include system metrics (such as JVM information and uptime), as well as metrics tracking web requests, data sources activity, and others. Overall metrics can be enabled or disabled, and groups can be individually enabled or disabled in configuration. In this case all metrics are enabled. To disable, change to false
, for example, per-environment.
To export metrics to Google Cloud Monitoring, enable Stackdriver as an export location for Micrometer. Then, create queries and monitor your metrics on Google Cloud Monitoring.
Finally, add the following to your build file, in order to use Stackdriver for Micrometer for this project:
build.gradle
implementation("io.micronaut.micrometer:micronaut-micrometer-registry-stackdriver")
pom.xml
<dependency>
<groupId>io.micronaut.micrometer</groupId>
<artifactId>micronaut-micrometer-registry-stackdriver</artifactId>
<scope>compile</scope>
</dependency>
1.2. Create a Google Cloud Platform Project #
Create a new GCP project named “gdk-guides” (follow the instructions in Creating and managing projects).
1.2.1. Install the Cloud SDK
The Cloud SDK includes the gcloud
command-line tool.
- Initialize the Cloud SDK:
gcloud init
- Log in to the Google Cloud Platform:
gcloud auth login
- Authenticate application default credentials:
gcloud auth application-default login
- Select your project:
gcloud config set project gdk-guides
-
Configure the project in gcp/src/main/resources/application.properties:
micronaut.metrics.export.stackdriver.enabled=true micronaut.metrics.export.stackdriver.projectId=gdk-guides
1.3. Database Migration with Flyway #
To create the database schema, the application uses the Micronaut integration with Flyway. Flyway automates schema changes, significantly simplifying schema management tasks, such as migrating, rolling back, and reproducing in multiple environments.
-
You specified Flyway as a project feature in the GDK Launcher, so the build file includes it as a dependency:
build.gradle
implementation("io.micronaut.flyway:micronaut-flyway") runtimeOnly("org.flywaydb:flyway-mysql")
pom.xml
<dependency> <groupId>io.micronaut.flyway</groupId> <artifactId>micronaut-flyway</artifactId> <scope>compile</scope> </dependency> <dependency> <groupId>org.flywaydb</groupId> <artifactId>flyway-mysql</artifactId> <scope>runtime</scope> </dependency>
-
The file gcp/src/main/resources/application.properties was modified to enable Flyway to perform migrations on the default datasources, by adding the following:
flyway.datasources.default.enabled=true
Configuring multiple
datasources
is as simple. You can also specify directories that will be used for migrating each datasource. For more information, see Micronaut integration with Flyway. -
The GDK Launcher created a migration file in the lib/src/main/resources/db/migration/ directory named V1__schema.sql to contain the database schema, as follows:
DROP TABLE IF EXISTS book; CREATE TABLE book ( id BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY, name VARCHAR(255) NOT NULL UNIQUE, isbn CHAR(13) NOT NULL UNIQUE ); INSERT INTO book (isbn, name) VALUES ("9781491950357", "Building Microservices"), ("9781680502398", "Release It!"), ("9780321601919", "Continuous Delivery"), ("9781617294549", "Microservices Patterns");
Flyway migration is automatically triggered before your application starts and Flyway reads migration file(s) from the lib/src/main/resources/db/migration/ directory. During application startup, Flyway runs the commands in the SQL file and creates the schema needed for the application.
Using the SQL statements in V1__schema.sql, Flyway creates a table with the name book
and populates it with four records.
1.4. Domain Entity #
The GDK Launcher created a Book
domain class that uses Micronaut Data JDBC in a file named lib/src/main/java/com/example/Book.java, as follows:
package com.example;
import io.micronaut.data.annotation.GeneratedValue;
import io.micronaut.data.annotation.Id;
import io.micronaut.data.annotation.MappedEntity;
import static io.micronaut.data.annotation.GeneratedValue.Type.AUTO;
import io.micronaut.serde.annotation.Serdeable;
import jakarta.validation.constraints.Size;
@Serdeable
@MappedEntity // <1>
public class Book {
@Id
@GeneratedValue(AUTO)
private Long id;
private String name;
@Size(min=13, max=13)
private String isbn;
public Book(String isbn, String name) {
this.isbn = isbn;
this.name = name;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
}
1 The annotation @MappedEntity
maps the class to the table defined in the schema.
1.5. Repository Interface #
A repository interface defines the operations to access the database. Micronaut Data implements the interface at compilation time.
The GDK Launcher created a BookRepository
interface in a file named lib/src/main/java/com/example/BookRepository.java:
package com.example;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.data.jdbc.annotation.JdbcRepository;
import io.micronaut.data.repository.CrudRepository;
import jakarta.validation.constraints.NotBlank;
import java.util.Optional;
import static io.micronaut.data.model.query.builder.sql.Dialect.MYSQL;
@JdbcRepository(dialect = MYSQL) // <1>
public interface BookRepository extends CrudRepository<Book, Long> { // <2>
@NonNull
Optional<Book> findByIsbn(@NotBlank String isbn);
}
1 A database dialect is specified with the @JdbcRepository
annotation.
2 The Book
is the root entity, and the primary key type is Long
.
1.6. Controller Class #
The GDK Launcher created a controller to access Book
instances (and to trigger the JDBC metric data) in a file named lib/src/main/java/com/example/BookController.java with the following contents:
package com.example;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.scheduling.TaskExecutors;
import io.micronaut.scheduling.annotation.ExecuteOn;
import io.micrometer.core.annotation.Timed;
import io.micrometer.core.annotation.Counted;
import java.util.Optional;
@Controller("/books") // <1>
@ExecuteOn(TaskExecutors.IO)
class BookController {
private final BookRepository bookRepository;
BookController(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
@Get // <2>
@Timed("books.index") // <3>
Iterable<Book> index() {
return bookRepository.findAll();
}
@Get("/{isbn}") // <3>
@Counted("books.find") // <4>
Optional<Book> findBook(String isbn) {
return bookRepository.findByIsbn(isbn);
}
}
1 The class is defined as a controller with the @Controller
annotation mapped to the path /books
.
2 Use a Micrometer @Timed
annotation, with the value “books.index”, to create a timer metric.
3 The controller maps a GET
request to /books
, which returns a list of Book
.
4 Use a Micrometer @Counted
annotation, with the value “books.find”, to create a counter metric.
2. Create Tests #
Note: You require a running Docker container to run the tests.
-
For tests to run correctly, the GDK Launcher created a test configuration file named gcp/src/test/resources/application-test.properties with the following contents:
customMetrics.initialDelay=10h datasources.default.db-type=mysql datasources.default.dialect=MYSQL datasources.default.driverClassName=com.mysql.cj.jdbc.Driver flyway.datasources.default.enabled=true micronaut.metrics.enabled=true micronaut.metrics.export.cloudwatch.enabled=false micronaut.metrics.export.oraclecloud.enabled=false
The configuration enables the metrics and specifies a MySQL datasource. Since it does not specify a URL for the source, Micronaut Test Resources automatically creates a test container for the database. The configuration also enables Flyway to create the
books
tables the same way as in a production environment. -
The GDK Launcher created a test class, named
BookControllerMetricsTest
, to verify metrics functionality in a file named gcp/src/test/java/com/example/BookControllerMetricsTest.java with the following contents:package com.example; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tags; import io.micrometer.core.instrument.Timer; import io.micronaut.core.type.Argument; import io.micronaut.http.HttpRequest; import io.micronaut.http.client.HttpClient; import io.micronaut.http.client.annotation.Client; import io.micronaut.logging.LoggingSystem; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import jakarta.inject.Inject; import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import java.util.concurrent.TimeUnit; import static io.micronaut.logging.LogLevel.ALL; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @MicronautTest class BookControllerMetricsTest { @Inject MeterRegistry meterRegistry; @Inject LoggingSystem loggingSystem; @Inject @Client("/") HttpClient httpClient; @Test void testExpectedMeters() { Set<String> names = meterRegistry.getMeters().stream() .map(meter -> meter.getId().getName()) .collect(Collectors.toSet()); // check that a subset of expected meters exist assertTrue(names.contains("jvm.memory.max")); assertTrue(names.contains("process.uptime")); assertTrue(names.contains("system.cpu.usage")); assertTrue(names.contains("logback.events")); assertTrue(names.contains("hikaricp.connections.max")); // these will be lazily created assertFalse(names.contains("http.client.requests")); assertFalse(names.contains("http.server.requests")); } @Test void testHttp() { Timer timer = meterRegistry.timer("http.server.requests", Tags.of( "exception", "none", "method", "GET", "status", "200", "uri", "/books")); assertEquals(0, timer.count()); Timer bookIndexTimer = meterRegistry.timer("books.index", Tags.of("exception", "none")); assertEquals(0, bookIndexTimer.count()); httpClient.toBlocking().retrieve( HttpRequest.GET("/books"), Argument.listOf(Book.class)); assertEquals(1, timer.count()); assertEquals(1, bookIndexTimer.count()); assertTrue(0.0 < bookIndexTimer.totalTime(TimeUnit.MILLISECONDS)); assertTrue(0.0 < bookIndexTimer.max(TimeUnit.MILLISECONDS)); Counter bookFindCounter = meterRegistry.counter("books.find", Tags.of("result", "success", "exception", "none")); assertEquals(0, bookFindCounter.count()); httpClient.toBlocking().retrieve( HttpRequest.GET("/books/9781491950357"), Argument.of(Book.class)); assertEquals(1, bookFindCounter.count()); } @Test void testLogback() { Counter counter = meterRegistry.counter("logback.events", Tags.of("level", "info")); double initial = counter.count(); Logger logger = LoggerFactory.getLogger("testing.testing"); loggingSystem.setLogLevel("testing.testing", ALL); logger.trace("trace"); logger.debug("debug"); logger.info("info"); logger.warn("warn"); logger.error("error"); assertEquals(initial + 1, counter.count(), 0.000001); } @Test void testMetricsEndpoint() { Map<String, Object> response = httpClient.toBlocking().retrieve( HttpRequest.GET("/metrics"), Argument.mapOf(String.class, Object.class)); assertTrue(response.containsKey("names")); assertTrue(response.get("names") instanceof List); List<String> names = (List<String>) response.get("names"); // check that a subset of expected meters exist assertTrue(names.contains("jvm.memory.max")); assertTrue(names.contains("process.uptime")); assertTrue(names.contains("system.cpu.usage")); assertTrue(names.contains("logback.events")); assertTrue(names.contains("hikaricp.connections.max")); } @Test void testOneMetricEndpoint() { Map<String, Object> response = httpClient.toBlocking().retrieve( HttpRequest.GET("/metrics/jvm.memory.used"), Argument.mapOf(String.class, Object.class)); String name = (String) response.get("name"); assertEquals("jvm.memory.used", name); List<Map<String, Object>> measurements = (List<Map<String, Object>>) response.get("measurements"); assertEquals(1, measurements.size()); double value = (double) measurements.get(0).get("value"); assertTrue(value > 0); } }
The tests verify that certain metrics are present, including the ones that you enabled in the application configuration file, and the ones that you collected thanks to the
@Counter
and@Timer
annotations.Note that, since the
@MicronautTest
annotation is used, Micronaut initializes the application context and the embedded server with the endpoints you created earlier. (For more information, see the Micronaut Test guide.)
3. Create Custom Metrics #
-
The GDK Launcher created a service that retrieves information from the database and publishes custom metrics based on it. The custom metrics provide the number of books about microservices. The service is in a file named lib/src/main/java/com/example/MicroserviceBooksNumberService.java with the following contents:
package com.example; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Timer; import io.micronaut.scheduling.annotation.Scheduled; import jakarta.inject.Singleton; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.StreamSupport; @Singleton public class MicroserviceBooksNumberService { private final Logger log = LoggerFactory.getLogger(getClass().getName()); private final BookRepository bookRepository; private final Counter checks; private final Timer time; private final AtomicInteger microserviceBooksNumber = new AtomicInteger(0); private static final String SEARCH_KEY = "microservice"; MicroserviceBooksNumberService(BookRepository bookRepository, MeterRegistry meterRegistry) { // <1> this.bookRepository = bookRepository; checks = meterRegistry.counter("microserviceBooksNumber.checks"); time = meterRegistry.timer("microserviceBooksNumber.time"); meterRegistry.gauge("microserviceBooksNumber.latest", microserviceBooksNumber); } @Scheduled(fixedRate = "${customMetrics.updateFrequency:1h}", initialDelay = "${customMetrics.initialDelay:0s}") // <2> public void updateNumber() { time.record(() -> { try { Iterable<Book> allBooks = bookRepository.findAll(); long booksNumber = StreamSupport.stream(allBooks.spliterator(), false) .filter(b -> b.getName().toLowerCase().contains(SEARCH_KEY)) .count(); checks.increment(); microserviceBooksNumber.set((int) booksNumber); } catch (Exception e) { log.error("Problem setting the number of microservice books", e); } }); } }
1 The code registers custom meters, queries the database, counts books containing
microservice
in the name and updates themicroserviceBooksNumber.latest
meter with the value.microserviceBooksNumber.checks
stores the number of updates performed, andmicroserviceBooksNumber.time
stores the total time spent on the updates for the metric.2 The metric update is scheduled. The
customMetrics.updateFrequency
parameter corresponds to the update rate and has the default value of one hour. ThecustomMetrics.initialDelay
parameter corresponds to a delay after application startup before metrics calculation and has a default value of zero seconds. -
Create a test for the custom metrics in a class named
MicroserviceBooksNumberTest
. Create a new file named gcp/src/test/java/com/example/MicroserviceBooksNumberTest.java with the following contents:package com.example; import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.Gauge; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Timer; import io.micronaut.test.extensions.junit5.annotation.MicronautTest; import jakarta.inject.Inject; import org.junit.jupiter.api.Test; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @MicronautTest class MicroserviceBooksNumberTest { @Inject MeterRegistry meterRegistry; @Inject MicroserviceBooksNumberService service; @Test void testMicroserviceBooksNumberUpdates() { Counter counter = meterRegistry.counter("microserviceBooksNumber.checks"); Timer timer = meterRegistry.timer("microserviceBooksNumber.time"); Gauge gauge = meterRegistry.get("microserviceBooksNumber.latest").gauge(); assertEquals(0.0, counter.count()); assertEquals(0.0, timer.totalTime(MILLISECONDS)); assertEquals(0.0, gauge.value()); int checks = 3; for (int i = 0; i < checks; i++) { // <1> service.updateNumber(); } assertEquals((double) checks, counter.count()); assertTrue(timer.totalTime(MILLISECONDS) > 0); assertEquals(2.0, gauge.value()); } }
1 The test calls the service three times and verifies that the metrics are collected correctly. Because the Flyway schema added two books with titles containing the word “microservices”, the value of
microserviceBooksNumber.latest
is2.0
.To make sure that the test works as expected and only three updates of the metric are performed, change the test configuration (in the file gcp/src/test/resources/application-test.properties) so that it does not perform scheduled updates for the custom metric. TTo achieve this, change the
customMetrics.initialDelay
parameter to a large value, such as10
hours. For example:customMetrics.initialDelay=10h
4. Run the Tests #
To run the tests, use the following command:
./gradlew :gcp:test
Then open the file gcp/build/reports/tests/test/index.html in a browser to view the results.
./mvnw install -pl lib -am && ./mvnw test -pl gcp
The tests could be run locally without any cloud credentials, as metrics are not exported to the cloud, and a database is automatically provided by Micronaut test containers.
5. Configure the Application #
-
The GDK Launcher configured an initial datasource in gcp/src/main/resources/application.properties with the following properties:
datasources.default.db-type=mysql datasources.default.dialect=MYSQL datasources.default.driverClassName=com.mysql.cj.jdbc.Driver
You can set values for the missing
datasources.default.url
,datasources.default.username
, anddatasources.default.password
properties with these environment variables:export DATASOURCES_DEFAULT_URL=jdbc:mysql://<Datasource URL, IP, or localhost>:3306/gdk export DATASOURCES_DEFAULT_USERNAME=<Username> export DATASOURCES_DEFAULT_PASSWORD=<Password>
set DATASOURCES_DEFAULT_URL=jdbc:mysql://<Datasource URL, IP, or localhost>:3306/gdk set DATASOURCES_DEFAULT_USERNAME=<Username> set DATASOURCES_DEFAULT_PASSWORD=<Password>
$ENV:DATASOURCES_DEFAULT_URL = "jdbc:mysql://<Datasource URL, IP, or localhost>:3306/gdk" $ENV:DATASOURCES_DEFAULT_USERNAME = "<Username>" $ENV:DATASOURCES_DEFAULT_PASSWORD = "<Password>"
This approach requires an existing database, or requires you to manually start a server in a Docker container. Instead, to simplify things, do not set the environment variables: if you do not specify a datasource URL, Micronaut Test Resources automatically starts a MySQL server in a Docker container when running the application locally or running tests.
6. Run the Application #
To run the application, use the following command, which starts the application on port 8080.
./gradlew :gcp:run
Alternatively, to cause the custom metric update to occur more frequently (to see the effects on metrics), start the application with a configuration override to update every five seconds, as follows:
./gradlew :gcp:run --args="-customMetrics.updateFrequency=5s"
./mvnw install -pl lib -am
./mvnw mn:run -pl gcp
Alternatively, to cause the custom metric update to occur more frequently (to see the effects on metrics), start the application with a configuration override to update every five seconds, as follows:
./mvnw install -pl lib -am
./mvnw mn:run -pl gcp -Dmn.appArgs="-customMetrics.updateFrequency=5s"
Send a few test requests with cURL, as follows:
- Get all the books:
curl localhost:8080/books
[{"id": 1, "name": "Building Microservices", "isbn": "9781491950357"}, {"id": 2, "name": "Release It!", "isbn": "9781680502398"}, {"id": 3, "name": "Continuous Delivery", "isbn": "9780321601919"}]
- Get a book by its ISBN:
curl localhost:8080/books/9781680502398
{"id": 2, "name": "Release It!", "isbn": "9781680502398"}
- Get a list of all the available metrics:
curl localhost:8080/metrics
{"names": [ "books.find", "books.index", ..., "microserviceBooksNumber.latest", "microserviceBooksNumber.time", ..., "http.server.requests", "process.uptime", ... ]}
- Get the value of a particular metric:
curl localhost:8080/metrics/http.server.requests
{"name": "http.server.requests", "measurements": [ {"statistic": "COUNT", "value": 3.0}, {"statistic": "TOTAL_TIME", "value": 0.6045995000000001}, {"statistic": "MAX", "value": 0.0343463} ], ... }
- Get the value of the metric that you created on the
books
endpoint:curl localhost:8080/metrics/books.index
{"name": "books.index", "measurements": [ {"statistic": "COUNT", "value": 1.0}, {"statistic": "TOTAL_TIME", "value": 0.3218636}, {"statistic":"MAX","value":0.0} ], ... }
- Get the value of the custom metric calculating the number of books with
microservice
in their title:curl localhost:8080/metrics/microserviceBooksNumber.latest
{"name": "microserviceBooksNumber.latest", "measurements": [{"statistic": "VALUE", "value" :2.0}] }
7. Monitor Metrics on Google Cloud Monitoring #
Use the Google Cloud Monitoring’s Metrics Explorer UI to view the collected metrics.
-
Sign in to the GCP console, open the navigation menu, and find Metrics Explorer:
-
In the query editor, create a query that you want to monitor. Find the metrics you want to observe by opening the metrics drop-down list(#1), searching for them in the search bar (#2), and selecting them, for example
microserviceBooksNumber.latest
(#3). Click Apply to run the query:
-
Observe the changes of the value over time on the graph:
8. Generate a Native Executable using GraalVM #
The GDK supports compiling Java applications ahead-of-time into native executables using GraalVM Native Image. You can use the Gradle plugin for GraalVM Native Image building/Maven plugin for GraalVM Native Image building. Packaged as a native executable, it significantly reduces application startup time and memory footprint.
Prerequisites: Make sure you have installed a GraalVM JDK. The easiest way to get started is with SDKMAN!. For other installation options, visit the Downloads section.
8.1. Build the Native Executable #
To generate a native executable, run the following command:
./gradlew :gcp:nativeCompile
./mvnw install -pl lib -am
./mvnw package -pl gcp -Dpackaging=native-image
8.2. Run the Native Executable #
To start the native executable, run a Docker container with a MySQL database:
docker run -it --rm \
--name "mysql.8" \
-p 3306:3306 \
-e MYSQL_DATABASE=gdk \
-e MYSQL_USER=guide_user \
-e MYSQL_PASSWORD=User123User! \
-e MYSQL_ALLOW_EMPTY_PASSWORD=true \
mysql:8
Set environment variables to provide values for the missing datasource url, username, and password properties:
export DATASOURCES_DEFAULT_URL=jdbc:mysql://localhost:3306/gdk
export DATASOURCES_DEFAULT_USERNAME=guide_user
export DATASOURCES_DEFAULT_PASSWORD=User123User!
set DATASOURCES_DEFAULT_URL=jdbc:mysql://localhost:3306/gdk
set DATASOURCES_DEFAULT_USERNAME=guide_user
set DATASOURCES_DEFAULT_PASSWORD=User123User!
$ENV:DATASOURCES_DEFAULT_URL = "jdbc:mysql://localhost:3306/gdk"
$ENV:DATASOURCES_DEFAULT_USERNAME = "guide_user"
$ENV:DATASOURCES_DEFAULT_PASSWORD = "User123User!"
Start the generated native executable:
Note: With Gradle you can run
./gradlew :gcp:nativeRun
to start the native executable in development mode, which uses Micronaut Test Resources and therefore doesn’t require you to start a MySQL server.
Run the same curl
commands from above to confirm that the application works the same way as before, but with faster startup and response times.
Then return to Google Cloud Monitoring’s Metrics Explorer console to review the metrics.
9. Clean Up #
When you have completed the guide, you can clean up the resources you created on Google Cloud Platform so you will not be billed for them in the future.
9.1. Delete the Project #
The easiest way to eliminate billing is to delete the project you created.
Deleting a project has the following consequences:
-
If you used an existing project, you will also delete any other work you have done in the project.
-
You cannot reuse the project ID of a deleted project. If you created a custom project ID that you plan to use in the future, you should delete the resources inside the project instead. This ensures that URLs that use the project ID, such as an appspot.com URL, remain available.
-
If you are exploring multiple guides, reusing projects instead of deleting them prevents you from exceeding project quota limits.
9.1.1. Via the CLI
To delete the project using the Google Cloud CLI, run the following command:
gcloud projects delete gdk-guides
9.1.2. Via the Cloud Platform Console
-
In the Cloud Platform Console, go to the Projects page.
-
In the project list, select the project you want to delete and click Delete project. Select the check box next to the project name and click Delete project.
-
In the dialog box, enter the project ID, and then click Shut down to delete the project.
Summary #
This guide demonstrated how to create a Micronaut application that collects standard and custom metrics, then publishes and monitors them on Google Cloud Monitoring. The application uses Micronaut Micrometer to expose application metric data with Micrometer.