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

eu.xenit.alfred.telemetry.webscripts.MetricsDetailWebScript Maven / Gradle / Ivy

package eu.xenit.alfred.telemetry.webscripts;

import eu.xenit.alfred.telemetry.service.MeterRegistryService;
import eu.xenit.alfred.telemetry.util.MeterRegistryUtil;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.Statistic;
import io.micrometer.core.instrument.Tag;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import org.apache.http.HttpStatus;
import org.springframework.extensions.webscripts.Cache;
import org.springframework.extensions.webscripts.DeclarativeWebScript;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;

public class MetricsDetailWebScript extends DeclarativeWebScript {

    private final MeterRegistryService meterRegistryService;

    public MetricsDetailWebScript(MeterRegistryService meterRegistryService) {
        this.meterRegistryService = meterRegistryService;
    }

    @Override
    protected Map executeImpl(WebScriptRequest req, Status status, Cache cache) {
        final Map model = new HashMap<>();

        final String requiredMetricName = extractRequiredMetricName(req);
        final List tags = extractTags(req);

        Collection meters = meterRegistryService.findFirstMatchingMeters(requiredMetricName, tags);
        if (meters.isEmpty()) {
            throw new WebScriptException(HttpStatus.SC_NOT_FOUND, "'" + requiredMetricName + "' meter not found");
        }
        Map samples = MeterRegistryUtil.getSamples(meters);
        Map> availableTags = MeterRegistryUtil.getAvailableTags(meters);
        tags.forEach((t) -> availableTags.remove(t.getKey()));
        Meter.Id meterId = meters.iterator().next().getId();

        model.put("name", requiredMetricName);
        model.put("description", meterId.getDescription());
        model.put("baseUnit", meterId.getBaseUnit());
        model.put("measurements", asList(samples, Sample::new));
        model.put("availableTags", asList(availableTags, AvailableTag::new));

        return model;
    }

    private String extractRequiredMetricName(WebScriptRequest request) {
        final String ret = request.getServiceMatch().getTemplateVars().get("meterName");
        if (ret == null || ret.isEmpty()) {
            throw new WebScriptException(HttpStatus.SC_NOT_FOUND, "Include a meter name in the path");
        }
        return ret;
    }

    private List extractTags(WebScriptRequest request) {
        return parseTags(request.getParameterValues("tag"));
    }

    private static List parseTags(String[] tags) {
        if (tags == null) {
            return Collections.emptyList();
        }
        return Arrays.stream(tags).map(MetricsDetailWebScript::parseTag).collect(Collectors.toList());
    }

    private static Tag parseTag(String tag) {
        String[] parts = tag.split(":", 2);
        if (parts.length != 2) {
            throw new WebScriptException(HttpStatus.SC_UNPROCESSABLE_ENTITY,
                    "Each tag parameter must be in the form 'key:value' but was: " + tag);
        }
        return Tag.of(parts[0], parts[1]);
    }

    private static  List asList(Map map, BiFunction mapper) {
        return map.entrySet().stream().map((entry) -> mapper.apply(entry.getKey(), entry.getValue()))
                .collect(Collectors.toList());
    }

    /* MODEL */

    /**
     * A set of tags for further dimensional drilldown and their potential values.
     */
    public static final class AvailableTag {

        private final String tag;

        private final Set values;

        AvailableTag(String tag, Set values) {
            this.tag = tag;
            this.values = values;
        }

        public String getTag() {
            return this.tag;
        }

        public Set getValues() {
            return this.values;
        }

    }

    /**
     * A measurement sample combining a {@link Statistic statistic} and a value.
     */
    public static final class Sample {

        private final Statistic statistic;

        private final Double value;

        Sample(Statistic statistic, Double value) {
            this.statistic = statistic;
            this.value = value;
        }

        public Statistic getStatistic() {
            return this.statistic;
        }

        public Double getValue() {
            return this.value;
        }

        @Override
        public String toString() {
            return "MeasurementSample{" + "statistic=" + this.statistic + ", value=" + this.value + '}';
        }

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy