ai.vespa.metricsproxy.metric.model.MetricsPacket Maven / Gradle / Ivy
// Copyright Vespa.ai. 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 java.time.Instant;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import static java.util.stream.Collectors.joining;
/**
* Represents a packet of metrics (with meta information) that belong together because they:
*
* - share both the same dimensions and consumers, AND
* - represent the same source, e.g. a vespa service or the system hardware.
*
*
* @author gjoranv
*/
public class MetricsPacket {
private final int statusCode;
private final String statusMessage;
private final ServiceId service;
private final Instant timestamp;
private final Map metrics;
private final Map dimensions;
private final Set consumers;
private MetricsPacket(int statusCode, String statusMessage, Instant timestamp, ServiceId service,
Map metrics, Map dimensions, Set consumers ) {
this.statusCode = statusCode;
this.statusMessage = statusMessage;
this.timestamp = timestamp;
this.service = service;
this.metrics = Collections.unmodifiableMap(metrics); // Retain order for tests
this.dimensions = Collections.unmodifiableMap(dimensions); // Retain order for tests
this.consumers = Set.copyOf(consumers);
}
public Map metrics() { return metrics; }
public Map dimensions() { return dimensions; }
public Set consumers() { return consumers; }
public Instant timestamp() { return timestamp; }
public ServiceId service() { return service; }
public int statusCode() { return statusCode; }
public String statusMessage() { return statusMessage; }
@Override
public String toString() {
return "MetricsPacket{" +
"statusCode=" + statusCode +
", statusMessage='" + statusMessage + '\'' +
", timestamp=" + timestamp +
", service=" + service.id +
", metrics=" + idMapToString(metrics, id -> id.id) +
", dimensions=" + idMapToString(dimensions, id -> id.id) +
", consumers=" + consumers.stream().map(id -> id.id).collect(joining(",", "[", "]")) +
'}';
}
private static String idMapToString(Map map, Function idMapper) {
return map.entrySet().stream()
.map(entry -> idMapper.apply(entry.getKey()) + "=" + entry.getValue())
.collect(joining(",", "{", "}"));
}
public static class Builder {
// Set defaults here, and use null guard in all setters.
// Except for 'service' for which we require an explicit non-null value.
private ServiceId service;
private int statusCode = 0;
private String statusMessage = "";
private Instant timestamp = Instant.EPOCH;
private Map metrics = new LinkedHashMap<>();
private final Map dimensions = new LinkedHashMap<>();
private Set consumers = Set.of();
public Builder(ServiceId service) {
Objects.requireNonNull(service, "Service cannot be null.");
this.service = service;
}
public Builder service(ServiceId service) {
if (service == null) throw new IllegalArgumentException("Service cannot be null.");
this.service = service;
return this;
}
public Builder statusCode(Integer statusCode) {
if (statusCode != null) this.statusCode = statusCode;
return this;
}
public Builder statusMessage(String statusMessage) {
if (statusMessage != null) this.statusMessage = statusMessage;
return this;
}
public Builder timestamp(Instant timestamp) {
if (timestamp != null) this.timestamp = timestamp;
return this;
}
public Builder putMetrics(Collection extraMetrics) {
if (extraMetrics != null)
extraMetrics.forEach(metric -> metrics.put(metric.getName(), metric.getValue()));
return this;
}
public Builder putMetric(MetricId id, Number value) {
metrics.put(id, value);
return this;
}
public Builder retainMetrics(Set idsToRetain) {
metrics.keySet().retainAll(idsToRetain);
return this;
}
public Builder applyOutputNames(Map> outputNamesById) {
Map newMetrics = new LinkedHashMap<>();
outputNamesById.forEach((id, outputNames) -> {
if (metrics.containsKey(id))
outputNames.forEach(outputName -> newMetrics.put(outputName, metrics.get(id)));
});
metrics = newMetrics;
return this;
}
public Builder putDimension(DimensionId id, String value) {
dimensions.put(id, value);
return this;
}
public Builder putDimensions(Map extraDimensions) {
if (extraDimensions != null) dimensions.putAll(extraDimensions);
return this;
}
public Builder putDimensionsIfAbsent(Map extraDimensions) {
if (extraDimensions != null) extraDimensions.forEach(dimensions::putIfAbsent);
return this;
}
/**
* Returns a modifiable copy of the dimension IDs of this builder, usually for use with {@link #retainDimensions(Collection)}.
*/
public Set getDimensionIds() {
return new LinkedHashSet<>(dimensions.keySet());
}
public String getDimensionValue(DimensionId id) {
return dimensions.get(id);
}
public Builder retainDimensions(Collection idsToRetain) {
dimensions.keySet().retainAll(idsToRetain);
return this;
}
public Builder addConsumers(Set extraConsumers) {
if ((extraConsumers != null) && !extraConsumers.isEmpty()) {
if (consumers.isEmpty()) {
if (extraConsumers.size() == 1) {
consumers = Set.of(extraConsumers.iterator().next());
return this;
}
consumers = new LinkedHashSet<>(extraConsumers.size());
} else if (consumers.size() == 1) {
var copy = new LinkedHashSet(extraConsumers.size() + 1);
copy.addAll(consumers);
consumers = copy;
}
consumers.addAll(extraConsumers);
}
return this;
}
public boolean hasConsumer(ConsumerId id) {
return consumers.contains(id);
}
public MetricsPacket build() {
return new MetricsPacket(statusCode, statusMessage, timestamp, service, metrics, dimensions, consumers);
}
public boolean hasMetrics() {
return ! metrics.isEmpty();
}
public Instant getTimestamp() { return timestamp; }
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy