diff options
author | gjoranv <gv@verizonmedia.com> | 2019-12-19 15:44:55 +0100 |
---|---|---|
committer | gjoranv <gv@verizonmedia.com> | 2019-12-20 02:30:17 +0100 |
commit | cc973932ac847f4ad66cb68a149e0683af428548 (patch) | |
tree | be2f962ca29ed47049aa5d17f7f71d70d2e1b629 /metrics-proxy | |
parent | 60f91ea8c4778e3bd08963dec3d500d8482ef0d4 (diff) |
Add JSON model for full application output
- Multiple nodes yielding one GenericJsonModel each.
Diffstat (limited to 'metrics-proxy')
3 files changed, 248 insertions, 0 deletions
diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/json/GenericApplicationModel.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/json/GenericApplicationModel.java new file mode 100644 index 00000000000..b3ebd469b13 --- /dev/null +++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/metric/model/json/GenericApplicationModel.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.metric.model.json; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.IOException; +import java.util.List; + +import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_ABSENT; + +/** + * @author gjoranv + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonInclude(NON_ABSENT) +public class GenericApplicationModel { + + @JsonProperty("nodes") + public List<GenericJsonModel> nodes; + + public String serialize() { + ObjectMapper mapper = JacksonUtil.createObjectMapper(); + try { + return mapper.writeValueAsString(this); + } catch (IOException e) { + throw new JsonRenderingException("Could not render application nodes. Check the log for details.", e); + } + } + +} diff --git a/metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/model/json/GenericApplicationModelTest.java b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/model/json/GenericApplicationModelTest.java new file mode 100644 index 00000000000..eac63998402 --- /dev/null +++ b/metrics-proxy/src/test/java/ai/vespa/metricsproxy/metric/model/json/GenericApplicationModelTest.java @@ -0,0 +1,122 @@ +/* + * 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.http.application.Node; +import ai.vespa.metricsproxy.metric.model.MetricsPacket; +import ai.vespa.metricsproxy.metric.model.ServiceId; +import ai.vespa.metricsproxy.metric.model.StatusCode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import static ai.vespa.metricsproxy.TestUtil.getFileContents; +import static ai.vespa.metricsproxy.metric.ExternalMetrics.VESPA_NODE_SERVICE_ID; +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 ai.vespa.metricsproxy.metric.model.json.JacksonUtil.createObjectMapper; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * @author gjoranv + */ +public class GenericApplicationModelTest { + private static final String TEST_FILE = "generic-application.json"; + + @Test + public void deserialize_serialize_roundtrip() throws IOException { + GenericApplicationModel model = genericJsonModelFromTestFile(); + + // Do some sanity checking + assertEquals(2, model.nodes.size()); + GenericJsonModel node0Model = model.nodes.get(0); + assertEquals("node0", node0Model.node.name); + assertEquals(1, node0Model.services.size()); + GenericService service = node0Model.services.get(0); + assertEquals(1, service.metrics.size()); + assertEquals(4, service.metrics.get(0).values.get("queries.count"), 0.001d); + + GenericJsonModel node1Model = model.nodes.get(1); + GenericNode node1 = node1Model.node; + assertEquals("node1", node1.name); + assertEquals(32.444, node1.metrics.get(0).values.get("cpu.util"), 0.001d); + + assertThatSerializedModelEqualsTestFile(model); + } + + @Test + public void metrics_packets_can_be_converted_to_generic_json_model() throws Exception { + var nodePacket = new MetricsPacket.Builder(VESPA_NODE_SERVICE_ID) + .timestamp(123456L) + .putMetric(toMetricId("node-metric"), 1.234) + .putDimension(toDimensionId("node-dim"), "node-dim-value") + .build(); + + var servicePacket = new MetricsPacket.Builder(toServiceId("my-service")) + .timestamp(123456L) + .statusCode(0) + .putMetric(toMetricId("service-metric"), 1234) + .putDimension(toDimensionId("service-dim"), "service-dim-value") + .build(); + + + var metricsByNode = Map.of(toNode("node0"), List.of(nodePacket, servicePacket)); + + GenericApplicationModel model = GenericJsonUtil.toGenericApplicationModel(metricsByNode); + + GenericJsonModel nodeModel = model.nodes.get(0); + assertNotNull(nodeModel.node); + assertEquals("node0", nodeModel.node.name); + assertEquals(1, nodeModel.node.metrics.size()); + GenericMetrics nodeMetrics = nodeModel.node.metrics.get(0); + assertEquals(1.234, nodeMetrics.values.get("node-metric"), 0.001d); + assertEquals("node-dim-value", nodeMetrics.dimensions.get("node-dim")); + + assertEquals(1, nodeModel.services.size()); + GenericService service = nodeModel.services.get(0); + assertEquals(StatusCode.UP.status, service.status.code); + assertEquals("", service.status.description); + + assertEquals(1, service.metrics.size()); + GenericMetrics serviceMetrics = service.metrics.get(0); + assertEquals(1234L, serviceMetrics.values.get("service-metric").longValue()); + assertEquals("service-dim-value", serviceMetrics.dimensions.get("service-dim")); + + // Visual inspection + System.out.println(createObjectMapper().writerWithDefaultPrettyPrinter().writeValueAsString(nodeModel)); + } + + private MetricsPacket createMetricsPacket(String service, Number metricsValue, boolean isNode) { + return new MetricsPacket.Builder(isNode ? VESPA_NODE_SERVICE_ID : toServiceId(service)) + .timestamp(1234L) + .statusCode(0) + .putMetric(toMetricId(service + "-metric"), metricsValue) + .putDimension(toDimensionId(service + "-dim"), isNode ? "node-dim-value" : "service-dim-value") + .build(); + + } + + private static void assertThatSerializedModelEqualsTestFile(GenericApplicationModel model) { + String serialized = model.serialize(); + String trimmed = serialized.trim().replaceAll("\\s+", ""); + + String expected = getFileContents(TEST_FILE).trim().replaceAll("\\s+", ""); + assertEquals(expected, trimmed); + } + + private static GenericApplicationModel genericJsonModelFromTestFile() throws IOException { + ObjectMapper mapper = createObjectMapper(); + return mapper.readValue(getFileContents(TEST_FILE), GenericApplicationModel.class); + } + + private static Node toNode(String name) { + return new Node(name, "host", 0, "path"); + } +} diff --git a/metrics-proxy/src/test/resources/generic-application.json b/metrics-proxy/src/test/resources/generic-application.json new file mode 100644 index 00000000000..231f86a9b3b --- /dev/null +++ b/metrics-proxy/src/test/resources/generic-application.json @@ -0,0 +1,90 @@ +{ + "nodes": [ + { + "node": { + "name": "node0", + "timestamp": 1234, + "metrics": [ + { + "values": { + "cpu.util": 16.222 + }, + "dimensions": { + "state": "active" + } + } + ] + }, + "services": [ + { + "name": "searchnode", + "timestamp": 1234, + "status": { + "code": "up" + }, + "metrics": [ + { + "values": { + "queries.count": 4 + }, + "dimensions": { + "documentType": "music" + } + } + ] + } + ] + }, + { + "node": { + "name": "node1", + "timestamp": 1234, + "metrics": [ + { + "values": { + "cpu.util": 32.444 + }, + "dimensions": { + "state": "active" + } + } + ] + }, + "services": [ + { + "name": "searchnode", + "timestamp": 1234, + "status": { + "code": "up" + }, + "metrics": [ + { + "values": { + "queries.count": 8 + }, + "dimensions": { + "documentType": "music" + } + } + ] + }, + { + "name": "slobrok", + "timestamp": 1234, + "status": { + "code": "unknown", + "description": "Unable to fetch metrics from service 'slobrok'" + }, + "metrics": [ + { + "values": {}, + "dimensions": { + "instance": "slobrok0" + } + } + ] + } + ] + } + ] +}
\ No newline at end of file |