/*******************************************************************************
* Copyright (c) 2005, 2014 springside.github.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
*******************************************************************************/
package org.springside.modules.metrics.reporter;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.Charset;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import javax.net.SocketFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springside.modules.metrics.MetricRegistry;
import org.springside.modules.metrics.Reporter;
import org.springside.modules.metrics.metric.Counter;
import org.springside.modules.metrics.metric.CounterMetric;
import org.springside.modules.metrics.metric.Gauge;
import org.springside.modules.metrics.metric.Histogram;
import org.springside.modules.metrics.metric.HistogramMetric;
import org.springside.modules.metrics.metric.Timer;
import org.springside.modules.metrics.metric.TimerMetric;
/**
* 输出到Graphite.
*/
public class GraphiteReporter implements Reporter {
public static final String DEFAULT_PREFIX = "metrics";
private static final Pattern WHITESPACE = Pattern.compile("[\\s]+");
private static final Charset UTF_8 = Charset.forName("UTF-8");
private static Logger logger = LoggerFactory.getLogger(GraphiteReporter.class);
private String prefix;
private InetSocketAddress address;
private SocketFactory socketFactory;
private Socket socket;
private Writer writer;
// use to only print connection error message once.
private GraphiteConnStatus graphiteConnStatus = GraphiteConnStatus.CONN_OK;
public GraphiteReporter(InetSocketAddress address) {
this(address, DEFAULT_PREFIX);
}
public GraphiteReporter(InetSocketAddress address, String prefix) {
this.prefix = prefix;
this.address = address;
this.socketFactory = SocketFactory.getDefault();
}
@Override
public void report(Map<String, Gauge> gauges, Map<String, Counter> counters, Map<String, Histogram> histograms,
Map<String, Timer> timers) {
try {
connect();
long timestamp = System.currentTimeMillis() / 1000;
for (Map.Entry<String, Gauge> entry : gauges.entrySet()) {
reportGauge(entry.getKey(), entry.getValue().latestMetric, timestamp);
}
for (Map.Entry<String, Counter> entry : counters.entrySet()) {
reportCounter(entry.getKey(), entry.getValue().latestMetric, timestamp);
}
for (Map.Entry<String, Histogram> entry : histograms.entrySet()) {
reportHistogram(entry.getKey(), entry.getValue().latestMetric, timestamp);
}
for (Map.Entry<String, Timer> entry : timers.entrySet()) {
reportTimer(entry.getKey(), entry.getValue().latestMetric, timestamp);
}
flush();
onConnSuccess();
} catch (IOException e) {
onConnFail(e);
} finally {
try {
close();
} catch (IOException e) {
logger.warn("Error disconnecting from Graphite", e);
}
}
}
private void reportGauge(String name, Number gauge, long timestamp) throws IOException {
send(MetricRegistry.name(prefix, name, "gauge"), format(gauge), timestamp);
}
private void reportCounter(String name, CounterMetric counter, long timestamp) throws IOException {
send(MetricRegistry.name(prefix, name, "count"), format(counter.latestCount), timestamp);
}
private void reportHistogram(String name, HistogramMetric histogram, long timestamp) throws IOException {
send(MetricRegistry.name(prefix, name, "min"), format(histogram.min), timestamp);
send(MetricRegistry.name(prefix, name, "max"), format(histogram.max), timestamp);
send(MetricRegistry.name(prefix, name, "avg"), format(histogram.avg), timestamp);
for (Entry<Double, Long> pct : histogram.pcts.entrySet()) {
send(MetricRegistry.name(prefix, name, format(pct.getKey()).replace('.', '_')), format(pct.getValue()),
timestamp);
}
}
private void reportTimer(String name, TimerMetric timer, long timestamp) throws IOException {
send(MetricRegistry.name(prefix, name, "count"), format(timer.counterMetric.latestCount), timestamp);
send(MetricRegistry.name(prefix, name, "min"), format(timer.histogramMetric.min), timestamp);
send(MetricRegistry.name(prefix, name, "max"), format(timer.histogramMetric.max), timestamp);
send(MetricRegistry.name(prefix, name, "avg"), format(timer.histogramMetric.avg), timestamp);
for (Entry<Double, Long> pct : timer.histogramMetric.pcts.entrySet()) {
send(MetricRegistry.name(prefix, name, format(pct.getKey()).replace('.', '_')), format(pct.getValue()),
timestamp);
}
}
private void connect() throws IllegalStateException, IOException {
if (socket != null) {
throw new IllegalStateException("Already connected");
}
this.socket = socketFactory.createSocket(address.getAddress(), address.getPort());
this.writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), UTF_8));
}
private void send(String name, String value, long timestamp) throws IOException {
try {
writer.write(sanitize(name));
writer.write(' ');
writer.write(sanitize(value));
writer.write(' ');
writer.write(format(timestamp));
writer.write('\n');
} catch (IOException e) {
throw e;
}
}
private void flush() throws IOException {
writer.flush();
}
private void close() throws IOException {
if (writer != null) {
writer.flush();
}
if (socket != null) {
socket.close();
}
this.socket = null;
this.writer = null;
}
private String format(long n) {
return Long.toString(n);
}
private String format(double v) {
return String.format(Locale.US, "%2.2f", v);
}
private String format(Number o) {
if (o instanceof Float) {
return format(((Float) o).doubleValue());
} else if (o instanceof Double) {
return format(((Double) o).doubleValue());
} else if (o instanceof Short) {
return format(((Short) o).longValue());
} else if (o instanceof Integer) {
return format(((Integer) o).longValue());
} else if (o instanceof Long) {
return format(((Long) o).longValue());
}
return null;
}
private String sanitize(String s) {
return WHITESPACE.matcher(s).replaceAll("-");
}
private void onConnFail(Exception exception) {
if (graphiteConnStatus != GraphiteConnStatus.CONN_NOK) {
logger.warn("Unable to report to Graphite", exception);
graphiteConnStatus = GraphiteConnStatus.CONN_NOK;
}
}
private void onConnSuccess() {
if (graphiteConnStatus != GraphiteConnStatus.CONN_OK) {
logger.info("Graphite connection is recovered.");
graphiteConnStatus = GraphiteConnStatus.CONN_OK;
}
}
private enum GraphiteConnStatus {
CONN_OK, CONN_NOK
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
}