/*
* Copyright 2014 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.thrift.io.HeaderTBaseSerializer;
import com.navercorp.pinpoint.thrift.io.HeaderTBaseSerializerFactory;
import org.apache.thrift.TBase;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
/**
* @author netspider
* @author emeroad
* @author koo.taejin
*/
public class UdpDataSender 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;
// Caution. not thread safe
protected final DatagramPacket reusePacket = new DatagramPacket(new byte[1], 1);
protected final DatagramSocket udpSocket;
// Caution. not thread safe
private final HeaderTBaseSerializer serializer = new HeaderTBaseSerializerFactory(false, UDP_MAX_PACKET_LENGTH, false).createSerializer();
private final AsyncQueueingExecutor<Object> executor;
public UdpDataSender(String host, int port, String threadName, int queueSize) {
this(host, port, threadName, queueSize, SOCKET_TIMEOUT, SEND_BUFFER_SIZE);
}
public UdpDataSender(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("UdpDataSender initialized. host={}, port={}", host, port);
this.udpSocket = createSocket(host, port, timeout, sendBufferSize);
this.executor = createAsyncQueueingExecutor(queueSize, threadName);
}
@Override
public boolean send(TBase<?, ?> data) {
return executor.execute(data);
}
@Override
public void stop() {
executor.stop();
}
private DatagramSocket createSocket(String host, int port, int timeout, int sendBufferSize) {
try {
final DatagramSocket datagramSocket = new DatagramSocket();
datagramSocket.setSoTimeout(timeout);
datagramSocket.setSendBufferSize(sendBufferSize);
if (logger.isInfoEnabled()) {
final int checkSendBufferSize = datagramSocket.getSendBufferSize();
if (sendBufferSize != checkSendBufferSize) {
logger.info("DatagramSocket.setSendBufferSize() error. {}!={}", sendBufferSize, checkSendBufferSize);
}
}
final InetSocketAddress serverAddress = new InetSocketAddress(host, port);
datagramSocket.connect(serverAddress);
return datagramSocket;
} catch (SocketException e) {
throw new IllegalStateException("DatagramSocket create fail. Cause" + e.getMessage(), e);
}
}
protected void sendPacket(Object message) {
if (message instanceof TBase) {
final TBase dto = (TBase) message;
// do not copy bytes because it's single threaded
final byte[] internalBufferData = serialize(this.serializer, dto);
if (internalBufferData == null) {
logger.warn("interBufferData is null");
return;
}
final int internalBufferSize = this.serializer.getInterBufferSize();
if (isLimit(internalBufferSize)) {
// When packet size is greater than UDP packet size limit, it's better to discard packet than let the socket API fails.
logger.warn("discard packet. Caused:too large message. size:{}, {}", internalBufferSize, dto);
return;
}
// it's safe to reuse because it's single threaded
reusePacket.setData(internalBufferData, 0, internalBufferSize);
try {
udpSocket.send(reusePacket);
if (isDebug) {
logger.debug("Data sent. size:{}, {}", internalBufferSize, dto);
}
} catch (IOException e) {
logger.info("packet send error. size:{}, {}", internalBufferSize, dto, e);
}
} else {
logger.warn("sendPacket fail. invalid type:{}", message != null ? message.getClass() : null);
return;
}
}
// for test
protected boolean isLimit(int interBufferSize) {
if (interBufferSize > UDP_MAX_PACKET_LENGTH) {
return true;
}
return false;
}
}