diff --git a/pom.xml b/pom.xml index caea56440..b9984bd2f 100644 --- a/pom.xml +++ b/pom.xml @@ -62,6 +62,7 @@ simpleclient_jetty simpleclient_jetty_jdk8 simpleclient_vertx + simpleclient_undertow benchmark diff --git a/simpleclient_undertow/pom.xml b/simpleclient_undertow/pom.xml new file mode 100644 index 000000000..90c8f2a65 --- /dev/null +++ b/simpleclient_undertow/pom.xml @@ -0,0 +1,97 @@ + + + 4.0.0 + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + 1.7 + 1.7 + + + + + + + io.prometheus + parent + 0.0.27-SNAPSHOT + + + io.prometheus + simpleclient_undertow + bundle + + Prometheus Java Simpleclient Undertow + + Undertow Web Handler exporter for the simpleclient + + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + + kislitsyn + Anton Kislitsyn + anton.v.kislitsyn@gmail.com + + + + + UTF-8 + + + + + io.prometheus + simpleclient + 0.0.27-SNAPSHOT + + + io.prometheus + simpleclient_common + 0.0.27-SNAPSHOT + + + io.undertow + undertow-core + 1.4.20.Final + provided + + + + io.undertow + undertow-servlet + 1.4.20.Final + test + + + org.apache.commons + commons-lang3 + 3.4 + test + + + junit + junit + 4.11 + test + + + org.assertj + assertj-core + 2.6.0 + test + + + diff --git a/simpleclient_undertow/src/main/java/io/prometheus/client/undertow/MetricsHandler.java b/simpleclient_undertow/src/main/java/io/prometheus/client/undertow/MetricsHandler.java new file mode 100644 index 000000000..0899d3978 --- /dev/null +++ b/simpleclient_undertow/src/main/java/io/prometheus/client/undertow/MetricsHandler.java @@ -0,0 +1,85 @@ +package io.prometheus.client.undertow; + +import io.prometheus.client.CollectorRegistry; +import io.prometheus.client.exporter.common.TextFormat; +import io.undertow.server.HttpHandler; +import io.undertow.server.HttpServerExchange; +import io.undertow.server.handlers.BlockingHandler; +import io.undertow.util.Headers; + +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.util.Collections; +import java.util.Deque; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Metrics Handler for Undertow. + *

+ * Example usage: + *

+ * {@code
+ * Undertow.builder()
+ *      .addHttpListener(port, host)
+ *      .setHandler(path().addExactPath("/metrics", new MetricsHandler()))
+ *      .build();
+ * }
+ * 
+ */ +public class MetricsHandler implements HttpHandler { + + private final HttpHandler handler; + + /** + * Construct a MetricsHandler for the default registry + */ + public MetricsHandler() { + this(CollectorRegistry.defaultRegistry); + } + + /** + * Construct a MetricsHandler for the given registry + * + * @param registry collector registry + */ + public MetricsHandler(CollectorRegistry registry) { + handler = new BlockingHandler(new Handler(registry)); + } + + + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + handler.handleRequest(exchange); + } + + private class Handler implements HttpHandler { + + private static final String NAME_PARAMETER = "name[]"; + + private final CollectorRegistry registry; + + public Handler(CollectorRegistry registry) { + this.registry = registry; + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + exchange.getResponseHeaders().put(Headers.CONTENT_TYPE, TextFormat.CONTENT_TYPE_004); + try (Writer writer = new OutputStreamWriter(exchange.getOutputStream())) { + TextFormat.write004(writer, registry.filteredMetricFamilySamples(parse(exchange.getQueryParameters()))); + writer.flush(); + } + } + + private Set parse(Map> params) { + Deque includedParam = params.get(NAME_PARAMETER); + if (includedParam == null) { + return Collections.emptySet(); + } else { + return new HashSet<>(includedParam); + } + } + } +} diff --git a/simpleclient_undertow/src/test/java/io/prometheus/client/undertow/MetricsHandlerTest.java b/simpleclient_undertow/src/test/java/io/prometheus/client/undertow/MetricsHandlerTest.java new file mode 100644 index 000000000..9d7209379 --- /dev/null +++ b/simpleclient_undertow/src/test/java/io/prometheus/client/undertow/MetricsHandlerTest.java @@ -0,0 +1,103 @@ +package io.prometheus.client.undertow; + +import io.prometheus.client.CollectorRegistry; +import io.prometheus.client.Gauge; +import io.undertow.Undertow; +import io.undertow.server.HttpHandler; +import org.apache.commons.lang3.StringUtils; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Scanner; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MetricsHandlerTest { + + private static Undertow server; + private static URI testURI; + + @BeforeClass + public static void setUp() throws IOException, URISyntaxException { + + String host = InetAddress.getLocalHost().getHostAddress(); + + ServerSocket socket = new ServerSocket(0); + Integer port = socket.getLocalPort(); + socket.close(); + + CollectorRegistry registry = new CollectorRegistry(); + HttpHandler handler = new MetricsHandler(registry); + + server = Undertow.builder() + .addHttpListener(port, host) + .setHandler(handler) + .build(); + + testURI = new URI("http://" + host + ":" + port); + + server.start(); + + Gauge.build("a", "a help").register(registry); + Gauge.build("b", "b help").register(registry); + Gauge.build("c", "c help").register(registry); + } + + @AfterClass + public static void tearDown() { + server.stop(); + } + + @Test + public void metricsRequest_shouldReturnMetrics() { + String out = makeRequest(); + + assertThat(out).contains("a 0.0"); + assertThat(out).contains("b 0.0"); + assertThat(out).contains("c 0.0"); + } + + @Test + public void metricsRequest_shouldAllowFilteringMetrics() { + String out = makeRequest("name[]=b", "name[]=c"); + + assertThat(out).doesNotContain("a 0.0"); + assertThat(out).contains("b 0.0"); + assertThat(out).contains("c 0.0"); + } + + + private String makeRequest(String... values) { + URI uri = testURI; + if (values.length > 0) { + uri = createURI(testURI, values); + } + + try (Scanner scanner = new Scanner(uri.toURL().openStream(), "UTF-8").useDelimiter("\\A")) { + return scanner.next(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private URI createURI(URI uri, String[] values) { + String query = StringUtils.join(values, "&"); + try { + return new URI(uri.getScheme(), + uri.getUserInfo(), + uri.getHost(), + uri.getPort(), + uri.getPath(), + query, + uri.getFragment()); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } + } +}