/*
* 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 java.io.IOException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import org.apache.thrift.TBase;
import org.apache.thrift.TException;
import com.navercorp.pinpoint.common.util.PinpointThreadFactory;
import com.navercorp.pinpoint.thrift.io.ChunkHeaderBufferedTBaseSerializer;
import com.navercorp.pinpoint.thrift.io.ChunkHeaderBufferedTBaseSerializerFactory;
import com.navercorp.pinpoint.thrift.io.ChunkHeaderBufferedTBaseSerializerFlushHandler;
/**
* split & buffering
*
* only use pair collector-ChunkedUDPReceiver
*
* @author jaehong.kim
*
*/
public class BufferedUdpDataSender extends UdpDataSender {
private static final int CHUNK_SIZE = 1024 * 16;
private static final String SCHEDULED_FLUSH = "BufferedUdpDataSender-ScheduledFlush";
private final ChunkHeaderBufferedTBaseSerializer chunkHeaderBufferedSerializer = new ChunkHeaderBufferedTBaseSerializerFactory().createSerializer();
private final Thread flushThread;
public BufferedUdpDataSender(String host, int port, String threadName, int queueSize) {
this(host, port, threadName, queueSize, SOCKET_TIMEOUT, SEND_BUFFER_SIZE, CHUNK_SIZE);
}
public BufferedUdpDataSender(String host, int port, String threadName, int queueSize, int timeout, int sendBufferSize, int chunkSize) {
super(host, port, threadName, queueSize, timeout, sendBufferSize);
chunkHeaderBufferedSerializer.setChunkSize(chunkSize);
chunkHeaderBufferedSerializer.setFlushHandler(new ChunkHeaderBufferedTBaseSerializerFlushHandler() {
@Override
public void handle(byte[] buffer, int offset, int length) {
if (buffer == null) {
logger.warn("interBufferData is null");
return;
}
final int internalBufferSize = length;
if (isLimit(internalBufferSize)) {
logger.warn("discard packet. Caused:too large message. size:{}", internalBufferSize);
return;
}
// We can reuse this because this runs in single thread
reusePacket.setData(buffer, 0, internalBufferSize);
try {
udpSocket.send(reusePacket);
if (isDebug) {
logger.debug("Data sent. {size={}}", internalBufferSize);
}
} catch (IOException e) {
logger.warn("packet send error. size:{}", internalBufferSize, e);
}
}
});
flushThread = startScheduledFlush();
}
// for test
String getFlushThreadName() {
return flushThread.getName();
}
private Thread startScheduledFlush() {
final ThreadFactory threadFactory = new PinpointThreadFactory(SCHEDULED_FLUSH, true);
final Thread thread = threadFactory.newThread(new Runnable() {
@Override
public void run() {
final Thread currentThread = Thread.currentThread();
while (!currentThread.isInterrupted()) {
try {
chunkHeaderBufferedSerializer.flush();
} catch (TException e) {
logger.warn("Failed to flush. caused={}", e.getMessage(), e);
}
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException ignored) {
currentThread.interrupt();
}
}
logger.info("stop ScheduledFlush {} - {}", currentThread.getName(), currentThread.getId());
}
});
logger.info("stop ScheduledFlush {} - {}", thread.getName(), thread.getId());
thread.start();
return thread;
}
@Override
protected void sendPacket(Object message) {
if (message instanceof TBase) {
try {
final TBase<?, ?> packet = (TBase<?, ?>) message;
chunkHeaderBufferedSerializer.add(packet);
logger.debug("Send packet {}", packet);
} catch (TException e) {
logger.warn("sendPacket fail.", e);
}
} else {
logger.warn("sendPacket fail. invalid type:{}", message != null ? message.getClass() : null);
return;
}
}
@Override
public void stop() {
super.stop();
stopFlushThread();
}
private void stopFlushThread() {
final Thread flushThread = this.flushThread;
// terminate thread
flushThread.interrupt();
try {
flushThread.join(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}