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(); } }