// Copyright Yahoo. 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.model.ConsumerId; import ai.vespa.metricsproxy.metric.model.MetricId; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.stream.Collector; import java.util.stream.Collectors; import static com.yahoo.stream.CustomCollectors.toLinkedMap; import static java.util.Collections.unmodifiableSet; import static java.util.stream.Collectors.collectingAndThen; /** * Contains metrics consumers and their metrics, and mappings between these. * All collections are final and immutable. * * @author gjoranv */ public class MetricsConsumers { // All metrics for each consumer. private final Map> consumerMetrics; private final Map> configuredMetricByMetricByConsumer; // All consumers for each metric (more useful than the opposite map). private final Map> consumersByMetric; // All consumers for each metric, by metric id private final Map>> consumersByMetricByMetricId; public MetricsConsumers(ConsumersConfig config) { consumerMetrics = config.consumer().stream().collect( toUnmodifiableLinkedMap(consumer -> ConsumerId.toConsumerId(consumer.name()), consumer -> convert(consumer.metric()))); configuredMetricByMetricByConsumer = new HashMap<>(); consumerMetrics.forEach((consumer, configuredList) -> configuredMetricByMetricByConsumer.put(consumer, configuredList.stream().collect(Collectors.toMap(ConfiguredMetric::id, Function.identity())))); consumersByMetric = createConsumersByMetric(consumerMetrics); consumersByMetricByMetricId = new HashMap<>(); consumersByMetric.forEach((configuredMetric, consumers) -> { var consumersByMetric = consumersByMetricByMetricId.computeIfAbsent(configuredMetric.id(), id -> new HashMap<>()); var consumerSet = consumersByMetric.computeIfAbsent(configuredMetric, id -> new HashSet<>()); consumerSet.addAll(consumers); }); } /** * @param consumer The consumer * @return The metrics for the given consumer. */ public List getMetricDefinitions(ConsumerId consumer) { return consumerMetrics.get(consumer); } public Map> getConsumersByMetric() { return consumersByMetric; } public Map> getConsumersByMetric(MetricId id) { return consumersByMetricByMetricId.get(id); } public Map getMetricsForConsumer(ConsumerId consumerId) { return configuredMetricByMetricByConsumer.get(consumerId); } public Set getAllConsumers() { return unmodifiableSet(consumerMetrics.keySet()); } /** * Helper function to create mapping from metric to consumers. * TODO: consider reversing the mapping in metrics-consumers.def instead: metric{}.consumer[] */ private static Map> createConsumersByMetric(Map> metricsByConsumer) { Map> consumersByMetric = new LinkedHashMap<>(); metricsByConsumer.forEach( (consumer, metrics) -> metrics.forEach( metric -> consumersByMetric.computeIfAbsent(metric, unused -> new HashSet<>()) .add(consumer))); Map> unmodifiableConsumersByMetric = new LinkedHashMap<>(); consumersByMetric.forEach((configuredMetric, consumerIds) -> unmodifiableConsumersByMetric.put(configuredMetric, Set.copyOf(consumerIds))); return Collections.unmodifiableMap(unmodifiableConsumersByMetric); } public static Collector> toUnmodifiableLinkedMap(Function keyMapper, Function valueMapper) { return collectingAndThen(toLinkedMap(keyMapper, valueMapper), Collections::unmodifiableMap); } private List convert(List configMetrics) { List metrics = new ArrayList<>(configMetrics.size()); configMetrics.forEach(m -> metrics.add(new ConfiguredMetric(m))); return metrics; } }