-
Notifications
You must be signed in to change notification settings - Fork 101
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improved abstractions + better object construction #96
Merged
Merged
Changes from 6 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
978ac48
abstract class and blocking+nonblocking processor
truthbk 6de75c6
better construction + class abstractions
truthbk b712319
[test] fix them - less is more
truthbk f6b83b9
[processor] multi-worker support
truthbk d06938b
blocking processor: remove unused imports
truthbk 6345002
[test] we can reuse port, support more scenarios
truthbk bfc9cdb
[maven] add checkstyle
truthbk 02e8845
[maven] add checkstyle
truthbk f3a81b6
[checkstyle] fix all checkstyle offenses
truthbk 98b374c
[javadoc] fix issues with javadoc
truthbk 5be086b
[mvn] silence illegal access warning; not real.
truthbk 4e6b027
[travis] attempt to checkstyle with a different version on Java7
truthbk File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
656 changes: 24 additions & 632 deletions
656
src/main/java/com/timgroup/statsd/NonBlockingStatsDClient.java
Large diffs are not rendered by default.
Oops, something went wrong.
206 changes: 206 additions & 0 deletions
206
src/main/java/com/timgroup/statsd/NonBlockingStatsDClientBuilder.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
package com.timgroup.statsd; | ||
|
||
import java.net.InetAddress; | ||
import java.net.InetSocketAddress; | ||
import java.net.SocketAddress; | ||
import java.net.UnknownHostException; | ||
import java.util.concurrent.Callable; | ||
|
||
import jnr.unixsocket.UnixSocketAddress; | ||
|
||
public class NonBlockingStatsDClientBuilder { | ||
|
||
/** | ||
* 1400 chosen as default here so that the number of bytes in a message plus the number of bytes required | ||
* for additional udp headers should be under the 1500 Maximum Transmission Unit for ethernet. | ||
* See https://github.com/DataDog/java-dogstatsd-client/pull/17 for discussion. | ||
*/ | ||
|
||
public int port = NonBlockingStatsDClient.DEFAULT_DOGSTATSD_PORT; | ||
public int queueSize = NonBlockingStatsDClient.DEFAULT_QUEUE_SIZE; | ||
public int timeout = NonBlockingStatsDClient.SOCKET_TIMEOUT_MS; | ||
public int bufferPoolSize = NonBlockingStatsDClient.DEFAULT_POOL_SIZE; | ||
public int socketBufferSize = NonBlockingStatsDClient.SOCKET_BUFFER_BYTES; | ||
public int maxPacketSizeBytes = NonBlockingStatsDClient.DEFAULT_MAX_PACKET_SIZE_BYTES; | ||
public int processorWorkers = NonBlockingStatsDClient.DEFAULT_PROCESSOR_WORKERS; | ||
public int senderWorkers = NonBlockingStatsDClient.DEFAULT_SENDER_WORKERS; | ||
public boolean blocking; | ||
|
||
public Callable<SocketAddress> addressLookup; | ||
|
||
public String hostname; | ||
public String prefix; | ||
public String entityID; | ||
public String[] constantTags; | ||
|
||
public StatsDClientErrorHandler errorHandler; | ||
|
||
public NonBlockingStatsDClientBuilder() { } | ||
|
||
public NonBlockingStatsDClientBuilder port(int val) { | ||
port = val; | ||
return this; | ||
} | ||
public NonBlockingStatsDClientBuilder queueSize(int val) { | ||
queueSize = val; | ||
return this; | ||
} | ||
public NonBlockingStatsDClientBuilder timeout(int val) { | ||
timeout = val; | ||
return this; | ||
} | ||
public NonBlockingStatsDClientBuilder bufferPoolSize(int val) { | ||
bufferPoolSize = val; | ||
return this; | ||
} | ||
public NonBlockingStatsDClientBuilder socketBufferSize(int val) { | ||
socketBufferSize = val; | ||
return this; | ||
} | ||
public NonBlockingStatsDClientBuilder maxPacketSizeBytes(int val) { | ||
maxPacketSizeBytes = val; | ||
return this; | ||
} | ||
public NonBlockingStatsDClientBuilder processorWorkers(int val) { | ||
processorWorkers = val; | ||
return this; | ||
} | ||
public NonBlockingStatsDClientBuilder senderWorkers(int val) { | ||
senderWorkers = val; | ||
return this; | ||
} | ||
public NonBlockingStatsDClientBuilder blocking(boolean val) { | ||
blocking = val; | ||
return this; | ||
} | ||
public NonBlockingStatsDClientBuilder addressLookup(Callable<SocketAddress> val) { | ||
addressLookup = val; | ||
return this; | ||
} | ||
public NonBlockingStatsDClientBuilder hostname(String val) { | ||
hostname = val; | ||
return this; | ||
} | ||
public NonBlockingStatsDClientBuilder prefix(String val) { | ||
prefix = val; | ||
return this; | ||
} | ||
public NonBlockingStatsDClientBuilder entityID(String val) { | ||
entityID = val; | ||
return this; | ||
} | ||
public NonBlockingStatsDClientBuilder constantTags(String... val) { | ||
constantTags = val; | ||
return this; | ||
} | ||
public NonBlockingStatsDClientBuilder errorHandler(StatsDClientErrorHandler val) { | ||
errorHandler = val; | ||
return this; | ||
} | ||
public NonBlockingStatsDClient build() throws StatsDClientException { | ||
if (addressLookup != null) { | ||
return new NonBlockingStatsDClient(prefix, queueSize, constantTags, errorHandler, | ||
addressLookup, timeout, socketBufferSize, maxPacketSizeBytes, entityID, | ||
bufferPoolSize, processorWorkers, senderWorkers, blocking); | ||
} else { | ||
return new NonBlockingStatsDClient(prefix, queueSize, constantTags, errorHandler, | ||
staticStatsDAddressResolution(hostname, port), timeout, socketBufferSize, maxPacketSizeBytes, | ||
entityID, bufferPoolSize, processorWorkers, senderWorkers, blocking); | ||
} | ||
} | ||
|
||
/** | ||
* Create dynamic lookup for the given host name and port. | ||
* | ||
* @param hostname | ||
* the host name of the targeted StatsD server. If the environment variable | ||
* "DD_AGENT_HOST" is set, this parameter is overwritten by the environment | ||
* variable value. | ||
* @param port | ||
* the port of the targeted StatsD server. If the environment variable | ||
* "DD_DOGSTATSD_PORT" is set, this parameter is overwritten by the environment | ||
* variable value. | ||
* @return a function to perform the lookup | ||
*/ | ||
public static Callable<SocketAddress> volatileAddressResolution(final String hostname, final int port) { | ||
return new Callable<SocketAddress>() { | ||
@Override public SocketAddress call() throws UnknownHostException { | ||
if (port == 0) { // Hostname is a file path to the socket | ||
return new UnixSocketAddress(hostname); | ||
} else { | ||
return new InetSocketAddress(InetAddress.getByName(hostname), port); | ||
} | ||
} | ||
}; | ||
} | ||
|
||
/** | ||
* Lookup the address for the given host name and cache the result. | ||
* | ||
* @param hostname the host name of the targeted StatsD server | ||
* @param port the port of the targeted StatsD server | ||
* @return a function that cached the result of the lookup | ||
* @throws Exception if the lookup fails, i.e. {@link UnknownHostException} | ||
*/ | ||
public static Callable<SocketAddress> staticAddressResolution(final String hostname, final int port) throws Exception { | ||
final SocketAddress address = volatileAddressResolution(hostname, port).call(); | ||
return new Callable<SocketAddress>() { | ||
@Override public SocketAddress call() { | ||
return address; | ||
} | ||
}; | ||
} | ||
|
||
protected static Callable<SocketAddress> staticStatsDAddressResolution(String hostname, int port) throws StatsDClientException { | ||
try { | ||
if (hostname == null) { | ||
hostname = getHostnameFromEnvVar(); | ||
port = getPortFromEnvVar(port); | ||
} | ||
|
||
return staticAddressResolution(hostname, port); | ||
} catch (final Exception e) { | ||
throw new StatsDClientException("Failed to lookup StatsD host", e); | ||
} | ||
} | ||
|
||
/** | ||
* Retrieves host name from the environment variable "DD_AGENT_HOST" | ||
* | ||
* @return host name from the environment variable "DD_AGENT_HOST" | ||
* | ||
* @throws StatsDClientException if the environment variable is not set | ||
*/ | ||
private static String getHostnameFromEnvVar() { | ||
final String hostname = System.getenv(NonBlockingStatsDClient.DD_AGENT_HOST_ENV_VAR); | ||
if (hostname == null) { | ||
throw new StatsDClientException("Failed to retrieve agent hostname from environment variable", null); | ||
} | ||
return hostname; | ||
} | ||
|
||
/** | ||
* Retrieves dogstatsd port from the environment variable "DD_DOGSTATSD_PORT" | ||
* | ||
* @return dogstatsd port from the environment variable "DD_DOGSTATSD_PORT" | ||
* | ||
* @throws StatsDClientException if the environment variable is an integer | ||
*/ | ||
private static int getPortFromEnvVar(final int defaultPort) { | ||
final String statsDPortString = System.getenv(NonBlockingStatsDClient.DD_DOGSTATSD_PORT_ENV_VAR); | ||
if (statsDPortString == null) { | ||
return defaultPort; | ||
} else { | ||
try { | ||
final int statsDPort = Integer.parseInt(statsDPortString); | ||
return statsDPort; | ||
} catch (final NumberFormatException e) { | ||
throw new StatsDClientException("Failed to parse " | ||
+ NonBlockingStatsDClient.DD_DOGSTATSD_PORT_ENV_VAR + "environment variable value", e); | ||
} | ||
} | ||
} | ||
|
||
|
||
} | ||
|
107 changes: 107 additions & 0 deletions
107
src/main/java/com/timgroup/statsd/StatsDBlockingProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package com.timgroup.statsd; | ||
|
||
import java.nio.ByteBuffer; | ||
|
||
import java.util.concurrent.ArrayBlockingQueue; | ||
import java.util.concurrent.BlockingQueue; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
public class StatsDBlockingProcessor extends StatsDProcessor { | ||
|
||
private final BlockingQueue<String> messages; | ||
|
||
StatsDBlockingProcessor(final int queueSize, final StatsDClientErrorHandler handler, | ||
final int maxPacketSizeBytes, final int poolSize, final int workers) | ||
throws Exception { | ||
|
||
super(queueSize, handler, maxPacketSizeBytes, poolSize, workers); | ||
this.messages = new ArrayBlockingQueue<String>(queueSize); | ||
} | ||
|
||
@Override | ||
boolean send(final String message) { | ||
try { | ||
if (!shutdown) { | ||
messages.put(message); | ||
return true; | ||
} | ||
} catch (InterruptedException e) { | ||
} | ||
|
||
return false; | ||
} | ||
|
||
@Override | ||
public void run() { | ||
|
||
for (int i=0 ; i<workers ; i++) { | ||
executor.submit(new Runnable() { | ||
public void run() { | ||
boolean empty; | ||
ByteBuffer sendBuffer; | ||
|
||
try { | ||
sendBuffer = bufferPool.borrow(); | ||
} catch(final InterruptedException e) { | ||
handler.handle(e); | ||
return; | ||
} | ||
|
||
while (!(messages.isEmpty() && shutdown)) { | ||
|
||
try { | ||
|
||
if (Thread.interrupted()) { | ||
return; | ||
} | ||
|
||
final String message = messages.poll(WAIT_SLEEP_MS, TimeUnit.MILLISECONDS); | ||
if (message != null) { | ||
final byte[] data = message.getBytes(MESSAGE_CHARSET); | ||
if (sendBuffer.capacity() < data.length) { | ||
throw new InvalidMessageException(MESSAGE_TOO_LONG, message); | ||
} | ||
if (sendBuffer.remaining() < (data.length + 1)) { | ||
outboundQueue.put(sendBuffer); | ||
sendBuffer = bufferPool.borrow(); | ||
} | ||
if (sendBuffer.position() > 0) { | ||
sendBuffer.put((byte) '\n'); | ||
} | ||
sendBuffer.put(data); | ||
if (null == messages.peek()) { | ||
outboundQueue.put(sendBuffer); | ||
sendBuffer = bufferPool.borrow(); | ||
} | ||
} | ||
} catch (final InterruptedException e) { | ||
if (shutdown) { | ||
endSignal.countDown(); | ||
return; | ||
} | ||
} catch (final Exception e) { | ||
handler.handle(e); | ||
} | ||
} | ||
endSignal.countDown(); | ||
} | ||
}); | ||
} | ||
|
||
boolean done = false; | ||
while(!done) { | ||
try { | ||
endSignal.await(); | ||
done = true; | ||
} catch (final InterruptedException e) { } | ||
} | ||
} | ||
|
||
boolean isShutdown() { | ||
return shutdown; | ||
} | ||
|
||
void shutdown() { | ||
shutdown = true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: As
shutdown
is defined inStatsDProcessor
,isShutdown()
andshutdown()
could be moved toStatsDProcessor
. Same comment could also be applied forStatsDNonBlockingProcessor
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, code was already defined in
StatsdProcessor
just forgot doing this cleanup. Thank you! Great catch again.