From 6c0ed461c65bbaa90a208049031e9eef33fe6c6a Mon Sep 17 00:00:00 2001 From: gjoranv Date: Mon, 26 Aug 2019 12:00:10 +0200 Subject: Add missing test for invalid path --- .../test/java/ai/vespa/metricsproxy/http/MetricsHandlerTest.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/MetricsHandlerTest.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/MetricsHandlerTest.java index 66220464e3e..ab0c6c7e6a6 100644 --- a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/MetricsHandlerTest.java +++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/MetricsHandlerTest.java @@ -113,7 +113,7 @@ public class MetricsHandlerTest { @Ignore @Test - public void visually_inspect_values_response() throws Exception{ + public void visually_inspect_values_response() throws Exception { String response = testDriver.sendRequest(VALUES_URI).readAll(); ObjectMapper mapper = createObjectMapper(); var jsonModel = mapper.readValue(response, GenericJsonModel.class); @@ -199,6 +199,13 @@ public class MetricsHandlerTest { assertEquals("custom-val", dummy1Metrics.dimensions.get("consumer-dim")); } + @Test + public void invalid_path_yields_error_response() throws Exception { + String response = testDriver.sendRequest(V1_URI + "/invalid").readAll(); + JSONObject root = new JSONObject(response); + assertTrue(root.has("error")); + } + private void assertDownServiceHealth(String consumer) { GenericJsonModel jsonModel = getResponseAsJsonModel(consumer); -- cgit v1.2.3 From abec40773cd964b7244278b96b72748d76385760 Mon Sep 17 00:00:00 2001 From: gjoranv Date: Mon, 26 Aug 2019 11:19:15 +0200 Subject: Extract rest api utils to new class --- .../ai/vespa/metricsproxy/http/MetricsHandler.java | 38 ++------------- .../ai/vespa/metricsproxy/http/RestApiUtil.java | 55 ++++++++++++++++++++++ 2 files changed, 58 insertions(+), 35 deletions(-) create mode 100644 metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/RestApiUtil.java diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/MetricsHandler.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/MetricsHandler.java index 8fcab6dfcab..ea9bb38245f 100644 --- a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/MetricsHandler.java +++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/MetricsHandler.java @@ -13,20 +13,16 @@ import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.container.jdisc.ThreadedHttpRequestHandler; import com.yahoo.restapi.Path; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; -import java.net.URI; +import java.util.List; import java.util.concurrent.Executor; -import java.util.logging.Level; +import static ai.vespa.metricsproxy.http.RestApiUtil.resourceListResponse; import static com.yahoo.jdisc.Response.Status.INTERNAL_SERVER_ERROR; import static com.yahoo.jdisc.Response.Status.METHOD_NOT_ALLOWED; import static com.yahoo.jdisc.Response.Status.NOT_FOUND; import static com.yahoo.jdisc.Response.Status.OK; import static com.yahoo.jdisc.http.HttpRequest.Method.GET; -import static java.util.logging.Level.WARNING; /** * Http handler for the metrics/v1 rest api. @@ -55,21 +51,12 @@ public class MetricsHandler extends ThreadedHttpRequestHandler { Path path = new Path(request.getUri()); - if (path.matches(V1_PATH)) return v1Response(request.getUri()); + if (path.matches(V1_PATH)) return resourceListResponse(request.getUri(), List.of(VALUES_PATH)); if (path.matches(VALUES_PATH)) return valuesResponse(request); return new ErrorResponse(NOT_FOUND, "No content at given path"); } - private JsonResponse v1Response(URI requestUri) { - try { - return new JsonResponse(OK, v1Content(requestUri)); - } catch (JSONException e) { - log.log(WARNING, "Bad JSON construction in " + V1_PATH + " response", e); - return new ErrorResponse(INTERNAL_SERVER_ERROR, "An error occurred, please try path '" + VALUES_PATH + "'"); - } - } - private JsonResponse valuesResponse(HttpRequest request) { try { return new JsonResponse(OK, valuesFetcher.fetch(request.getProperty("consumer"))); @@ -78,23 +65,4 @@ public class MetricsHandler extends ThreadedHttpRequestHandler { } } - // TODO: Use jackson with a "Resources" class instead of JSONObject - private String v1Content(URI requestUri) throws JSONException { - int port = requestUri.getPort(); - String host = requestUri.getHost(); - StringBuilder base = new StringBuilder("http://"); - base.append(host); - if (port >= 0) { - base.append(":").append(port); - } - String uriBase = base.toString(); - JSONArray linkList = new JSONArray(); - for (String api : new String[] {VALUES_PATH}) { - JSONObject resource = new JSONObject(); - resource.put("url", uriBase + api); - linkList.put(resource); - } - return new JSONObject().put("resources", linkList).toString(4); - } - } diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/RestApiUtil.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/RestApiUtil.java new file mode 100644 index 00000000000..27411b5dbd9 --- /dev/null +++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/RestApiUtil.java @@ -0,0 +1,55 @@ +/* + * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + */ + +package ai.vespa.metricsproxy.http; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.net.URI; +import java.util.List; +import java.util.logging.Logger; + +import static com.yahoo.jdisc.Response.Status.INTERNAL_SERVER_ERROR; +import static com.yahoo.jdisc.Response.Status.OK; +import static java.util.logging.Level.WARNING; + +/** + * @author gjoranv + */ +public class RestApiUtil { + private static Logger log = Logger.getLogger(RestApiUtil.class.getName()); + + + static JsonResponse resourceListResponse(URI requestUri, List resources) { + try { + return new JsonResponse(OK, RestApiUtil.resourceList(requestUri, resources)); + } catch (JSONException e) { + log.log(WARNING, "Bad JSON construction in generated resource list for " + requestUri.getPath(), e); + return new ErrorResponse(INTERNAL_SERVER_ERROR, + "An error occurred when generating the list of api resources."); + } + } + + // TODO: Use jackson with a "Resources" class instead of JSONObject + private static String resourceList(URI requestUri, List resources) throws JSONException { + int port = requestUri.getPort(); + String host = requestUri.getHost(); + StringBuilder base = new StringBuilder("http://"); + base.append(host); + if (port >= 0) { + base.append(":").append(port); + } + String uriBase = base.toString(); + JSONArray linkList = new JSONArray(); + for (String api : resources) { + JSONObject resource = new JSONObject(); + resource.put("url", uriBase + api); + linkList.put(resource); + } + return new JSONObject().put("resources", linkList).toString(4); + } + +} -- cgit v1.2.3 From 4a1e122f1fc87b7f024bbf1449188718dba0b0c3 Mon Sep 17 00:00:00 2001 From: gjoranv Date: Tue, 27 Aug 2019 13:49:50 +0200 Subject: Make ValuesFetcher return MetricsPackets instead of json .. to allow using it for other handlers that require other formats --- .../src/main/java/ai/vespa/metricsproxy/http/MetricsHandler.java | 5 ++++- .../src/main/java/ai/vespa/metricsproxy/http/ValuesFetcher.java | 5 ++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/MetricsHandler.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/MetricsHandler.java index ea9bb38245f..e191c5e1c43 100644 --- a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/MetricsHandler.java +++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/MetricsHandler.java @@ -6,6 +6,7 @@ package ai.vespa.metricsproxy.http; import ai.vespa.metricsproxy.core.MetricsConsumers; import ai.vespa.metricsproxy.core.MetricsManager; +import ai.vespa.metricsproxy.metric.model.MetricsPacket; import ai.vespa.metricsproxy.metric.model.json.JsonRenderingException; import ai.vespa.metricsproxy.service.VespaServices; import com.google.inject.Inject; @@ -18,6 +19,7 @@ import java.util.List; import java.util.concurrent.Executor; import static ai.vespa.metricsproxy.http.RestApiUtil.resourceListResponse; +import static ai.vespa.metricsproxy.metric.model.json.GenericJsonUtil.toGenericJsonModel; import static com.yahoo.jdisc.Response.Status.INTERNAL_SERVER_ERROR; import static com.yahoo.jdisc.Response.Status.METHOD_NOT_ALLOWED; import static com.yahoo.jdisc.Response.Status.NOT_FOUND; @@ -59,7 +61,8 @@ public class MetricsHandler extends ThreadedHttpRequestHandler { private JsonResponse valuesResponse(HttpRequest request) { try { - return new JsonResponse(OK, valuesFetcher.fetch(request.getProperty("consumer"))); + List metrics = valuesFetcher.fetch(request.getProperty("consumer")); + return new JsonResponse(OK, toGenericJsonModel(metrics).serialize()); } catch (JsonRenderingException e) { return new ErrorResponse(INTERNAL_SERVER_ERROR, e.getMessage()); } diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/ValuesFetcher.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/ValuesFetcher.java index 4686d9c1751..f99fe962f38 100644 --- a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/ValuesFetcher.java +++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/ValuesFetcher.java @@ -41,14 +41,13 @@ public class ValuesFetcher { this.metricsConsumers = metricsConsumers; } - public String fetch(String requestedConsumer) throws JsonRenderingException { + public List fetch(String requestedConsumer) throws JsonRenderingException { ConsumerId consumer = getConsumerOrDefault(requestedConsumer); - List metrics = metricsManager.getMetrics(vespaServices.getVespaServices(), Instant.now()) + return metricsManager.getMetrics(vespaServices.getVespaServices(), Instant.now()) .stream() .filter(metricsPacket -> metricsPacket.consumers().contains(consumer)) .collect(Collectors.toList()); - return toGenericJsonModel(metrics).serialize(); } private ConsumerId getConsumerOrDefault(String consumer) { -- cgit v1.2.3 From b6a3f24a4a34002d1d49246d739921cc23e33564 Mon Sep 17 00:00:00 2001 From: gjoranv Date: Tue, 27 Aug 2019 14:47:11 +0200 Subject: Extract a base class for http handler tests. --- .../metricsproxy/http/HttpHandlerTestBase.java | 100 +++++++++++++++++++++ .../metricsproxy/http/MetricsHandlerTest.java | 82 ++--------------- 2 files changed, 105 insertions(+), 77 deletions(-) create mode 100644 metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/HttpHandlerTestBase.java diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/HttpHandlerTestBase.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/HttpHandlerTestBase.java new file mode 100644 index 00000000000..bd6fba84e35 --- /dev/null +++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/HttpHandlerTestBase.java @@ -0,0 +1,100 @@ +/* + * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + */ + +package ai.vespa.metricsproxy.http; + +import ai.vespa.metricsproxy.TestUtil; +import ai.vespa.metricsproxy.core.ConsumersConfig; +import ai.vespa.metricsproxy.core.MetricsConsumers; +import ai.vespa.metricsproxy.core.MetricsManager; +import ai.vespa.metricsproxy.metric.HealthMetric; +import ai.vespa.metricsproxy.metric.Metric; +import ai.vespa.metricsproxy.metric.dimensions.ApplicationDimensions; +import ai.vespa.metricsproxy.metric.dimensions.ApplicationDimensionsConfig; +import ai.vespa.metricsproxy.metric.dimensions.NodeDimensions; +import ai.vespa.metricsproxy.metric.dimensions.NodeDimensionsConfig; +import ai.vespa.metricsproxy.metric.model.MetricsPacket; +import ai.vespa.metricsproxy.service.DownService; +import ai.vespa.metricsproxy.service.DummyService; +import ai.vespa.metricsproxy.service.VespaService; +import ai.vespa.metricsproxy.service.VespaServices; +import com.google.common.collect.ImmutableList; +import com.yahoo.container.jdisc.RequestHandlerTestDriver; + +import java.time.Instant; +import java.util.List; + +import static ai.vespa.metricsproxy.http.ValuesFetcher.DEFAULT_PUBLIC_CONSUMER_ID; +import static ai.vespa.metricsproxy.metric.ExternalMetrics.VESPA_NODE_SERVICE_ID; +import static ai.vespa.metricsproxy.service.DummyService.METRIC_1; + +/** + * Base class for http handler tests. + * + * @author gjoranv + */ +@SuppressWarnings("UnstableApiUsage") +public class HttpHandlerTestBase { + + protected static final List testServices = ImmutableList.of( + new DummyService(0, ""), + new DummyService(1, ""), + new DownService(HealthMetric.getDown("No response"))); + + protected static final VespaServices vespaServices = new VespaServices(testServices); + + protected static final String DEFAULT_CONSUMER = "default"; + protected static final String CUSTOM_CONSUMER = "custom-consumer"; + + protected static final String CPU_METRIC = "cpu"; + + protected static final String URI_BASE = "http://localhost"; + + protected static RequestHandlerTestDriver testDriver; + + + protected static MetricsManager getMetricsManager() { + MetricsManager metricsManager = TestUtil.createMetricsManager(vespaServices, getMetricsConsumers(), getApplicationDimensions(), getNodeDimensions()); + metricsManager.setExtraMetrics(ImmutableList.of( + new MetricsPacket.Builder(VESPA_NODE_SERVICE_ID) + .timestamp(Instant.now().getEpochSecond()) + .putMetrics(ImmutableList.of(new Metric(CPU_METRIC, 12.345))))); + return metricsManager; + } + + protected static MetricsConsumers getMetricsConsumers() { + var defaultConsumerDimension = new ConsumersConfig.Consumer.Metric.Dimension.Builder() + .key("consumer-dim").value("default-val"); + + var customConsumerDimension = new ConsumersConfig.Consumer.Metric.Dimension.Builder() + .key("consumer-dim").value("custom-val"); + + return new MetricsConsumers(new ConsumersConfig.Builder() + .consumer(new ConsumersConfig.Consumer.Builder() + .name(DEFAULT_PUBLIC_CONSUMER_ID.id) + .metric(new ConsumersConfig.Consumer.Metric.Builder() + .name(CPU_METRIC) + .outputname(CPU_METRIC)) + .metric(new ConsumersConfig.Consumer.Metric.Builder() + .name(METRIC_1) + .outputname(METRIC_1) + .dimension(defaultConsumerDimension))) + .consumer(new ConsumersConfig.Consumer.Builder() + .name(CUSTOM_CONSUMER) + .metric(new ConsumersConfig.Consumer.Metric.Builder() + .name(METRIC_1) + .outputname(METRIC_1) + .dimension(customConsumerDimension))) + .build()); + } + + protected static ApplicationDimensions getApplicationDimensions() { + return new ApplicationDimensions(new ApplicationDimensionsConfig.Builder().build()); + } + + protected static NodeDimensions getNodeDimensions() { + return new NodeDimensions(new NodeDimensionsConfig.Builder().build()); + } + +} diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/MetricsHandlerTest.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/MetricsHandlerTest.java index ab0c6c7e6a6..83cd97bbeda 100644 --- a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/MetricsHandlerTest.java +++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/MetricsHandlerTest.java @@ -4,27 +4,11 @@ package ai.vespa.metricsproxy.http; -import ai.vespa.metricsproxy.TestUtil; -import ai.vespa.metricsproxy.core.ConsumersConfig; -import ai.vespa.metricsproxy.core.ConsumersConfig.Consumer; -import ai.vespa.metricsproxy.core.MetricsConsumers; -import ai.vespa.metricsproxy.core.MetricsManager; -import ai.vespa.metricsproxy.metric.HealthMetric; -import ai.vespa.metricsproxy.metric.Metric; -import ai.vespa.metricsproxy.metric.dimensions.ApplicationDimensions; -import ai.vespa.metricsproxy.metric.dimensions.ApplicationDimensionsConfig; -import ai.vespa.metricsproxy.metric.dimensions.NodeDimensions; -import ai.vespa.metricsproxy.metric.dimensions.NodeDimensionsConfig; -import ai.vespa.metricsproxy.metric.model.MetricsPacket; import ai.vespa.metricsproxy.metric.model.json.GenericJsonModel; import ai.vespa.metricsproxy.metric.model.json.GenericMetrics; import ai.vespa.metricsproxy.metric.model.json.GenericService; import ai.vespa.metricsproxy.service.DownService; -import ai.vespa.metricsproxy.service.DummyService; -import ai.vespa.metricsproxy.service.VespaService; -import ai.vespa.metricsproxy.service.VespaServices; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.ImmutableList; import com.yahoo.container.jdisc.RequestHandlerTestDriver; import org.json.JSONArray; import org.json.JSONObject; @@ -33,15 +17,11 @@ import org.junit.Ignore; import org.junit.Test; import java.io.IOException; -import java.time.Instant; -import java.util.List; import java.util.concurrent.Executors; import static ai.vespa.metricsproxy.core.VespaMetrics.INSTANCE_DIMENSION_ID; import static ai.vespa.metricsproxy.http.MetricsHandler.V1_PATH; import static ai.vespa.metricsproxy.http.MetricsHandler.VALUES_PATH; -import static ai.vespa.metricsproxy.http.ValuesFetcher.DEFAULT_PUBLIC_CONSUMER_ID; -import static ai.vespa.metricsproxy.metric.ExternalMetrics.VESPA_NODE_SERVICE_ID; import static ai.vespa.metricsproxy.metric.model.StatusCode.DOWN; import static ai.vespa.metricsproxy.metric.model.json.JacksonUtil.createObjectMapper; import static ai.vespa.metricsproxy.service.DummyService.METRIC_1; @@ -56,35 +36,17 @@ import static org.junit.Assert.fail; * @author gjoranv */ @SuppressWarnings("UnstableApiUsage") -public class MetricsHandlerTest { +public class MetricsHandlerTest extends HttpHandlerTestBase { - private static final List testServices = ImmutableList.of( - new DummyService(0, ""), - new DummyService(1, ""), - new DownService(HealthMetric.getDown("No response"))); - - private static final VespaServices vespaServices = new VespaServices(testServices); - - private static final String DEFAULT_CONSUMER = "default"; - private static final String CUSTOM_CONSUMER = "custom-consumer"; - - private static final String CPU_METRIC = "cpu"; - - private static final String URI_BASE = "http://localhost"; private static final String V1_URI = URI_BASE + V1_PATH; private static final String VALUES_URI = URI_BASE + VALUES_PATH; - - private static RequestHandlerTestDriver testDriver; - @BeforeClass public static void setup() { - MetricsManager metricsManager = TestUtil.createMetricsManager(vespaServices, getMetricsConsumers(), getApplicationDimensions(), getNodeDimensions()); - metricsManager.setExtraMetrics(ImmutableList.of( - new MetricsPacket.Builder(VESPA_NODE_SERVICE_ID) - .timestamp(Instant.now().getEpochSecond()) - .putMetrics(ImmutableList.of(new Metric(CPU_METRIC, 12.345))))); - MetricsHandler handler = new MetricsHandler(Executors.newSingleThreadExecutor(), metricsManager, vespaServices, getMetricsConsumers()); + MetricsHandler handler = new MetricsHandler(Executors.newSingleThreadExecutor(), + getMetricsManager(), + vespaServices, + getMetricsConsumers()); testDriver = new RequestHandlerTestDriver(handler); } @@ -233,38 +195,4 @@ public class MetricsHandlerTest { throw new RuntimeException(); } - private static MetricsConsumers getMetricsConsumers() { - var defaultConsumerDimension = new Consumer.Metric.Dimension.Builder() - .key("consumer-dim").value("default-val"); - - var customConsumerDimension = new Consumer.Metric.Dimension.Builder() - .key("consumer-dim").value("custom-val"); - - return new MetricsConsumers(new ConsumersConfig.Builder() - .consumer(new Consumer.Builder() - .name(DEFAULT_PUBLIC_CONSUMER_ID.id) - .metric(new Consumer.Metric.Builder() - .name(CPU_METRIC) - .outputname(CPU_METRIC)) - .metric(new Consumer.Metric.Builder() - .name(METRIC_1) - .outputname(METRIC_1) - .dimension(defaultConsumerDimension))) - .consumer(new Consumer.Builder() - .name(CUSTOM_CONSUMER) - .metric(new Consumer.Metric.Builder() - .name(METRIC_1) - .outputname(METRIC_1) - .dimension(customConsumerDimension))) - .build()); - } - - private static ApplicationDimensions getApplicationDimensions() { - return new ApplicationDimensions(new ApplicationDimensionsConfig.Builder().build()); - } - - private static NodeDimensions getNodeDimensions() { - return new NodeDimensions(new NodeDimensionsConfig.Builder().build()); - } - } -- cgit v1.2.3 From 3007317e7fcfc0bc11e41f878bb7e2a98bd9fbfc Mon Sep 17 00:00:00 2001 From: gjoranv Date: Tue, 27 Aug 2019 11:44:41 +0200 Subject: Add Prometheus http api --- metrics-proxy/pom.xml | 8 ++ .../ai/vespa/metricsproxy/http/ErrorResponse.java | 4 +- .../ai/vespa/metricsproxy/http/JsonResponse.java | 4 +- .../ai/vespa/metricsproxy/http/RestApiUtil.java | 2 +- .../ai/vespa/metricsproxy/http/TextResponse.java | 31 +++++++ .../ai/vespa/metricsproxy/http/ValuesFetcher.java | 2 +- .../http/prometheus/PrometheusHandler.java | 73 +++++++++++++++++ .../metric/model/prometheus/PrometheusModel.java | 52 ++++++++++++ .../prometheus/PrometheusRenderingException.java | 16 ++++ .../metric/model/prometheus/PrometheusUtil.java | 76 +++++++++++++++++ .../http/prometheus/PrometheusHandlerTest.java | 95 ++++++++++++++++++++++ .../vespa/metricsproxy/service/DummyService.java | 2 +- parent/pom.xml | 11 +++ 13 files changed, 369 insertions(+), 7 deletions(-) create mode 100644 metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/TextResponse.java create mode 100644 metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/prometheus/PrometheusHandler.java create mode 100644 metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/prometheus/PrometheusModel.java create mode 100644 metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/prometheus/PrometheusRenderingException.java create mode 100644 metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/prometheus/PrometheusUtil.java create mode 100644 metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/prometheus/PrometheusHandlerTest.java diff --git a/metrics-proxy/pom.xml b/metrics-proxy/pom.xml index 26caa3f66af..99354ee22e8 100644 --- a/metrics-proxy/pom.xml +++ b/metrics-proxy/pom.xml @@ -115,6 +115,14 @@ http-utils ${project.version} + + io.prometheus + simpleclient + + + io.prometheus + simpleclient_common + org.apache.httpcomponents httpclient diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/ErrorResponse.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/ErrorResponse.java index daa69191506..d3296331a6a 100644 --- a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/ErrorResponse.java +++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/ErrorResponse.java @@ -15,12 +15,12 @@ import static java.util.logging.Level.WARNING; /** * @author gjoranv */ -class ErrorResponse extends JsonResponse { +public class ErrorResponse extends JsonResponse { private static Logger log = Logger.getLogger(ErrorResponse.class.getName()); private static ObjectMapper objectMapper = new ObjectMapper(); - ErrorResponse(int code, String message) { + public ErrorResponse(int code, String message) { super(code, asErrorJson(message)); } diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/JsonResponse.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/JsonResponse.java index 758a043a823..9f31e9356e8 100644 --- a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/JsonResponse.java +++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/JsonResponse.java @@ -13,10 +13,10 @@ import java.nio.charset.Charset; /** * @author gjoranv */ -class JsonResponse extends HttpResponse { +public class JsonResponse extends HttpResponse { private final byte[] data; - JsonResponse(int code, String data) { + public JsonResponse(int code, String data) { super(code); this.data = data.getBytes(Charset.forName(DEFAULT_CHARACTER_ENCODING)); } diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/RestApiUtil.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/RestApiUtil.java index 27411b5dbd9..9d9256e17e8 100644 --- a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/RestApiUtil.java +++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/RestApiUtil.java @@ -23,7 +23,7 @@ public class RestApiUtil { private static Logger log = Logger.getLogger(RestApiUtil.class.getName()); - static JsonResponse resourceListResponse(URI requestUri, List resources) { + public static JsonResponse resourceListResponse(URI requestUri, List resources) { try { return new JsonResponse(OK, RestApiUtil.resourceList(requestUri, resources)); } catch (JSONException e) { diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/TextResponse.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/TextResponse.java new file mode 100644 index 00000000000..7dfc232d253 --- /dev/null +++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/TextResponse.java @@ -0,0 +1,31 @@ +/* + * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + */ + +package ai.vespa.metricsproxy.http; + +import com.yahoo.container.jdisc.HttpResponse; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; + +/** + * @author yj-jtakagi + * @author gjoranv + */ +public class TextResponse extends HttpResponse { + + private final byte[] data; + + public TextResponse(int code, String data) { + super(code); + this.data = data.getBytes(Charset.forName(DEFAULT_CHARACTER_ENCODING)); + } + + @Override + public void render(OutputStream outputStream) throws IOException { + outputStream.write(data); + } + +} diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/ValuesFetcher.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/ValuesFetcher.java index f99fe962f38..00f2078ef57 100644 --- a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/ValuesFetcher.java +++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/ValuesFetcher.java @@ -33,7 +33,7 @@ public class ValuesFetcher { private final VespaServices vespaServices; private final MetricsConsumers metricsConsumers; - ValuesFetcher(MetricsManager metricsManager, + public ValuesFetcher(MetricsManager metricsManager, VespaServices vespaServices, MetricsConsumers metricsConsumers) { this.metricsManager = metricsManager; diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/prometheus/PrometheusHandler.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/prometheus/PrometheusHandler.java new file mode 100644 index 00000000000..7c018ea6d85 --- /dev/null +++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/prometheus/PrometheusHandler.java @@ -0,0 +1,73 @@ +/* + * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + */ + +package ai.vespa.metricsproxy.http.prometheus; + +import ai.vespa.metricsproxy.core.MetricsConsumers; +import ai.vespa.metricsproxy.core.MetricsManager; +import ai.vespa.metricsproxy.http.ErrorResponse; +import ai.vespa.metricsproxy.http.JsonResponse; +import ai.vespa.metricsproxy.http.TextResponse; +import ai.vespa.metricsproxy.http.ValuesFetcher; +import ai.vespa.metricsproxy.metric.model.MetricsPacket; +import ai.vespa.metricsproxy.metric.model.json.JsonRenderingException; +import ai.vespa.metricsproxy.service.VespaServices; +import com.google.inject.Inject; +import com.yahoo.container.jdisc.HttpRequest; +import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.container.jdisc.ThreadedHttpRequestHandler; +import com.yahoo.restapi.Path; + +import java.util.List; +import java.util.concurrent.Executor; + +import static ai.vespa.metricsproxy.http.RestApiUtil.resourceListResponse; +import static ai.vespa.metricsproxy.metric.model.prometheus.PrometheusUtil.toPrometheusModel; +import static com.yahoo.jdisc.Response.Status.INTERNAL_SERVER_ERROR; +import static com.yahoo.jdisc.Response.Status.METHOD_NOT_ALLOWED; +import static com.yahoo.jdisc.Response.Status.NOT_FOUND; +import static com.yahoo.jdisc.Response.Status.OK; +import static com.yahoo.jdisc.http.HttpRequest.Method.GET; + +/** + * @author gjoranv + */ +public class PrometheusHandler extends ThreadedHttpRequestHandler { + + static final String V1_PATH = "/prometheus/v1"; + static final String VALUES_PATH = V1_PATH + "/values"; + + private final ValuesFetcher valuesFetcher; + + @Inject + public PrometheusHandler(Executor executor, + MetricsManager metricsManager, + VespaServices vespaServices, + MetricsConsumers metricsConsumers) { + super(executor); + valuesFetcher = new ValuesFetcher(metricsManager, vespaServices, metricsConsumers); + } + + @Override + public HttpResponse handle(HttpRequest request) { + if (request.getMethod() != GET) return new JsonResponse(METHOD_NOT_ALLOWED, "Only GET is supported"); + + Path path = new Path(request.getUri()); + + if (path.matches(V1_PATH)) return resourceListResponse(request.getUri(), List.of(VALUES_PATH)); + if (path.matches(VALUES_PATH)) return valuesResponse(request); + + return new ErrorResponse(NOT_FOUND, "No content at given path"); + } + + private TextResponse valuesResponse(HttpRequest request) { + try { + List metrics = valuesFetcher.fetch(request.getProperty("consumer")); + return new TextResponse(OK, toPrometheusModel(metrics).serialize()); + } catch (JsonRenderingException e) { + return new TextResponse(INTERNAL_SERVER_ERROR, e.getMessage()); + } + } + +} diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/prometheus/PrometheusModel.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/prometheus/PrometheusModel.java new file mode 100644 index 00000000000..83119a552f1 --- /dev/null +++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/prometheus/PrometheusModel.java @@ -0,0 +1,52 @@ +/* + * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + */ + +package ai.vespa.metricsproxy.metric.model.prometheus; + +import io.prometheus.client.Collector; +import io.prometheus.client.exporter.common.TextFormat; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * @author yj-jtakagi + * @author gjoranv + */ +public class PrometheusModel implements Enumeration { + private static Logger log = Logger.getLogger(PrometheusModel.class.getName()); + + private final Iterator metricFamilySamplesIterator; + + PrometheusModel(List metricFamilySamples) { + this.metricFamilySamplesIterator = metricFamilySamples.iterator(); + } + + @Override + public boolean hasMoreElements() { + return metricFamilySamplesIterator.hasNext(); + } + + @Override + public Collector.MetricFamilySamples nextElement() { + return metricFamilySamplesIterator.next(); + } + + public String serialize() { + var writer = new StringWriter(); + try { + TextFormat.write004(writer, this); + } catch (IOException e) { + log.log(Level.WARNING, "Got exception when rendering metrics:", e); + throw new PrometheusRenderingException("Could not render metrics. Check the log for details."); + } + return writer.toString(); + } + +} diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/prometheus/PrometheusRenderingException.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/prometheus/PrometheusRenderingException.java new file mode 100644 index 00000000000..68db7e64bfd --- /dev/null +++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/prometheus/PrometheusRenderingException.java @@ -0,0 +1,16 @@ +/* + * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + */ + +package ai.vespa.metricsproxy.metric.model.prometheus; + +/** + * @author gjoranv + */ +public class PrometheusRenderingException extends RuntimeException { + + PrometheusRenderingException(String message) { + super(message); + } + +} diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/prometheus/PrometheusUtil.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/prometheus/PrometheusUtil.java new file mode 100644 index 00000000000..cbd4ad2ef8d --- /dev/null +++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/prometheus/PrometheusUtil.java @@ -0,0 +1,76 @@ +/* + * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + */ + +package ai.vespa.metricsproxy.metric.model.prometheus; + +import ai.vespa.metricsproxy.metric.model.MetricsPacket; +import ai.vespa.metricsproxy.metric.model.ServiceId; +import io.prometheus.client.Collector; +import io.prometheus.client.Collector.MetricFamilySamples; +import io.prometheus.client.Collector.MetricFamilySamples.Sample; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; + +/** + * @author yj-jtakagi + * @author gjoranv + */ +public class PrometheusUtil { + + public static PrometheusModel toPrometheusModel(List metricsPackets) { + Map> packetsByService = metricsPackets.stream() + .collect(Collectors.groupingBy(packet -> packet.service)); + + List metricFamilySamples = new ArrayList<>(packetsByService.size()); + + packetsByService.forEach(((serviceId, packets) -> { + Map> samples = new HashMap<>(); + + var serviceName = Collector.sanitizeMetricName(serviceId.id); + for (var packet : packets) { + var dimensions = packet.dimensions(); + List labels = new ArrayList<>(dimensions.size()); + List labelValues = new ArrayList<>(dimensions.size()); + for (var entry : dimensions.entrySet()) { + var labelName = Collector.sanitizeMetricName(entry.getKey().id); + labels.add(labelName); + labelValues.add(entry.getValue()); + } + + for (var metric : packet.metrics().entrySet()) { + var metricName = serviceName + "_" + Collector.sanitizeMetricName(metric.getKey().id); + List sampleList; + if (samples.containsKey(metricName)) { + sampleList = samples.get(metricName); + } else { + sampleList = new ArrayList<>(); + samples.put(metricName, sampleList); + metricFamilySamples.add(new MetricFamilySamples(metricName, Collector.Type.UNTYPED, "", sampleList)); + } + sampleList.add(new Sample(metricName, labels, labelValues, metric.getValue().doubleValue(), packet.timestamp * 1000)); + } + } + // convert status message to 0,1 metric + var firstPacket = packets.get(0); + String statusMetricName = serviceName + "_status"; + // MetricsPacket status 0 means OK, but it's the opposite in Prometheus. + double statusMetricValue = (firstPacket.statusCode == 0) ? 1.0 : 0.0; + List sampleList = singletonList(new Sample(statusMetricName, emptyList(), emptyList(), + statusMetricValue, firstPacket.timestamp * 1000)); + metricFamilySamples.add(new MetricFamilySamples(statusMetricName, Collector.Type.UNTYPED, "status of service", sampleList)); + })); + + return new PrometheusModel(metricFamilySamples); + } + +} diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/prometheus/PrometheusHandlerTest.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/prometheus/PrometheusHandlerTest.java new file mode 100644 index 00000000000..d0ce2837568 --- /dev/null +++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/prometheus/PrometheusHandlerTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + */ + +package ai.vespa.metricsproxy.http.prometheus; + +import ai.vespa.metricsproxy.http.HttpHandlerTestBase; +import ai.vespa.metricsproxy.service.DummyService; +import com.yahoo.container.jdisc.RequestHandlerTestDriver; +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.concurrent.Executors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * @author gjoranv + */ +@SuppressWarnings("UnstableApiUsage") +public class PrometheusHandlerTest extends HttpHandlerTestBase { + + private static final String V1_URI = URI_BASE + PrometheusHandler.V1_PATH; + private static final String VALUES_URI = URI_BASE + PrometheusHandler.VALUES_PATH; + + private static String valuesResponse; + + @BeforeClass + public static void setup() { + PrometheusHandler handler = new PrometheusHandler(Executors.newSingleThreadExecutor(), + getMetricsManager(), + vespaServices, + getMetricsConsumers()); + testDriver = new RequestHandlerTestDriver(handler); + valuesResponse = testDriver.sendRequest(VALUES_URI).readAll(); + } + + @Test + public void v1_response_contains_values_uri() throws Exception { + String response = testDriver.sendRequest(V1_URI).readAll(); + JSONObject root = new JSONObject(response); + assertTrue(root.has("resources")); + + JSONArray resources = root.getJSONArray("resources"); + assertEquals(1, resources.length()); + + JSONObject valuesUrl = resources.getJSONObject(0); + assertEquals(VALUES_URI, valuesUrl.getString("url")); + } + + @Ignore + @Test + public void visually_inspect_values_response() { + System.out.println(valuesResponse); + } + + @Test + public void response_contains_node_status() { + assertTrue(valuesResponse.contains("vespa_node_status 1.0")); + } + + @Test + public void response_contains_node_metrics() { + String cpu = getLine(valuesResponse, CPU_METRIC + "{"); + assertTrue(cpu.contains("} 12.345")); // metric value + assertTrue(cpu.contains("{vespaVersion=")); + } + + @Test + public void response_contains_service_status() { + assertTrue(valuesResponse.contains("vespa_dummy_status 1.0")); + assertTrue(valuesResponse.contains("vespa_down_service_status 0.0")); + } + + @Test + public void response_contains_service_metrics() { + String dummy0 = getLine(valuesResponse, DummyService.NAME + "0"); + assertTrue(dummy0.contains("c_test")); // metric name + assertTrue(dummy0.contains("} 1.0")); // metric value + assertTrue(dummy0.contains("consumer_dim=\"default-val\"")); + } + + // Find the first line that contains the given string + private String getLine(String raw, String searchString) { + for (var s : raw.split("\\n")) { + if (s.contains(searchString)) + return s; + } + throw new IllegalArgumentException("No line containing string: " + searchString); + } +} diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/DummyService.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/DummyService.java index 380a992aead..cf559628490 100644 --- a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/DummyService.java +++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/DummyService.java @@ -11,7 +11,7 @@ import ai.vespa.metricsproxy.metric.Metrics; * @author Unknown */ public class DummyService extends VespaService { - static final String NAME = "dummy"; + public static final String NAME = "dummy"; public static final String METRIC_1 = "c.test"; public static final String METRIC_2 = "val"; diff --git a/parent/pom.xml b/parent/pom.xml index a81f64d1725..1b22bcefb18 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -435,6 +435,16 @@ airline 0.7 + + io.prometheus + simpleclient + ${prometheus.client.version} + + + io.prometheus + simpleclient_common + ${prometheus.client.version} + org.ow2.asm asm @@ -777,6 +787,7 @@ all 2.22.0 5.4.2 + 0.6.0 3.7.0 -- cgit v1.2.3 From 819d1c497ef38d2d034173db4c71d3498d989a06 Mon Sep 17 00:00:00 2001 From: gjoranv Date: Tue, 27 Aug 2019 16:43:51 +0200 Subject: Add vespa service as dimension instead of prefix to metric names. --- .../metricsproxy/metric/model/prometheus/PrometheusUtil.java | 4 +++- .../metricsproxy/http/prometheus/PrometheusHandlerTest.java | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/prometheus/PrometheusUtil.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/prometheus/PrometheusUtil.java index cbd4ad2ef8d..d43baa9c9c9 100644 --- a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/prometheus/PrometheusUtil.java +++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/prometheus/PrometheusUtil.java @@ -46,9 +46,11 @@ public class PrometheusUtil { labels.add(labelName); labelValues.add(entry.getValue()); } + labels.add("vespa_service"); + labelValues.add(serviceName); for (var metric : packet.metrics().entrySet()) { - var metricName = serviceName + "_" + Collector.sanitizeMetricName(metric.getKey().id); + var metricName = Collector.sanitizeMetricName(metric.getKey().id); List sampleList; if (samples.containsKey(metricName)) { sampleList = samples.get(metricName); diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/prometheus/PrometheusHandlerTest.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/prometheus/PrometheusHandlerTest.java index d0ce2837568..69cdd9954e4 100644 --- a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/prometheus/PrometheusHandlerTest.java +++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/prometheus/PrometheusHandlerTest.java @@ -81,9 +81,20 @@ public class PrometheusHandlerTest extends HttpHandlerTestBase { String dummy0 = getLine(valuesResponse, DummyService.NAME + "0"); assertTrue(dummy0.contains("c_test")); // metric name assertTrue(dummy0.contains("} 1.0")); // metric value + } + + @Test + public void service_metrics_have_configured_dimensions() { + String dummy0 = getLine(valuesResponse, DummyService.NAME + "0"); assertTrue(dummy0.contains("consumer_dim=\"default-val\"")); } + @Test + public void service_metrics_have_vespa_service_dimension() { + String dummy0 = getLine(valuesResponse, DummyService.NAME + "0"); + assertTrue(dummy0.contains("vespa_service=\"vespa_dummy\"")); + } + // Find the first line that contains the given string private String getLine(String raw, String searchString) { for (var s : raw.split("\\n")) { -- cgit v1.2.3 From 6089418702460cf36e3e0a552e56ff07242af672 Mon Sep 17 00:00:00 2001 From: gjoranv Date: Thu, 29 Aug 2019 15:48:37 +0200 Subject: Add PrometheusHandler to metrics-proxy clusters. + Add unit test verifying that http handlers are set up. --- .../metricsproxy/MetricsProxyContainerCluster.java | 14 +++++++++----- .../MetricsProxyContainerClusterTest.java | 20 +++++++++++++++++++- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java index 091c5a3acb4..44d981cf1c2 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java @@ -10,6 +10,7 @@ import ai.vespa.metricsproxy.core.MetricsManager; import ai.vespa.metricsproxy.core.MonitoringConfig; import ai.vespa.metricsproxy.core.VespaMetrics; import ai.vespa.metricsproxy.http.MetricsHandler; +import ai.vespa.metricsproxy.http.prometheus.PrometheusHandler; import ai.vespa.metricsproxy.metric.ExternalMetrics; import ai.vespa.metricsproxy.metric.dimensions.ApplicationDimensions; import ai.vespa.metricsproxy.metric.dimensions.ApplicationDimensionsConfig; @@ -22,6 +23,7 @@ import com.yahoo.config.model.producer.AbstractConfigProducerRoot; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Zone; import com.yahoo.container.handler.ThreadpoolConfig; +import com.yahoo.container.jdisc.ThreadedHttpRequestHandler; import com.yahoo.osgi.provider.model.ComponentModel; import com.yahoo.search.config.QrStartConfig; import com.yahoo.vespa.model.VespaModel; @@ -74,6 +76,7 @@ public class MetricsProxyContainerCluster extends ContainerCluster clazz, String bindingPath) { Handler> metricsHandler = new Handler<>( - new ComponentModel(MetricsHandler.class.getName(), null, METRICS_PROXY_BUNDLE_NAME, null)); - metricsHandler.addServerBindings("http://*" + METRICS_HANDLER_BINDING, - "http://*" + METRICS_HANDLER_BINDING + "/*"); + new ComponentModel(clazz.getName(), null, METRICS_PROXY_BUNDLE_NAME, null)); + metricsHandler.addServerBindings("http://*" + bindingPath, + "http://*" + bindingPath + "/*"); addComponent(metricsHandler); } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerClusterTest.java index 48c3e9fdda9..db29dbba306 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerClusterTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerClusterTest.java @@ -5,7 +5,10 @@ package com.yahoo.vespa.model.admin.metricsproxy; import ai.vespa.metricsproxy.core.ConsumersConfig; +import ai.vespa.metricsproxy.http.MetricsHandler; +import ai.vespa.metricsproxy.http.prometheus.PrometheusHandler; import ai.vespa.metricsproxy.metric.dimensions.ApplicationDimensionsConfig; +import com.yahoo.component.ComponentSpecification; import com.yahoo.config.model.test.MockApplicationPackage; import com.yahoo.config.provision.Zone; import com.yahoo.container.BundlesConfig; @@ -15,10 +18,14 @@ import com.yahoo.vespa.model.VespaModel; import com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyContainerCluster.AppDimensionNames; import com.yahoo.vespa.model.admin.monitoring.Metric; import com.yahoo.vespa.model.admin.monitoring.MetricSet; +import com.yahoo.vespa.model.container.component.Component; +import com.yahoo.vespa.model.container.component.Handler; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import java.util.Collection; + import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyContainerCluster.METRICS_PROXY_BUNDLE_FILE; import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyContainerCluster.zoneString; import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyModelTester.CLUSTER_CONFIG_ID; @@ -36,13 +43,15 @@ import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyModelTester.g import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyModelTester.getQrStartConfig; import static com.yahoo.vespa.model.admin.monitoring.DefaultPublicConsumer.DEFAULT_PUBLIC_CONSUMER_ID; import static com.yahoo.vespa.model.admin.monitoring.DefaultPublicMetrics.defaultPublicMetricSet; -import static com.yahoo.vespa.model.admin.monitoring.VespaMetricsConsumer.VESPA_CONSUMER_ID; import static com.yahoo.vespa.model.admin.monitoring.DefaultVespaMetrics.defaultVespaMetricSet; import static com.yahoo.vespa.model.admin.monitoring.NetworkMetrics.networkMetricSet; import static com.yahoo.vespa.model.admin.monitoring.SystemMetrics.systemMetricSet; import static com.yahoo.vespa.model.admin.monitoring.VespaMetricSet.vespaMetricSet; +import static com.yahoo.vespa.model.admin.monitoring.VespaMetricsConsumer.VESPA_CONSUMER_ID; import static java.util.Collections.singleton; +import static java.util.stream.Collectors.toList; import static org.hamcrest.CoreMatchers.endsWith; +import static org.hamcrest.CoreMatchers.hasItem; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; @@ -90,6 +99,15 @@ public class MetricsProxyContainerClusterTest { assertFalse(config.jvm().verbosegc()); } + @Test + public void http_handlers_are_set_up() { + VespaModel model = getModel(servicesWithAdminOnly(), self_hosted); + Collection> handlers = model.getAdmin().getMetricsProxyCluster().getHandlers(); + Collection handlerClasses = handlers.stream().map(Component::getClassId).collect(toList()); + + assertThat(handlerClasses, hasItem(ComponentSpecification.fromString(MetricsHandler.class.getName()))); + assertThat(handlerClasses, hasItem(ComponentSpecification.fromString(PrometheusHandler.class.getName()))); + } @Test public void default_public_consumer_is_set_up_for_self_hosted() { -- cgit v1.2.3 From c232a31df58fd153ff08b5a209556f4151861558 Mon Sep 17 00:00:00 2001 From: gjoranv Date: Thu, 29 Aug 2019 16:13:36 +0200 Subject: Avoid duplicating string constants --- .../model/admin/metricsproxy/MetricsProxyContainerCluster.java | 7 ++----- .../src/main/java/ai/vespa/metricsproxy/http/MetricsHandler.java | 2 +- .../ai/vespa/metricsproxy/http/prometheus/PrometheusHandler.java | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java index 44d981cf1c2..6cd0a80bee3 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerCluster.java @@ -75,9 +75,6 @@ public class MetricsProxyContainerCluster extends ContainerCluster clazz, String bindingPath) { diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/MetricsHandler.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/MetricsHandler.java index e191c5e1c43..0d641b6cca4 100644 --- a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/MetricsHandler.java +++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/MetricsHandler.java @@ -33,7 +33,7 @@ import static com.yahoo.jdisc.http.HttpRequest.Method.GET; */ public class MetricsHandler extends ThreadedHttpRequestHandler { - static final String V1_PATH = "/metrics/v1"; + public static final String V1_PATH = "/metrics/v1"; static final String VALUES_PATH = V1_PATH + "/values"; private final ValuesFetcher valuesFetcher; diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/prometheus/PrometheusHandler.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/prometheus/PrometheusHandler.java index 7c018ea6d85..f2009280128 100644 --- a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/prometheus/PrometheusHandler.java +++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/prometheus/PrometheusHandler.java @@ -35,7 +35,7 @@ import static com.yahoo.jdisc.http.HttpRequest.Method.GET; */ public class PrometheusHandler extends ThreadedHttpRequestHandler { - static final String V1_PATH = "/prometheus/v1"; + public static final String V1_PATH = "/prometheus/v1"; static final String VALUES_PATH = V1_PATH + "/values"; private final ValuesFetcher valuesFetcher; -- cgit v1.2.3