/*
* Copyright 2016 NAVER Corp.
*
* 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 com.navercorp.pinpoint.profiler.sender;
import com.navercorp.pinpoint.rpc.PinpointSocketException;
import com.navercorp.pinpoint.rpc.buffer.ByteBufferFactory;
import com.navercorp.pinpoint.rpc.buffer.ByteBufferFactoryLocator;
import com.navercorp.pinpoint.rpc.buffer.ByteBufferType;
import com.navercorp.pinpoint.thrift.io.ByteBufferOutputStream;
import com.navercorp.pinpoint.thrift.io.HeaderTBaseSerializer2;
import com.navercorp.pinpoint.thrift.io.HeaderTBaseSerializerFactory2;
import org.apache.thrift.TBase;
import org.apache.thrift.TException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
/**
* @Author Taejin Koo
*/
public class NioUDPDataSender extends AbstractDataSender implements DataSender {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
protected final boolean isDebug = logger.isDebugEnabled();
public static final int SOCKET_TIMEOUT = 1000 * 5;
public static final int SEND_BUFFER_SIZE = 1024 * 64 * 16;
public static final int UDP_MAX_PACKET_LENGTH = 65507;
private final DatagramChannel datagramChannel;
private final HeaderTBaseSerializer2 serializer;
private final ByteBufferOutputStream byteBufferOutputStream;
private final AsyncQueueingExecutor<Object> executor;
private volatile boolean closed = false;
public NioUDPDataSender(String host, int port, String threadName, int queueSize) {
this(host, port, threadName, queueSize, SOCKET_TIMEOUT, SEND_BUFFER_SIZE);
}
public NioUDPDataSender(String host, int port, String threadName, int queueSize, int timeout, int sendBufferSize) {
if (host == null ) {
throw new NullPointerException("host must not be null");
}
if (threadName == null) {
throw new NullPointerException("threadName must not be null");
}
if (queueSize <= 0) {
throw new IllegalArgumentException("queueSize");
}
if (timeout <= 0) {
throw new IllegalArgumentException("timeout");
}
if (sendBufferSize <= 0) {
throw new IllegalArgumentException("sendBufferSize");
}
// TODO If fail to create socket, stop agent start
logger.info("NioUDPDataSender initialized. host={}, port={}", host, port);
this.datagramChannel = createChannel(host, port, timeout, sendBufferSize);
HeaderTBaseSerializerFactory2 serializerFactory = new HeaderTBaseSerializerFactory2();
this.serializer = serializerFactory.createSerializer();
ByteBufferFactory bufferFactory = ByteBufferFactoryLocator.getFactory(ByteBufferType.DIRECT);
ByteBuffer byteBuffer = bufferFactory.getBuffer(UDP_MAX_PACKET_LENGTH);
this.byteBufferOutputStream = new ByteBufferOutputStream(byteBuffer);
this.executor = createAsyncQueueingExecutor(queueSize, threadName);
}
private DatagramChannel createChannel(String host, int port, int timeout, int sendBufferSize) {
DatagramChannel datagramChannel = null;
DatagramSocket socket = null;
try {
datagramChannel = DatagramChannel.open();
socket = datagramChannel.socket();
socket.setSoTimeout(timeout);
socket.setSendBufferSize(sendBufferSize);
if (logger.isWarnEnabled()) {
final int checkSendBufferSize = socket.getSendBufferSize();
if (sendBufferSize != checkSendBufferSize) {
logger.warn("DatagramChannel.setSendBufferSize() error. {}!={}", sendBufferSize, checkSendBufferSize);
}
}
InetSocketAddress serverAddress = new InetSocketAddress(host, port);
datagramChannel.connect(serverAddress);
return datagramChannel;
} catch (IOException e) {
if (socket != null) {
socket.close();
}
if (datagramChannel != null) {
try {
datagramChannel.close();
} catch (IOException ignored) {
}
}
throw new IllegalStateException("DatagramChannel create fail. Cause" + e.getMessage(), e);
}
}
@Override
public boolean send(TBase<?, ?> data) {
return executor.execute(data);
}
@Override
public void stop() {
try {
closed = true;
executor.stop();
} finally {
try {
byteBufferOutputStream.close();
} catch (IOException e) {
// ignore
}
}
}
protected void sendPacket(Object message) {
if (closed) {
throw new PinpointSocketException("NioUDPDataSender already closed.");
}
if (message instanceof TBase) {
byteBufferOutputStream.clear();
final TBase dto = (TBase) message;
// do not copy bytes because it's single threaded
try {
serializer.serialize(dto, byteBufferOutputStream);
} catch (TException e) {
throw new PinpointSocketException("Serialize " + dto + " failed. Error:" + e.getMessage(), e);
}
ByteBuffer byteBuffer = byteBufferOutputStream.getByteBuffer();
int bufferSize = byteBuffer.remaining();
try {
datagramChannel.write(byteBuffer);
} catch (IOException e) {
final Thread currentThread = Thread.currentThread();
if (currentThread.isInterrupted()) {
logger.warn("{} thread interrupted.", currentThread.getName());
throw new PinpointSocketException(currentThread.getName() + " thread interrupted.", e);
} else {
throw new PinpointSocketException("packet send error. size:" + bufferSize + ", " + dto, e);
}
}
} else {
logger.warn("sendPacket fail. invalid type:{}", message != null ? message.getClass() : null);
return;
}
}
}