aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorgjoranv <gv@verizonmedia.com>2020-02-13 15:07:10 +0100
committergjoranv <gv@verizonmedia.com>2020-02-14 16:10:20 +0100
commitf778c6865f31b16b0837954c854fff811389b18f (patch)
tree40703fa61bf40c54bd8fc7b38ed409554b0cf548
parenta68d0f6b3585becf18d791ca83f061e7cf61f514 (diff)
Add metrics/v2 handler
- Add new config so that hostname and role can be added to output - Refactor tests to avoid duplicating code
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainer.java18
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerTest.java15
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyModelTester.java2
-rw-r--r--metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/ValuesFetcher.java10
-rw-r--r--metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/Node.java2
-rw-r--r--metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/PublicDimensionsProcessor.java2
-rw-r--r--metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/metrics/MetricsV2Handler.java92
-rw-r--r--metrics-proxy/src/main/resources/configdefinitions/node-info.def5
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/HttpHandlerTestBase.java6
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/metrics/MetricsHandlerTestBase.java196
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/metrics/MetricsV1HandlerTest.java173
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/metrics/MetricsV2HandlerTest.java53
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/prometheus/PrometheusHandlerTest.java3
13 files changed, 406 insertions, 171 deletions
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainer.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainer.java
index fd924eb2a0f..2b1a763b148 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainer.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainer.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.model.admin.metricsproxy;
+import ai.vespa.metricsproxy.http.metrics.NodeInfoConfig;
import ai.vespa.metricsproxy.metric.dimensions.NodeDimensions;
import ai.vespa.metricsproxy.metric.dimensions.NodeDimensionsConfig;
import ai.vespa.metricsproxy.metric.dimensions.PublicDimensions;
@@ -28,6 +29,7 @@ import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyContainerClus
*/
public class MetricsProxyContainer extends Container implements
NodeDimensionsConfig.Producer,
+ NodeInfoConfig.Producer,
RpcConnectorConfig.Producer,
VespaServicesConfig.Producer
{
@@ -121,7 +123,21 @@ public class MetricsProxyContainer extends Container implements
}
}
- private void addMetricsProxyComponent(Class<?> componentClass) {
+ @Override
+ public void getConfig(NodeInfoConfig.Builder builder) {
+ builder.role(getNodeRole())
+ .hostname(getHostName());
+ }
+
+ private String getNodeRole() {
+ String hostConfigId = getHost().getConfigId();
+ if (! isHostedVespa) return hostConfigId;
+ return getHostResource().spec().membership()
+ .map(ClusterMembership::stringValue)
+ .orElse(hostConfigId);
+ }
+
+ private void addMetricsProxyComponent(Class<?> componentClass) {
addSimpleComponent(componentClass.getName(), null, METRICS_PROXY_BUNDLE_NAME);
}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerTest.java b/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerTest.java
index 621cebd6246..30616b2c322 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyContainerTest.java
@@ -1,6 +1,7 @@
// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.admin.metricsproxy;
+import ai.vespa.metricsproxy.http.metrics.NodeInfoConfig;
import ai.vespa.metricsproxy.metric.dimensions.NodeDimensionsConfig;
import ai.vespa.metricsproxy.metric.dimensions.PublicDimensions;
import ai.vespa.metricsproxy.rpc.RpcConnectorConfig;
@@ -10,10 +11,10 @@ import com.yahoo.vespa.model.test.VespaModelTester;
import org.junit.Test;
import static com.yahoo.config.model.api.container.ContainerServiceType.METRICS_PROXY_CONTAINER;
-import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyModelTester.CLUSTER_CONFIG_ID;
import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyModelTester.CONTAINER_CONFIG_ID;
import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyModelTester.TestMode.hosted;
import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyModelTester.TestMode.self_hosted;
+import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyModelTester.containerConfigId;
import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyModelTester.getModel;
import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyModelTester.getNodeDimensionsConfig;
@@ -90,13 +91,23 @@ public class MetricsProxyContainerTest {
String services = servicesWithContent();
VespaModel hostedModel = getModel(services, hosted);
assertEquals(1, hostedModel.getHosts().size());
- String configId = CLUSTER_CONFIG_ID + "/" + hostedModel.getHosts().iterator().next().getHostname();
+ String configId = containerConfigId(hostedModel, hosted);
NodeDimensionsConfig config = getNodeDimensionsConfig(hostedModel, configId);
assertEquals("content", config.dimensions(PublicDimensions.INTERNAL_CLUSTER_TYPE));
assertEquals("my-content", config.dimensions(PublicDimensions.INTERNAL_CLUSTER_ID));
}
+ @Test
+ public void node_info_config_is_generated() {
+ String services = servicesWithContent();
+ VespaModel hostedModel = getModel(services, hosted);
+ String configId = containerConfigId(hostedModel, hosted);
+
+ NodeInfoConfig config = hostedModel.getConfig(NodeInfoConfig.class, configId);
+ assertTrue(config.role().startsWith("content/my-content/0/"));
+ assertTrue(config.hostname().startsWith("node-1-3-9-"));
+ }
@Test
public void vespa_services_config_has_all_services() {
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyModelTester.java b/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyModelTester.java
index f3140aafdaf..7cbc9db5eb2 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyModelTester.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/admin/metricsproxy/MetricsProxyModelTester.java
@@ -49,7 +49,7 @@ class MetricsProxyModelTester {
return tester.createModel(servicesXml, true);
}
- static String configId(VespaModel model, MetricsProxyModelTester.TestMode mode) {
+ static String containerConfigId(VespaModel model, MetricsProxyModelTester.TestMode mode) {
return (mode == hosted)
? CLUSTER_CONFIG_ID + "/" + model.getHosts().iterator().next().getHostname()
: CONTAINER_CONFIG_ID;
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 768c1beebef..ae0ef2fa57a 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
@@ -46,6 +46,16 @@ public class ValuesFetcher {
.collect(Collectors.toList());
}
+ public List<MetricsPacket.Builder> fetchMetricsAsBuilders(String requestedConsumer) throws JsonRenderingException {
+ ConsumerId consumer = getConsumerOrDefault(requestedConsumer, metricsConsumers);
+
+ return metricsManager.getMetricsAsBuilders(vespaServices.getVespaServices(), Instant.now())
+ .stream()
+ .filter(builder -> builder.hasConsumer(consumer))
+ .collect(Collectors.toList());
+ }
+
+
public List<MetricsPacket> fetchAllMetrics() throws JsonRenderingException {
return metricsManager.getMetrics(vespaServices.getVespaServices(), Instant.now());
}
diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/Node.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/Node.java
index c8a8e65be5d..c439a037774 100644
--- a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/Node.java
+++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/Node.java
@@ -25,7 +25,7 @@ public class Node {
}
public Node(String role, String hostname, int port, String path) {
- Objects.requireNonNull(role, "Null configId is not allowed");
+ Objects.requireNonNull(role, "Null role is not allowed");
Objects.requireNonNull(hostname, "Null hostname is not allowed");
Objects.requireNonNull(path, "Null path is not allowed");
diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/PublicDimensionsProcessor.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/PublicDimensionsProcessor.java
index 395ec0bea4f..4d1d57644b5 100644
--- a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/PublicDimensionsProcessor.java
+++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/application/PublicDimensionsProcessor.java
@@ -21,7 +21,7 @@ public class PublicDimensionsProcessor implements MetricsProcessor {
private final int maxDimensions;
private Set<DimensionId> publicDimensions = getPublicDimensions();
- PublicDimensionsProcessor(int maxDimensions) {
+ public PublicDimensionsProcessor(int maxDimensions) {
int numCommonDimensions = PublicDimensions.commonDimensions.size();
if (numCommonDimensions > maxDimensions) {
throw new IllegalArgumentException(String.format(
diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/metrics/MetricsV2Handler.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/metrics/MetricsV2Handler.java
new file mode 100644
index 00000000000..71d7857e48a
--- /dev/null
+++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/http/metrics/MetricsV2Handler.java
@@ -0,0 +1,92 @@
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package ai.vespa.metricsproxy.http.metrics;
+
+import ai.vespa.metricsproxy.core.MetricsConsumers;
+import ai.vespa.metricsproxy.core.MetricsManager;
+import ai.vespa.metricsproxy.http.ValuesFetcher;
+import ai.vespa.metricsproxy.http.application.ClusterIdDimensionProcessor;
+import ai.vespa.metricsproxy.http.application.Node;
+import ai.vespa.metricsproxy.http.application.PublicDimensionsProcessor;
+import ai.vespa.metricsproxy.http.application.ServiceIdDimensionProcessor;
+import ai.vespa.metricsproxy.metric.model.MetricsPacket;
+import ai.vespa.metricsproxy.metric.model.processing.MetricsProcessor;
+import ai.vespa.metricsproxy.service.VespaServices;
+import com.google.inject.Inject;
+import com.yahoo.container.handler.metrics.ErrorResponse;
+import com.yahoo.container.handler.metrics.HttpHandlerBase;
+import com.yahoo.container.handler.metrics.JsonResponse;
+import com.yahoo.container.jdisc.HttpResponse;
+import com.yahoo.restapi.Path;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.Executor;
+import java.util.logging.Level;
+
+import static ai.vespa.metricsproxy.metric.model.json.GenericJsonUtil.toGenericApplicationModel;
+import static ai.vespa.metricsproxy.metric.model.processing.MetricsProcessor.applyProcessors;
+import static com.yahoo.jdisc.Response.Status.INTERNAL_SERVER_ERROR;
+import static com.yahoo.jdisc.Response.Status.OK;
+import static java.util.Collections.singletonMap;
+import static java.util.stream.Collectors.toList;
+
+/**
+ * Http handler for the metrics/v2 rest api.
+ *
+ * @author gjoranv
+ */
+public class MetricsV2Handler extends HttpHandlerBase {
+
+ public static final String V2_PATH = "/metrics/v2";
+ public static final String VALUES_PATH = V2_PATH + "/values";
+ private static final int MAX_DIMENSIONS = 10;
+
+ private final ValuesFetcher valuesFetcher;
+ private final NodeInfoConfig nodeInfoConfig;
+
+ @Inject
+ public MetricsV2Handler(Executor executor,
+ MetricsManager metricsManager,
+ VespaServices vespaServices,
+ MetricsConsumers metricsConsumers,
+ NodeInfoConfig nodeInfoConfig) {
+ super(executor);
+ this.nodeInfoConfig = nodeInfoConfig;
+ valuesFetcher = new ValuesFetcher(metricsManager, vespaServices, metricsConsumers);
+ }
+
+ @Override
+ public Optional<HttpResponse> doHandle(URI requestUri, Path apiPath, String consumer) {
+ if (apiPath.matches(V2_PATH)) return Optional.of(resourceListResponse(requestUri, List.of(VALUES_PATH)));
+ if (apiPath.matches(VALUES_PATH)) return Optional.of(valuesResponse(consumer));
+ return Optional.empty();
+ }
+
+ private JsonResponse valuesResponse(String consumer) {
+ try {
+ List<MetricsPacket.Builder> builders = valuesFetcher.fetchMetricsAsBuilders(consumer);
+ List<MetricsPacket> metrics = processAndBuild(builders,
+ new ServiceIdDimensionProcessor(),
+ new ClusterIdDimensionProcessor(),
+ new PublicDimensionsProcessor(MAX_DIMENSIONS));
+
+ Node localNode = new Node(nodeInfoConfig.role(), nodeInfoConfig.hostname(), 0, "");
+ Map<Node, List<MetricsPacket>> metricsByNode = singletonMap(localNode, metrics);
+ return new JsonResponse(OK, toGenericApplicationModel(metricsByNode).serialize());
+ } catch (Exception e) {
+ log.log(Level.WARNING, "Got exception when rendering metrics:", e);
+ return new ErrorResponse(INTERNAL_SERVER_ERROR, e.getMessage());
+ }
+ }
+
+ private static List<MetricsPacket> processAndBuild(List<MetricsPacket.Builder> builders,
+ MetricsProcessor... processors) {
+ return builders.stream()
+ .map(builder -> applyProcessors(builder, processors))
+ .map(MetricsPacket.Builder::build)
+ .collect(toList());
+ }
+
+}
diff --git a/metrics-proxy/src/main/resources/configdefinitions/node-info.def b/metrics-proxy/src/main/resources/configdefinitions/node-info.def
new file mode 100644
index 00000000000..e66433a96d0
--- /dev/null
+++ b/metrics-proxy/src/main/resources/configdefinitions/node-info.def
@@ -0,0 +1,5 @@
+# Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package=ai.vespa.metricsproxy.http.metrics
+
+role string
+hostname string
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
index 77c3a719cd9..d776368687d 100644
--- a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/HttpHandlerTestBase.java
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/HttpHandlerTestBase.java
@@ -24,6 +24,7 @@ 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.metric.dimensions.PublicDimensions.REASON;
import static ai.vespa.metricsproxy.service.DummyService.METRIC_1;
/**
@@ -61,11 +62,12 @@ public class HttpHandlerTestBase {
}
protected static MetricsConsumers getMetricsConsumers() {
+ // Must use a whitelisted dimension to avoid it being removed for the MetricsV2Handler
var defaultConsumerDimension = new ConsumersConfig.Consumer.Metric.Dimension.Builder()
- .key("consumer-dim").value("default-val");
+ .key(REASON).value("default-val");
var customConsumerDimension = new ConsumersConfig.Consumer.Metric.Dimension.Builder()
- .key("consumer-dim").value("custom-val");
+ .key(REASON).value("custom-val");
return new MetricsConsumers(new ConsumersConfig.Builder()
.consumer(new ConsumersConfig.Consumer.Builder()
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/metrics/MetricsHandlerTestBase.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/metrics/MetricsHandlerTestBase.java
new file mode 100644
index 00000000000..1c5ce695155
--- /dev/null
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/metrics/MetricsHandlerTestBase.java
@@ -0,0 +1,196 @@
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package ai.vespa.metricsproxy.http.metrics;
+
+import ai.vespa.metricsproxy.http.HttpHandlerTestBase;
+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 com.fasterxml.jackson.databind.ObjectMapper;
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static ai.vespa.metricsproxy.metric.dimensions.PublicDimensions.INTERNAL_SERVICE_ID;
+import static ai.vespa.metricsproxy.metric.dimensions.PublicDimensions.REASON;
+import static ai.vespa.metricsproxy.metric.dimensions.PublicDimensions.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;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author gjoranv
+ */
+public abstract class MetricsHandlerTestBase<MODEL> extends HttpHandlerTestBase {
+
+ static String rootUri;
+ static String valuesUri;
+
+ Class<MODEL> modelClass;
+
+ abstract GenericJsonModel getGenericJsonModel(MODEL model);
+
+ private MODEL getResponseAsJsonModel(String consumer) {
+ String response = testDriver.sendRequest(valuesUri + "?consumer=" + consumer).readAll();
+ try {
+ return createObjectMapper().readValue(response, modelClass);
+ } catch (IOException e) {
+ fail("Failed to create json model: " + e.getMessage());
+ throw new RuntimeException(e);
+ }
+ }
+
+ private GenericJsonModel getResponseAsGenericJsonModel(String consumer) {
+ return getGenericJsonModel(getResponseAsJsonModel(consumer));
+ }
+
+ @Test
+ public void invalid_path_yields_error_response() throws Exception {
+ String response = testDriver.sendRequest(rootUri + "/invalid").readAll();
+ JSONObject root = new JSONObject(response);
+ assertTrue(root.has("error"));
+ }
+
+ @Test
+ public void root_response_contains_values_uri() throws Exception {
+ String response = testDriver.sendRequest(rootUri).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(valuesUri, valuesUrl.getString("url"));
+ }
+
+ @Ignore
+ @Test
+ public void visually_inspect_values_response() throws Exception {
+ String response = testDriver.sendRequest(valuesUri).readAll();
+ ObjectMapper mapper = createObjectMapper();
+ var jsonModel = mapper.readValue(response, modelClass);
+ System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonModel));
+ }
+
+ @Test
+ public void no_explicit_consumer_gives_the_default_consumer() {
+ String responseDefaultConsumer = testDriver.sendRequest(valuesUri + "?consumer=default").readAll();
+ String responseNoConsumer = testDriver.sendRequest(valuesUri).readAll();
+ assertEqualsExceptTimestamps(responseDefaultConsumer, responseNoConsumer);
+ }
+
+ @Test
+ public void unknown_consumer_gives_the_default_consumer() {
+ String response = testDriver.sendRequest(valuesUri).readAll();
+ String responseUnknownConsumer = testDriver.sendRequest(valuesUri + "?consumer=not_defined").readAll();
+ assertEqualsExceptTimestamps(response, responseUnknownConsumer);
+ }
+
+ private void assertEqualsExceptTimestamps(String s1, String s2) {
+ assertEquals(replaceTimestamps(s1), replaceTimestamps(s2));
+ }
+
+ private String replaceTimestamps(String s) {
+ return s.replaceAll("timestamp\":\\d+,", "timestamp\":1,");
+ }
+
+ @Test
+ public void response_contains_node_metrics() {
+ GenericJsonModel jsonModel = getResponseAsGenericJsonModel(DEFAULT_CONSUMER);
+
+ assertNotNull(jsonModel.node);
+ assertEquals(1, jsonModel.node.metrics.size());
+ assertEquals(12.345, jsonModel.node.metrics.get(0).values.get(CPU_METRIC), 0.0001d);
+ }
+
+ @Test
+ public void response_contains_service_metrics() {
+ GenericJsonModel jsonModel = getResponseAsGenericJsonModel(DEFAULT_CONSUMER);
+
+ assertEquals(2, jsonModel.services.size());
+ GenericService dummyService = jsonModel.services.get(0);
+ assertEquals(2, dummyService.metrics.size());
+
+ GenericMetrics dummy0Metrics = getMetricsForService("dummy0", dummyService);
+ assertEquals(1L, dummy0Metrics.values.get(METRIC_1).longValue());
+ assertEquals("default-val", dummy0Metrics.dimensions.get(REASON));
+
+ GenericMetrics dummy1Metrics = getMetricsForService("dummy1", dummyService);
+ assertEquals(6L, dummy1Metrics.values.get(METRIC_1).longValue());
+ assertEquals("default-val", dummy1Metrics.dimensions.get(REASON));
+ }
+
+ @Test
+ public void custom_consumer_gets_only_its_whitelisted_metrics() {
+ GenericJsonModel jsonModel = getResponseAsGenericJsonModel(CUSTOM_CONSUMER);
+
+ assertNotNull(jsonModel.node);
+ // TODO: see comment in ExternalMetrics.setExtraMetrics
+ // assertEquals(0, jsonModel.node.metrics.size());
+
+ assertEquals(2, jsonModel.services.size());
+ GenericService dummyService = jsonModel.services.get(0);
+ assertEquals(2, dummyService.metrics.size());
+
+ GenericMetrics dummy0Metrics = getMetricsForService("dummy0", dummyService);
+ assertEquals("custom-val", dummy0Metrics.dimensions.get(REASON));
+
+ GenericMetrics dummy1Metrics = getMetricsForService("dummy1", dummyService);
+ assertEquals("custom-val", dummy1Metrics.dimensions.get(REASON));
+ }
+
+ private static GenericMetrics getMetricsForService(String serviceInstance, GenericService service) {
+ for (var metrics : service.metrics) {
+ if (getServiceIdDimension(metrics).equals(serviceInstance))
+ return metrics;
+ }
+ fail("Could not find metrics for service instance " + serviceInstance);
+ throw new RuntimeException();
+ }
+
+ @Test
+ public void all_timestamps_are_equal_and_non_zero() {
+ GenericJsonModel jsonModel = getResponseAsGenericJsonModel(DEFAULT_CONSUMER);
+
+ Long nodeTimestamp = jsonModel.node.timestamp;
+ assertNotEquals(0L, (long) nodeTimestamp);
+ for (var service : jsonModel.services)
+ assertEquals(nodeTimestamp, service.timestamp);
+ }
+
+ @Test
+ public void all_consumers_get_health_from_service_that_is_down() {
+ assertDownServiceHealth(DEFAULT_CONSUMER);
+ assertDownServiceHealth(CUSTOM_CONSUMER);
+ }
+
+ private void assertDownServiceHealth(String consumer) {
+ GenericJsonModel jsonModel = getResponseAsGenericJsonModel(consumer);
+
+ GenericService downService = jsonModel.services.get(1);
+ assertEquals(DOWN.status, downService.status.code);
+ assertEquals("No response", downService.status.description);
+
+ // Service should output metric dimensions, even without metrics, because they contain important info about the service.
+ assertEquals(1, downService.metrics.size());
+ assertEquals(0, downService.metrics.get(0).values.size());
+ assertFalse(downService.metrics.get(0).dimensions.isEmpty());
+ assertEquals(DownService.NAME, getServiceIdDimension(downService.metrics.get(0)));
+ }
+
+ private static String getServiceIdDimension(GenericMetrics metrics) {
+ var instanceDimension = metrics.dimensions.get(INTERNAL_SERVICE_ID);
+ return instanceDimension != null ? instanceDimension : metrics.dimensions.get(SERVICE_ID);
+ }
+
+}
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/metrics/MetricsV1HandlerTest.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/metrics/MetricsV1HandlerTest.java
index 22f61114622..fe823466f7b 100644
--- a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/metrics/MetricsV1HandlerTest.java
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/metrics/MetricsV1HandlerTest.java
@@ -1,46 +1,30 @@
// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package ai.vespa.metricsproxy.http.metrics;
-import ai.vespa.metricsproxy.http.HttpHandlerTestBase;
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 com.fasterxml.jackson.databind.ObjectMapper;
import com.yahoo.container.jdisc.RequestHandlerTestDriver;
-import org.json.JSONArray;
-import org.json.JSONObject;
+import org.junit.Before;
import org.junit.BeforeClass;
-import org.junit.Ignore;
-import org.junit.Test;
-import java.io.IOException;
import java.util.concurrent.Executors;
-import static ai.vespa.metricsproxy.core.VespaMetrics.INSTANCE_DIMENSION_ID;
import static ai.vespa.metricsproxy.http.metrics.MetricsV1Handler.V1_PATH;
import static ai.vespa.metricsproxy.http.metrics.MetricsV1Handler.VALUES_PATH;
-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;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
/**
* @author gjoranv
*/
@SuppressWarnings("UnstableApiUsage")
-public class MetricsV1HandlerTest extends HttpHandlerTestBase {
+public class MetricsV1HandlerTest extends MetricsHandlerTestBase<GenericJsonModel> {
private static final String V1_URI = URI_BASE + V1_PATH;
private static final String VALUES_URI = URI_BASE + VALUES_PATH;
+
@BeforeClass
public static void setup() {
+ rootUri = V1_URI;
+ valuesUri = VALUES_URI;
var handler = new MetricsV1Handler(Executors.newSingleThreadExecutor(),
getMetricsManager(),
vespaServices,
@@ -48,149 +32,14 @@ public class MetricsV1HandlerTest extends HttpHandlerTestBase {
testDriver = new RequestHandlerTestDriver(handler);
}
- private GenericJsonModel getResponseAsJsonModel(String consumer) {
- String response = testDriver.sendRequest(VALUES_URI + "?consumer=" + consumer).readAll();
- try {
- return createObjectMapper().readValue(response, GenericJsonModel.class);
- } catch (IOException e) {
- fail("Failed to create json model: " + e.getMessage());
- throw new RuntimeException(e);
- }
- }
-
- @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() throws Exception {
- String response = testDriver.sendRequest(VALUES_URI).readAll();
- ObjectMapper mapper = createObjectMapper();
- var jsonModel = mapper.readValue(response, GenericJsonModel.class);
- System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonModel));
- }
-
- @Test
- public void no_explicit_consumer_gives_the_default_consumer() {
- String responseDefaultConsumer = testDriver.sendRequest(VALUES_URI + "?consumer=default").readAll();
- String responseNoConsumer = testDriver.sendRequest(VALUES_URI).readAll();
- assertEqualsExceptTimestamps(responseDefaultConsumer, responseNoConsumer);
- }
-
- @Test
- public void unknown_consumer_gives_the_default_consumer() {
- String response = testDriver.sendRequest(VALUES_URI).readAll();
- String responseUnknownConsumer = testDriver.sendRequest(VALUES_URI + "?consumer=not_defined").readAll();
- assertEqualsExceptTimestamps(response, responseUnknownConsumer);
- }
-
- private void assertEqualsExceptTimestamps(String s1, String s2) {
- assertEquals(replaceTimestamps(s1), replaceTimestamps(s2));
- }
-
- @Test
- public void response_contains_node_metrics() {
- GenericJsonModel jsonModel = getResponseAsJsonModel(DEFAULT_CONSUMER);
-
- assertNotNull(jsonModel.node);
- assertEquals(1, jsonModel.node.metrics.size());
- assertEquals(12.345, jsonModel.node.metrics.get(0).values.get(CPU_METRIC), 0.0001d);
- }
-
- @Test
- public void response_contains_service_metrics() {
- GenericJsonModel jsonModel = getResponseAsJsonModel(DEFAULT_CONSUMER);
-
- assertEquals(2, jsonModel.services.size());
- GenericService dummyService = jsonModel.services.get(0);
- assertEquals(2, dummyService.metrics.size());
-
- GenericMetrics dummy0Metrics = getMetricsForInstance("dummy0", dummyService);
- assertEquals(1L, dummy0Metrics.values.get(METRIC_1).longValue());
- assertEquals("default-val", dummy0Metrics.dimensions.get("consumer-dim"));
-
- GenericMetrics dummy1Metrics = getMetricsForInstance("dummy1", dummyService);
- assertEquals(6L, dummy1Metrics.values.get(METRIC_1).longValue());
- assertEquals("default-val", dummy1Metrics.dimensions.get("consumer-dim"));
- }
-
- @Test
- public void all_consumers_get_health_from_service_that_is_down() {
- assertDownServiceHealth(DEFAULT_CONSUMER);
- assertDownServiceHealth(CUSTOM_CONSUMER);
- }
-
- @Test
- public void all_timestamps_are_equal_and_non_zero() {
- GenericJsonModel jsonModel = getResponseAsJsonModel(DEFAULT_CONSUMER);
-
- Long nodeTimestamp = jsonModel.node.timestamp;
- assertNotEquals(0L, (long) nodeTimestamp);
- for (var service : jsonModel.services)
- assertEquals(nodeTimestamp, service.timestamp);
- }
-
- @Test
- public void custom_consumer_gets_only_its_whitelisted_metrics() {
- GenericJsonModel jsonModel = getResponseAsJsonModel(CUSTOM_CONSUMER);
-
- assertNotNull(jsonModel.node);
- // TODO: see comment in ExternalMetrics.setExtraMetrics
- // assertEquals(0, jsonModel.node.metrics.size());
-
- assertEquals(2, jsonModel.services.size());
- GenericService dummyService = jsonModel.services.get(0);
- assertEquals(2, dummyService.metrics.size());
-
- GenericMetrics dummy0Metrics = getMetricsForInstance("dummy0", dummyService);
- assertEquals("custom-val", dummy0Metrics.dimensions.get("consumer-dim"));
-
- GenericMetrics dummy1Metrics = getMetricsForInstance("dummy1", dummyService);
- 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);
-
- GenericService downService = jsonModel.services.get(1);
- assertEquals(DOWN.status, downService.status.code);
- assertEquals("No response", downService.status.description);
-
- // Service should output metric dimensions, even without metrics, because they contain important info about the service.
- assertEquals(1, downService.metrics.size());
- assertEquals(0, downService.metrics.get(0).values.size());
- assertFalse(downService.metrics.get(0).dimensions.isEmpty());
- assertEquals(DownService.NAME, downService.metrics.get(0).dimensions.get(INSTANCE_DIMENSION_ID.id));
- }
-
- private String replaceTimestamps(String s) {
- return s.replaceAll("timestamp\":\\d+,", "timestamp\":1,");
+ @Before
+ public void initModelClass() {
+ modelClass = GenericJsonModel.class;
}
- private static GenericMetrics getMetricsForInstance(String instance, GenericService service) {
- for (var metrics : service.metrics) {
- if (metrics.dimensions.get(INSTANCE_DIMENSION_ID.id).equals(instance))
- return metrics;
- }
- fail("Could not find metrics for service instance " + instance);
- throw new RuntimeException();
+ @Override
+ GenericJsonModel getGenericJsonModel(GenericJsonModel genericJsonModel) {
+ return genericJsonModel;
}
}
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/metrics/MetricsV2HandlerTest.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/metrics/MetricsV2HandlerTest.java
new file mode 100644
index 00000000000..27ee6be4be3
--- /dev/null
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/http/metrics/MetricsV2HandlerTest.java
@@ -0,0 +1,53 @@
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package ai.vespa.metricsproxy.http.metrics;
+
+import ai.vespa.metricsproxy.metric.model.json.GenericApplicationModel;
+import ai.vespa.metricsproxy.metric.model.json.GenericJsonModel;
+import com.yahoo.container.jdisc.RequestHandlerTestDriver;
+import org.junit.Before;
+import org.junit.BeforeClass;
+
+import java.util.concurrent.Executors;
+
+import static ai.vespa.metricsproxy.http.metrics.MetricsV2Handler.V2_PATH;
+import static ai.vespa.metricsproxy.http.metrics.MetricsV2Handler.VALUES_PATH;
+
+/**
+ * @author gjoranv
+ */
+@SuppressWarnings("UnstableApiUsage")
+public class MetricsV2HandlerTest extends MetricsHandlerTestBase<GenericApplicationModel> {
+
+ private static final String V2_URI = URI_BASE + V2_PATH;
+ private static final String VALUES_URI = URI_BASE + VALUES_PATH;
+
+
+ @BeforeClass
+ public static void setup() {
+ rootUri = V2_URI;
+ valuesUri = VALUES_URI;
+ var handler = new MetricsV2Handler(Executors.newSingleThreadExecutor(),
+ getMetricsManager(),
+ vespaServices,
+ getMetricsConsumers(),
+ nodeInfoConfig());
+ testDriver = new RequestHandlerTestDriver(handler);
+ }
+
+ @Before
+ public void initModelClass() {
+ modelClass = GenericApplicationModel.class;
+ }
+
+ @Override
+ GenericJsonModel getGenericJsonModel(GenericApplicationModel genericApplicationModel) {
+ return genericApplicationModel.nodes.get(0);
+ }
+
+ private static NodeInfoConfig nodeInfoConfig() {
+ return new NodeInfoConfig.Builder()
+ .role("my-role")
+ .hostname("my-hostname")
+ .build();
+ }
+}
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 a85f0425b4b..a224c4090b3 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
@@ -12,6 +12,7 @@ import org.junit.Test;
import java.util.concurrent.Executors;
+import static ai.vespa.metricsproxy.metric.dimensions.PublicDimensions.REASON;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -83,7 +84,7 @@ public class PrometheusHandlerTest extends HttpHandlerTestBase {
@Test
public void service_metrics_have_configured_dimensions() {
String dummy0 = getLine(valuesResponse, DummyService.NAME + "0");
- assertTrue(dummy0.contains("consumer_dim=\"default-val\""));
+ assertTrue(dummy0.contains(REASON + "=\"default-val\""));
}
@Test