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

org.restheart.metrics.RequestsMetricsCollector Maven / Gradle / Ivy

There is a newer version: 8.1.6
Show newest version
/*-
 * ========================LICENSE_START=================================
 * restheart-metrics
 * %%
 * Copyright (C) 2014 - 2024 SoftInstigate
 * %%
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see .
 * =========================LICENSE_END==================================
 */

package org.restheart.metrics;

import io.undertow.util.PathTemplate;
import io.undertow.util.PathTemplateMatcher;
import io.undertow.util.PathTemplateMatcher.PathMatchResult;
import org.restheart.exchange.ServiceRequest;
import org.restheart.exchange.ServiceResponse;
import org.restheart.plugins.Inject;
import org.restheart.plugins.InterceptPoint;
import org.restheart.plugins.OnInit;
import org.restheart.plugins.PluginsRegistry;
import org.restheart.plugins.RegisterPlugin;
import org.restheart.plugins.WildcardInterceptor;
import static org.restheart.metrics.MetricsService.METRICS_REGISTRIES_PREFIX;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.codahale.metrics.SharedMetricRegistries;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@RegisterPlugin(name = "requestsMetricsCollector",
        description = "collects http requests metrics",
        interceptPoint = InterceptPoint.REQUEST_BEFORE_AUTH,
        enabledByDefault = false)
public class RequestsMetricsCollector implements WildcardInterceptor {
    private static final Logger LOGGER = LoggerFactory.getLogger(RequestsMetricsCollector.class);

    @Inject("config")
    private Map config;

    @Inject("registry")
    private PluginsRegistry pluginsRegistry;

    // include is a set because we want to check all path templates that match the request
    private Set> include = new HashSet<>();
    private PathTemplateMatcher exclude = new PathTemplateMatcher<>();

    @OnInit
    public void onInit() {
        List _include = argOrDefault(config, "include", new ArrayList<>());
        List _exclude = argOrDefault(config, "exclude", new ArrayList<>());

        _include.stream().map(path -> {
                try {
                    var ret =  PathTemplate.create(path);
                    LOGGER.debug("Add include path {}", ret.getTemplateString());
                    return ret;
                } catch(Throwable t) {
                    LOGGER.warn("Wrong include path {}", path , t);
                    return null;
                }
            })
            .filter(pathTemplate -> pathTemplate != null)
            .forEach(pathTemplate ->{
                final var ptm = new PathTemplateMatcher();
                ptm.add(pathTemplate, true);
                this.include.add(ptm);
            });

        _exclude.stream().map(path -> {
                try {
                    var ret =  PathTemplate.create(path);
                    LOGGER.debug("Add exclude path {}", ret.getTemplateString());
                    return ret;
                } catch(Throwable t) {
                    LOGGER.warn("Wrong exclude path {}", path , t);
                    return null;
                }
            })
            .filter(pathTemplate -> pathTemplate != null)
            .forEach(pathTemplate -> this.exclude.add(pathTemplate, true));
    }

    @Override
    public void handle(ServiceRequest request, ServiceResponse response) throws Exception {
        var exchange = request.getExchange();

        if (!exchange.isComplete()) {
            final var startTime = System.currentTimeMillis();
            final var uri = request.getPath();

            // template string /{db}/{coll}
            // uri /foo/bar

            final var matchedTemplates = this.include.stream()
                .map(ptm -> ptm.match(uri))
                .filter(pmr -> pmr != null)
                .collect(Collectors.toList());

            LOGGER.debug("Matched path templates {}", matchedTemplates.stream().map(t -> t.getMatchedTemplate()).collect(Collectors.toList()));

            try {
                exchange.addExchangeCompleteListener((httpServerExchange, nextListener) -> {
                    matchedTemplates.forEach(mt -> addMetrics(mt, startTime, request, response));
                    nextListener.proceed();
                });
            } catch(Throwable t) {
                LOGGER.warn("Error adding metric collector to request {} {}", request.getMethod(), request.getPath(), t);
            }
        }
    }

    @Override
    public boolean resolve(ServiceRequest request, ServiceResponse response) {
        var uri = request.getPath();
        var matchInclude = this.include.stream().anyMatch(ptm -> ptm.match(uri) != null);

        if (matchInclude) {
            var matchExclude = this.exclude.match(uri);

             if (matchExclude != null && matchExclude.getValue()) {
                LOGGER.debug("Matched exclude path {}", matchExclude.getMatchedTemplate());
                LOGGER.debug("Return false since matched exclude path {}", matchExclude.getMatchedTemplate());
                return false;
             }

             LOGGER.debug("Return true since matched include paths");
             return true;
        }

        LOGGER.debug("Return false since did't match any include path");
        return false;
    }

    private void addMetrics(PathMatchResult pathTemplate, long startTime, ServiceRequest request, ServiceResponse response) {
        var registyName = METRICS_REGISTRIES_PREFIX.concat(pathTemplate.getMatchedTemplate());
        var registry = SharedMetricRegistries.getOrCreate(registyName);
        var duration = System.currentTimeMillis() - startTime;
        var _status = response.getStatusCode() > 0 ? response.getStatusCode() : 200;

        var status = new MetricLabel("response_status_code", _status + "");
        var method = new MetricLabel("request_method", request.getMethod().toString());
        var matchedTemplate = new MetricLabel("path_template", pathTemplate.getMatchedTemplate());

        var matchParams = pathTemplate.getParameters().entrySet().stream()
            .filter(p -> !p.getKey().equals("*"))
            .sorted()
            .map(param -> new MetricLabel("path_template_param_".concat(param.getKey()), param.getValue()))
            .collect(Collectors.toList());

        var t1wp = new ArrayList();
        t1wp.addAll(MetricLabel.collect(method, matchedTemplate, status));
        t1wp.addAll(matchParams);

        // custom labels
        var customLabels = Metrics.getMetricLabels(request);
        if (customLabels != null) {
            t1wp.addAll(customLabels);
        }

        registry.timer(new MetricNameAndLabels("http_requests", t1wp).toString()).update(duration, TimeUnit.MILLISECONDS);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy