diff options
Diffstat (limited to 'metrics-proxy/src/main/java/ai/vespa/metricsproxy/service/MetricsParser.java')
-rw-r--r-- | metrics-proxy/src/main/java/ai/vespa/metricsproxy/service/MetricsParser.java | 119 |
1 files changed, 68 insertions, 51 deletions
diff --git a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/service/MetricsParser.java b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/service/MetricsParser.java index bb21485e0e7..e8f6459a1bc 100644 --- a/metrics-proxy/src/main/java/ai/vespa/metricsproxy/service/MetricsParser.java +++ b/metrics-proxy/src/main/java/ai/vespa/metricsproxy/service/MetricsParser.java @@ -13,8 +13,10 @@ import java.io.InputStream; import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import static ai.vespa.metricsproxy.metric.model.DimensionId.toDimensionId; @@ -25,20 +27,22 @@ import static ai.vespa.metricsproxy.metric.model.DimensionId.toDimensionId; * @author Jo Kristian Bergum */ public class MetricsParser { - public interface Consumer { - void consume(Metric metric); + public interface Collector { + void accept(Metric metric); } private static final ObjectMapper jsonMapper = new ObjectMapper(); - public static void parse(String data, Consumer consumer) throws IOException { + public static void parse(String data, Collector consumer) throws IOException { parse(jsonMapper.createParser(data), consumer); } - static void parse(InputStream data, Consumer consumer) throws IOException { + static void parse(InputStream data, Collector consumer) throws IOException { parse(jsonMapper.createParser(data), consumer); } - private static void parse(JsonParser parser, Consumer consumer) throws IOException { + + // Top level 'metrics' object, with e.g. 'time', 'status' and 'metrics'. + private static void parse(JsonParser parser, Collector consumer) throws IOException { if (parser.nextToken() != JsonToken.START_OBJECT) { throw new IOException("Expected start of object, got " + parser.currentToken()); } @@ -55,6 +59,7 @@ public class MetricsParser { } } } + static private Instant parseSnapshot(JsonParser parser) throws IOException { if (parser.getCurrentToken() != JsonToken.START_OBJECT) { throw new IOException("Expected start of 'snapshot' object, got " + parser.currentToken()); @@ -75,59 +80,97 @@ public class MetricsParser { return timestamp; } - static private void parseMetricValues(JsonParser parser, Instant timestamp, Consumer consumer) throws IOException { + // 'metrics' object with 'snapshot' and 'values' arrays + static private void parseMetrics(JsonParser parser, Collector consumer) throws IOException { + if (parser.getCurrentToken() != JsonToken.START_OBJECT) { + throw new IOException("Expected start of 'metrics' object, got " + parser.currentToken()); + } + Instant timestamp = Instant.now(); + for (parser.nextToken(); parser.getCurrentToken() != JsonToken.END_OBJECT; parser.nextToken()) { + String fieldName = parser.getCurrentName(); + JsonToken token = parser.nextToken(); + if (fieldName.equals("snapshot")) { + timestamp = parseSnapshot(parser); + } else if (fieldName.equals("values")) { + parseMetricValues(parser, timestamp, consumer); + } else { + if (token == JsonToken.START_OBJECT || token == JsonToken.START_ARRAY) { + parser.skipChildren(); + } + } + } + } + + // 'values' array + static private void parseMetricValues(JsonParser parser, Instant timestamp, Collector consumer) throws IOException { if (parser.getCurrentToken() != JsonToken.START_ARRAY) { throw new IOException("Expected start of 'metrics:values' array, got " + parser.currentToken()); } - Map<Long, Map<DimensionId, String>> uniqueDimensions = new HashMap<>(); + Map<Set<Dimension>, Map<DimensionId, String>> uniqueDimensions = new HashMap<>(); while (parser.nextToken() == JsonToken.START_OBJECT) { handleValue(parser, timestamp, consumer, uniqueDimensions); } } - static private void parseMetrics(JsonParser parser, Consumer consumer) throws IOException { - if (parser.getCurrentToken() != JsonToken.START_OBJECT) { - throw new IOException("Expected start of 'metrics' object, got " + parser.currentToken()); - } - Instant timestamp = Instant.now(); + // One item in the 'values' array, where each item has 'name', 'values' and 'dimensions' + static private void handleValue(JsonParser parser, Instant timestamp, Collector consumer, + Map<Set<Dimension>, Map<DimensionId, String>> uniqueDimensions) throws IOException { + String name = ""; + String description = ""; + Map<DimensionId, String> dim = Map.of(); + List<Map.Entry<String, Number>> values = List.of(); for (parser.nextToken(); parser.getCurrentToken() != JsonToken.END_OBJECT; parser.nextToken()) { String fieldName = parser.getCurrentName(); JsonToken token = parser.nextToken(); - if (fieldName.equals("snapshot")) { - timestamp = parseSnapshot(parser); + if (fieldName.equals("name")) { + name = parser.getText(); + } else if (fieldName.equals("description")) { + description = parser.getText(); + } else if (fieldName.equals("dimensions")) { + dim = parseDimensions(parser, uniqueDimensions); } else if (fieldName.equals("values")) { - parseMetricValues(parser, timestamp, consumer); + values = parseValues(name+".", parser); } else { if (token == JsonToken.START_OBJECT || token == JsonToken.START_ARRAY) { parser.skipChildren(); } } } + for (Map.Entry<String, Number> value : values) { + consumer.accept(new Metric(MetricId.toMetricId(value.getKey()), value.getValue(), timestamp, dim, description)); + } } private static Map<DimensionId, String> parseDimensions(JsonParser parser, - Map<Long, Map<DimensionId, String>> uniqueDimensions) throws IOException { - List<Map.Entry<String, String>> dims = new ArrayList<>(); - int keyHash = 0; - int valueHash = 0; + Map<Set<Dimension>, Map<DimensionId, String>> uniqueDimensions) throws IOException { + + Set<Dimension> dimensions = new HashSet<>(); + for (parser.nextToken(); parser.getCurrentToken() != JsonToken.END_OBJECT; parser.nextToken()) { String fieldName = parser.getCurrentName(); JsonToken token = parser.nextToken(); + if (token == JsonToken.VALUE_STRING){ String value = parser.getValueAsString(); - dims.add(Map.entry(fieldName, value)); - keyHash ^= fieldName.hashCode(); - valueHash ^= value.hashCode(); + dimensions.add(Dimension.of(fieldName, value)); } else if (token == JsonToken.VALUE_NULL) { // TODO Should log a warning if this happens } else { throw new IllegalArgumentException("Dimension '" + fieldName + "' must be a string"); } } - Long uniqueKey = (((long) keyHash) << 32) | (valueHash & 0xffffffffL); - return uniqueDimensions.computeIfAbsent(uniqueKey, key -> dims.stream().collect(Collectors.toUnmodifiableMap(e -> toDimensionId(e.getKey()), Map.Entry::getValue))); + return uniqueDimensions.computeIfAbsent(dimensions, + key -> dimensions.stream().collect(Collectors.toUnmodifiableMap( + dim -> toDimensionId(dim.id), dim -> dim.value))); + } + + record Dimension(String id, String value) { + static Dimension of(String id, String value) { + return new Dimension(id, value); + } } + private static List<Map.Entry<String, Number>> parseValues(String prefix, JsonParser parser) throws IOException { List<Map.Entry<String, Number>> metrics = new ArrayList<>(); for (parser.nextToken(); parser.getCurrentToken() != JsonToken.END_OBJECT; parser.nextToken()) { @@ -144,31 +187,5 @@ public class MetricsParser { } return metrics; } - static private void handleValue(JsonParser parser, Instant timestamp, Consumer consumer, - Map<Long, Map<DimensionId, String>> uniqueDimensions) throws IOException { - String name = ""; - String description = ""; - Map<DimensionId, String> dim = Map.of(); - List<Map.Entry<String, Number>> values = List.of(); - for (parser.nextToken(); parser.getCurrentToken() != JsonToken.END_OBJECT; parser.nextToken()) { - String fieldName = parser.getCurrentName(); - JsonToken token = parser.nextToken(); - if (fieldName.equals("name")) { - name = parser.getText(); - } else if (fieldName.equals("description")) { - description = parser.getText(); - } else if (fieldName.equals("dimensions")) { - dim = parseDimensions(parser, uniqueDimensions); - } else if (fieldName.equals("values")) { - values = parseValues(name+".", parser); - } else { - if (token == JsonToken.START_OBJECT || token == JsonToken.START_ARRAY) { - parser.skipChildren(); - } - } - } - for (Map.Entry<String, Number> value : values) { - consumer.consume(new Metric(MetricId.toMetricId(value.getKey()), value.getValue(), timestamp, dim, description)); - } - } + } |