Commit cd1baf18 authored by Johnny Lim's avatar Johnny Lim Committed by Stephane Nicoll

Support filtered scrape for Prometheus

See gh-21545
parent c848f80c
...@@ -15,3 +15,14 @@ include::{snippets}/prometheus/curl-request.adoc[] ...@@ -15,3 +15,14 @@ include::{snippets}/prometheus/curl-request.adoc[]
The resulting response is similar to the following: The resulting response is similar to the following:
include::{snippets}/prometheus/http-response.adoc[] include::{snippets}/prometheus/http-response.adoc[]
[[prometheus-retrieving-query-parameters]]
=== Query Parameters
The endpoint uses query parameters to limit the samples that it returns.
The following table shows the supported query parameters:
[cols="2,4"]
include::{snippets}/prometheus/request-parameters.adoc[]
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -28,6 +28,8 @@ import org.springframework.context.annotation.Configuration; ...@@ -28,6 +28,8 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Import;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.requestParameters;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
...@@ -35,6 +37,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. ...@@ -35,6 +37,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* Tests for generating documentation describing the {@link PrometheusScrapeEndpoint}. * Tests for generating documentation describing the {@link PrometheusScrapeEndpoint}.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Johnny Lim
*/ */
class PrometheusScrapeEndpointDocumentationTests extends MockMvcEndpointDocumentationTests { class PrometheusScrapeEndpointDocumentationTests extends MockMvcEndpointDocumentationTests {
...@@ -43,6 +46,16 @@ class PrometheusScrapeEndpointDocumentationTests extends MockMvcEndpointDocument ...@@ -43,6 +46,16 @@ class PrometheusScrapeEndpointDocumentationTests extends MockMvcEndpointDocument
this.mockMvc.perform(get("/actuator/prometheus")).andExpect(status().isOk()).andDo(document("prometheus")); this.mockMvc.perform(get("/actuator/prometheus")).andExpect(status().isOk()).andDo(document("prometheus"));
} }
@Test
void filteredPrometheus() throws Exception {
this.mockMvc
.perform(get("/actuator/prometheus").param("includedNames",
"jvm_memory_used_bytes,jvm_memory_committed_bytes"))
.andExpect(status().isOk())
.andDo(document("prometheus", requestParameters(parameterWithName("includedNames")
.description("Restricts the samples to those that match the names. Optional.").optional())));
}
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
@Import(BaseDocumentationConfiguration.class) @Import(BaseDocumentationConfiguration.class)
static class TestConfiguration { static class TestConfiguration {
......
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -19,19 +19,24 @@ package org.springframework.boot.actuate.metrics.export.prometheus; ...@@ -19,19 +19,24 @@ package org.springframework.boot.actuate.metrics.export.prometheus;
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
import java.io.Writer; import java.io.Writer;
import java.util.Enumeration;
import java.util.Set;
import io.prometheus.client.Collector.MetricFamilySamples;
import io.prometheus.client.CollectorRegistry; import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.exporter.common.TextFormat; import io.prometheus.client.exporter.common.TextFormat;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation; import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint; import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpoint;
import org.springframework.lang.Nullable;
/** /**
* {@link Endpoint @Endpoint} that outputs metrics in a format that can be scraped by the * {@link Endpoint @Endpoint} that outputs metrics in a format that can be scraped by the
* Prometheus server. * Prometheus server.
* *
* @author Jon Schneider * @author Jon Schneider
* @author Johnny Lim
* @since 2.0.0 * @since 2.0.0
*/ */
@WebEndpoint(id = "prometheus") @WebEndpoint(id = "prometheus")
...@@ -44,10 +49,13 @@ public class PrometheusScrapeEndpoint { ...@@ -44,10 +49,13 @@ public class PrometheusScrapeEndpoint {
} }
@ReadOperation(produces = TextFormat.CONTENT_TYPE_004) @ReadOperation(produces = TextFormat.CONTENT_TYPE_004)
public String scrape() { public String scrape(@Nullable Set<String> includedNames) {
try { try {
Writer writer = new StringWriter(); Writer writer = new StringWriter();
TextFormat.write004(writer, this.collectorRegistry.metricFamilySamples()); Enumeration<MetricFamilySamples> samples = (includedNames != null)
? this.collectorRegistry.filteredMetricFamilySamples(includedNames)
: this.collectorRegistry.metricFamilySamples();
TextFormat.write004(writer, samples);
return writer.toString(); return writer.toString();
} }
catch (IOException ex) { catch (IOException ex) {
......
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
package org.springframework.boot.actuate.metrics.export.prometheus; package org.springframework.boot.actuate.metrics.export.prometheus;
import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.prometheus.PrometheusMeterRegistry; import io.micrometer.prometheus.PrometheusMeterRegistry;
import io.prometheus.client.CollectorRegistry; import io.prometheus.client.CollectorRegistry;
...@@ -28,17 +29,30 @@ import org.springframework.context.annotation.Configuration; ...@@ -28,17 +29,30 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.test.web.reactive.server.WebTestClient; import org.springframework.test.web.reactive.server.WebTestClient;
import static org.assertj.core.api.Assertions.assertThat;
/** /**
* Tests for {@link PrometheusScrapeEndpoint}. * Tests for {@link PrometheusScrapeEndpoint}.
* *
* @author Jon Schneider * @author Jon Schneider
* @author Johnny Lim
*/ */
class PrometheusScrapeEndpointIntegrationTests { class PrometheusScrapeEndpointIntegrationTests {
@WebEndpointTest @WebEndpointTest
void scrapeHasContentTypeText004(WebTestClient client) { void scrapeHasContentTypeText004(WebTestClient client) {
client.get().uri("/actuator/prometheus").exchange().expectStatus().isOk().expectHeader() client.get().uri("/actuator/prometheus").exchange().expectStatus().isOk().expectHeader()
.contentType(MediaType.parseMediaType(TextFormat.CONTENT_TYPE_004)); .contentType(MediaType.parseMediaType(TextFormat.CONTENT_TYPE_004)).expectBody(String.class)
.value((body) -> assertThat(body).contains("counter1_total").contains("counter2_total")
.contains("counter3_total"));
}
@WebEndpointTest
void scrapeWithIncludedNames(WebTestClient client) {
client.get().uri("/actuator/prometheus?includedNames=counter1_total,counter2_total").exchange().expectStatus()
.isOk().expectHeader().contentType(MediaType.parseMediaType(TextFormat.CONTENT_TYPE_004))
.expectBody(String.class).value((body) -> assertThat(body).contains("counter1_total")
.contains("counter2_total").doesNotContain("counter3_total"));
} }
@Configuration(proxyBeanMethods = false) @Configuration(proxyBeanMethods = false)
...@@ -56,7 +70,11 @@ class PrometheusScrapeEndpointIntegrationTests { ...@@ -56,7 +70,11 @@ class PrometheusScrapeEndpointIntegrationTests {
@Bean @Bean
MeterRegistry registry(CollectorRegistry registry) { MeterRegistry registry(CollectorRegistry registry) {
return new PrometheusMeterRegistry((k) -> null, registry, Clock.SYSTEM); PrometheusMeterRegistry meterRegistry = new PrometheusMeterRegistry((k) -> null, registry, Clock.SYSTEM);
Counter.builder("counter1").register(meterRegistry);
Counter.builder("counter2").register(meterRegistry);
Counter.builder("counter3").register(meterRegistry);
return meterRegistry;
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment