summaryrefslogtreecommitdiffstats
path: root/metrics-proxy/src/test
diff options
context:
space:
mode:
Diffstat (limited to 'metrics-proxy/src/test')
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/TestUtil.java27
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/core/MetricsManagerTest.java244
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/ExternalMetricsTest.java73
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/MetricsTest.java99
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/model/MetricsPacketTest.java111
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/model/json/JsonUtilTest.java63
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/model/json/YamasJsonModelTest.java94
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/rpc/IntegrationTester.java144
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/rpc/RpcHealthMetricsTest.java97
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/rpc/RpcMetricsTest.java208
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/ConfigSentinelClientTest.java104
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/ConfigSentinelDummy.java61
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/ContainerServiceTest.java67
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/DummyService.java36
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/MetricsFetcherTest.java90
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/MockConfigSentinelClient.java50
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/MockHttpServer.java56
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/SystemPollerTest.java45
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/VespaServiceTest.java77
-rw-r--r--metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/VespaServicesTest.java42
-rw-r--r--metrics-proxy/src/test/resources/health-check-failed.response.json7
-rw-r--r--metrics-proxy/src/test/resources/health-check.response.json6
-rw-r--r--metrics-proxy/src/test/resources/metrics-container-state-multi-chain.json281
-rw-r--r--metrics-proxy/src/test/resources/metrics-state.json42
-rw-r--r--metrics-proxy/src/test/resources/metrics-storage-simple.json38
-rw-r--r--metrics-proxy/src/test/resources/rpc-json-output-check.json1
-rw-r--r--metrics-proxy/src/test/resources/yamas-array-no-routing.json19
-rw-r--r--metrics-proxy/src/test/resources/yamas-array.json26
28 files changed, 2208 insertions, 0 deletions
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/TestUtil.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/TestUtil.java
new file mode 100644
index 00000000000..5997f85a46f
--- /dev/null
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/TestUtil.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+ */
+
+package ai.vespa.metricsproxy;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.stream.Collectors;
+
+/**
+ * @author gjoranv
+ */
+public class TestUtil {
+
+ public static String getContents(String filename) {
+ InputStream in = TestUtil.class.getClassLoader().getResourceAsStream(filename);
+ if (in == null) {
+ throw new RuntimeException("File not found: " + filename);
+ }
+ return new BufferedReader(new InputStreamReader(in)).lines().collect(Collectors.joining("\n"));
+ }
+}
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/core/MetricsManagerTest.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/core/MetricsManagerTest.java
new file mode 100644
index 00000000000..81634323269
--- /dev/null
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/core/MetricsManagerTest.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+ */
+
+package ai.vespa.metricsproxy.core;
+
+import ai.vespa.metricsproxy.core.ConsumersConfig.Consumer;
+import ai.vespa.metricsproxy.metric.ExternalMetrics;
+import ai.vespa.metricsproxy.metric.Metric;
+import ai.vespa.metricsproxy.metric.Metrics;
+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.DimensionId;
+import ai.vespa.metricsproxy.metric.model.MetricsPacket;
+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 org.junit.Before;
+import org.junit.Test;
+
+import java.time.Instant;
+import java.util.List;
+import java.util.Map;
+
+import static ai.vespa.metricsproxy.core.MetricsManager.VESPA_VERSION;
+import static ai.vespa.metricsproxy.core.VespaMetrics.METRIC_TYPE_DIMENSION_ID;
+import static ai.vespa.metricsproxy.core.VespaMetrics.VESPA_CONSUMER_ID;
+import static ai.vespa.metricsproxy.metric.ExternalMetrics.ROLE_DIMENSION;
+import static ai.vespa.metricsproxy.metric.model.DimensionId.toDimensionId;
+import static ai.vespa.metricsproxy.metric.model.MetricId.toMetricId;
+import static ai.vespa.metricsproxy.metric.model.ServiceId.toServiceId;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author gjoranv
+ */
+public class MetricsManagerTest {
+
+ private MetricsManager metricsManager;
+
+ private static final String SERVICE_0_ID = "dummy/id/0";
+ private static final String SERVICE_1_ID = "dummy/id/1";
+
+ private static final List<VespaService> testServices = ImmutableList.of(
+ new DummyService(0, SERVICE_0_ID),
+ new DummyService(1, SERVICE_1_ID));
+
+ private static final String WHITELISTED_METRIC_ID = "whitelisted";
+
+ @Before
+ public void setupMetricsManager() {
+ metricsManager = getMetricsManager();
+ }
+
+ @Test
+ public void each_service_gets_separate_metrics_packets() {
+ List<MetricsPacket> packets = metricsManager.getMetrics(testServices, Instant.EPOCH);
+ assertThat(packets.size(), is(2));
+
+ assertThat(packets.get(0).dimensions().get(toDimensionId("instance")), is("dummy0"));
+ assertThat(packets.get(0).metrics().get(toMetricId("c.test")), is(1.0));
+ assertThat(packets.get(0).metrics().get(toMetricId("val")), is(1.05));
+
+ assertThat(packets.get(1).dimensions().get(toDimensionId("instance")), is("dummy1"));
+ assertThat(packets.get(1).metrics().get(toMetricId("c.test")), is(6.0));
+ assertThat(packets.get(1).metrics().get(toMetricId("val")), is(2.35));
+ }
+
+ @Test
+ public void verify_expected_output_from_getMetricsById() {
+ String dummy0Metrics = metricsManager.getMetricsByConfigId("dummy/id/0");
+ assertThat(dummy0Metrics, containsString("'dummy.id.0'.val=1.050"));
+ assertThat(dummy0Metrics, containsString("'dummy.id.0'.c_test=1"));
+
+ String dummy1Metrics = metricsManager.getMetricsByConfigId("dummy/id/1");
+ assertThat(dummy1Metrics, containsString("'dummy.id.1'.val=2.350"));
+ assertThat(dummy1Metrics, containsString("'dummy.id.1'.c_test=6"));
+ }
+
+ @Test
+ public void getServices_returns_service_types() {
+ assertThat(metricsManager.getAllVespaServices(), is("dummy"));
+ }
+
+ @Test
+ public void global_dimensions_are_added_but_do_not_override_metric_dimensions() {
+ List<MetricsPacket> packets = metricsManager.getMetrics(testServices, Instant.EPOCH);
+ assertEquals(2, packets.size());
+ assertGlobalDimensions(packets.get(0).dimensions());
+ assertGlobalDimensions(packets.get(1).dimensions());
+ }
+
+ private void assertGlobalDimensions(Map<DimensionId, String> dimensions) {
+ assertTrue(dimensions.containsKey(VESPA_VERSION));
+ assertEquals("value", dimensions.get(toDimensionId("global")));
+ assertEquals("metric-dim", dimensions.get(toDimensionId("dim0")));
+ }
+
+
+ @Test
+ public void system_metrics_are_added() {
+ VespaService service0 = testServices.get(0);
+ Metrics oldSystemMetrics = service0.getSystemMetrics();
+
+ service0.getSystemMetrics().add(new Metric("cpu", 1));
+
+ List<MetricsPacket> packets = metricsManager.getMetrics(testServices, Instant.EPOCH);
+ assertEquals(3, packets.size());
+
+ MetricsPacket systemPacket = packets.get(0); // system metrics are added before other metrics
+ assertThat(systemPacket.metrics().get(toMetricId("cpu")), is(1.0));
+ assertThat(systemPacket.dimensions().get(toDimensionId("metrictype")), is("system"));
+
+ service0.setSystemMetrics(oldSystemMetrics);
+ }
+
+ @Test
+ public void extra_metrics_packets_containing_whitelisted_metrics_are_added() {
+ metricsManager.setExtraMetrics(ImmutableList.of(
+ new MetricsPacket.Builder(toServiceId("foo"))
+ .putMetrics(ImmutableList.of(new Metric(WHITELISTED_METRIC_ID, 0)))));
+
+ List<MetricsPacket> packets = metricsManager.getMetrics(testServices, Instant.EPOCH);
+ assertThat(packets.size(), is(3));
+ }
+
+ @Test
+ public void extra_metrics_packets_without_whitelisted_metrics_are_not_added() {
+ metricsManager.setExtraMetrics(ImmutableList.of(
+ new MetricsPacket.Builder(toServiceId("foo"))
+ .putMetrics(ImmutableList.of(new Metric("not-whitelisted", 0)))));
+
+ List<MetricsPacket> packets = metricsManager.getMetrics(testServices, Instant.EPOCH);
+ assertThat(packets.size(), is(2));
+ }
+
+ @Test
+ public void extra_dimensions_are_added_to_metrics_packets_that_do_not_have_those_dimensions() {
+ metricsManager.setExtraMetrics(ImmutableList.of(
+ new MetricsPacket.Builder(toServiceId("foo"))
+ .putMetrics(ImmutableList.of(new Metric(WHITELISTED_METRIC_ID, 0)))
+ .putDimension(ROLE_DIMENSION, "role from extraMetrics")));
+
+ List<MetricsPacket> packets = metricsManager.getMetrics(testServices, Instant.EPOCH);
+ for (MetricsPacket packet : packets) {
+ assertThat(packet.dimensions().get(ROLE_DIMENSION), is("role from extraMetrics"));
+ }
+ }
+
+ @Test
+ public void extra_dimensions_do_not_overwrite_existing_dimension_values() {
+ metricsManager.setExtraMetrics(ImmutableList.of(
+ new MetricsPacket.Builder(toServiceId("foo"))
+ .putMetrics(ImmutableList.of(new Metric(WHITELISTED_METRIC_ID, 0)))
+ .putDimension(METRIC_TYPE_DIMENSION_ID, "from extraMetrics")));
+
+ List<MetricsPacket> packets = metricsManager.getMetrics(testServices, Instant.EPOCH);
+ assertThat(packets.get(0).dimensions().get(METRIC_TYPE_DIMENSION_ID), is("standard"));
+ assertThat(packets.get(1).dimensions().get(METRIC_TYPE_DIMENSION_ID), is("standard"));
+ assertThat(packets.get(2).dimensions().get(METRIC_TYPE_DIMENSION_ID), is("from extraMetrics"));
+ }
+
+ @Test
+ public void timestamp_is_adjusted_when_metric_is_less_than_one_minute_younger_than_start_time() {
+ Instant START_TIME = Instant.ofEpochSecond(0);
+ Instant METRIC_TIME = Instant.ofEpochSecond(59);
+ assertEquals(START_TIME, getAdjustedTimestamp(START_TIME, METRIC_TIME));
+ }
+
+ @Test
+ public void timestamp_is_adjusted_when_metric_is_less_than_one_minute_older_than_start_time() {
+ Instant START_TIME = Instant.ofEpochSecond(59);
+ Instant METRIC_TIME = Instant.ofEpochSecond(0);
+ assertEquals(START_TIME, getAdjustedTimestamp(START_TIME, METRIC_TIME));
+ }
+
+ @Test
+ public void timestamp_is_not_adjusted_when_metric_is_at_least_one_minute_younger_than_start_time() {
+ Instant START_TIME = Instant.ofEpochSecond(0);
+ Instant METRIC_TIME = Instant.ofEpochSecond(60);
+ assertEquals(METRIC_TIME, getAdjustedTimestamp(START_TIME, METRIC_TIME));
+ }
+
+ @Test
+ public void timestamp_is_not_adjusted_when_metric_is_at_least_one_minute_older_than_start_time() {
+ Instant START_TIME = Instant.ofEpochSecond(60);
+ Instant METRIC_TIME = Instant.ofEpochSecond(0);
+ assertEquals(METRIC_TIME, getAdjustedTimestamp(START_TIME, METRIC_TIME));
+ }
+
+ private Instant getAdjustedTimestamp(Instant startTime, Instant metricTime) {
+ MetricsPacket.Builder builder = new MetricsPacket.Builder(toServiceId("foo"))
+ .timestamp(metricTime.getEpochSecond());
+ return MetricsManager.adjustTimestamp(builder, startTime).getTimestamp();
+ }
+
+ private MetricsManager getMetricsManager() {
+ VespaServices vespaServices = new VespaServices(testServices);
+ MetricsConsumers consumers = getMetricsConsumers();
+ VespaMetrics metrics = new VespaMetrics(consumers, vespaServices);
+
+ return new MetricsManager(vespaServices, metrics, new ExternalMetrics(consumers),
+ getApplicationDimensions(),getNodeDimensions());
+ }
+
+ private static MetricsConsumers getMetricsConsumers() {
+ Consumer.Metric.Dimension.Builder metricDimension = new Consumer.Metric.Dimension.Builder()
+ .key("dim0").value("metric-dim");
+
+ return new MetricsConsumers(new ConsumersConfig.Builder()
+ .consumer(new Consumer.Builder()
+ .name(VESPA_CONSUMER_ID.id)
+ .metric(new Consumer.Metric.Builder()
+ .name(WHITELISTED_METRIC_ID)
+ .outputname(WHITELISTED_METRIC_ID))
+ .metric(new Consumer.Metric.Builder()
+ .name(DummyService.METRIC_1)
+ .outputname(DummyService.METRIC_1)
+ .dimension(metricDimension))
+ .metric(new Consumer.Metric.Builder()
+ .name(DummyService.METRIC_2)
+ .outputname(DummyService.METRIC_2)
+ .dimension(metricDimension)))
+ .build());
+ }
+
+ private ApplicationDimensions getApplicationDimensions() {
+ return new ApplicationDimensions(new ApplicationDimensionsConfig.Builder()
+ .dimensions("global", "value").build());
+ }
+
+ private NodeDimensions getNodeDimensions() {
+ return new NodeDimensions(new NodeDimensionsConfig.Builder()
+ .dimensions("dim0", "should not override metric dim").build());
+ }
+
+}
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/ExternalMetricsTest.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/ExternalMetricsTest.java
new file mode 100644
index 00000000000..11c271d46e4
--- /dev/null
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/ExternalMetricsTest.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.metric;
+
+import ai.vespa.metricsproxy.core.ConsumersConfig;
+import ai.vespa.metricsproxy.core.MetricsConsumers;
+import ai.vespa.metricsproxy.metric.model.ConsumerId;
+import ai.vespa.metricsproxy.metric.model.MetricsPacket;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+import java.util.List;
+
+import static ai.vespa.metricsproxy.metric.ExternalMetrics.VESPA_NODE_SERVICE_ID;
+import static ai.vespa.metricsproxy.metric.model.ConsumerId.toConsumerId;
+import static ai.vespa.metricsproxy.metric.model.ServiceId.toServiceId;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author gjoranv
+ */
+public class ExternalMetricsTest {
+ private static final ConsumerId CUSTOM_CONSUMER_1 = toConsumerId("consumer-1");
+ private static final ConsumerId CUSTOM_CONSUMER_2 = toConsumerId("consumer-2");
+
+ @Test
+ public void extra_metrics_are_added() {
+ MetricsConsumers noConsumers = new MetricsConsumers(new ConsumersConfig.Builder().build());
+ ExternalMetrics externalMetrics = new ExternalMetrics(noConsumers);
+
+ externalMetrics.setExtraMetrics(ImmutableList.of(
+ new MetricsPacket.Builder(toServiceId("foo"))));
+
+ List<MetricsPacket.Builder> packets = externalMetrics.getMetrics();
+ assertEquals(1, packets.size());
+ }
+
+ @Test
+ public void service_id_is_set_to_vespa_node_id() {
+ MetricsConsumers noConsumers = new MetricsConsumers(new ConsumersConfig.Builder().build());
+ ExternalMetrics externalMetrics = new ExternalMetrics(noConsumers);
+ externalMetrics.setExtraMetrics(ImmutableList.of(
+ new MetricsPacket.Builder(toServiceId("replace_with_vespa_node_id"))));
+
+ List<MetricsPacket.Builder> packets = externalMetrics.getMetrics();
+ assertEquals(1, packets.size());
+ assertEquals(VESPA_NODE_SERVICE_ID, packets.get(0).build().service);
+ }
+
+ @Test
+ public void custom_consumers_are_added() {
+ ConsumersConfig consumersConfig = new ConsumersConfig.Builder()
+ .consumer(new ConsumersConfig.Consumer.Builder().name(CUSTOM_CONSUMER_1.id))
+ .consumer(new ConsumersConfig.Consumer.Builder().name(CUSTOM_CONSUMER_2.id))
+ .build();
+ MetricsConsumers consumers = new MetricsConsumers(consumersConfig);
+ ExternalMetrics externalMetrics = new ExternalMetrics(consumers);
+
+ externalMetrics.setExtraMetrics(ImmutableList.of(
+ new MetricsPacket.Builder(toServiceId("foo"))));
+
+ List<MetricsPacket.Builder> packets = externalMetrics.getMetrics();
+ assertEquals(1, packets.size());
+
+ List<ConsumerId> consumerIds = packets.get(0).build().consumers();
+ assertEquals(2, consumerIds.size());
+ assertEquals(CUSTOM_CONSUMER_1, consumerIds.get(0));
+ assertEquals(CUSTOM_CONSUMER_2, consumerIds.get(1));
+ }
+
+}
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/MetricsTest.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/MetricsTest.java
new file mode 100644
index 00000000000..b9e6377c27b
--- /dev/null
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/MetricsTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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;
+
+import ai.vespa.metricsproxy.service.DummyService;
+import ai.vespa.metricsproxy.service.VespaService;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Unknowm
+ */
+public class MetricsTest {
+
+ @Test
+ public void testIterator() {
+ Metrics m = new Metrics();
+ long now = System.currentTimeMillis() / 1000;
+ m.add(new Metric("a", 1, now));
+ m.add(new Metric("b", 2.5, now));
+
+ //should expire after 0 seconds
+ m.add(new Metric("c", 2, now));
+
+ Map<String, Number> map = new HashMap<>();
+
+ for (Metric metric: m.getMetrics()) {
+ String k = metric.getName();
+
+ assertThat(map.containsKey(k), is(false));
+ map.put(k, metric.getValue());
+ }
+
+ assertThat(map.get("a").intValue(), is(1));
+ assertThat(map.get("b").doubleValue(), is(2.5));
+ }
+
+ @Test
+ public void testBasicMetric() {
+ Metrics m = new Metrics();
+ m.add(new Metric("count", 1, System.currentTimeMillis() / 1000));
+ assertThat(m.get("count").intValue(), is(1));
+ }
+
+ @Test
+ public void testHealthMetric() {
+ HealthMetric m = HealthMetric.get(null, null);
+ assertThat(m.isOk(), is(false));
+ m = HealthMetric.get("up", "test message");
+ assertThat(m.isOk(), is(true));
+ assertThat(m.getMessage(), is("test message"));
+ m = HealthMetric.get("ok", "test message");
+ assertThat(m.isOk(), is(true));
+ assertThat(m.getMessage(), is("test message"));
+
+ m = HealthMetric.get("bad", "test message");
+ assertThat(m.isOk(), is(false));
+ assertThat(m.getStatus(), is("bad"));
+ }
+
+ @Test
+ public void testMetricFormatter() {
+ MetricsFormatter formatter = new MetricsFormatter(false, false);
+ VespaService service = new DummyService(0, "config.id");
+ String data = formatter.format(service, "key", 1);
+ assertThat(data, is("'config.id'.key=1"));
+
+ formatter = new MetricsFormatter(true, false);
+ data = formatter.format(service, "key", 1);
+ assertThat(data, is("dummy.'config.id'.key=1"));
+
+
+ formatter = new MetricsFormatter(true, true);
+ data = formatter.format(service, "key", 1);
+ assertThat(data, is("dummy.config.'id'.key=1"));
+
+ formatter = new MetricsFormatter(false, true);
+ data = formatter.format(service, "key", 1);
+ assertThat(data, is("config.'id'.key=1"));
+ }
+
+ @Test
+ public void testTimeAdjustment() {
+ assertThat(Metric.adjustTime(0L, 0L), is(0L));
+ assertThat(Metric.adjustTime(59L, 59L), is(59L));
+ assertThat(Metric.adjustTime(60L, 60L), is(60L));
+ assertThat(Metric.adjustTime(59L, 60L), is(60L));
+ assertThat(Metric.adjustTime(60L, 59L), is(60L));
+ assertThat(Metric.adjustTime(59L, 61L), is(59L));
+ }
+
+}
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/model/MetricsPacketTest.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/model/MetricsPacketTest.java
new file mode 100644
index 00000000000..d522a56a9ac
--- /dev/null
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/model/MetricsPacketTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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;
+
+import ai.vespa.metricsproxy.metric.Metric;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static ai.vespa.metricsproxy.metric.model.ConsumerId.toConsumerId;
+import static ai.vespa.metricsproxy.metric.model.MetricId.toMetricId;
+import static ai.vespa.metricsproxy.metric.model.ServiceId.toServiceId;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * @author gjoranv
+ */
+public class MetricsPacketTest {
+
+ @Test
+ public void service_cannot_be_null() {
+ try {
+ MetricsPacket packet = new MetricsPacket.Builder(null)
+ .statusCode(0)
+ .statusMessage("")
+ .timestamp(0L)
+ .build();
+ fail("Expected exception due to null service.");
+ } catch (Exception e) {
+ assertEquals("Service cannot be null.", e.getMessage());
+ }
+ }
+
+ @Test
+ public void consumers_are_always_distinct() {
+ ConsumerId DUPLICATE_CONSUMER = toConsumerId("duplicateConsumer");
+
+ MetricsPacket packet = new MetricsPacket.Builder(toServiceId("foo"))
+ .statusCode(0)
+ .statusMessage("")
+ .addConsumers(Collections.singleton(DUPLICATE_CONSUMER))
+ .addConsumers(Collections.singleton(DUPLICATE_CONSUMER))
+ .build();
+ assertEquals(1, packet.consumers().size());
+ }
+
+ @Test
+ public void builder_can_retain_subset_of_metrics() {
+ MetricsPacket packet = new MetricsPacket.Builder(toServiceId("foo"))
+ .putMetrics(ImmutableList.of(
+ new Metric("remove", 1),
+ new Metric("keep", 2)))
+ .retainMetrics(ImmutableSet.of(toMetricId("keep"), toMetricId("non-existent")))
+ .build();
+
+ assertFalse("should not contain 'remove'", packet.metrics().containsKey(toMetricId("remove")));
+ assertTrue("should contain 'keep'", packet.metrics().containsKey(toMetricId("keep")));
+ assertFalse("should not contain 'non-existent'", packet.metrics().containsKey(toMetricId("non-existent")));
+ }
+
+ @Test
+ public void builder_applies_output_names() {
+ String ONE = "one";
+ String TWO = "two";
+ String THREE = "three";
+ String NON_EXISTENT = "non-existent";
+ MetricId ONE_ID = toMetricId(ONE);
+ MetricId TWO_ID = toMetricId(TWO);
+ MetricId THREE_ID = toMetricId(THREE);
+ MetricId NON_EXISTENT_ID = toMetricId(NON_EXISTENT);
+
+ Map<MetricId, List<String>> outputNamesById = ImmutableMap.of(
+ toMetricId(ONE), ImmutableList.of(ONE),
+ toMetricId(TWO), ImmutableList.of(TWO, "dos"),
+ toMetricId(THREE), ImmutableList.of("3"),
+ toMetricId(NON_EXISTENT), ImmutableList.of(NON_EXISTENT));
+
+ MetricsPacket packet = new MetricsPacket.Builder(toServiceId("foo"))
+ .putMetrics(ImmutableList.of(
+ new Metric(ONE, 1),
+ new Metric(TWO, 2),
+ new Metric(THREE, 3)))
+ .applyOutputNames(outputNamesById)
+ .build();
+
+ // Only original name
+ assertTrue(packet.metrics().containsKey(ONE_ID));
+
+ // Both names
+ assertTrue(packet.metrics().containsKey(TWO_ID));
+ assertTrue(packet.metrics().containsKey(toMetricId("dos")));
+
+ // Only new name
+ assertFalse(packet.metrics().containsKey(THREE_ID));
+ assertTrue(packet.metrics().containsKey(toMetricId("3")));
+
+ // Non-existent metric not added
+ assertFalse(packet.metrics().containsKey(NON_EXISTENT_ID));
+ }
+
+}
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/model/json/JsonUtilTest.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/model/json/JsonUtilTest.java
new file mode 100644
index 00000000000..28912293fdb
--- /dev/null
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/model/json/JsonUtilTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.json;
+
+import ai.vespa.metricsproxy.metric.model.MetricsPacket;
+import org.junit.Test;
+
+import java.util.List;
+
+import static ai.vespa.metricsproxy.metric.model.ServiceId.toServiceId;
+import static ai.vespa.metricsproxy.metric.model.json.JsonUtil.toMetricsPackets;
+import static java.util.Collections.singleton;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author gjoranv
+ */
+public class JsonUtilTest {
+ @Test
+ public void json_model_gets_null_status_by_default() {
+ MetricsPacket packet = new MetricsPacket.Builder(toServiceId("foo"))
+ .build();
+ YamasJsonModel jsonModel = JsonUtil.toYamasArray(singleton(packet)).metrics.get(0);
+ assertNull(jsonModel.status_code);
+ assertNull(jsonModel.status_msg);
+ }
+
+ @Test
+ public void status_is_included_in_json_model_when_explicitly_asked_for() {
+ MetricsPacket packet = new MetricsPacket.Builder(toServiceId("foo"))
+ .build();
+ YamasJsonModel jsonModel = JsonUtil.toYamasArray(singleton(packet), true).metrics.get(0);
+ assertNotNull(jsonModel.status_code);
+ assertNotNull(jsonModel.status_msg);
+ }
+
+ @Test
+ public void timestamp_0_in_packet_is_translated_to_null_in_json_model() {
+ MetricsPacket packet = new MetricsPacket.Builder(toServiceId("foo"))
+ .timestamp(0L)
+ .build();
+ YamasJsonModel jsonModel = JsonUtil.toYamasArray(singleton(packet)).metrics.get(0);
+ assertNull(jsonModel.timestamp);
+ }
+
+ @Test
+ public void empty_consumers_is_translated_to_null_routing_in_json_model() {
+ MetricsPacket packet = new MetricsPacket.Builder(toServiceId("foo"))
+ .build();
+ YamasJsonModel jsonModel = JsonUtil.toYamasArray(singleton(packet)).metrics.get(0);
+ assertNull(jsonModel.routing);
+ }
+
+ @Test
+ public void empty_json_string_yields_empty_packet_list() {
+ List<MetricsPacket.Builder> builders = toMetricsPackets("");
+ assertTrue(builders.isEmpty());
+ }
+}
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/model/json/YamasJsonModelTest.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/model/json/YamasJsonModelTest.java
new file mode 100644
index 00000000000..e91ff32e3b4
--- /dev/null
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/model/json/YamasJsonModelTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.json;
+
+import ai.vespa.metricsproxy.metric.model.MetricsPacket;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.junit.Test;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Collections;
+
+import static ai.vespa.metricsproxy.metric.model.ConsumerId.toConsumerId;
+import static ai.vespa.metricsproxy.metric.model.MetricId.toMetricId;
+import static ai.vespa.metricsproxy.metric.model.ServiceId.toServiceId;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Tests for YamasJsonModel and YamasArrayJsonModel
+ *
+ * @author smorgrav
+ * @author gjoranv
+ */
+public class YamasJsonModelTest {
+
+ private static final String EXPECTED_JSON = "{\"metrics\":[{\"status_code\":0,\"timestamp\":1400047900,\"application\":\"vespa.searchnode\",\"metrics\":{\"cpu\":55.5555555555555,\"memory_virt\":22222222222,\"memory_rss\":5555555555},\"dimensions\":{\"applicationName\":\"app\",\"tenantName\":\"tenant\",\"metrictype\":\"system\",\"instance\":\"searchnode\",\"applicationInstance\":\"default\",\"clustername\":\"cluster\"},\"routing\":{\"yamas\":{\"namespaces\":[\"Vespa\"]}},\"status_msg\":\"Data collected successfully\"}]}";
+
+ @Test
+ public void array_definition_creates_correct_json() throws IOException {
+ YamasJsonModel jsonModel = getYamasJsonModel("yamas-array.json");
+
+ YamasArrayJsonModel yamasData = new YamasArrayJsonModel();
+ yamasData.add(jsonModel);
+
+ assertEquals(EXPECTED_JSON, yamasData.serialize());
+ }
+
+ @Test
+ public void deserialize_serialize_roundtrip() throws IOException {
+ YamasJsonModel jsonModel = getYamasJsonModel("yamas-array.json");
+
+ // Do some sanity checking
+ assertEquals("vespa.searchnode", jsonModel.application);
+ assertEquals("Vespa", jsonModel.routing.get("yamas").namespaces.get(0));
+ assertEquals(5.555555555E9, jsonModel.metrics.get("memory_rss"), 0.1d); //Not using custom double renderer
+
+ // Serialize and verify
+ YamasArrayJsonModel yamasArray = new YamasArrayJsonModel();
+ yamasArray.add(jsonModel);
+ String string = yamasArray.serialize();
+ assertEquals(EXPECTED_JSON, string);
+ }
+
+ @Test
+ public void deserialize_serialize_roundtrip_with_metrics_packet() throws IOException {
+ YamasJsonModel jsonModel = getYamasJsonModel("yamas-array.json");
+ MetricsPacket metricsPacket = JsonUtil.toMetricsPacketBuilder(jsonModel).build();
+
+ // Do some sanity checking
+ assertEquals(toServiceId("vespa.searchnode"), metricsPacket.service);
+ assertEquals(toConsumerId("Vespa"), metricsPacket.consumers().get(0));
+ assertEquals(5.555555555E9, metricsPacket.metrics().get(toMetricId("memory_rss")).doubleValue(), 0.1d); //Not using custom double rendrer
+
+ // Serialize and verify
+ YamasArrayJsonModel yamasArray = JsonUtil.toYamasArray(Collections.singleton(metricsPacket), true);
+ String string = yamasArray.serialize();
+ assertEquals(EXPECTED_JSON, string);
+ }
+
+ @Test
+ public void missing_routing_object_makes_it_null() throws IOException {
+ // Read file that was taken from production (real -life example that is)
+ String filename = getClass().getClassLoader().getResource("yamas-array-no-routing.json").getFile();
+ BufferedReader reader = Files.newBufferedReader(Paths.get(filename));
+ ObjectMapper mapper = new ObjectMapper();
+ YamasJsonModel jsonModel = mapper.readValue(reader, YamasJsonModel.class);
+
+ // Do some sanity checking
+ assertNull(jsonModel.routing);
+ }
+
+ private YamasJsonModel getYamasJsonModel(String testFile) throws IOException {
+ String filename = getClass().getClassLoader().getResource(testFile).getFile();
+ BufferedReader reader = Files.newBufferedReader(Paths.get(filename));
+ ObjectMapper mapper = new ObjectMapper();
+ return mapper.readValue(reader, YamasJsonModel.class);
+ }
+
+}
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/rpc/IntegrationTester.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/rpc/IntegrationTester.java
new file mode 100644
index 00000000000..6bbf4ae1ef5
--- /dev/null
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/rpc/IntegrationTester.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+ */
+
+package ai.vespa.metricsproxy.rpc;
+
+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.core.MonitoringConfig;
+import ai.vespa.metricsproxy.core.VespaMetrics;
+import ai.vespa.metricsproxy.metric.ExternalMetrics;
+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.ConsumerId;
+import ai.vespa.metricsproxy.metric.model.ServiceId;
+import ai.vespa.metricsproxy.service.HttpMetricFetcher;
+import ai.vespa.metricsproxy.service.MockHttpServer;
+import ai.vespa.metricsproxy.service.VespaServices;
+import ai.vespa.metricsproxy.service.VespaServicesConfig;
+import ai.vespa.metricsproxy.service.VespaServicesConfig.Service;
+
+import java.io.IOException;
+
+import static ai.vespa.metricsproxy.core.VespaMetrics.VESPA_CONSUMER_ID;
+import static ai.vespa.metricsproxy.metric.model.ConsumerId.toConsumerId;
+import static ai.vespa.metricsproxy.metric.model.ServiceId.toServiceId;
+import static ai.vespa.metricsproxy.service.HttpMetricFetcher.STATE_PATH;
+
+
+/**
+ * Setup and shutdown of config and servers for integration-style unit tests.
+ *
+ * @author hmusum
+ * @author gjoranv
+ */
+public class IntegrationTester implements AutoCloseable {
+
+ static final String MONITORING_SYSTEM = "test-system";
+ static final ConsumerId CUSTOM_CONSUMER_ID = toConsumerId("custom-consumer");
+ static final String SERVICE_1_CONFIG_ID = "container/qrserver.0";
+ static final String SERVICE_2_CONFIG_ID = "storage/cluster.storage/storage/0";
+
+ private final int httpPort;
+ private final int rpcPort;
+ private final RpcConnector connector;
+ private final MockHttpServer mockHttpServer;
+ private final VespaServices vespaServices;
+
+ static {
+ HttpMetricFetcher.CONNECTION_TIMEOUT = 60000; // 60 secs in unit tests
+ }
+
+ IntegrationTester(int httpPort, int rpcPort) {
+ if (httpPort == 0 || rpcPort == 0) {
+ throw new IllegalArgumentException("http port and rpc port must be defined");
+ }
+ this.httpPort = httpPort;
+ this.rpcPort = rpcPort;
+ try {
+ mockHttpServer = new MockHttpServer(httpPort, null, STATE_PATH);
+ } catch (IOException e) {
+ throw new RuntimeException("Unable to start web server on port:" + httpPort);
+ }
+
+ vespaServices = new VespaServices(servicesConfig(), monitoringConfig(), null);
+ MetricsConsumers consumers = new MetricsConsumers(consumersConfig());
+ VespaMetrics vespaMetrics = new VespaMetrics(consumers, vespaServices);
+ ExternalMetrics externalMetrics = new ExternalMetrics(consumers);
+ ApplicationDimensions appDimensions = new ApplicationDimensions(applicationDimensionsConfig());
+ NodeDimensions nodeDimensions = new NodeDimensions(nodeDimensionsConfig());
+
+ connector = new RpcConnector(rpcConnectorConfig());
+ RpcServer server = new RpcServer(connector, vespaServices, new MetricsManager(vespaServices, vespaMetrics, externalMetrics, appDimensions, nodeDimensions));
+ }
+
+ MockHttpServer httpServer() {
+ return mockHttpServer;
+ }
+
+ VespaServices vespaServices() { return vespaServices; }
+
+ @Override
+ public void close() {
+ mockHttpServer.close();
+ this.connector.stop();
+ }
+
+ private RpcConnectorConfig rpcConnectorConfig() {
+ return new RpcConnectorConfig.Builder()
+ .port(rpcPort)
+ .build();
+ }
+
+ private ConsumersConfig consumersConfig() {
+ return new ConsumersConfig.Builder()
+ .consumer(createConsumer(VESPA_CONSUMER_ID, "foo.count", "foo_count"))
+ .consumer(createConsumer(CUSTOM_CONSUMER_ID, "foo.count", "foo.count"))
+ .build();
+ }
+
+ private static Consumer.Builder createConsumer(ConsumerId consumerId, String metricName, String outputName) {
+ return new Consumer.Builder()
+ .name(consumerId.id)
+ .metric(new Consumer.Metric.Builder()
+ .dimension(new Consumer.Metric.Dimension.Builder().key("somekey").value("somevalue"))
+ .name(metricName)
+ .outputname(outputName));
+ }
+
+ private VespaServicesConfig servicesConfig() {
+ return new VespaServicesConfig.Builder()
+ .service(createService(toServiceId("qrserver"), SERVICE_1_CONFIG_ID, httpPort))
+ .service(createService(toServiceId("storagenode"), SERVICE_2_CONFIG_ID, httpPort))
+ .build();
+ }
+
+ private static Service.Builder createService(ServiceId serviceId, String configId, int port) {
+ return new Service.Builder()
+ .name(serviceId.id)
+ .configId(configId)
+ .port(port)
+ .healthport(port)
+ .dimension(new Service.Dimension.Builder().key("serviceDim").value("serviceDimValue"));
+ }
+
+ private MonitoringConfig monitoringConfig() {
+ return new MonitoringConfig.Builder()
+ .systemName(MONITORING_SYSTEM)
+ .build();
+ }
+
+ private ApplicationDimensionsConfig applicationDimensionsConfig() {
+ return new ApplicationDimensionsConfig.Builder().build();
+ }
+
+ private NodeDimensionsConfig nodeDimensionsConfig() {
+ return new NodeDimensionsConfig.Builder().build();
+ }
+
+}
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/rpc/RpcHealthMetricsTest.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/rpc/RpcHealthMetricsTest.java
new file mode 100644
index 00000000000..20fb69e410e
--- /dev/null
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/rpc/RpcHealthMetricsTest.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+ */
+
+package ai.vespa.metricsproxy.rpc;
+
+import ai.vespa.metricsproxy.TestUtil;
+import ai.vespa.metricsproxy.metric.HealthMetric;
+import ai.vespa.metricsproxy.service.MockHttpServer;
+import ai.vespa.metricsproxy.service.VespaService;
+import com.yahoo.jrt.Request;
+import com.yahoo.jrt.Spec;
+import com.yahoo.jrt.StringValue;
+import com.yahoo.jrt.Supervisor;
+import com.yahoo.jrt.Target;
+import com.yahoo.jrt.Transport;
+import org.junit.Test;
+
+import java.util.List;
+
+import static ai.vespa.metricsproxy.rpc.IntegrationTester.SERVICE_1_CONFIG_ID;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author jobergum
+ * @author gjoranv
+ */
+public class RpcHealthMetricsTest {
+
+ private static final String HEALTH_OK_RESPONSE =
+ TestUtil.getContents("health-check.response.json");
+ private static final String HEALTH_FAILED_RESPONSE =
+ TestUtil.getContents("health-check-failed.response.json");
+ private static final String WANTED_RPC_RESPONSE =
+ TestUtil.getContents("rpc-json-output-check.json").trim();
+
+ // see factory/doc/port-ranges.txt
+ private static final int httpPort = 18635;
+ private static final int rpcPort = 18636;
+
+ @Test
+ public void expected_response_is_returned() {
+ try (IntegrationTester tester = new IntegrationTester(httpPort, rpcPort)) {
+
+ MockHttpServer mockHttpServer = tester.httpServer();
+ mockHttpServer.setResponse(HEALTH_OK_RESPONSE);
+ List<VespaService> services = tester.vespaServices().getInstancesById(SERVICE_1_CONFIG_ID);
+
+ assertThat(services.size(), is(1));
+ VespaService qrserver = services.get(0);
+ HealthMetric h = qrserver.getHealth();
+ assertNotNull("Health metric should never be null", h);
+ assertThat("Status failed, reason = " + h.getMessage(), h.isOk(), is(true));
+ assertThat(h.getMessage(), is("WORKING"));
+
+ mockHttpServer.setResponse(HEALTH_FAILED_RESPONSE);
+ h = qrserver.getHealth();
+ assertNotNull("Health metric should never be null", h);
+ assertThat("Status should be failed" + h.getMessage(), h.isOk(), is(false));
+ assertThat(h.getMessage(), is("SOMETHING FAILED"));
+
+ String jsonRPCMessage = getHealthMetrics(qrserver.getMonitoringName());
+ assertThat(jsonRPCMessage, is(WANTED_RPC_RESPONSE));
+ }
+ }
+
+ @Test
+ public void non_existent_service_name_returns_an_error_message() {
+ try (IntegrationTester tester = new IntegrationTester(httpPort, rpcPort)) {
+ String jsonRPCMessage = getHealthMetrics("non-existing service");
+ assertThat(jsonRPCMessage, is("105: No service with name 'non-existing service'"));
+ }
+ }
+
+ private String getHealthMetrics(String service) {
+ Supervisor supervisor = new Supervisor(new Transport());
+ Target target = supervisor.connect(new Spec("localhost", rpcPort));
+ Request req = new Request("getHealthMetricsForYamas");
+ req.parameters().add(new StringValue(service));
+ String returnValue;
+
+ target.invokeSync(req, 20.0);
+ if (req.checkReturnTypes("s")) {
+ returnValue = req.returnValues().get(0).asString();
+ } else {
+ System.out.println("RpcServer invocation failed " + req.errorCode() + ": " + req.errorMessage());
+ returnValue = req.errorCode() + ": " + req.errorMessage();
+ }
+ target.close();
+ supervisor.transport().shutdown().join();
+
+ return returnValue;
+ }
+
+}
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/rpc/RpcMetricsTest.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/rpc/RpcMetricsTest.java
new file mode 100644
index 00000000000..f264fd13ddc
--- /dev/null
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/rpc/RpcMetricsTest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+ */
+
+package ai.vespa.metricsproxy.rpc;
+
+import ai.vespa.metricsproxy.TestUtil;
+import ai.vespa.metricsproxy.metric.Metric;
+import ai.vespa.metricsproxy.metric.Metrics;
+import ai.vespa.metricsproxy.metric.model.ConsumerId;
+import ai.vespa.metricsproxy.service.VespaService;
+import com.yahoo.jrt.Request;
+import com.yahoo.jrt.Spec;
+import com.yahoo.jrt.StringValue;
+import com.yahoo.jrt.Supervisor;
+import com.yahoo.jrt.Target;
+import com.yahoo.jrt.Transport;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Test;
+
+import java.util.List;
+
+import static ai.vespa.metricsproxy.core.VespaMetrics.VESPA_CONSUMER_ID;
+import static ai.vespa.metricsproxy.metric.model.DimensionId.toDimensionId;
+import static ai.vespa.metricsproxy.rpc.IntegrationTester.CUSTOM_CONSUMER_ID;
+import static ai.vespa.metricsproxy.rpc.IntegrationTester.MONITORING_SYSTEM;
+import static ai.vespa.metricsproxy.rpc.IntegrationTester.SERVICE_1_CONFIG_ID;
+import static ai.vespa.metricsproxy.rpc.IntegrationTester.SERVICE_2_CONFIG_ID;
+import static ai.vespa.metricsproxy.service.VespaServices.ALL_SERVICES;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author jobergum
+ * @author gjoranv
+ */
+public class RpcMetricsTest {
+
+ private static final String METRICS_RESPONSE_CCL =
+ TestUtil.getContents("metrics-storage-simple.json").trim();
+
+ // see factory/doc/port-ranges.txt
+ private static final int httpPort = 18633;
+ private static final int rpcPort = 18634;
+
+ @Test
+ public void testGetMetrics() throws Exception {
+ try (IntegrationTester tester = new IntegrationTester(httpPort, rpcPort)) {
+ tester.httpServer().setResponse(METRICS_RESPONSE_CCL);
+ List<VespaService> services = tester.vespaServices().getInstancesById(SERVICE_1_CONFIG_ID);
+
+ assertThat("#Services should be 1 for config id " + SERVICE_1_CONFIG_ID, services.size(), is(1));
+
+ VespaService qrserver = services.get(0);
+ assertThat(qrserver.getMonitoringName(), is(MONITORING_SYSTEM + VespaService.SEPARATOR + "qrserver"));
+
+ Metrics metrics = qrserver.getMetrics();
+ assertThat("Fetched number of metrics is not correct", metrics.size(), is(2));
+ Metric m = metrics.getMetric("foo.count");
+ assertNotNull("Did not find expected metric with name 'foo.count'", m);
+ Metric m2 = metrics.getMetric("bar.count");
+ assertNotNull("Did not find expected metric with name 'bar.count'", m2);
+
+ // Setup RPC client
+ Supervisor supervisor = new Supervisor(new Transport());
+ Target target = supervisor.connect(new Spec("localhost", rpcPort));
+
+ verifyMetricsFromRpcRequest(qrserver, target);
+
+ services = tester.vespaServices().getInstancesById(SERVICE_2_CONFIG_ID);
+ assertThat("#Services should be 1 for config id " + SERVICE_2_CONFIG_ID, services.size(), is(1));
+
+ VespaService storageService = services.get(0);
+ verfiyMetricsFromServiceObject(storageService);
+
+ String metricsById = getMetricsById(storageService.getConfigId(), target);
+ assertThat(metricsById, is("'storage.cluster.storage.storage.0'.foo_count=1 "));
+
+ String jsonResponse = getMetricsForYamas("non-existing", target).trim();
+ assertThat(jsonResponse, is("105: No service with name 'non-existing'"));
+
+ verifyMetricsFromRpcRequestForAllServices(target);
+
+ // Shutdown RPC
+ target.close();
+ supervisor.transport().shutdown().join();
+ }
+ }
+
+ private static void verifyMetricsFromRpcRequest(VespaService service, Target target) throws JSONException {
+ String jsonResponse = getMetricsForYamas(service.getMonitoringName(), target).trim();
+ JSONArray metrics = new JSONObject(jsonResponse).getJSONArray("metrics");
+ assertThat("Expected 3 metric messages", metrics.length(), is(3));
+ for (int i = 0; i < metrics.length() - 1; i++) { // The last "metric message" contains only status code/message
+ JSONObject jsonObject = metrics.getJSONObject(i);
+ assertFalse(jsonObject.has("status_code"));
+ assertFalse(jsonObject.has("status_msg"));
+ assertThat(jsonObject.getJSONObject("dimensions").getString("foo"), is("bar"));
+ assertThat(jsonObject.getJSONObject("dimensions").getString("bar"), is("foo"));
+ assertThat(jsonObject.getJSONObject("dimensions").getString("serviceDim"), is("serviceDimValue"));
+ assertThat(jsonObject.getJSONObject("routing").getJSONObject("yamas").getJSONArray("namespaces").length(), is(1));
+ if (jsonObject.getJSONObject("metrics").has("foo_count")) {
+ assertThat(jsonObject.getJSONObject("metrics").getInt("foo_count"), is(1));
+ assertThat(jsonObject.getJSONObject("routing").getJSONObject("yamas").getJSONArray("namespaces").get(0), is(VESPA_CONSUMER_ID.id));
+ } else {
+ assertThat(jsonObject.getJSONObject("metrics").getInt("foo.count"), is(1));
+ assertThat(jsonObject.getJSONObject("routing").getJSONObject("yamas").getJSONArray("namespaces").get(0), is(CUSTOM_CONSUMER_ID.id));
+ }
+ }
+
+ verifyStatusMessage(metrics.getJSONObject(metrics.length() - 1));
+ }
+
+ private void verfiyMetricsFromServiceObject(VespaService service) {
+ Metrics storageMetrics = service.getMetrics();
+ assertThat(storageMetrics.size(), is(2));
+ Metric foo = storageMetrics.getMetric("foo.count");
+ assertNotNull("Did not find expected metric with name 'foo.count'", foo);
+ assertThat("Expected 2 dimensions for metric foo", foo.getDimensions().size(), is(2));
+ assertThat("Metric foo did not contain correct dimension mapping for key = foo.count", foo.getDimensions().containsKey(toDimensionId("foo")), is(true));
+ assertThat("Metric foo did not contain correct dimension", foo.getDimensions().get(toDimensionId("foo")), is("bar"));
+ assertThat("Metric foo did not contain correct dimension", foo.getDimensions().containsKey(toDimensionId("bar")), is(true));
+ assertThat("Metric foo did not contain correct dimension for key = bar", foo.getDimensions().get(toDimensionId("bar")), is("foo"));
+ }
+
+ private void verifyMetricsFromRpcRequestForAllServices(Target target) throws JSONException {
+ // Verify that metrics for all services can be retrieved in one request.
+ String allServicesResponse = getMetricsForYamas(ALL_SERVICES, target).trim();
+ JSONArray allServicesMetrics = new JSONObject(allServicesResponse).getJSONArray("metrics");
+ assertThat(allServicesMetrics.length(), is(5));
+ }
+
+ @Test
+ public void testGetAllMetricNames() {
+ try (IntegrationTester tester = new IntegrationTester(httpPort, rpcPort)) {
+
+ tester.httpServer().setResponse(METRICS_RESPONSE_CCL);
+ List<VespaService> services = tester.vespaServices().getInstancesById(SERVICE_1_CONFIG_ID);
+
+ assertThat(services.size(), is(1));
+ Metrics metrics = services.get(0).getMetrics();
+ assertThat("Fetched number of metrics is not correct", metrics.size(), is(2));
+ Metric m = metrics.getMetric("foo.count");
+ assertNotNull("Did not find expected metric with name 'foo.count'", m);
+
+
+ Metric m2 = metrics.getMetric("bar.count");
+ assertNotNull("Did not find expected metric with name 'bar'", m2);
+
+ // Setup RPC
+ Supervisor supervisor = new Supervisor(new Transport());
+ Target target = supervisor.connect(new Spec("localhost", rpcPort));
+
+ String response = getAllMetricNamesForService(services.get(0).getMonitoringName(), VESPA_CONSUMER_ID, target);
+ assertThat(response, is("foo.count=ON;output-name=foo_count,bar.count=OFF,"));
+
+ // Shutdown RPC
+ target.close();
+ supervisor.transport().shutdown().join();
+ }
+ }
+
+ private static String getMetricsForYamas(String service, Target target) {
+ Request req = new Request("getMetricsForYamas");
+ req.parameters().add(new StringValue(service));
+ return invoke(req, target);
+ }
+
+ private String getMetricsById(String service, Target target) {
+ Request req = new Request("getMetricsById");
+ req.parameters().add(new StringValue(service));
+ return invoke(req, target);
+ }
+
+ private String getAllMetricNamesForService(String service, ConsumerId consumer, Target target) {
+ Request req = new Request("getAllMetricNamesForService");
+ req.parameters().add(new StringValue(service));
+ req.parameters().add(new StringValue(consumer.id));
+ return invoke(req, target);
+ }
+
+ private static String invoke(Request req, Target target) {
+ String returnValue;
+ target.invokeSync(req, 20.0);
+ if (req.checkReturnTypes("s")) {
+ returnValue = req.returnValues().get(0).asString();
+ } else {
+ System.out.println(req.methodName() + " from rpcserver - Invocation failed "
+ + req.errorCode() + ": " + req.errorMessage());
+ returnValue = req.errorCode() + ": " + req.errorMessage();
+ }
+ return returnValue;
+ }
+
+ private static void verifyStatusMessage(JSONObject jsonObject) throws JSONException {
+ assertThat(jsonObject.getInt("status_code"), is(0));
+ assertThat(jsonObject.getString("status_msg"), notNullValue());
+ assertThat(jsonObject.getString("application"), notNullValue());
+ assertThat(jsonObject.getString("routing"), notNullValue());
+ assertThat(jsonObject.length(), is(4));
+ }
+
+}
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/ConfigSentinelClientTest.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/ConfigSentinelClientTest.java
new file mode 100644
index 00000000000..bd61b8443aa
--- /dev/null
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/ConfigSentinelClientTest.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+ */
+
+package ai.vespa.metricsproxy.service;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Unknown
+ */
+public class ConfigSentinelClientTest {
+
+ @Test
+ public void testConfigSentinelClient() {
+ ConfigSentinelDummy configsentinel = new ConfigSentinelDummy();
+ List<VespaService> services = new ArrayList<>();
+ VespaService docproc = new VespaService("docprocservice", "docproc/cluster.x.indexing/0");
+ VespaService searchnode4 = new VespaService("searchnode4", "search/cluster.x/g0/c1/r1");
+ VespaService qrserver = new VespaService("qrserver", "container/qrserver.0");
+
+ services.add(searchnode4);
+ services.add(qrserver);
+ services.add(docproc);
+
+ MockConfigSentinelClient client = new MockConfigSentinelClient(configsentinel);
+ client.updateServiceStatuses(services);
+
+ assertThat(qrserver.getPid(), is(6520));
+ assertThat(qrserver.getState(), is("RUNNING"));
+ assertThat(qrserver.isAlive(), is(true));
+ assertThat(searchnode4.getPid(), is(6534));
+ assertThat(searchnode4.getState(), is("RUNNING"));
+ assertThat(searchnode4.isAlive(), is(true));
+
+ assertThat(docproc.getPid(), is(-1));
+ assertThat(docproc.getState(), is("FINISHED"));
+ assertThat(docproc.isAlive(), is(false));
+
+
+ configsentinel.reConfigure();
+
+ client.ping(docproc);
+ assertThat(docproc.getPid(), is(100));
+ assertThat(docproc.getState(), is("RUNNING"));
+ assertThat(docproc.isAlive(), is(true));
+
+ //qrserver has yet not been checked
+ assertThat(qrserver.isAlive(), is(true));
+
+ client.updateServiceStatuses(services);
+
+ assertThat(docproc.getPid(), is(100));
+ assertThat(docproc.getState(), is("RUNNING"));
+ assertThat(docproc.isAlive(), is(true));
+ //qrserver is no longer running on this node - so should be false
+ assertThat(qrserver.isAlive(), is(false));
+ }
+
+ @Test
+ public void testElastic() throws Exception {
+ String response = "container state=RUNNING mode=AUTO pid=14338 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"get/container.0\"\n" +
+ "container-clustercontroller state=RUNNING mode=AUTO pid=25020 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"admin/cluster-controllers/0\"\n" +
+ "distributor state=RUNNING mode=AUTO pid=25024 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"search/distributor/0\"\n" +
+ "docprocservice state=RUNNING mode=AUTO pid=11973 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"docproc/cluster.search.indexing/0\"\n" +
+ "logd state=RUNNING mode=AUTO pid=25016 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"hosts/vespa19.dev.gq1.yahoo.com/logd\"\n" +
+ "logserver state=RUNNING mode=AUTO pid=25018 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"admin/logserver\"\n" +
+ "metricsproxy state=RUNNING mode=AUTO pid=13107 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"hosts/vespa19.dev.gq1.yahoo.com/metricsproxy\"\n" +
+ "searchnode state=RUNNING mode=AUTO pid=25023 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"search/search/cluster.search/0\"\n" +
+ "slobrok state=RUNNING mode=AUTO pid=25019 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"admin/slobrok.0\"\n" +
+ "topleveldispatch state=RUNNING mode=AUTO pid=25026 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"search/search/cluster.search/tlds/tld.0\"\n" +
+ "\n";
+
+ ConfigSentinelDummy configsentinel = new ConfigSentinelDummy(response);
+ List<VespaService> services = new ArrayList<>();
+
+ VespaService container = VespaService.create("container", "get/container.0", -1);
+
+ VespaService containerClusterController =
+ VespaService.create("container-clustercontroller", "get/container.0", -1);
+
+ VespaService notPresent = VespaService.create("dummy","fake", -1);
+
+ services.add(container);
+ services.add(containerClusterController);
+ services.add(notPresent);
+
+ MockConfigSentinelClient client = new MockConfigSentinelClient(configsentinel);
+ client.updateServiceStatuses(services);
+ assertThat(container.isAlive(),is(true));
+ assertThat(container.getPid(),is(14338));
+ assertThat(container.getState(),is("RUNNING"));
+
+ assertThat(containerClusterController.isAlive(),is(true));
+ assertThat(containerClusterController.getPid(),is(25020));
+ assertThat(containerClusterController.getState(),is("RUNNING"));
+ }
+}
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/ConfigSentinelDummy.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/ConfigSentinelDummy.java
new file mode 100644
index 00000000000..108f5c18e1d
--- /dev/null
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/ConfigSentinelDummy.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+ */
+package ai.vespa.metricsproxy.service;
+
+/**
+ * @author Eirik Nygaard
+ */
+public class ConfigSentinelDummy {
+ private String serviceList =
+ "docprocservice state=FINISHED mode=MANUAL pid=6555 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"docproc/cluster.x.indexing/0\"\n"
+ + "distributor state=RUNNING mode=AUTO pid=6548 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"storage/cluster.storage/distributor/0\"\n"
+ + "fleetcontroller state=RUNNING mode=AUTO pid=6543 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"storage/cluster.storage/fleetcontroller/0\"\n"
+ + "storagenode state=RUNNING mode=AUTO pid=6539 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"storage/cluster.storage/storage/0\"\n"
+ + "searchnode4 state=RUNNING mode=AUTO pid=6534 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"search/cluster.x/g0/c1/r1\"\n"
+ + "qrserver2 state=RUNNING mode=AUTO pid=6521 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"container/qrserver.1\"\n"
+ + "logserver state=RUNNING mode=AUTO pid=6518 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"admin/logserver\"\n"
+ + "logd state=RUNNING mode=AUTO pid=6517 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"hosts/dell-bl5s7.trondheim.corp.yahoo.com/logd\"\n"
+ + "searchnode2 state=RUNNING mode=AUTO pid=6527 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"search/cluster.x/g0/c0/r1\"\n"
+ + "topleveldispatch2 state=RUNNING mode=AUTO pid=6525 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"search/cluster.x/tlds/tld.1\"\n"
+ + "topleveldispatch state=RUNNING mode=AUTO pid=6524 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"search/cluster.x/tlds/tld.0\"\n"
+ + "clustercontroller2 state=RUNNING mode=AUTO pid=6523 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"search/cluster.x/rtx/1\"\n"
+ + "clustercontroller state=RUNNING mode=AUTO pid=6522 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"search/cluster.x/rtx/0\"\n"
+ + "slobrok state=RUNNING mode=AUTO pid=6519 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"admin/slobrok.0\"\n"
+ + "searchnode3 state=RUNNING mode=AUTO pid=6529 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"search/cluster.x/g0/c1/r0\"\n"
+ + "searchnode state=RUNNING mode=AUTO pid=6526 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"search/cluster.x/g0/c0/r0\"\n"
+ + "qrserver state=RUNNING mode=AUTO pid=6520 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"container/qrserver.0\"\n"
+ + "\n";
+
+
+ public ConfigSentinelDummy() {
+ }
+
+ public ConfigSentinelDummy(String response) {
+ serviceList = response;
+ }
+
+ public void reConfigure() {
+ this.serviceList = "docprocservice state=RUNNING mode=AUTO pid=100 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"docproc/cluster.x.indexing/0\"\n"
+ + "distributor state=RUNNING mode=AUTO pid=6548 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"storage/cluster.storage/distributor/0\"\n"
+ + "fleetcontroller state=RUNNING mode=AUTO pid=6543 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"storage/cluster.storage/fleetcontroller/0\"\n"
+ + "storagenode state=RUNNING mode=AUTO pid=6539 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"storage/cluster.storage/storage/0\"\n"
+ + "searchnode4 state=RUNNING mode=AUTO pid=6534 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"search/cluster.x/g0/c1/r1\"\n"
+ + "qrserver2 state=RUNNING mode=AUTO pid=6521 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"container/qrserver.1\"\n"
+ + "logserver state=RUNNING mode=AUTO pid=6518 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"admin/logserver\"\n"
+ + "logd state=RUNNING mode=AUTO pid=6517 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"hosts/dell-bl5s7.trondheim.corp.yahoo.com/logd\"\n"
+ + "searchnode2 state=RUNNING mode=AUTO pid=6527 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"search/cluster.x/g0/c0/r1\"\n"
+ + "topleveldispatch2 state=RUNNING mode=AUTO pid=6525 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"search/cluster.x/tlds/tld.1\"\n"
+ + "topleveldispatch state=RUNNING mode=AUTO pid=6524 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"search/cluster.x/tlds/tld.0\"\n"
+ + "clustercontroller2 state=RUNNING mode=AUTO pid=6523 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"search/cluster.x/rtx/1\"\n"
+ + "clustercontroller state=RUNNING mode=AUTO pid=6522 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"search/cluster.x/rtx/0\"\n"
+ + "slobrok state=RUNNING mode=AUTO pid=6519 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"admin/slobrok.0\"\n"
+ + "searchnode3 state=RUNNING mode=AUTO pid=6529 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"search/cluster.x/g0/c1/r0\"\n"
+ + "searchnode state=RUNNING mode=AUTO pid=6526 exitstatus=0 autostart=TRUE autorestart=TRUE id=\"search/cluster.x/g0/c0/r0\"\n"
+ + "\n";
+ }
+
+ public String getServiceList() {
+ return serviceList;
+ }
+}
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/ContainerServiceTest.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/ContainerServiceTest.java
new file mode 100644
index 00000000000..4174b18f3a7
--- /dev/null
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/ContainerServiceTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+ */
+
+package ai.vespa.metricsproxy.service;
+
+import ai.vespa.metricsproxy.TestUtil;
+import ai.vespa.metricsproxy.metric.Metric;
+import org.json.JSONException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static ai.vespa.metricsproxy.metric.model.DimensionId.toDimensionId;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Unknown
+ */
+public class ContainerServiceTest {
+
+ private MockHttpServer service;
+ private int csPort;
+
+ @BeforeClass
+ public static void init() {
+ HttpMetricFetcher.CONNECTION_TIMEOUT = 60000; // 60 secs in unit tests
+ }
+
+ @Before
+ public void setupHTTPServer() {
+ csPort = 18637; // see factory/doc/port-ranges.txt
+ try {
+ String response = TestUtil.getContents("metrics-container-state-multi-chain.json");
+ service = new MockHttpServer(csPort, response, HttpMetricFetcher.METRICS_PATH);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Test
+ public void testMultipleQueryDimensions() throws JSONException {
+ int count = 0;
+ VespaService service = VespaService.create("service1", "id", csPort);
+ for (Metric m : service.getMetrics().getMetrics()) {
+ if (m.getName().equals("queries.rate")) {
+ count++;
+ System.out.println("Name: " + m.getName() + " value: " + m.getValue());
+ if (m.getDimensions().get(toDimensionId("chain")).equals("asvBlendingResult")) {
+ assertThat((Double)m.getValue(), is(26.4));
+ } else if (m.getDimensions().get(toDimensionId("chain")).equals("blendingResult")) {
+ assertThat((Double)m.getValue(), is(0.36666666666666664));
+ } else {
+ assertThat("Unknown unknown chain", false, is(true));
+ }
+ }
+ }
+ assertThat(count, is(2));
+ }
+
+ @After
+ public void shutdown() {
+ this.service.close();
+ }
+}
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
new file mode 100644
index 00000000000..380a992aead
--- /dev/null
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/DummyService.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+ */
+
+package ai.vespa.metricsproxy.service;
+
+import ai.vespa.metricsproxy.metric.Metric;
+import ai.vespa.metricsproxy.metric.Metrics;
+
+/**
+ * @author Unknown
+ */
+public class DummyService extends VespaService {
+ static final String NAME = "dummy";
+ public static final String METRIC_1 = "c.test";
+ public static final String METRIC_2 = "val";
+
+ private final int num;
+
+ public DummyService(int num, String configid) {
+ super(NAME, NAME + num, configid);
+ this.num = num;
+ }
+
+ @Override
+ public Metrics getMetrics() {
+ Metrics m = new Metrics();
+
+ long timestamp = System.currentTimeMillis() / 1000;
+ m.add(new Metric(METRIC_1, 5 * num + 1, timestamp));
+ m.add(new Metric(METRIC_2, 1.3 * num + 1.05, timestamp));
+
+ return m;
+ }
+
+}
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/MetricsFetcherTest.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/MetricsFetcherTest.java
new file mode 100644
index 00000000000..27e1bb97943
--- /dev/null
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/MetricsFetcherTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+ */
+
+package ai.vespa.metricsproxy.service;
+
+import ai.vespa.metricsproxy.TestUtil;
+import ai.vespa.metricsproxy.metric.Metrics;
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Unknowm
+ */
+public class MetricsFetcherTest {
+ private static int port = 9; //port number is not used in this test
+
+ @Test
+ public void testStateFormatMetricsParse() {
+ String jsonData = TestUtil.getContents("metrics-state.json");
+ RemoteMetricsFetcher fetcher = new RemoteMetricsFetcher(new DummyService(0, "dummy/id/0"), port);
+ Metrics metrics = fetcher.createMetrics(jsonData, 0);
+ assertThat("Wrong number of metrics", metrics.size(), is(10));
+ assertThat("Wrong value for metric", metrics.get("query_hits.count").intValue(), is(28));
+ assertThat("Wrong value for metric ", metrics.get("queries.rate").doubleValue(), is(0.4667));
+ assertThat("Wrong timestamp", metrics.getTimeStamp(), is(1334134700L));
+ }
+
+ @Test
+ public void testEmptyJson() {
+ String jsonData = "{}";
+ RemoteMetricsFetcher fetcher = new RemoteMetricsFetcher(new DummyService(0, "dummy/id/0"), port);
+ Metrics metrics = fetcher.createMetrics(jsonData, 0);
+ assertThat("Wrong number of metrics", metrics.size(), is(0));
+ }
+
+ @Test
+ public void testErrors() {
+ String jsonData;
+ Metrics metrics;
+
+ RemoteMetricsFetcher fetcher = new RemoteMetricsFetcher(new DummyService(0, "dummy/id/0"), port);
+
+ jsonData = "";
+ metrics = fetcher.createMetrics(jsonData, 0);
+ assertThat("Wrong number of metrics", metrics.size(), is(0));
+
+ jsonData = "{\n" +
+ "\"status\" : {\n" +
+ " \"code\" : \"up\",\n" +
+ " \"message\" : \"Everything ok here\"\n" +
+ "}\n" +
+ "}";
+ metrics = fetcher.createMetrics(jsonData, 0);
+ assertThat("Wrong number of metrics", metrics.size(), is(0));
+
+ jsonData = "{\n" +
+ "\"status\" : {\n" +
+ " \"code\" : \"up\",\n" +
+ " \"message\" : \"Everything ok here\"\n" +
+ "},\n" +
+ "\"metrics\" : {\n" +
+ " \"snapshot\" : {\n" +
+ " \"from\" : 1334134640.089,\n" +
+ " \"to\" : 1334134700.088\n" +
+ " },\n" +
+ " \"values\" : [\n" +
+ " {\n" +
+ " \"name\" : \"queries\",\n" +
+ " \"description\" : \"Number of queries executed during snapshot interval\",\n" +
+ " \"values\" : {\n" +
+ " \"count\" : null,\n" +
+ " \"rate\" : 0.4667\n" +
+ " },\n" +
+ " \"dimensions\" : {\n" +
+ " \"searcherid\" : \"x\"\n" +
+ " }\n" +
+ " }\n" + "" +
+ " ]\n" +
+ "}\n" +
+ "}";
+
+ metrics = fetcher.createMetrics(jsonData, 0);
+ assertThat("Wrong number of metrics", metrics.size(), is(0));
+ }
+}
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/MockConfigSentinelClient.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/MockConfigSentinelClient.java
new file mode 100644
index 00000000000..917c529e63e
--- /dev/null
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/MockConfigSentinelClient.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+ */
+package ai.vespa.metricsproxy.service;
+
+import com.yahoo.log.LogLevel;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+
+/**
+ * Mock config sentinel
+ *
+ * @author hmusum
+ */
+public class MockConfigSentinelClient extends ConfigSentinelClient {
+ private final ConfigSentinelDummy configSentinel;
+ private final static Logger log = Logger.getLogger(MockConfigSentinelClient.class.getPackage().getName());
+
+ public MockConfigSentinelClient(ConfigSentinelDummy configSentinel) {
+ super();
+ this.configSentinel = configSentinel;
+ }
+
+ @Override
+ protected synchronized void setStatus(List<VespaService> services) throws Exception {
+ List<VespaService> updatedServices = new ArrayList<>();
+ String[] lines = configSentinel.getServiceList().split("\n");
+ for (String line : lines) {
+ if (line.equals("")) {
+ break;
+ }
+
+ VespaService s = parseServiceString(line, services);
+ if (s != null) {
+ updatedServices.add(s);
+ }
+ }
+
+ //Check if there are services that were not found in
+ //from the sentinel
+ for (VespaService s : services) {
+ if (!updatedServices.contains(s)) {
+ log.log(LogLevel.DEBUG, "Service " + s + " is no longer found with sentinel - setting alive = false");
+ s.setAlive(false);
+ }
+ }
+ }
+}
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/MockHttpServer.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/MockHttpServer.java
new file mode 100644
index 00000000000..fdf2fae3081
--- /dev/null
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/MockHttpServer.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+ */
+
+package ai.vespa.metricsproxy.service;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+
+/**
+ * @author jobergum
+ */
+public class MockHttpServer {
+
+ private String response;
+ private HttpServer server;
+
+ /**
+ * Mock http server that will return response as body
+ *
+ * @param port the port to listen to
+ * @param response the response to return along with 200 OK
+ * @param path the file path that the server will accept requests for. E.g /state/v1/metrics
+ */
+ public MockHttpServer(int port, String response, String path) throws IOException {
+ this.response = response;
+ this.server = HttpServer.create(new InetSocketAddress(port), 10);
+ this.server.createContext(path, new MyHandler());
+ this.server.setExecutor(null); // creates a default executor
+ this.server.start();
+ System.out.println("Started web server on port " + port);
+ }
+
+ public synchronized void setResponse(String r) {
+ this.response = r;
+ }
+
+ public void close() {
+ this.server.stop(0);
+ }
+
+ private class MyHandler implements HttpHandler {
+ public void handle(HttpExchange t) throws IOException {
+ t.sendResponseHeaders(200, response.length());
+ OutputStream os = t.getResponseBody();
+ os.write(response.getBytes());
+ os.close();
+ }
+ }
+
+}
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/SystemPollerTest.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/SystemPollerTest.java
new file mode 100644
index 00000000000..a42d52b7ea6
--- /dev/null
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/SystemPollerTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+ */
+
+package ai.vespa.metricsproxy.service;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Unknown
+ */
+public class SystemPollerTest {
+
+ @Test
+ public void testSystemPoller() {
+ DummyService s = new DummyService(0, "id");
+ List<VespaService> services = new ArrayList<>();
+ services.add(s);
+
+ SystemPoller poller = new SystemPoller(services, 60*5);
+ assertThat(s.isAlive(), is(false));
+ services.remove(0);
+ poller.setServices(services);
+ long n = poller.getPidJiffies(s);
+ assertThat(n, is(0L));
+ long[] memusage = poller.getMemoryUsage(s);
+ assertThat(memusage[0], is(0L));
+ assertThat(memusage[1], is(0L));
+ }
+
+ @Test
+ public void testCPUJiffies() {
+ String line = "cpu1 102180864 789 56766899 12800020140 1654757 0 0";
+ CpuJiffies n = new CpuJiffies(line);
+ assertThat(n.getCpuId(), is(1));
+ assertThat(n.getTotalJiffies(), is(12960623449L));
+ }
+
+}
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/VespaServiceTest.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/VespaServiceTest.java
new file mode 100644
index 00000000000..13be98db23a
--- /dev/null
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/VespaServiceTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+ */
+
+package ai.vespa.metricsproxy.service;
+
+import ai.vespa.metricsproxy.TestUtil;
+import ai.vespa.metricsproxy.metric.Metrics;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * @author Unknown
+ */
+public class VespaServiceTest {
+ private MockHttpServer service;
+ private int csPort;
+ private static final String response;
+
+ static {
+ response = TestUtil.getContents("metrics-state.json");
+ HttpMetricFetcher.CONNECTION_TIMEOUT = 60000; // 60 secs in unit tests
+ }
+
+ @Before
+ public void setupHTTPServer() {
+ csPort = 18632; // see factory/doc/port-ranges.txt
+ try {
+ service = new MockHttpServer(csPort, response, HttpMetricFetcher.METRICS_PATH);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Test
+ public void testService() {
+ VespaService service = new VespaService("qrserver", "container/qrserver.0");
+ assertThat(service.getServiceName(), is("qrserver"));
+ assertThat(service.getInstanceName(), is("qrserver"));
+ assertThat(service.getPid(), is(-1));
+ assertThat(service.getConfigId(), is("container/qrserver.0"));
+
+
+ service = VespaService.create("qrserver2", "container/qrserver.0", -1);
+ assertThat(service.getServiceName(), is("qrserver"));
+ assertThat(service.getInstanceName(), is("qrserver2"));
+ assertThat(service.getPid(), is(-1));
+ assertThat(service.getConfigId(), is("container/qrserver.0"));
+ }
+
+ @Test
+ // TODO: Make it possible to test this without running a HTTP server to create the response
+ public void testMetricsFetching() {
+ VespaService service = VespaService.create("service1", "id", csPort);
+ Metrics metrics = service.getMetrics();
+ assertThat(metrics.getMetric("queries.count").getValue().intValue(), is(28));
+
+ // Shutdown server and check that no metrics are returned (should use empty metrics
+ // when unable to fetch new metrics)
+ shutdown();
+
+ metrics = service.getMetrics();
+ assertThat(metrics.size(), is(0));
+ }
+
+ @After
+ public void shutdown() {
+ this.service.close();
+ }
+
+}
diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/VespaServicesTest.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/VespaServicesTest.java
new file mode 100644
index 00000000000..bd0b670ca35
--- /dev/null
+++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/service/VespaServicesTest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+ */
+
+package ai.vespa.metricsproxy.service;
+
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+import java.util.List;
+
+import static ai.vespa.metricsproxy.service.VespaServices.ALL_SERVICES;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+/**
+ * TODO: add more tests
+ *
+ * @author gjoranv
+ */
+public class VespaServicesTest {
+
+ @Test
+ public void services_can_be_retrieved_from_monitoring_name() {
+ List<VespaService> dummyServices = ImmutableList.of(
+ new DummyService(0, "dummy/id/0"),
+ new DummyService(1, "dummy/id/1"));
+ VespaServices services = new VespaServices(dummyServices);
+
+ assertThat(services.getMonitoringServices("vespa.dummy").size(), is(2));
+ }
+
+ @Test
+ public void all_services_can_be_retrieved_by_using_special_name() {
+ List<VespaService> dummyServices = ImmutableList.of(
+ new DummyService(0, "dummy/id/0"));
+ VespaServices services = new VespaServices(dummyServices);
+
+ assertThat(services.getMonitoringServices(ALL_SERVICES).size(), is(1));
+ }
+
+}
diff --git a/metrics-proxy/src/test/resources/health-check-failed.response.json b/metrics-proxy/src/test/resources/health-check-failed.response.json
new file mode 100644
index 00000000000..e118f10ec5e
--- /dev/null
+++ b/metrics-proxy/src/test/resources/health-check-failed.response.json
@@ -0,0 +1,7 @@
+{
+ "status": {
+ "code": "down",
+ "message":"SOMETHING FAILED"
+ },
+ "metrics": []
+ }
diff --git a/metrics-proxy/src/test/resources/health-check.response.json b/metrics-proxy/src/test/resources/health-check.response.json
new file mode 100644
index 00000000000..8e3858ec5d8
--- /dev/null
+++ b/metrics-proxy/src/test/resources/health-check.response.json
@@ -0,0 +1,6 @@
+{
+ "status": {
+ "code": "OK",
+ "message":"WORKING"
+ }
+ } \ No newline at end of file
diff --git a/metrics-proxy/src/test/resources/metrics-container-state-multi-chain.json b/metrics-proxy/src/test/resources/metrics-container-state-multi-chain.json
new file mode 100644
index 00000000000..76d0be50cca
--- /dev/null
+++ b/metrics-proxy/src/test/resources/metrics-container-state-multi-chain.json
@@ -0,0 +1,281 @@
+{
+ "metrics": {
+ "snapshot": {
+ "from": 1.383132197389E9,
+ "to": 1.383132257389E9
+ },
+ "values": [
+ {
+ "name": "search_connections",
+ "values": {
+ "average": 3.459204315576534,
+ "count": 1,
+ "last": 3.459204315576534,
+ "max": 3.459204315576534,
+ "min": 3.459204315576534,
+ "rate": 0.016666666666666666
+ }
+ },
+ {
+ "name": "active_queries",
+ "values": {
+ "average": 0,
+ "count": 1,
+ "last": 0,
+ "max": 0,
+ "min": 0,
+ "rate": 0.016666666666666666
+ }
+ },
+ {
+ "dimensions": {
+ "serverName": "qrs-server",
+ "serverPort": "4080"
+ },
+ "name": "serverNumFailedResponseWrites",
+ "values": {
+ "count": 85,
+ "rate": 1.4166666666666667
+ }
+ },
+ {
+ "dimensions": {
+ "serverName": "qrs-server",
+ "serverPort": "4080"
+ },
+ "name": "serverNumFailedResponses",
+ "values": {
+ "count": 8,
+ "rate": 0.13333333333333333
+ }
+ },
+ {
+ "dimensions": {
+ "serverName": "qrs-server",
+ "serverPort": "4080"
+ },
+ "name": "serverNumConnections",
+ "values": {
+ "count": 1630,
+ "rate": 27.166666666666668
+ }
+ },
+ {
+ "dimensions": {
+ "serverName": "qrs-server",
+ "serverPort": "4080"
+ },
+ "name": "serverNumSuccessfulResponses",
+ "values": {
+ "count": 1621,
+ "rate": 27.016666666666666
+ }
+ },
+ {
+ "dimensions": {
+ "serverName": "qrs-server",
+ "serverPort": "4080"
+ },
+ "name": "serverNetworkLatency",
+ "values": {
+ "average": 0.11715958713775308,
+ "count": 20152,
+ "last": 0,
+ "max": 55,
+ "min": 0,
+ "rate": 335.8666666666667
+ }
+ },
+ {
+ "dimensions": {
+ "serverName": "qrs-server",
+ "serverPort": "4080"
+ },
+ "name": "serverNumSuccessfulResponseWrites",
+ "values": {
+ "count": 20152,
+ "rate": 335.8666666666667
+ }
+ },
+ {
+ "dimensions": {
+ "serverName": "qrs-server",
+ "serverPort": "4080"
+ },
+ "name": "serverTotalSuccessfulResponseLatency",
+ "values": {
+ "average": 90.88401253918495,
+ "count": 1595,
+ "last": 80,
+ "max": 233,
+ "min": 0,
+ "rate": 26.583333333333332
+ }
+ },
+ {
+ "dimensions": {
+ "serverName": "qrs-server",
+ "serverPort": "4080"
+ },
+ "name": "serverNumRequests",
+ "values": {
+ "count": 1633,
+ "rate": 27.216666666666665
+ }
+ },
+ {
+ "dimensions": {
+ "serverName": "qrs-server",
+ "serverPort": "4080"
+ },
+ "name": "serverTotalFailedResponseLatency",
+ "values": {
+ "average": 0.75,
+ "count": 8,
+ "last": 1,
+ "max": 1,
+ "min": 0,
+ "rate": 0.13333333333333333
+ }
+ },
+ {
+ "dimensions": {"chain": "asvBlendingResult"},
+ "name": "query_latency",
+ "values": {
+ "average": 83.35949367088608,
+ "count": 1580,
+ "last": 61,
+ "max": 224,
+ "min": 12,
+ "rate": 26.333333333333332
+ }
+ },
+ {
+ "dimensions": {"chain": "asvBlendingResult"},
+ "name": "max_query_latency",
+ "values": {
+ "average": 83.35949367088608,
+ "count": 1580,
+ "last": 61,
+ "max": 224,
+ "min": 12,
+ "rate": 26.333333333333332
+ }
+ },
+ {
+ "dimensions": {"chain": "asvBlendingResult"},
+ "name": "peak_qps",
+ "values": {
+ "average": 25.87656434951563,
+ "count": 6,
+ "last": 23.681592039800993,
+ "max": 29.7659845295212,
+ "min": 23.681592039800993,
+ "rate": 0.1
+ }
+ },
+ {
+ "dimensions": {"chain": "asvBlendingResult"},
+ "name": "queries",
+ "values": {
+ "count": 1584,
+ "rate": 26.4
+ }
+ },
+ {
+ "dimensions": {"chain": "asvBlendingResult"},
+ "name": "mean_query_latency",
+ "values": {
+ "average": 83.35949367088608,
+ "count": 1580,
+ "last": 61,
+ "max": 224,
+ "min": 12,
+ "rate": 26.333333333333332
+ }
+ },
+ {
+ "dimensions": {"chain": "asvBlendingResult"},
+ "name": "hits_per_query",
+ "values": {
+ "average": 173.70126582278482,
+ "count": 1580,
+ "last": 175,
+ "max": 175,
+ "min": 5,
+ "rate": 26.333333333333332
+ }
+ },
+ {
+ "dimensions": {"chain": "blendingResult"},
+ "name": "query_latency",
+ "values": {
+ "average": 39.40909090909091,
+ "count": 22,
+ "last": 26,
+ "max": 174,
+ "min": 13,
+ "rate": 0.36666666666666664
+ }
+ },
+ {
+ "dimensions": {"chain": "blendingResult"},
+ "name": "max_query_latency",
+ "values": {
+ "average": 39.40909090909091,
+ "count": 22,
+ "last": 26,
+ "max": 174,
+ "min": 13,
+ "rate": 0.36666666666666664
+ }
+ },
+ {
+ "dimensions": {"chain": "blendingResult"},
+ "name": "peak_qps",
+ "values": {
+ "average": 0.5890415170417276,
+ "count": 3,
+ "last": 0.40488561981240295,
+ "max": 0.864528399757932,
+ "min": 0.40488561981240295,
+ "rate": 0.05
+ }
+ },
+ {
+ "dimensions": {"chain": "blendingResult"},
+ "name": "queries",
+ "values": {
+ "count": 22,
+ "rate": 0.36666666666666664
+ }
+ },
+ {
+ "dimensions": {"chain": "blendingResult"},
+ "name": "mean_query_latency",
+ "values": {
+ "average": 39.40909090909091,
+ "count": 22,
+ "last": 26,
+ "max": 174,
+ "min": 13,
+ "rate": 0.36666666666666664
+ }
+ },
+ {
+ "dimensions": {"chain": "blendingResult"},
+ "name": "hits_per_query",
+ "values": {
+ "average": 47.5,
+ "count": 22,
+ "last": 28,
+ "max": 176,
+ "min": 5,
+ "rate": 0.36666666666666664
+ }
+ }
+ ]
+ },
+ "status": {"code": "up"},
+ "time": 1383132269767
+} \ No newline at end of file
diff --git a/metrics-proxy/src/test/resources/metrics-state.json b/metrics-proxy/src/test/resources/metrics-state.json
new file mode 100644
index 00000000000..b7773e5fb8b
--- /dev/null
+++ b/metrics-proxy/src/test/resources/metrics-state.json
@@ -0,0 +1,42 @@
+{
+"status" : {
+ "code" : "up",
+ "message" : "Everything ok here"
+},
+"metrics" : {
+ "snapshot" : {
+ "from" : 1334134640.089,
+ "to" : 1334134700.088
+ },
+ "values" : [
+ {
+ "name" : "queries",
+ "description" : "Number of queries executed during snapshot interval",
+ "values" : {
+ "count" : 28,
+ "rate" : 0.4667
+ },
+ "dimensions" : {
+ "searcherid" : "x"
+ }
+ },
+ {
+ "name" : "query_hits",
+ "description" : "Number of documents matched per query during snapshot interval",
+ "values" : {
+ "count" : 28,
+ "rate" : 0.4667,
+ "average" : 128.3,
+ "min" : 0,
+ "max" : 10000,
+ "sum" : 3584,
+ "median" : 124.0,
+ "std_deviation": 5.43
+ },
+ "dimensions" : {
+ "searcherid" : "x"
+ }
+ }
+ ]
+}
+}
diff --git a/metrics-proxy/src/test/resources/metrics-storage-simple.json b/metrics-proxy/src/test/resources/metrics-storage-simple.json
new file mode 100644
index 00000000000..00715b52046
--- /dev/null
+++ b/metrics-proxy/src/test/resources/metrics-storage-simple.json
@@ -0,0 +1,38 @@
+{
+ "status" : {
+ "code" : "up",
+ "message": "All good"
+ },
+
+ "metrics" : {
+ "snapshot" : {
+ "from": 1335523285,
+ "to": 1335525685
+ },
+ "values": [
+
+ {
+ "name" : "foo",
+ "values" : {
+ "count" : 1
+ },
+ "dimensions" : {
+ "foo": "bar",
+ "bar" : "foo"
+ }
+ },
+
+ {
+ "name" : "bar",
+ "values" : {
+ "count" : 2
+ },
+ "dimensions" : {
+ "d0": "d0val",
+ "d1" : "d1val"
+ }
+ }
+ ]
+
+ }
+}
diff --git a/metrics-proxy/src/test/resources/rpc-json-output-check.json b/metrics-proxy/src/test/resources/rpc-json-output-check.json
new file mode 100644
index 00000000000..701a06d82b2
--- /dev/null
+++ b/metrics-proxy/src/test/resources/rpc-json-output-check.json
@@ -0,0 +1 @@
+{"metrics":[{"status_code":1,"application":"test-system.qrserver","dimensions":{"metrictype":"health","instance":"qrserver"},"status_msg":"SOMETHING FAILED"}]} \ No newline at end of file
diff --git a/metrics-proxy/src/test/resources/yamas-array-no-routing.json b/metrics-proxy/src/test/resources/yamas-array-no-routing.json
new file mode 100644
index 00000000000..8f21e8253b9
--- /dev/null
+++ b/metrics-proxy/src/test/resources/yamas-array-no-routing.json
@@ -0,0 +1,19 @@
+{
+ "status_code" : 0,
+ "timestamp" : 1400047900,
+ "application" : "vespa.searchnode",
+ "metrics" : {
+ "cpu" : 55.5555555555555,
+ "memory_virt" : 22222222222,
+ "memory_rss" : 5555555555
+ },
+ "dimensions" : {
+ "applicationName" : "app",
+ "tenantName" : "tenant",
+ "metrictype" : "system",
+ "instance" : "searchnode",
+ "applicationInstance" : "default",
+ "clustername" : "cluster"
+ },
+ "status_msg" : "Data collected successfully"
+}
diff --git a/metrics-proxy/src/test/resources/yamas-array.json b/metrics-proxy/src/test/resources/yamas-array.json
new file mode 100644
index 00000000000..c9293623b25
--- /dev/null
+++ b/metrics-proxy/src/test/resources/yamas-array.json
@@ -0,0 +1,26 @@
+{
+ "status_code" : 0,
+ "timestamp" : 1400047900,
+ "application" : "vespa.searchnode",
+ "metrics" : {
+ "cpu" : 55.5555555555555,
+ "memory_virt" : 22222222222,
+ "memory_rss" : 5555555555
+ },
+ "dimensions" : {
+ "applicationName" : "app",
+ "tenantName" : "tenant",
+ "metrictype" : "system",
+ "instance" : "searchnode",
+ "applicationInstance" : "default",
+ "clustername" : "cluster"
+ },
+ "routing" : {
+ "yamas" : {
+ "namespaces" : [
+ "Vespa"
+ ]
+ }
+ },
+ "status_msg" : "Data collected successfully"
+}