/**
* Helios, OpenSource Monitoring
* Brought to you by the Helios Development Group
*
* Copyright 2007, Helios Development Group and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
*/
package org.helios.apmrouter.sender.netty;
import org.helios.apmrouter.OpCode;
import org.helios.apmrouter.jmx.mbeanserver.AgentMBeanServerConnectionFactory;
import org.helios.apmrouter.metric.AgentIdentity;
import org.helios.apmrouter.sender.AbstractSender;
import org.helios.apmrouter.sender.SynchOpSupport;
import org.helios.apmrouter.trace.DirectMetricCollection;
import org.helios.apmrouter.trace.DirectMetricCollection.SplitDMC;
import org.helios.apmrouter.trace.DirectMetricCollection.SplitReader;
import org.jboss.netty.bootstrap.ConnectionlessBootstrap;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.buffer.DirectChannelBufferFactory;
import org.jboss.netty.channel.*;
import org.jboss.netty.channel.socket.nio.NioDatagramChannelFactory;
import org.jboss.netty.handler.logging.LoggingHandler;
import org.jboss.netty.logging.InternalLogLevel;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.URI;
import java.nio.channels.ClosedChannelException;
import java.util.concurrent.CountDownLatch;
import static org.helios.apmrouter.util.Methods.nvl;
/**
* <p>Title: UDPSender</p>
* <p>Description: A Netty unicast UDP sender implementation</p>
* <p>Company: Helios Development Group LLC</p>
* @author Whitehead (nwhitehead AT heliosdev DOT org)
* <p><code>org.helios.apmrouter.sender.netty.UDPSender</code></p>
* FIXME: Where to start ...... 1. Listen on channel closed exceptions, start reconnect loop, count dropped metrics in the interrim
*/
public class UDPSender extends AbstractSender {
/** The maximum size of the payload this sender can reliably expect to be transmitted */
public static final int MAXSIZE = 1024;
/** The netty bootstrap */
protected final ConnectionlessBootstrap bstrap;
/** The logging handler for debug */
private LoggingHandler loggingHandler;
/** The listener handle to handle requests/responses from the server */
protected final SimpleChannelUpstreamHandler listenerHandler = new SimpleChannelUpstreamHandler() {
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
log("[Listener] Caught exception event [" + e.getCause() + "]");
e.getCause().printStackTrace(System.err);
super.exceptionCaught(ctx, e);
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
if(e.getChannel().getLocalAddress().equals(e.getRemoteAddress())) { /* No Op */
} else {
Object msg = e.getMessage();
if(msg instanceof ChannelBuffer) {
ChannelBuffer buff = (ChannelBuffer)msg;
OpCode opCode = OpCode.valueOf(buff.readByte());
switch (opCode) {
case JMX_REQUEST:
AgentMBeanServerConnectionFactory.handleJMXRequest(e.getChannel(), e.getRemoteAddress(), buff);
break;
case JMX_MBS_INQUIRY:
AgentMBeanServerConnectionFactory.sendMBeanServerDomains(e.getChannel(), e.getRemoteAddress());
break;
case RESET:
metricCatalog.resetTokens();
ChannelBuffer rsetConfirm = ChannelBuffers.buffer(1);
rsetConfirm.writeByte(OpCode.RESET_CONFIRM.op());
senderChannel.write(rsetConfirm,e.getRemoteAddress());
break;
case CONFIRM_METRIC:
int keyLength = buff.readInt();
byte[] keyBytes = new byte[keyLength];
buff.readBytes(keyBytes);
String key = new String(keyBytes);
SynchOpSupport.cancelLatch(key);
break;
case PING_RESPONSE:
decodePing(buff);
break;
case PING:
long pingKey = buff.readLong();
ChannelBuffer ping = ChannelBuffers.buffer(1+8);
ping.writeByte(OpCode.PING_RESPONSE.op());
ping.writeLong(pingKey);
senderChannel.write(ping,e.getRemoteAddress());
break;
case HELLO_CONFIRM:
SynchOpSupport.cancelLatch("Hello");
break;
case WHO:
byte[] hostBytes = AgentIdentity.ID.getHostName().getBytes();
byte[] agentBytes = AgentIdentity.ID.getAgentName().getBytes();
ChannelBuffer cb = ChannelBuffers.directBuffer(1 + 4 + 4 + hostBytes.length + agentBytes.length);
cb.writeByte(OpCode.WHO_RESPONSE.op());
cb.writeInt(hostBytes.length);
cb.writeBytes(hostBytes);
cb.writeInt(agentBytes.length);
cb.writeBytes(agentBytes);
SocketAddress sa = e.getRemoteAddress();
log("Sending WHO Response to [" + sa + "]");
e.getChannel().write(cb, sa);
//AgentMBeanServerConnectionFactory.sendMBeanServerDomains(e.getChannel(), e.getRemoteAddress());
break;
case SEND_METRIC_TOKEN:
int fqnLength = buff.readInt();
byte[] bytes = new byte[fqnLength];
buff.readBytes(bytes);
String fqn = new String(bytes);
long token = buff.readLong();
metricCatalog.setToken(fqn, token);
processedTokens.incrementAndGet();
break;
case ON_METRIC_URI_EVENT:
onMetricURIEvent(buff);
break;
case METRIC_URI_SUB_CONFIRM:
case METRIC_URI_UNSUB_CONFIRM:
onMetricURIOpResponse(buff);
default:
break;
}
}
}
}
};
/**
* Executed when a disconnect is detected
*/
protected void processDisconnect() {
connected.set(false);
senderChannel = null;
}
/**
* Returns a built instance of a UDPSender for the passed URI
* @param serverURI The host/port to send to in the form of a URI. e.g. <b><code>udp://myhostname:2094</code></b>.
* @return a UDPSender
*/
public static UDPSender getInstance(URI serverURI) {
UDPSender sender = (UDPSender) senders.get(nvl(serverURI, "Server URI"));
if(sender==null) {
synchronized(senders) {
sender = (UDPSender) senders.get(serverURI);
if(sender==null) {
sender = new UDPSender(serverURI);
senders.put(serverURI, sender);
}
}
}
return sender;
}
/**
* Creates a new UDPSender
* @param serverURI The host/port to send to
*/
private UDPSender(final URI serverURI) {
super(serverURI);
//InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory());
channelStateListener.addChannelStateAware(this);
loggingHandler = new LoggingHandler(InternalLogLevel.ERROR, true);
channelFactory = new NioDatagramChannelFactory(workerPool);
bstrap = new ConnectionlessBootstrap(channelFactory);
bstrap.setPipelineFactory(this);
bstrap.setOption("broadcast", true);
bstrap.setOption("localAddress", new InetSocketAddress(0));
bstrap.setOption("remoteAddress", new InetSocketAddress(serverURI.getHost(), serverURI.getPort()));
bstrap.setOption("receiveBufferSizePredictorFactory", new FixedReceiveBufferSizePredictorFactory(2048));
listeningSocketAddress = new InetSocketAddress("0.0.0.0", 0);
//listeningSocketAddress = new InetSocketAddress("127.0.0.1", 0);
//senderChannel = (NioDatagramChannel) channelFactory.newChannel(getPipeline());
senderChannel = bstrap.bind();
closeGroup.add(senderChannel);
log("Listening on [" + senderChannel.getLocalAddress()+ "]");
// senderChannel.bind().addListener(new ChannelFutureListener() {
// public void operationComplete(ChannelFuture f) throws Exception {
// if(f.isSuccess()) {
// log("Listening on [" + f.getChannel().getLocalAddress()+ "]");
// } else {
// log("Failed to start listener. Stack trace follows");
// f.getCause().printStackTrace(System.err);
//
// }
//
// }
// });
senderChannel.getConfig().setBufferFactory(new DirectChannelBufferFactory());
// senderChannel.connect(socketAddress).addListener(new ChannelFutureListener() {
// @Override
// public void operationComplete(ChannelFuture future) throws Exception {
// connected.set(true);
// sentryState.setState(SentryState.CALLBACK);
// }
// });
//socketAddress = new InetSocketAddress("239.192.74.66", 25826);
sendHello();
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.sender.netty.handler.ChannelStateAware#getInterestedChannelStates()
*/
@Override
public ChannelState[] getInterestedChannelStates() {
return new ChannelState[]{ChannelState.CONNECTED};
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.sender.netty.handler.ChannelStateAware#onChannelStateEvent(boolean, org.jboss.netty.channel.ChannelStateEvent)
*/
@Override
public void onChannelStateEvent(boolean upstream, ChannelStateEvent stateEvent) {
if(upstream && stateEvent.getValue().equals(Boolean.FALSE)) {
processDisconnect();
log("Channel Disconnected");
}
}
/**
* {@inheritDoc}
* @see org.jboss.netty.channel.ChannelPipelineFactory#getPipeline()
*/
@Override
public ChannelPipeline getPipeline() {
ChannelPipeline pipeline = Channels.pipeline();
//pipeline.addLast("logging", loggingHandler);
pipeline.addLast("metric-encoder", metricEncoder);
pipeline.addLast("listener", listenerHandler);
return pipeline;
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.sender.AbstractSender#doSendHello()
*/
@Override
public void doSendHello() {
ChannelBuffer cb = ChannelBuffers.buffer(1);
cb.writeByte(OpCode.HELLO.op());
senderChannel.write(cb, socketAddress);
}
/**
* {@inheritDoc}
* @see org.helios.apmrouter.sender.ISender#send(org.helios.apmrouter.trace.DirectMetricCollection)
*/
@Override
public void send(final DirectMetricCollection dcm) {
if(shutdown.get()) return;
if(dcm==null) return;
final int METRIC_COUNT = dcm.getMetricCount();
try {
if(dcm.getSize()<MAXSIZE) {
final int mcount = dcm.getMetricCount();
ChannelFuture channelFuture = senderChannel.write(dcm, socketAddress);
//System.out.println("Sent to [" + socketAddress + "]");
channelFuture.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
if(future.isSuccess()) {
sent.addAndGet(mcount);
} else {
//long d = failed.addAndGet(mcount);
//System.err.println("SenderFactory Fails:" + d );
if(future.getCause()!=null && !shutdown.get()) {
if(future.getCause() instanceof ClosedChannelException) {
log("SenderFactory Channel Disconnected");
processDisconnect();
} else {
future.getCause().printStackTrace(System.err);
}
}
}
}
});
return;
}
SplitDMC sr = dcm.newSplitReader(MAXSIZE);
for(final DirectMetricCollection d: sr) {
final boolean last = !sr.hasNext();
final int mcount = d.getMetricCount();
ChannelFuture channelFuture = senderChannel.write(d, socketAddress);
channelFuture.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
if(future.isSuccess()) {
sent.addAndGet(mcount);
} else {
//long d = failed.addAndGet(mcount);
//System.err.println("SenderFactory Fails:" + d );
if(future.getCause()!=null) {
if(future.getCause() instanceof ClosedChannelException) {
log("SenderFactory Channel Disconnected");
processDisconnect();
} else {
future.getCause().printStackTrace(System.err);
}
}
}
}
});
if(last) channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
//ch.close();
}
});
}
dcm.destroy();
dropped.addAndGet(((SplitReader)sr).getDrops());
} catch (Exception cce) {
if(cce instanceof ClosedChannelException) {
log("SenderFactory Channel Disconnected");
processDisconnect();
} else {
cce.printStackTrace(System.err);
}
}
}
}