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

io.micrometer.influx.InfluxMeterRegistry Maven / Gradle / Ivy

There is a newer version: 1.14.1
Show newest version
/*
 * Copyright 2017 VMware, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.micrometer.influx;

import io.micrometer.common.util.StringUtils;
import io.micrometer.core.instrument.*;
import io.micrometer.core.instrument.step.StepMeterRegistry;
import io.micrometer.core.instrument.util.DoubleFormat;
import io.micrometer.core.instrument.util.MeterPartition;
import io.micrometer.core.instrument.util.NamedThreadFactory;
import io.micrometer.core.ipc.http.HttpSender;
import io.micrometer.core.ipc.http.HttpUrlConnectionSender;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.MalformedURLException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;

import static java.util.stream.Collectors.joining;

/**
 * {@link MeterRegistry} for InfluxDB. Since Micrometer 1.7, this supports InfluxDB v2 and
 * v1.
 *
 * @author Jon Schneider
 * @author Johnny Lim
 */
public class InfluxMeterRegistry extends StepMeterRegistry {

    private static final ThreadFactory DEFAULT_THREAD_FACTORY = new NamedThreadFactory("influx-metrics-publisher");

    private final InfluxConfig config;

    private final HttpSender httpClient;

    private final Logger logger = LoggerFactory.getLogger(InfluxMeterRegistry.class);

    private boolean databaseExists = false;

    @SuppressWarnings("deprecation")
    public InfluxMeterRegistry(InfluxConfig config, Clock clock) {
        this(config, clock, DEFAULT_THREAD_FACTORY,
                new HttpUrlConnectionSender(config.connectTimeout(), config.readTimeout()));
    }

    /**
     * @param config Configuration options for the registry that are describable as
     * properties.
     * @param clock The clock to use for timings.
     * @param threadFactory The thread factory to use to create the publishing thread.
     * @deprecated Use {@link #builder(InfluxConfig)} instead.
     */
    @Deprecated
    public InfluxMeterRegistry(InfluxConfig config, Clock clock, ThreadFactory threadFactory) {
        this(config, clock, threadFactory, new HttpUrlConnectionSender(config.connectTimeout(), config.readTimeout()));
    }

    private InfluxMeterRegistry(InfluxConfig config, Clock clock, ThreadFactory threadFactory, HttpSender httpClient) {
        super(config, clock);
        config().namingConvention(new InfluxNamingConvention());
        this.config = config;
        this.httpClient = httpClient;
        start(threadFactory);
    }

    @Override
    public void start(ThreadFactory threadFactory) {
        super.start(threadFactory);
        if (config.enabled()) {
            logger.info("Using InfluxDB API version {} to write metrics", config.apiVersion());
        }
    }

    public static Builder builder(InfluxConfig config) {
        return new Builder(config);
    }

    private void createDatabaseIfNecessary() {
        if (!config.autoCreateDb() || databaseExists || config.apiVersion() == InfluxApiVersion.V2)
            return;

        try {
            String createDatabaseQuery = new CreateDatabaseQueryBuilder(config.db())
                .setRetentionDuration(config.retentionDuration())
                .setRetentionPolicyName(config.retentionPolicy())
                .setRetentionReplicationFactor(config.retentionReplicationFactor())
                .setRetentionShardDuration(config.retentionShardDuration())
                .build();

            HttpSender.Request.Builder requestBuilder = httpClient
                .post(config.uri() + "/query?q=" + URLEncoder.encode(createDatabaseQuery, "UTF-8"))
                .withBasicAuthentication(config.userName(), config.password());
            config.apiVersion().addHeaderToken(config, requestBuilder);

            requestBuilder.send().onSuccess(response -> {
                logger.debug("influx database {} is ready to receive metrics", config.db());
                databaseExists = true;
            }).onError(response -> logger.error("unable to create database '{}': {}", config.db(), response.body()));
        }
        catch (Throwable e) {
            logger.error("unable to create database '{}'", config.db(), e);
        }
    }

    @Override
    protected void publish() {
        createDatabaseIfNecessary();

        try {
            String influxEndpoint = config.apiVersion().writeEndpoint(config);

            for (List batch : MeterPartition.partition(this, config.batchSize())) {
                HttpSender.Request.Builder requestBuilder = httpClient.post(influxEndpoint)
                    .withBasicAuthentication(config.userName(), config.password());
                config.apiVersion().addHeaderToken(config, requestBuilder);
                // @formatter:off
                requestBuilder
                    .withPlainText(batch.stream()
                        .flatMap(m -> m.match(
                                gauge -> writeGauge(gauge.getId(), gauge.value()),
                                counter -> writeCounter(counter.getId(), counter.count()),
                                this::writeTimer,
                                this::writeSummary,
                                this::writeLongTaskTimer,
                                gauge -> writeGauge(gauge.getId(), gauge.value(getBaseTimeUnit())),
                                counter -> writeCounter(counter.getId(), counter.count()),
                                this::writeFunctionTimer,
                                this::writeMeter))
                        .collect(joining("\n")))
                    .compressWhen(config::compressed)
                    .send()
                    .onSuccess(response -> {
                        logger.debug("successfully sent {} metrics to InfluxDB.", batch.size());
                        databaseExists = true;
                    })
                    .onError(response -> logger.error("failed to send metrics to influx: {}", response.body()));
                // @formatter:on
            }
        }
        catch (MalformedURLException e) {
            throw new IllegalArgumentException(
                    "Malformed InfluxDB publishing endpoint, see '" + config.prefix() + ".uri'", e);
        }
        catch (Throwable e) {
            logger.error("failed to send metrics to influx", e);
        }
    }

