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

io.kroxylicious.proxy.internal.MeterRegistries Maven / Gradle / Ivy

/*
 * Copyright Kroxylicious Authors.
 *
 * Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
 */
package io.kroxylicious.proxy.internal;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
import io.micrometer.core.instrument.config.MeterFilter;
import io.micrometer.core.instrument.config.MeterFilterReply;
import io.micrometer.prometheusmetrics.PrometheusConfig;
import io.micrometer.prometheusmetrics.PrometheusMeterRegistry;

import io.kroxylicious.proxy.config.MicrometerDefinition;
import io.kroxylicious.proxy.micrometer.MicrometerConfigurationHook;
import io.kroxylicious.proxy.micrometer.MicrometerConfigurationHookContributor;
import io.kroxylicious.proxy.service.ContributionManager;
import io.kroxylicious.proxy.tag.VisibleForTesting;

import static io.kroxylicious.proxy.service.Context.wrap;

public class MeterRegistries implements AutoCloseable {
    private final PrometheusMeterRegistry prometheusMeterRegistry;

    private static final Logger logger = LoggerFactory.getLogger(MeterRegistries.class);
    private final List hooks;

    public MeterRegistries(List micrometerConfig) {
        this.hooks = registerHooks(micrometerConfig);
        this.prometheusMeterRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
        Metrics.addRegistry(prometheusMeterRegistry);
    }

    private List registerHooks(List micrometerConfig) {
        CompositeMeterRegistry globalRegistry = Metrics.globalRegistry;
        preventDifferentTagNameRegistration(globalRegistry);
        var configurationHooks = micrometerConfig.stream()
                .map(f -> (MicrometerConfigurationHook) ContributionManager.INSTANCE.createInstance(MicrometerConfigurationHookContributor.class, f.type(),
                        wrap(f.config())))
                .toList();
        configurationHooks.forEach(micrometerConfigurationHook -> micrometerConfigurationHook.configure(globalRegistry));
        return configurationHooks;
    }

    /**
     * By default, when we register multiple meters with the same name but different tag names,
     * only the data for one of those meters can be scraped at the prometheus endpoint.
     * This is a limitation of the prometheus client. So we add a filter that explodes when we
     * attempt to register a metric with a different set of tags.
     *
     * @param registry registry
     */
    @VisibleForTesting
    static void preventDifferentTagNameRegistration(CompositeMeterRegistry registry) {
        registry.config().meterFilter(new MeterFilter() {
            @Override
            public MeterFilterReply accept(Meter.Id id) {
                boolean allTagsSame = registry.find(id.getName()).meters().stream().allMatch(meter -> tagNames(meter.getId()).equals(tagNames(id)));
                if (!allTagsSame) {
                    logger.error("Attempted to register a meter with id {} which is already registered but with a different set of tag names", id);
                    throw new IllegalArgumentException("tags for id " + id + " differ from existing meters registered");
                }
                return MeterFilterReply.ACCEPT;
            }
        });
    }

    private static Object tagNames(Meter.Id id1) {
        return id1.getTags().stream().map(Tag::getKey).collect(Collectors.toSet());
    }

    /**
     * Offers up a prometheus registry if available. Currently, we always have a prometheus registry but in
     * future we may wish to use a different micrometer backend. Clients should use the global
     * io.micrometer.core.instrument.Metrics static methods to record metrics, not this implementation. This is used to
     * support specialisations like scraping the prometheus metrics.
     */
    public Optional maybePrometheusMeterRegistry() {
        return Optional.ofNullable(prometheusMeterRegistry);
    }

    @Override
    public void close() {
        hooks.forEach(MicrometerConfigurationHook::close);
        // remove the meters we contributed to the global registry.
        var copy = List.copyOf(prometheusMeterRegistry.getMeters());
        copy.forEach(Metrics.globalRegistry::remove);
        Metrics.removeRegistry(prometheusMeterRegistry);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy