Skip to content

Commit

Permalink
Convert PrometheusSupport from a reactive WebServer Service to a Heli…
Browse files Browse the repository at this point in the history
…donFeatureSupport (#6837)

* Convert PrometheusSupport from a reactive WebServer Service to a Nima HttpServer or HelidonFeatureSupport

Signed-off-by: aserkes <andrii.serkes@oracle.com>
  • Loading branch information
aserkes authored May 19, 2023
1 parent 35a21fc commit 036d073
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 89 deletions.
13 changes: 4 additions & 9 deletions metrics/prometheus/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@

<dependencies>
<dependency>
<groupId>io.helidon.reactive.webserver</groupId>
<artifactId>helidon-reactive-webserver</artifactId>
<groupId>io.helidon.nima.service-common</groupId>
<artifactId>helidon-nima-service-common</artifactId>
</dependency>
<dependency>
<groupId>io.prometheus</groupId>
Expand All @@ -49,13 +49,8 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.helidon.reactive.webserver</groupId>
<artifactId>helidon-reactive-webserver-test-support</artifactId>
<groupId>io.helidon.nima.testing.junit5</groupId>
<artifactId>helidon-nima-testing-junit5-webserver</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, 2022 Oracle and/or its affiliates.
* Copyright (c) 2017, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,30 +19,33 @@
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

import io.helidon.common.http.HttpMediaType;
import io.helidon.reactive.webserver.Routing;
import io.helidon.reactive.webserver.ServerRequest;
import io.helidon.reactive.webserver.ServerResponse;
import io.helidon.reactive.webserver.Service;
import io.helidon.nima.servicecommon.HelidonFeatureSupport;
import io.helidon.nima.webserver.http.HttpRouting;
import io.helidon.nima.webserver.http.HttpRules;
import io.helidon.nima.webserver.http.HttpService;
import io.helidon.nima.webserver.http.ServerRequest;
import io.helidon.nima.webserver.http.ServerResponse;

import io.prometheus.client.Collector;
import io.prometheus.client.CollectorRegistry;

/**
* Support for Prometheus client endpoint.
* <p>
* Default and simplest use on {@link Routing} creates {@code /metrics} endpoint
* Default and simplest use on {@link HttpRouting} creates {@code /metrics} endpoint
* for {@link CollectorRegistry default CollectorRegistry}.
* <pre>{@code
* Routing.builder()
* .register(PrometheusSupport.create())
* HttpRouting.builder()
* ..addFeature(PrometheusSupport.create())
* }</pre>
* <p>
* It is possible to use
*/
public final class PrometheusSupport implements Service {
public final class PrometheusSupport extends HelidonFeatureSupport {

private static final System.Logger LOGGER = System.getLogger(PrometheusSupport.class.getName());

/**
* Standard path of Prometheus client resource: {@code /metrics}.
Expand All @@ -54,18 +57,23 @@ public final class PrometheusSupport implements Service {
private final CollectorRegistry collectorRegistry;
private final String path;

private PrometheusSupport(CollectorRegistry collectorRegistry, String path) {
this.collectorRegistry = collectorRegistry == null ? CollectorRegistry.defaultRegistry : collectorRegistry;
this.path = path == null ? DEFAULT_PATH : path;
private PrometheusSupport(Builder builder) {
super(LOGGER, builder, "prometheus");
this.collectorRegistry = builder.registry;
this.path = builder.path;
}

@Override
public void update(Routing.Rules rules) {
private void configureRoutes(HttpRules rules) {
rules.get(path, this::process);
}

@Override
public Optional<HttpService> service() {
return Optional.of(this::configureRoutes);
}

private void process(ServerRequest req, ServerResponse res) {
Set<String> filters = new HashSet<>(req.queryParams().all("name[]", List::of));
Set<String> filters = new HashSet<>(req.query().all("name[]", List::of));
Enumeration<Collector.MetricFamilySamples> mfs = collectorRegistry.filteredMetricFamilySamples(filters);
res.headers().contentType(CONTENT_TYPE);
res.send(compose(mfs));
Expand Down Expand Up @@ -171,7 +179,7 @@ private static String typeString(Collector.Type t) {
* @see #builder()
*/
public static PrometheusSupport create(CollectorRegistry collectorRegistry) {
return new PrometheusSupport(collectorRegistry, DEFAULT_PATH);
return builder().collectorRegistry(collectorRegistry).build();
}

/**
Expand All @@ -183,7 +191,7 @@ public static PrometheusSupport create(CollectorRegistry collectorRegistry) {
* @see #builder()
*/
public static PrometheusSupport create() {
return create(null);
return builder().build();
}

/**
Expand All @@ -200,12 +208,13 @@ public static Builder builder() {
/**
* A builder of {@link PrometheusSupport}.
*/
public static final class Builder implements io.helidon.common.Builder<Builder, PrometheusSupport> {
public static final class Builder extends HelidonFeatureSupport.Builder<Builder, PrometheusSupport> {

private CollectorRegistry registry = CollectorRegistry.defaultRegistry;
private String path;
private String path = DEFAULT_PATH;

private Builder() {
super("/");
}

/**
Expand Down Expand Up @@ -236,7 +245,7 @@ public Builder path(String path) {

@Override
public PrometheusSupport build() {
return new PrometheusSupport(registry, path == null ? DEFAULT_PATH : path);
return new PrometheusSupport(this);
}
}
}
5 changes: 2 additions & 3 deletions metrics/prometheus/src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2018, 2022 Oracle and/or its affiliates.
* Copyright (c) 2018, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -18,8 +18,7 @@
* Prometheus support.
*/
module io.helidon.metrics.prometheus {
requires io.helidon.reactive.webserver;

requires io.helidon.nima.servicecommon;
// prometheus :(
requires simpleclient;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2017, 2022 Oracle and/or its affiliates.
* Copyright (c) 2017, 2023 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,17 +16,17 @@

package io.helidon.metrics.prometheus;

import java.util.concurrent.TimeUnit;

import io.helidon.common.http.Http;
import io.helidon.reactive.webserver.Routing;
import io.helidon.reactive.webserver.testsupport.TestClient;
import io.helidon.reactive.webserver.testsupport.TestRequest;
import io.helidon.reactive.webserver.testsupport.TestResponse;
import io.helidon.nima.testing.junit5.webserver.ServerTest;
import io.helidon.nima.testing.junit5.webserver.SetUpRoute;
import io.helidon.nima.webclient.http1.Http1Client;
import io.helidon.nima.webclient.http1.Http1ClientResponse;
import io.helidon.nima.webserver.http.HttpRouting;

import io.prometheus.client.CollectorRegistry;
import io.prometheus.client.Counter;
import org.hamcrest.core.StringStartsWith;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

Expand All @@ -35,20 +35,25 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsNot.not;

@ServerTest
public class PrometheusSupportTest {

private Routing routing;
private Counter alpha;
private Counter beta;
private static CollectorRegistry registry = new CollectorRegistry();
private final Http1Client client;

PrometheusSupportTest(Http1Client client) {
this.client = client;
}

@SetUpRoute
static void routing(HttpRouting.Builder builder){
builder.addFeature(PrometheusSupport.create(registry)).build();
}

@BeforeEach
public void prepareRouting() {
CollectorRegistry registry = new CollectorRegistry();
// Routing
this.routing = Routing.builder()
.register(PrometheusSupport.create(registry))
.build();
// Metrics
public void prepareRegistry() {
this.alpha = Counter.build()
.name("alpha")
.help("Alpha help with \\ and \n.")
Expand All @@ -69,54 +74,53 @@ public void prepareRouting() {
}
}

private TestResponse doTestRequest(String nameQuery) throws Exception {
TestRequest request = TestClient.create(routing)
.path("/metrics");
if (nameQuery != null && !nameQuery.isEmpty()) {
request.queryParameter("name[]", nameQuery);
}
TestResponse response = request.get();
assertThat(response.status(), is(Http.Status.OK_200));
return response;
@AfterEach
public void clearRegistry() {
registry.clear();
}

@Test
public void simpleCall() throws Exception {
TestResponse response = doTestRequest(null);
assertThat(response.headers().first(Http.Header.CONTENT_TYPE).orElse(null),
StringStartsWith.startsWith("text/plain"));
String body = response.asString().get(5, TimeUnit.SECONDS);
assertThat(body, containsString("# HELP beta"));
assertThat(body, containsString("# TYPE beta counter"));
assertThat(body, containsString("beta 3.0"));
assertThat(body, containsString("# TYPE alpha counter"));
assertThat(body, containsString("# HELP alpha Alpha help with \\\\ and \\n."));
assertThat(body, containsString("alpha{method=\"bar\",} 6.0"));
assertThat(body, containsString("alpha{method=\"\\\"foo\\\" \\\\ \\n\",} 5.0"));
public void simpleCall() {
try (Http1ClientResponse response = client.get("/metrics").request()) {
assertThat(response.status(), is(Http.Status.OK_200));
assertThat(response.headers().first(Http.Header.CONTENT_TYPE).orElse(null),
StringStartsWith.startsWith("text/plain"));
String body = response.as(String.class);
assertThat(body, containsString("# HELP beta"));
assertThat(body, containsString("# TYPE beta counter"));
assertThat(body, containsString("beta 3.0"));
assertThat(body, containsString("# TYPE alpha counter"));
assertThat(body, containsString("# HELP alpha Alpha help with \\\\ and \\n."));
assertThat(body, containsString("alpha{method=\"bar\",} 6.0"));
assertThat(body, containsString("alpha{method=\"\\\"foo\\\" \\\\ \\n\",} 5.0"));
}
}

@Test
public void doubleCall() throws Exception {
TestResponse response = doTestRequest(null);
assertThat(response.headers().first(Http.Header.CONTENT_TYPE).orElse(null),
StringStartsWith.startsWith("text/plain"));
String body = response.asString().get(5, TimeUnit.SECONDS);
assertThat(body, containsString("alpha{method=\"bar\",} 6.0"));
assertThat(body, not(containsString("alpha{method=\"baz\"")));
alpha.labels("baz").inc();
response = doTestRequest(null);
body = response.asString().get(5, TimeUnit.SECONDS);
assertThat(body, containsString("alpha{method=\"baz\",} 1.0"));
public void doubleCall() {
try (Http1ClientResponse response = client.get("/metrics").request()) {
assertThat(response.headers().first(Http.Header.CONTENT_TYPE).orElse(null),
StringStartsWith.startsWith("text/plain"));
String body = response.as(String.class);
assertThat(body, containsString("alpha{method=\"bar\",} 6.0"));
assertThat(body, not(containsString("alpha{method=\"baz\"")));
alpha.labels("baz").inc();
}
try (Http1ClientResponse response = client.get("/metrics").request()) {
String body = response.as(String.class);
assertThat(body, containsString("alpha{method=\"baz\",} 1.0"));
}
}

@Test
public void filter() throws Exception {
TestResponse response = doTestRequest("alpha");
assertThat(response.status(), is(Http.Status.OK_200));
String body = response.asString().get(5, TimeUnit.SECONDS);
assertThat(body, not(containsString("# TYPE beta")));
assertThat(body, not(containsString("beta 3.0")));
assertThat(body, containsString("# TYPE alpha counter"));
assertThat(body, containsString("alpha{method=\"bar\",} 6.0"));
public void filter() {
try (Http1ClientResponse response = client.get("/metrics").queryParam("name[]", "alpha").request()) {
assertThat(response.status(), is(Http.Status.OK_200));
String body = response.as(String.class);
assertThat(body, not(containsString("# TYPE beta")));
assertThat(body, not(containsString("beta 3.0")));
assertThat(body, containsString("# TYPE alpha counter"));
assertThat(body, containsString("alpha{method=\"bar\",} 6.0"));
}
}
}

0 comments on commit 036d073

Please sign in to comment.