All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ai.vespa.metricsproxy.core.MetricsManager Maven / Gradle / Ivy

There is a newer version: 8.441.21
Show newest version
// Copyright Vespa.ai. 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.metric.ExternalMetrics;
import ai.vespa.metricsproxy.metric.dimensions.ApplicationDimensions;
import ai.vespa.metricsproxy.metric.dimensions.NodeDimensions;
import ai.vespa.metricsproxy.metric.model.ConsumerId;
import ai.vespa.metricsproxy.metric.model.DimensionId;
import ai.vespa.metricsproxy.metric.model.MetricsPacket;
import ai.vespa.metricsproxy.service.VespaService;
import ai.vespa.metricsproxy.service.VespaServices;

import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import static ai.vespa.metricsproxy.metric.ExternalMetrics.extractConfigserverDimensions;
import static java.util.logging.Level.FINE;

/**
 * Retrieves metrics and performs necessary conversions and additions of metadata.
 *
 * @author gjoranv
 */
public class MetricsManager {

    private static final Logger log = Logger.getLogger(MetricsManager.class.getName());

    static final DimensionId VESPA_VERSION = DimensionId.toDimensionId("vespaVersion");

    private final VespaServices vespaServices;
    private final VespaMetrics vespaMetrics;
    private final ExternalMetrics externalMetrics;
    private final ApplicationDimensions applicationDimensions;
    private final NodeDimensions nodeDimensions;

    private volatile Map extraDimensions = new HashMap<>();
    private volatile Instant externalMetricsUpdateTime = Instant.now();
    private static final Duration EXTERNAL_METRICS_TTL = Duration.ofMinutes(10);

    public MetricsManager(VespaServices vespaServices,
                          VespaMetrics vespaMetrics,
                          ExternalMetrics externalMetrics,
                          ApplicationDimensions applicationDimensions,
                          NodeDimensions nodeDimensions) {
        this.vespaServices = vespaServices;
        this.vespaMetrics = vespaMetrics;
        this.externalMetrics = externalMetrics;
        this.applicationDimensions = applicationDimensions;
        this.nodeDimensions = nodeDimensions;
    }

    /**
     * Returns all metrics for the given service that are whitelisted for the given consumer.
     */
    public String getMetricNamesForServiceAndConsumer(String service, ConsumerId consumer) {
        return vespaMetrics.getMetricNames(vespaServices.getMonitoringServices(service), consumer);
    }

    public String getMetricsByConfigId(String configId) {
        List services = vespaServices.getInstancesById(configId);
        vespaServices.updateServices(services);

        return vespaMetrics.getMetricsAsString(services);
    }

    /**
     * Returns the metrics for the given services. The empty list is returned if no services are given.
     *
     * @param services the services to retrieve metrics for
     * @return metrics for all matching services
     */
    public List getMetrics(List services, Instant startTime) {
        MetricsPacket.Builder[] builderArray = getMetricsBuildersAsArray(services, startTime, null);
        List metricsPackets = new ArrayList<>(builderArray.length);
        for (int i = 0; i < builderArray.length; i++) {
            metricsPackets.add(builderArray[i].build());
            builderArray[i] = null; // Set null to be able to GC the builder when packet has been created
        }
        return metricsPackets;
    }

    public List getMetrics(List services, Instant startTime, ConsumerId consumerId) {
        MetricsPacket.Builder [] builderArray = getMetricsBuildersAsArray(services, startTime, consumerId);
        List metricsPackets = new ArrayList<>(builderArray.length);
        for (int i = 0; i < builderArray.length; i++) {
            metricsPackets.add(builderArray[i].build());
            builderArray[i] = null; // Set null to be able to GC the builder when packet has been created
        }
        return metricsPackets;
    }

    private MetricsPacket.Builder[] getMetricsBuildersAsArray(List services, Instant startTime, ConsumerId consumerId) {
        List builders = getMetricsAsBuilders(services, startTime, consumerId);
        return builders.toArray(new MetricsPacket.Builder[0]);
    }

    /**
     * Returns the metrics for the given services, in mutable state for further processing.
     * NOTE: Use {@link #getMetrics(List, Instant)} instead, unless further processing of the metrics is necessary.
     */
    public List getMetricsAsBuilders(List services, Instant startTime, ConsumerId consumerId) {
        if (services.isEmpty()) return List.of();

        log.log(FINE, () -> "Updating services prior to fetching metrics, number of services= " + services.size());
        vespaServices.updateServices(services);

        List result = vespaMetrics.getMetrics(services, consumerId);
        log.log(FINE, () -> "Got " + result.size() + " metrics packets for vespa services.");

        purgeStaleMetrics();
        List externalPackets = externalMetrics.getMetrics().stream()
                .filter(MetricsPacket.Builder::hasMetrics)
                .toList();
        log.log(FINE, () -> "Got " + externalPackets.size() + " external metrics packets with whitelisted metrics.");

        result.addAll(externalPackets);

        Map globalDims = getGlobalDimensions();
        return result.stream()
                .map(builder -> builder.putDimensionsIfAbsent(globalDims))
                .map(builder -> builder.putDimensionsIfAbsent(extraDimensions))
                .map(builder -> adjustTimestamp(builder, startTime))
                .toList();
    }

    /**
     * Returns a merged map of all global dimensions.
     */
    private Map getGlobalDimensions() {
        Map globalDimensions = new LinkedHashMap<>(applicationDimensions.getDimensions());
        globalDimensions.putAll(nodeDimensions.getDimensions());
        return globalDimensions;
    }

    /**
     * If the metrics packet is less than one minute newer or older than the given startTime,
     * set its timestamp to the given startTime. This is done to ensure that metrics retrieved
     * from different sources for this invocation get the same timestamp, and a timestamp as close
     * as possible to the invocation from the external metrics retrieving client. The assumption
     * is that the client requests metrics periodically every minute.
     * 

* However, if the timestamp of the packet is too far off in time, we don't adjust it because * we would otherwise be masking a real problem with retrieving the metrics. */ static MetricsPacket.Builder adjustTimestamp(MetricsPacket.Builder builder, Instant startTime) { Duration age = Duration.between(startTime, builder.getTimestamp()); if (age.abs().minusMinutes(1).isNegative()) builder.timestamp(startTime); return builder; } /** * Returns the health metrics for the given services. The empty list is returned if no services are given. * * @param services The services to retrieve health metrics for. * @return Health metrics for all matching services. */ public List getHealthMetrics(List services) { if (services.isEmpty()) return List.of(); vespaServices.updateServices(services); // TODO: Add global dimensions to health metrics? return vespaMetrics.getHealthMetrics(services); } public void setExtraMetrics(List packets) { externalMetricsUpdateTime = Instant.now(); extraDimensions = extractConfigserverDimensions(packets); externalMetrics.setExtraMetrics(packets); } public Map getExtraDimensions() { purgeStaleMetrics(); return this.extraDimensions; } private void purgeStaleMetrics() { if (Duration.between(externalMetricsUpdateTime, Instant.now()).getSeconds() > EXTERNAL_METRICS_TTL.getSeconds()) { purgeExtraMetrics(); } } public void purgeExtraMetrics() { extraDimensions = new HashMap<>(); externalMetrics.setExtraMetrics(List.of()); } /** * Returns a space separated list of all distinct service names. */ public String getAllVespaServices() { return vespaServices.getVespaServices().stream() .map(VespaService::getServiceName) .distinct() .collect(Collectors.joining(" ")); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy