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

ai.vespa.metricsproxy.rpc.RpcServer 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.rpc;

import ai.vespa.metricsproxy.core.MetricsManager;
import ai.vespa.metricsproxy.metric.model.ConsumerId;
import ai.vespa.metricsproxy.metric.model.MetricsPacket;
import ai.vespa.metricsproxy.metric.model.json.YamasJsonUtil;
import ai.vespa.metricsproxy.service.VespaService;
import ai.vespa.metricsproxy.service.VespaServices;
import com.yahoo.jrt.ErrorCode;
import com.yahoo.jrt.Method;
import com.yahoo.jrt.Request;
import com.yahoo.jrt.StringValue;
import com.yahoo.security.tls.Capability;

import java.time.Instant;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import static ai.vespa.metricsproxy.metric.model.ConsumerId.toConsumerId;
import static ai.vespa.metricsproxy.metric.model.json.YamasJsonUtil.toJson;
import static ai.vespa.metricsproxy.metric.model.json.YamasJsonUtil.toMetricsPackets;
import static com.yahoo.collections.CollectionUtil.mkString;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.WARNING;

/**
 * Rpc server for the metrics proxy.
 *
 * When a new object is created after reconfiguration, it will claim ownership of the methods
 * in the given {@link RpcConnector}. This is ok because at the time this component is created,
 * all components it depends on are already created.
 *
 * @author gjoranv
 */
public final class RpcServer {

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

    private static final int LOG_SPENT_TIME_LIMIT = 10 * 1000; // ms. same as default client RPC timeout used in rpc_invoke

    private final VespaServices vespaServices;
    private final MetricsManager metricsManager;

    public RpcServer(RpcConnector connector, VespaServices vespaServices, MetricsManager metricsManager) {
        this.vespaServices = vespaServices;
        this.metricsManager = metricsManager;
        addMethods(connector);
        log.log(FINE, "RPC server created");
    }

    private void addMethods(RpcConnector connector) {
        // Add/replace this method first to increase likelihood of getting extra metrics and global dimensions
        connector.addMethod(
                new Method("setExtraMetrics", "s", "", this::setExtraMetrics)
                        .requireCapabilities(Capability.METRICSPROXY__MANAGEMENT_API)
                        .methodDesc("Set extra metrics that will be added to output from getMetricsForYamas.")
                        .paramDesc(0, "metricsJson", "The metrics in json format"));

        connector.addMethod(
                new Method("purgeExtraMetrics", "", "", this::purgeExtraMetrics)
                        .requireCapabilities(Capability.METRICSPROXY__MANAGEMENT_API)
                        .methodDesc("Purge metrics and dimensions populated by setExtraMetrics"));

        connector.addMethod(
                new Method("getMetricsById", "s", "s", this::getMetricsById)
                        .requireCapabilities(Capability.METRICSPROXY__METRICS_API)
                        .methodDesc("Get Vespa metrics for the service with the given Id")
                        .paramDesc(0, "id", "The id of the service")
                        .returnDesc(0, "ret", "Vespa metrics"));

        connector.addMethod(
                new Method("getServices", "", "s", this::getServices)
                        .requireCapabilities(Capability.METRICSPROXY__METRICS_API)
                        .methodDesc("Get Vespa services monitored by this metrics proxy")
                        .returnDesc(0, "ret", "Vespa metrics"));

        connector.addMethod(
                new Method("getMetricsForYamas", "s", "s", this::getMetricsForYamas)
                        .requireCapabilities(Capability.METRICSPROXY__METRICS_API)
                        .methodDesc("Get JSON formatted Vespa metrics for a given service name or 'all'")
                        .paramDesc(0, "service", "The vespa service name, or 'all'")
                        .returnDesc(0, "ret", "Vespa metrics"));

        connector.addMethod(
                new Method("getHealthMetricsForYamas", "s", "s", this::getHealthMetricsForYamas)
                        .requireCapabilities(Capability.METRICSPROXY__METRICS_API)
                        .methodDesc("Get JSON formatted Health check for a given service name or 'all'")
                        .paramDesc(0, "service", "The vespa service name")
                        .returnDesc(0, "ret", "Vespa metrics"));

        connector.addMethod(
                new Method("getAllMetricNamesForService", "ss", "s", this::getAllMetricNamesForService)
                        .requireCapabilities(Capability.METRICSPROXY__METRICS_API)
                        .methodDesc("Get metric names known for service ")
                        .paramDesc(0, "service", "The vespa service name'")
                        .paramDesc(1, "consumer", "The consumer'")
                        .returnDesc(0, "ret", "Metric names, one metric name per line"));
    }