    // VisibleForTesting
    Stream writeMeter(Meter m) {
        List fields = new ArrayList<>();
        for (Measurement measurement : m.measure()) {
            double value = measurement.getValue();
            if (!Double.isFinite(value)) {
                continue;
            }
            String fieldKey = measurement.getStatistic()
                .getTagValueRepresentation()
                .replaceAll("(.)(\\p{Upper})", "$1_$2")
                .toLowerCase();
            fields.add(new Field(fieldKey, value));
        }
        if (fields.isEmpty()) {
            return Stream.empty();
        }
        Meter.Id id = m.getId();
        return Stream.of(influxLineProtocol(id, id.getType().name().toLowerCase(), fields.stream()));
    }

    private Stream writeLongTaskTimer(LongTaskTimer timer) {
        Stream fields = Stream.of(new Field("active_tasks", timer.activeTasks()),
                new Field("duration", timer.duration(getBaseTimeUnit())));
        return Stream.of(influxLineProtocol(timer.getId(), "long_task_timer", fields));
    }

    // VisibleForTesting
    Stream writeCounter(Meter.Id id, double count) {
        if (Double.isFinite(count)) {
            return Stream.of(influxLineProtocol(id, "counter", Stream.of(new Field("value", count))));
        }
        return Stream.empty();
    }

    // VisibleForTesting
    Stream writeGauge(Meter.Id id, Double value) {
        if (Double.isFinite(value)) {
            return Stream.of(influxLineProtocol(id, "gauge", Stream.of(new Field("value", value))));
        }
        return Stream.empty();
    }

    // VisibleForTesting
    Stream writeFunctionTimer(FunctionTimer timer) {
        double sum = timer.totalTime(getBaseTimeUnit());
        if (Double.isFinite(sum)) {
            Stream.Builder builder = Stream.builder();
            builder.add(new Field("sum", sum));
            builder.add(new Field("count", timer.count()));
            double mean = timer.mean(getBaseTimeUnit());
            if (Double.isFinite(mean)) {
                builder.add(new Field("mean", mean));
            }
            return Stream.of(influxLineProtocol(timer.getId(), "histogram", builder.build()));
        }
        return Stream.empty();
    }

    private Stream writeTimer(Timer timer) {
        final Stream fields = Stream.of(new Field("sum", timer.totalTime(getBaseTimeUnit())),
                new Field("count", timer.count()), new Field("mean", timer.mean(getBaseTimeUnit())),
                new Field("upper", timer.max(getBaseTimeUnit())));

        return Stream.of(influxLineProtocol(timer.getId(), "histogram", fields));
    }

    private Stream writeSummary(DistributionSummary summary) {
        final Stream fields = Stream.of(new Field("sum", summary.totalAmount()),
                new Field("count", summary.count()), new Field("mean", summary.mean()),
                new Field("upper", summary.max()));

        return Stream.of(influxLineProtocol(summary.getId(), "histogram", fields));
    }

    private String influxLineProtocol(Meter.Id id, String metricType, Stream fields) {
        String tags = getConventionTags(id).stream()
            .filter(t -> StringUtils.isNotBlank(t.getValue()))
            .map(t -> "," + t.getKey() + "=" + t.getValue())
            .collect(joining(""));

        return getConventionName(id) + tags + ",metric_type=" + metricType + " "
                + fields.map(Field::toString).collect(joining(",")) + " " + clock.wallTime();
    }

    @Override
    protected final TimeUnit getBaseTimeUnit() {
        return TimeUnit.MILLISECONDS;
    }

    public static class Builder {

        private final InfluxConfig config;

        private Clock clock = Clock.SYSTEM;

        private ThreadFactory threadFactory = DEFAULT_THREAD_FACTORY;

        private HttpSender httpClient;

        @SuppressWarnings("deprecation")
        Builder(InfluxConfig config) {
            this.config = config;
            this.httpClient = new HttpUrlConnectionSender(config.connectTimeout(), config.readTimeout());
        }

        public Builder clock(Clock clock) {
            this.clock = clock;
            return this;
        }

        public Builder threadFactory(ThreadFactory threadFactory) {
            this.threadFactory = threadFactory;
            return this;
        }

        public Builder httpClient(HttpSender httpClient) {
            this.httpClient = httpClient;
            return this;
        }

        public InfluxMeterRegistry build() {
            return new InfluxMeterRegistry(config, clock, threadFactory, httpClient);
        }

    }

    static class Field {

        final String key;

        final double value;

        Field(String key, double value) {
            // `time` cannot be a field key or tag key
            if (key.equals("time")) {
                throw new IllegalArgumentException("'time' is an invalid field key in InfluxDB");
            }
            this.key = key;
            this.value = value;
        }

        @Override
        public String toString() {
            return key + "=" + DoubleFormat.decimalOrNan(value);
        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy