package com.outbrain.gruffalo.netty;
import com.google.common.base.Preconditions;
import com.outbrain.swinfra.metrics.api.Gauge;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.outbrain.gruffalo.util.HostName2MetricName;
import com.outbrain.swinfra.metrics.api.MetricFactory;
import com.outbrain.swinfra.metrics.api.Counter;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Time: 8/4/13 12:30 PM
*
* @author Eran Harel
*/
public class NettyGraphiteClient implements GraphiteClient {
private static final Logger log = LoggerFactory.getLogger(NettyGraphiteClient.class);
private final int inFlightBatchesLowThreshold;
private final int inFlightBatchesHighThreshold;
private final Throttler throttler;
private final AtomicInteger inFlightBatches = new AtomicInteger(0);
private final Counter errorCounter;
private final Counter pushBackCounter;
private final Counter reconnectCounter;
private final Counter rejectedCounter;
private final Counter publishedCounter;
private final String host;
private final ChannelFutureListener opListener = new ChannelFutureListener() {
@Override
public void operationComplete(final ChannelFuture future) throws Exception {
final int inFlightBaches = inFlightBatches.decrementAndGet();
if(inFlightBaches == inFlightBatchesLowThreshold) {
throttler.restoreClientReads();
}
if (future.isSuccess()) {
publishedCounter.inc();
} else {
errorCounter.inc();
if (log.isDebugEnabled()) {
log.debug("Failed to write to {}: {}", host, future.cause().toString());
}
}
}
};
private GraphiteClientChannelInitializer channelInitializer;
private volatile ChannelFuture channelFuture;
public NettyGraphiteClient(final Throttler throttler, final int inFlightBatchesHighThreshold, final MetricFactory metricFactory, final String host) {
Preconditions.checkArgument(0 < inFlightBatchesHighThreshold);
this.inFlightBatchesHighThreshold = inFlightBatchesHighThreshold;
this.inFlightBatchesLowThreshold = inFlightBatchesHighThreshold / 5;
Preconditions.checkNotNull(metricFactory, "metricFactory must not be null");
this.throttler = Preconditions.checkNotNull(throttler, "throttler must not be null");
this.host = host;
final String graphiteCompatibleHostName = HostName2MetricName.graphiteCompatibleHostPortName(host);
errorCounter = metricFactory.createCounter(getClass().getSimpleName(), graphiteCompatibleHostName + ".errors");
pushBackCounter = metricFactory.createCounter(getClass().getSimpleName(), graphiteCompatibleHostName + ".pushBack");
reconnectCounter = metricFactory.createCounter(getClass().getSimpleName(), graphiteCompatibleHostName + ".reconnect");
rejectedCounter = metricFactory.createCounter(getClass().getSimpleName(), graphiteCompatibleHostName + ".rejected");
publishedCounter = metricFactory.createCounter(getClass().getSimpleName(), graphiteCompatibleHostName + ".published");
metricFactory.registerGauge(getClass().getSimpleName(), graphiteCompatibleHostName + ".inFlightBatches", new Gauge<Integer>() {
@Override
public Integer getValue() {
return inFlightBatches.get();
}
});
log.info("Client for [{}] initialized", host);
}
public void setChannelInitializer(final GraphiteClientChannelInitializer channelInitializer) {
this.channelInitializer = channelInitializer;
}
@Override
public void connect() {
reconnectCounter.inc();
log.info("Client for [{}] is reconnecting", host);
channelFuture = channelInitializer.connect();
}
@Override
public boolean publishMetrics(final String metrics) {
if (channelFuture.isDone()) {
final int numInFlight = inFlightBatches.incrementAndGet();
if(inFlightBatchesHighThreshold <= numInFlight) {
onPushBack();
throttler.pushBackClients();
}
channelFuture.channel().writeAndFlush(metrics).addListener(opListener);
return true;
} else {
rejectedCounter.inc();
return false;
}
}
@Override
public void onPushBack() {
pushBackCounter.inc();
}
}