From b5473b6783cfba9a4d1fa1c22a48c2de0738a2e8 Mon Sep 17 00:00:00 2001 From: selim Date: Wed, 2 Mar 2022 12:28:09 +0100 Subject: [PATCH] build(metrics): expose microservice metrics --- docker-compose.yml | 13 ++++- pom.xml | 9 ++- prometheus.yml | 14 +++++ src/main/asciidoc/index.adoc | 29 +++++----- src/main/resources/application.properties | 5 +- .../coladay/common/PostgreSQLExtension.java | 1 + .../coladay/metrics/MetricsControllerIT.java | 56 ++++++++++++++++--- .../reservation/ReservationControllerIT.java | 4 -- 8 files changed, 98 insertions(+), 33 deletions(-) create mode 100644 prometheus.yml diff --git a/docker-compose.yml b/docker-compose.yml index aed0670..6e7183b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: app: image: 'selimyanat/coladay:latest' build: - context: . + context: ../.. container_name: coladay ports: - "9090:8080" @@ -33,4 +33,13 @@ services: - "9001:5432" environment: - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=secret \ No newline at end of file + - POSTGRES_PASSWORD=secret + prometheus: + image: 'prom/prometheus:v2.31.1' + command: --config.file=/etc/prometheus/prometheus.yml --log.level=debug + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - ./prometheus.yml:/etc/prometheus/prometheus.yml + ports: + - "7070:9090" diff --git a/pom.xml b/pom.xml index 3df4474..3d41141 100644 --- a/pom.xml +++ b/pom.xml @@ -58,12 +58,12 @@ spring-boot-starter-actuator - org.projectlombok - lombok + io.micrometer + micrometer-registry-prometheus - com.h2database - h2 + org.projectlombok + lombok org.postgresql @@ -72,7 +72,6 @@ org.liquibase liquibase-core - io.vavr diff --git a/prometheus.yml b/prometheus.yml new file mode 100644 index 0000000..fc0c34b --- /dev/null +++ b/prometheus.yml @@ -0,0 +1,14 @@ +global: + scrape_interval: 15s + evaluation_interval: 30s + +scrape_configs: + - job_name: prometheus + honor_labels: true + static_configs: + - targets: ["localhost:9090"] + - job_name: coladay + honor_labels: true + metrics_path: /actuator/prometheus + static_configs: + - targets: ["host.docker.internal:9091"] \ No newline at end of file diff --git a/src/main/asciidoc/index.adoc b/src/main/asciidoc/index.adoc index 0fcf3e9..fd329b9 100644 --- a/src/main/asciidoc/index.adoc +++ b/src/main/asciidoc/index.adoc @@ -121,20 +121,20 @@ The Reservation resources is used to create, delete, update and list resources. [[resources_create_reservation]] === Creating a reservation -A `POST` request is used to create a reservation. +A `POST` request used to create a reservation. operation::create-a-reservation[snippets='curl-request,http-request,http-response'] [[resources_list_reservations]] === List reservations -A `GET` request is used to list all reservations. +A `GET` request used to list all reservations. operation::get-all-reservations-by-page[snippets='curl-request,http-request,http-response'] === Cancel a reservation -A `DELETE` request is used to cancel a reservation. +A `DELETE` request used to cancel a reservation. operation::cancel-a-reservation[snippets='curl-request,http-request,http-response'] @@ -144,7 +144,7 @@ operation::cancel-a-reservation[snippets='curl-request,http-request,http-respons [[resources_list_rooms]] === List rooms -A `GET` request is used to list all rooms and their availabilities. +A `GET` request used to list all rooms and their availabilities. operation::list-all-rooms[snippets='curl-request,http-request,http-response'] @@ -155,32 +155,35 @@ operation::list-all-rooms[snippets='curl-request,http-request,http-response'] [[resources_list_users]] === List users -A `GET` request is used to list all users. +A `GET` request used to list all users. operation::list-all-users[snippets='curl-request,http-request,http-response'] [[resources_metrics]] == Metrics -IMPORTANT: For testing purpose, the examples below use the default application port 8080 to access -the metric endpoints. However, in production the metric endpoints are accessible by default on port -8081. - === List all metrics endpoints -A `GET` request is used to list all metrics endpoints. +A `GET` request used to list all metrics endpoints. operation::list-all-metrics-endpoints[snippets='curl-request,http-request,http-response'] === List all application metrics -A `GET` request is used to list all application metrics. +A `GET` request used to list all application metrics. operation::list-all-applications-metrics[snippets='curl-request,http-request,http-response'] === Read application health -A `GET` request is used to read application health. +A `GET` request used to read application health. + +operation::read-application-health[snippets='curl-request,http-request,http-response'] + +=== List all application metrics in prometheus format + +A `GET` request used to list all metrics endpoints in prometheus format. This endpoint is +generally called by Prometheus to scrap application metrics. -operation::read-application-health[snippets='curl-request,http-request,http-response'] \ No newline at end of file +operation::list-all-applications-metrics-in-prometheus-format[snippets='curl-request,http-request,http-response'] \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 0d1069d..ac7b962 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,3 +1,5 @@ +spring.application.name="coladay" + # Quota configuration coke.quota= ${COKE_QUOTA:100} pepsi.quota= ${PEPSI_QUOTA:100} @@ -18,8 +20,9 @@ spring.jpa.properties.hibernate.default_schema=coladay # Actuator +management.metrics.tags.application=coladay management.security.enabled=false -management.endpoints.web.exposure.include=health, metrics +management.endpoints.web.exposure.include=health, metrics, prometheus management.server.port=8081 # Log configuration diff --git a/src/test/java/com/sy/coladay/common/PostgreSQLExtension.java b/src/test/java/com/sy/coladay/common/PostgreSQLExtension.java index 8eac4f9..a770ed7 100644 --- a/src/test/java/com/sy/coladay/common/PostgreSQLExtension.java +++ b/src/test/java/com/sy/coladay/common/PostgreSQLExtension.java @@ -64,6 +64,7 @@ public void beforeAll(ExtensionContext extensionContext) throws Exception { @Override public void afterAll(ExtensionContext extensionContext) throws Exception { + postgres.stop(); } } diff --git a/src/test/java/com/sy/coladay/metrics/MetricsControllerIT.java b/src/test/java/com/sy/coladay/metrics/MetricsControllerIT.java index f366145..3455a1b 100644 --- a/src/test/java/com/sy/coladay/metrics/MetricsControllerIT.java +++ b/src/test/java/com/sy/coladay/metrics/MetricsControllerIT.java @@ -1,5 +1,6 @@ package com.sy.coladay.metrics; +import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.hasItem; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; @@ -9,6 +10,7 @@ import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -57,13 +59,15 @@ public void getActuator_return_200() { .accept(MediaType.APPLICATION_JSON)) .andDo(print()) .andExpect(status().isOk()) - // In case, we upgrade spring boot this test can catch regression and new functionality - .andExpect(jsonPath("$._links.length()", is(6))) + // In case, we upgrade spring boot or its configuration, this test can catch regression + // and new functionality + .andExpect(jsonPath("$._links.length()", is(7))) .andExpect(jsonPath("$._links.health-component-instance", notNullValue())) .andExpect(jsonPath("$._links.health-component", notNullValue())) .andExpect(jsonPath("$._links.health", notNullValue())) .andExpect(jsonPath("$._links.metrics", notNullValue())) .andExpect(jsonPath("$._links.metrics-requiredMetricName", notNullValue())) + .andExpect(jsonPath("$._links.prometheus", notNullValue())) .andDo(document("list-all-metrics-endpoints")) ; } @@ -86,13 +90,8 @@ public void getMetrics_return_200() { .andExpect(jsonPath("$.names", hasItem("jvm.memory.max"))) .andExpect(jsonPath("$.names", hasItem("jdbc.connections.min"))) .andExpect(jsonPath("$.names", hasItem("jdbc.connections.max"))) - .andExpect(jsonPath("$.names", hasItem("jdbc.connections.max"))) + .andExpect(jsonPath("$.names", hasItem("jdbc.connections.active"))) .andExpect(jsonPath("$.names", hasItem("system.cpu.usage"))) - .andExpect(jsonPath("$.names", hasItem("hikaricp.connections.max"))) - .andExpect(jsonPath("$.names", hasItem("hikaricp.connections.min"))) - .andExpect(jsonPath("$.names", hasItem("hikaricp.connections.idle"))) - .andExpect(jsonPath("$.names", hasItem("hikaricp.connections.pending"))) - .andExpect(jsonPath("$.names", hasItem("hikaricp.connections.usage"))) .andExpect(jsonPath("$.names", hasItem("process.uptime"))) .andExpect(jsonPath("$.names", hasItem("process.cpu.usage"))) .andExpect(jsonPath("$.names", hasItem("process.start.time"))) @@ -113,4 +112,45 @@ public void getHealth_return_200() { .andDo(document("read-application-health")); } + @Test + @SneakyThrows + public void getPrometheusMetrics_returns200() { + + mockMvc.perform(get("/actuator/prometheus") + .accept(MediaType.TEXT_PLAIN_VALUE)) + .andDo(print()) + // NOTE: This is a use case for consumer contract testing! + .andExpect(status().isOk()) + .andExpect(content().string(containsString("jvm_memory_used_bytes{application=\"coladay\",area=\"heap\",id=\"G1 Eden Space\",}"))) + .andExpect(content().string(containsString("jvm_memory_used_bytes{application=\"coladay\",area=\"heap\",id=\"G1 Old Gen\",}"))) + .andExpect(content().string(containsString("jvm_memory_used_bytes{application=\"coladay\",area=\"nonheap\",id=\"Metaspace\",}"))) + .andExpect(content().string(containsString("jvm_memory_used_bytes{application=\"coladay\",area=\"heap\",id=\"G1 Survivor Space\",}"))) + .andExpect(content().string(containsString("jvm_memory_used_bytes{application=\"coladay\",area=\"nonheap\",id=\"CodeHeap 'non-nmethods'\",}"))) + .andExpect(content().string(containsString("jvm_memory_used_bytes{application=\"coladay\",area=\"nonheap\",id=\"CodeHeap 'non-profiled nmethods'\",}"))) + .andExpect(content().string(containsString("jvm_memory_used_bytes{application=\"coladay\",area=\"nonheap\",id=\"Compressed Class Space\",}"))) + .andExpect(content().string(containsString("jvm_memory_used_bytes{application=\"coladay\",area=\"nonheap\",id=\"CodeHeap 'profiled nmethods'\",}"))) + .andExpect(content().string(containsString("system_cpu_usage{application=\"coladay\",}"))) + .andExpect(content().string(containsString("jdbc_connections_min{application=\"coladay" + + "\",name=\"dataSource\",}"))) + .andExpect(content().string(containsString("jdbc_connections_max{application=\"coladay" + + "\",name=\"dataSource\",}"))) + .andExpect(content().string(containsString("jdbc_connections_active{application=\"coladay" + + "\",name=\"dataSource\",}"))) + .andExpect(content().string(containsString("jvm_threads_states_threads{application" + + "=\"coladay\",state=\"runnable\",}"))) + .andExpect(content().string(containsString("jvm_threads_states_threads{application" + + "=\"coladay\",state=\"blocked\",}"))) + .andExpect(content().string(containsString("jvm_threads_states_threads" + + "{application=\"coladay\",state=\"waiting\",}"))) + .andExpect(content().string(containsString("jvm_threads_states_threads{application" + + "=\"coladay\",state=\"timed-waiting\",}"))) + .andExpect(content().string(containsString("jvm_threads_states_threads{application" + + "=\"coladay\",state=\"new\",}"))) + .andExpect(content().string(containsString("process_uptime_seconds"))) + .andExpect(content().string(containsString("process_cpu_usage"))) + .andExpect(content().string(containsString("process_start_time_seconds"))) + .andDo(document("list-all-applications-metrics-in-prometheus-format")) + ; + } + } diff --git a/src/test/java/com/sy/coladay/reservation/ReservationControllerIT.java b/src/test/java/com/sy/coladay/reservation/ReservationControllerIT.java index a82ca7f..0afd799 100644 --- a/src/test/java/com/sy/coladay/reservation/ReservationControllerIT.java +++ b/src/test/java/com/sy/coladay/reservation/ReservationControllerIT.java @@ -53,10 +53,6 @@ class ReservationControllerIT { User cokeUser; User pepsiUser; -// -// @Autowired -// private DataSource dataSource; - @BeforeEach void setUp(WebApplicationContext webApplicationContext,