net.bull.javamelody.internal.publish.Statsd Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of javamelody-core Show documentation
Show all versions of javamelody-core Show documentation
Monitoring of JavaEE applications
/*
* Copyright 2008-2019 by Emeric Vernat
*
* This file is part of Java Melody.
*
* 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 net.bull.javamelody.internal.publish;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;
import net.bull.javamelody.Parameter;
import net.bull.javamelody.internal.common.LOG;
/**
* Publish chart data to StatsD.
* @author Emeric Vernat
*/
class Statsd extends MetricsPublisher {
// see https://github.com/etsy/statsd/blob/master/examples/StatsdClient.java
private static final int DEFAULT_STATSD_PORT = 8125;
private static final int SOCKET_BUFFER_SIZE = 4096;
private final InetSocketAddress address;
private final String prefix;
private final DecimalFormat decimalFormat = new DecimalFormat("0.00",
DecimalFormatSymbols.getInstance(Locale.US));
private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
private final Writer bufferWriter = new OutputStreamWriter(buffer, StandardCharsets.UTF_8);
Statsd(InetAddress host, int port, String prefix) {
super();
this.address = new InetSocketAddress(host, port);
this.prefix = prefix;
}
static Statsd getInstance(String contextPath, String hostName) {
final String statsdAddress = Parameter.STATSD_ADDRESS.getValue();
if (statsdAddress != null) {
assert contextPath != null;
assert hostName != null;
final String address;
final int port;
final int index = statsdAddress.lastIndexOf(':');
if (index != -1) {
// we could use new URI("socket://" + statsdAddress).getHost() and getPort() to parse the address
address = statsdAddress.substring(0, index);
port = Integer.parseInt(statsdAddress.substring(index + 1));
} else {
address = statsdAddress;
port = DEFAULT_STATSD_PORT;
}
String statsdPrefix = Parameter.STATSD_PREFIX.getValue();
if (statsdPrefix == null) {
// contextPath est du genre "/testapp"
// hostName est du genre "www.host.com"
statsdPrefix = "javamelody." + contextPath.replace("/", "") + '.' + hostName + '.';
}
try {
return new Statsd(InetAddress.getByName(address), port, statsdPrefix);
} catch (final UnknownHostException e) {
throw new IllegalArgumentException("Invalid host: " + address, e);
}
}
return null;
}
@Override
public synchronized void addValue(String metric, double value) throws IOException {
// String.format(Locale.ENGLISH, "%s:%d|ms", key, value) for a timing value
// String.format(Locale.ENGLISH, "%s:%s|c", key, magnitude) to increment a counter
// String.format(Locale.ENGLISH, "%s:%s|g", key, value) for a gauge value
bufferWriter.append(prefix).append(metric).append(':');
bufferWriter.append(decimalFormat.format(value)).append("|g\n");
}
@Override
public synchronized void send() throws IOException {
try {
bufferWriter.flush();
final byte[] bytes = buffer.toByteArray();
final ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
try (DatagramChannel channel = createDatagramChannel()) {
final int nbSentBytes = channel.send(byteBuffer, address);
if (bytes.length != nbSentBytes) {
final String msg = String.format(
"Could not send entirely data to StatsD host %s:%d. Only sent %d bytes out of %d bytes",
address.getHostName(), address.getPort(), nbSentBytes, bytes.length);
LOG.warn(msg, new IOException(msg));
}
}
} catch (final ConnectException e) {
throw new IOException("Error connecting to StatsD at " + address.getHostName() + ':'
+ address.getPort(), e);
} finally {
// finally to be sure to not keep too much data in buffer
// including when the socket can't connect
buffer.reset();
}
}
private DatagramChannel createDatagramChannel() throws IOException {
final DatagramChannel channel = DatagramChannel.open();
// Put this in non-blocking mode so send does not block forever.
channel.configureBlocking(false);
// Increase the size of the output buffer so that the size is larger than our buffer size.
channel.setOption(StandardSocketOptions.SO_SNDBUF, SOCKET_BUFFER_SIZE);
return channel;
}
@Override
public void stop() {
// nothing
}
}