/** * Copyright 2013-2014 Recruit Technologies Co., Ltd. and contributors * (see CONTRIBUTORS.md) * * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. A copy of the * License is distributed with this work in the LICENSE.md file. You may * also obtain a copy of the License from * * 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 org.gennai.gungnir.metrics; import com.codahale.metrics.Counter; import com.codahale.metrics.Gauge; import com.codahale.metrics.Histogram; import com.codahale.metrics.Meter; import com.codahale.metrics.Metered; import com.codahale.metrics.MetricFilter; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.ScheduledReporter; import com.codahale.metrics.Snapshot; import com.codahale.metrics.Timer; import com.timgroup.statsd.NonBlockingStatsDClient; import com.timgroup.statsd.StatsDClient; import com.timgroup.statsd.StatsDClientErrorHandler; import com.timgroup.statsd.StatsDClientException; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Map; import java.util.Map.Entry; import java.util.SortedMap; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public final class StatsdReporter extends ScheduledReporter { private static final Logger LOG = LoggerFactory.getLogger(StatsdReporter.class); private final StatsDClient statsd; private final String prefix; private StatsdReporter(MetricRegistry registry, StatsDClient statsd, String prefix, TimeUnit rate, TimeUnit duration, MetricFilter filter) { super(registry, "statsd-reporter", filter, rate, duration); this.statsd = statsd; this.prefix = prefix; } public static Builder forRegistry(MetricRegistry registry) { return new Builder(registry); } public static final class Builder { private final MetricRegistry registry; private String prefix; private TimeUnit rate; private TimeUnit duration; private MetricFilter filter; private StatsDClient statsd; private Builder(MetricRegistry registry) { this.registry = registry; this.prefix = null; this.rate = TimeUnit.SECONDS; this.duration = TimeUnit.MILLISECONDS; this.filter = MetricFilter.ALL; } public Builder prefixedWith(String prefix) { this.prefix = prefix; return this; } public Builder convertRatesTo(TimeUnit rate) { this.rate = rate; return this; } public Builder convertDurationsTo(TimeUnit duration) { this.duration = duration; return this; } public Builder filter(MetricFilter filter) { this.filter = filter; return this; } public StatsdReporter build(String host, int port) { try { statsd = new NonBlockingStatsDClient(prefix, host, port, new StatsDClientErrorHandler() { @Override public void handle(Exception e) { LOG.warn("can't send to StatsD", e); } }); } catch (StatsDClientException e) { LOG.error("Failed to connect StatsD [{}:{}]", host, port, e); return null; } return build(statsd); } public StatsdReporter build(final StatsDClient statsd) { return new StatsdReporter(registry, statsd, prefix, rate, duration, filter); } } @Override public void report(@SuppressWarnings("rawtypes") SortedMap<String, Gauge> gauges, SortedMap<String, Counter> counters, SortedMap<String, Histogram> histograms, SortedMap<String, Meter> meters, SortedMap<String, Timer> timers) { for (@SuppressWarnings("rawtypes") Entry<String, Gauge> entry : gauges.entrySet()) { reportGauge(entry.getKey(), entry.getValue()); } for (Map.Entry<String, Counter> entry : counters.entrySet()) { reportCounter(entry.getKey(), entry.getValue()); } for (Map.Entry<String, Histogram> entry : histograms.entrySet()) { reportHistogram(entry.getKey(), entry.getValue()); } for (Map.Entry<String, Meter> entry : meters.entrySet()) { reportMetere(entry.getKey(), entry.getValue()); } for (Map.Entry<String, Timer> entry : timers.entrySet()) { reportTimer(entry.getKey(), entry.getValue()); } } @Override public void stop() { try { super.stop(); } finally { if (statsd != null) { statsd.stop(); } } } private void reportTimer(String name, Timer timer) { final Snapshot snapshot = timer.getSnapshot(); if (statsd != null) { statsd.gauge(prefix(name, "count"), timer.getCount()); statsd.gauge(prefix(name, "mean"), convertDuration(snapshot.getMean())); statsd.gauge(prefix(name, "min"), convertDuration(snapshot.getMin())); statsd.gauge(prefix(name, "stddev"), convertDuration(snapshot.getStdDev())); statsd.gauge(prefix(name, "p50"), convertDuration(snapshot.getMedian())); statsd.gauge(prefix(name, "p75"), convertDuration(snapshot.get75thPercentile())); statsd.gauge(prefix(name, "p95"), convertDuration(snapshot.get95thPercentile())); statsd.gauge(prefix(name, "p98"), convertDuration(snapshot.get98thPercentile())); statsd.gauge(prefix(name, "p99"), convertDuration(snapshot.get99thPercentile())); statsd.gauge(prefix(name, "p999"), convertDuration(snapshot.get999thPercentile())); statsd.gauge(prefix(name, "mean_rate"), convertRate(timer.getMeanRate())); statsd.gauge(prefix(name, "m1_rate"), convertRate(timer.getOneMinuteRate())); statsd.gauge(prefix(name, "m5_rate"), convertRate(timer.getFiveMinuteRate())); statsd.gauge(prefix(name, "m15_rate"), convertRate(timer.getFifteenMinuteRate())); } reportMetere(name, timer); } private void reportMetere(String name, Metered meter) { if (statsd != null) { statsd.gauge(prefix(name, "count"), meter.getCount()); statsd.gauge(prefix(name, "mean_rate"), convertRate(meter.getMeanRate())); statsd.gauge(prefix(name, "m1_rate"), convertRate(meter.getOneMinuteRate())); statsd.gauge(prefix(name, "m5_rate"), convertRate(meter.getFiveMinuteRate())); statsd.gauge(prefix(name, "m15_rate"), convertRate(meter.getFifteenMinuteRate())); } } private void reportHistogram(String name, Histogram histogram) { final Snapshot snapshot = histogram.getSnapshot(); if (statsd != null) { statsd.gauge(prefix(name, "count"), histogram.getCount()); statsd.gauge(prefix(name, "max"), snapshot.getMax()); statsd.gauge(prefix(name, "mean"), snapshot.getMean()); statsd.gauge(prefix(name, "min"), snapshot.getMin()); statsd.gauge(prefix(name, "stddev"), snapshot.getStdDev()); statsd.gauge(prefix(name, "p50"), snapshot.getMedian()); statsd.gauge(prefix(name, "p75"), snapshot.get75thPercentile()); statsd.gauge(prefix(name, "p95"), snapshot.get95thPercentile()); statsd.gauge(prefix(name, "p98"), snapshot.get98thPercentile()); statsd.gauge(prefix(name, "p99"), snapshot.get99thPercentile()); statsd.gauge(prefix(name, "p999"), snapshot.get999thPercentile()); } } private void reportCounter(String name, Counter counter) { if (statsd != null) { statsd.gauge(prefix(name), counter.getCount()); } } private void reportGauge(String name, Gauge<?> gauge) { Object value = gauge.getValue(); if (statsd != null && value != null) { if (value instanceof Float) { statsd.gauge(prefix(name), ((Float) value).doubleValue()); } else if (value instanceof Double) { statsd.gauge(prefix(name), ((Double) value).doubleValue()); } else if (value instanceof Byte) { statsd.gauge(prefix(name), ((Byte) value).longValue()); } else if (value instanceof Short) { statsd.gauge(prefix(name), ((Short) value).longValue()); } else if (value instanceof Integer) { statsd.gauge(prefix(name), ((Integer) value).longValue()); } else if (value instanceof Long) { statsd.gauge(prefix(name), ((Long) value).longValue()); } else if (value instanceof BigInteger) { statsd.gauge(prefix(name), ((BigInteger) value).longValue()); } else if (value instanceof BigDecimal) { statsd.gauge(prefix(name), ((BigDecimal) value).doubleValue()); } } } private String prefix(String... components) { return MetricRegistry.name(prefix, components); } }