    void getAllMetricNamesForService(Request req) {
        String service = req.parameters().get(0).asString();
        ConsumerId consumer = toConsumerId(req.parameters().get(1).asString());
        withExceptionHandling(req, () -> {
            String metricNames = metricsManager.getMetricNamesForServiceAndConsumer(service, consumer);
            req.returnValues().add(new StringValue(metricNames));
        });
    }

    void getMetricsById(Request req) {
        String id = req.parameters().get(0).asString();
        withExceptionHandling(req, () -> {
            String metricsString = metricsManager.getMetricsByConfigId(id);
            req.returnValues().add(new StringValue(metricsString));
        });
    }


    void getServices(Request req) {
        withExceptionHandling(req, () -> {
            String servicesString = metricsManager.getAllVespaServices();
            req.returnValues().add(new StringValue(servicesString));
        });
    }

    void getMetricsForYamas(Request req) {
        Instant startTime = Instant.now();
        req.detach();
        String service = req.parameters().get(0).asString();
        log.log(FINE, () -> "getMetricsForYamas called at " + startTime + " with argument: " + service);
        List services = vespaServices.getMonitoringServices(service);
        log.log(FINE, () -> "Getting metrics for services: " + mkString(services, "[", ", ", "]"));
        if (services.isEmpty()) setNoServiceError(req, service);
        else withExceptionHandling(req, () -> {
            List packets = YamasJsonUtil.appendOptionalStatusPacket(metricsManager.getMetrics(services, startTime));
            log.log(FINE,() -> "Returning metrics packets:\n" + mkString(packets, "\n"));
            req.returnValues().add(new StringValue(toJson(packets, false)));
        });
        req.returnRequest();
    }

    void getHealthMetricsForYamas(Request req) {
        req.detach();
        String service = req.parameters().get(0).asString();
        List services = vespaServices.getMonitoringServices(service);
        if (services.isEmpty()) setNoServiceError(req, service);
        else withExceptionHandling(req, () -> {
            List packets = YamasJsonUtil.appendOptionalStatusPacket(metricsManager.getHealthMetrics(services));
            req.returnValues().add(new StringValue(toJson(packets, true)));
        });
        req.returnRequest();
    }

    void setExtraMetrics(Request req) {
        String metricsJson = req.parameters().get(0).asString();
        log.log(FINE, () -> "setExtraMetrics called with argument: " + metricsJson);
        withExceptionHandling(req, () -> metricsManager.setExtraMetrics(toMetricsPackets(metricsJson)));
    }

    void purgeExtraMetrics(Request req) {
        withExceptionHandling(req, metricsManager::purgeExtraMetrics);
    }

    private static void withExceptionHandling(Request req, ThrowingRunnable runnable) {
        try {
            TimeTracker timeTracker = new TimeTracker(req);
            runnable.run();
            timeTracker.logSpentTime();
        } catch (Exception e) {
            log.log(WARNING, "Got exception when running RPC command " + req.methodName(), e);
            setMethodFailedError(req, e);
        } catch (Error e) {
            log.log(WARNING, "Got error when running RPC command " + req.methodName(), e);
            setMethodFailedError(req, e);
        } catch (Throwable t) {
            log.log(WARNING, "Got throwable (non-error, non-exception) when running RPC command " + req.methodName(), t);
            setMethodFailedError(req, t);
        }
    }

    private static void setMethodFailedError(Request req, Throwable t) {
        String msg = "Request failed due to internal error: " + t.getClass().getName() + ": " + t.getMessage();
        req.setError(ErrorCode.METHOD_FAILED, msg);
        req.returnValues().add(new StringValue(""));
    }

    private static void setNoServiceError(Request req, String serviceName) {
        String msg = "No service with name '" + serviceName + "'";
        req.setError(ErrorCode.BAD_REQUEST, msg);
        req.returnValues().add(new StringValue(""));
    }


    private static class TimeTracker {
        private final long startTime = System.currentTimeMillis();
        private final Request request;

        private TimeTracker(Request request) {
            this.request = request;
        }

        long spentTime() {
            return System.currentTimeMillis() - startTime;
        }

        private void logSpentTime() {
            Level logLevel = Level.FINE;
            if (spentTime() > LOG_SPENT_TIME_LIMIT) {
                logLevel = Level.INFO;
            }
            if (log.isLoggable(logLevel)) {
                log.log(logLevel, "RPC request '" + request.methodName() + "' with parameters '" +
                        request.parameters() + "' took " + spentTime() + " ms");
            }
        }
    }

    private interface ThrowingRunnable {
        void run() throws Exception;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy