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

io.micrometer.statsd.StatsdMeterRegistry Maven / Gradle / Ivy

/**
 * Copyright 2017 Pivotal Software, 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 *

* http://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.statsd; import io.micrometer.core.annotation.Incubating; import io.micrometer.core.instrument.*; import io.micrometer.core.instrument.config.NamingConvention; import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; import io.micrometer.core.instrument.distribution.pause.PauseDetector; import io.micrometer.core.instrument.internal.DefaultMeter; import io.micrometer.core.instrument.util.DoubleFormat; import io.micrometer.core.instrument.util.HierarchicalNameMapper; import io.micrometer.core.instrument.util.TimeUtils; import io.micrometer.core.lang.Nullable; import io.micrometer.statsd.internal.FlavorStatsdLineBuilder; import io.micrometer.statsd.internal.LogbackMetricsSuppressingUnicastProcessor; import org.reactivestreams.Processor; import reactor.core.Disposable; import reactor.core.Disposables; import reactor.core.publisher.Flux; import reactor.core.publisher.UnicastProcessor; import reactor.ipc.netty.NettyPipeline; import reactor.ipc.netty.udp.UdpClient; import reactor.util.concurrent.Queues; import java.lang.reflect.InvocationTargetException; import java.util.Collection; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.function.ToDoubleFunction; import java.util.function.ToLongFunction; /** * @author Jon Schneider */ public class StatsdMeterRegistry extends MeterRegistry { private final StatsdConfig statsdConfig; private final HierarchicalNameMapper nameMapper; private final Collection pollableMeters = new CopyOnWriteArrayList<>(); volatile Processor publisher; private Disposable.Swap udpClient = Disposables.swap(); private Disposable.Swap meterPoller = Disposables.swap(); @Nullable private Function lineBuilderGenerator; public StatsdMeterRegistry(StatsdConfig config, Clock clock) { this(config, HierarchicalNameMapper.DEFAULT, clock); } public StatsdMeterRegistry(StatsdConfig config, HierarchicalNameMapper nameMapper, Clock clock) { this(config, nameMapper, clock, null); } /** * Construct a new StatsD registry. * * @param config Configuration options. * @param nameMapper A strategy to flatten names to a hierarchical form. Only used by the Etsy flavor which * does not support tags. * @param clock The clock. * @param lineBuilderGenerator A function that builds a StatsD serializer for a given meter id. Used for completely * customizing the serialized form when you are dealing with a non-standard (and potentially * non-public) StatsD flavor. */ @Incubating(since = "1.0.0") public StatsdMeterRegistry(StatsdConfig config, HierarchicalNameMapper nameMapper, Clock clock, Function lineBuilderGenerator) { super(clock); this.lineBuilderGenerator = lineBuilderGenerator; this.statsdConfig = config; this.nameMapper = nameMapper; switch (statsdConfig.flavor()) { case DATADOG: config().namingConvention(NamingConvention.dot); break; case TELEGRAF: config().namingConvention(NamingConvention.snakeCase); break; default: config().namingConvention(NamingConvention.camelCase); } UnicastProcessor processor = UnicastProcessor.create(Queues.get(statsdConfig.queueSize()).get()); try { Class.forName("ch.qos.logback.classic.turbo.TurboFilter", false, getClass().getClassLoader()); this.publisher = new LogbackMetricsSuppressingUnicastProcessor(processor); } catch (ClassNotFoundException e) { this.publisher = processor; } if (config.enabled()) start(); } public void start() { UdpClient.create(statsdConfig.host(), statsdConfig.port()) .newHandler((in, out) -> out .options(NettyPipeline.SendOptions::flushOnEach) .sendString(publisher) .neverComplete() ) .subscribe(client -> { this.udpClient.replace(client); // now that we're connected, start polling gauges and other pollable meter types meterPoller.replace(Flux.interval(statsdConfig.pollingFrequency()) .doOnEach(n -> pollableMeters.forEach(StatsdPollable::poll)) .subscribe()); }); } public void stop() { udpClient.dispose(); meterPoller.dispose(); } @Override public void close() { stop(); super.close(); } @Override protected Gauge newGauge(Meter.Id id, @Nullable T obj, ToDoubleFunction valueFunction) { StatsdGauge gauge = new StatsdGauge<>(id, lineBuilder(id), publisher, obj, valueFunction, statsdConfig.publishUnchangedMeters()); pollableMeters.add(gauge); return gauge; } private StatsdLineBuilder lineBuilder(Meter.Id id) { if (lineBuilderGenerator == null) { lineBuilderGenerator = id2 -> new FlavorStatsdLineBuilder(id2, statsdConfig.flavor(), nameMapper, config()); } return lineBuilderGenerator.apply(id); } @Override protected Counter newCounter(Meter.Id id) { return new StatsdCounter(id, lineBuilder(id), publisher); } @Override protected LongTaskTimer newLongTaskTimer(Meter.Id id) { StatsdLongTaskTimer ltt = new StatsdLongTaskTimer(id, lineBuilder(id), publisher, clock, statsdConfig.publishUnchangedMeters()); pollableMeters.add(ltt); return ltt; } @SuppressWarnings("ConstantConditions") @Override protected Timer newTimer(Meter.Id id, DistributionStatisticConfig distributionStatisticConfig, PauseDetector pauseDetector) { Timer timer = new StatsdTimer(id, lineBuilder(id), publisher, clock, distributionStatisticConfig, pauseDetector, getBaseTimeUnit(), statsdConfig.step().toMillis()); if (distributionStatisticConfig.getPercentiles() != null) { for (double percentile : distributionStatisticConfig.getPercentiles()) { gauge(id.getName() + ".percentile", Tags.concat(getConventionTags(id), "phi", DoubleFormat.decimalOrNan(percentile)), timer, t -> t.percentile(percentile, getBaseTimeUnit())); } } for (Long bucket : distributionStatisticConfig.getHistogramBuckets(false)) { Tags bucketTags = Tags.concat(getConventionTags(id), "le", DoubleFormat.decimalOrWhole(TimeUtils.nanosToUnit(bucket, getBaseTimeUnit()))); gauge(id.getName() + ".histogram", bucketTags, timer, t -> t.histogramCountAtValue(bucket)); } return timer; } @SuppressWarnings("ConstantConditions") @Override protected DistributionSummary newDistributionSummary(Meter.Id id, DistributionStatisticConfig distributionStatisticConfig, double scale) { DistributionSummary summary = new StatsdDistributionSummary(id, lineBuilder(id), publisher, clock, distributionStatisticConfig, scale); if (distributionStatisticConfig.getPercentiles() != null) { for (double percentile : distributionStatisticConfig.getPercentiles()) { gauge(id.getName() + ".percentile", Tags.concat(getConventionTags(id), "phi", DoubleFormat.decimalOrNan(percentile)), summary, s -> s.percentile(percentile)); } } for (Long bucket : distributionStatisticConfig.getHistogramBuckets(false)) { Tags bucketTags = Tags.concat(getConventionTags(id), "le", DoubleFormat.decimalOrWhole(bucket)); gauge(id.getName() + ".histogram", bucketTags, summary, s -> s.histogramCountAtValue(bucket)); } return summary; } @Override protected FunctionCounter newFunctionCounter(Meter.Id id, T obj, ToDoubleFunction countFunction) { StatsdFunctionCounter fc = new StatsdFunctionCounter<>(id, obj, countFunction, lineBuilder(id), publisher); pollableMeters.add(fc); return fc; } @Override protected FunctionTimer newFunctionTimer(Meter.Id id, T obj, ToLongFunction countFunction, ToDoubleFunction totalTimeFunction, TimeUnit totalTimeFunctionUnits) { StatsdFunctionTimer ft = new StatsdFunctionTimer<>(id, obj, countFunction, totalTimeFunction, totalTimeFunctionUnits, getBaseTimeUnit(), lineBuilder(id), publisher); pollableMeters.add(ft); return ft; } @Override protected Meter newMeter(Meter.Id id, Meter.Type type, Iterable measurements) { measurements.forEach(ms -> { StatsdLineBuilder line = lineBuilder(id); switch (ms.getStatistic()) { case COUNT: case TOTAL: case TOTAL_TIME: pollableMeters.add(() -> publisher.onNext(line.count((long) ms.getValue(), ms.getStatistic()))); break; case VALUE: case ACTIVE_TASKS: case DURATION: case UNKNOWN: pollableMeters.add(() -> publisher.onNext(line.gauge(ms.getValue(), ms.getStatistic()))); break; } }); return new DefaultMeter(id, type, measurements); } @Override protected TimeUnit getBaseTimeUnit() { return TimeUnit.MILLISECONDS; } @Override protected DistributionStatisticConfig defaultHistogramConfig() { return DistributionStatisticConfig.builder() .expiry(statsdConfig.step()) .build() .merge(DistributionStatisticConfig.DEFAULT); } public int queueSize() { try { return (Integer) publisher.getClass().getMethod("size").invoke(publisher); } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { // should never happen return 0; } } public int queueCapacity() { try { return (Integer) publisher.getClass().getMethod("getBufferSize").invoke(publisher); } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { // should never happen return 0; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy