/* * 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.profiler.context.Span; import com.navercorp.pinpoint.profiler.context.SpanChunk; import com.navercorp.pinpoint.profiler.sender.planer.SendDataPlaner; import com.navercorp.pinpoint.profiler.sender.planer.SpanChunkStreamSendDataPlaner; import com.navercorp.pinpoint.profiler.util.ByteBufferUtils; import com.navercorp.pinpoint.profiler.util.ObjectPool; import com.navercorp.pinpoint.thrift.io.HeaderTBaseSerializer; import org.apache.thrift.TBase; 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; import java.util.Iterator; /** * @author Taejin Koo */ public class SpanStreamUdpSender extends AbstractDataSender { protected final Logger logger = LoggerFactory.getLogger(this.getClass()); public static final int SOCKET_TIMEOUT = 1000 * 5; public static final int SEND_BUFFER_SIZE = 1024 * 64 * 16; public static final int DEFAULT_BUFFER_SIZE = 1024 * 16; public static final int UDP_MAX_PACKET_LENGTH = 65507; private final SpanStreamSendDataFactory spanStreamSendDataFactory; private final DatagramChannel udpChannel; private final AsyncQueueingExecutor<Object> executor; private final ObjectPool<HeaderTBaseSerializer> serializerPool; private final SpanStreamSendDataSerializer spanStreamSendDataSerializer; private final StandbySpanStreamDataSendWorker standbySpanStreamDataSendWorker; public SpanStreamUdpSender(String host, int port, String threadName, int queueSize) { this(host, port, threadName, queueSize, SOCKET_TIMEOUT, SEND_BUFFER_SIZE); } public SpanStreamUdpSender(String host, int port, String threadName, int queueSize, int timeout, int sendBufferSize) { this(host, port, threadName, queueSize, timeout, sendBufferSize, DEFAULT_BUFFER_SIZE); } public SpanStreamUdpSender(String host, int port, String threadName, int queueSize, int timeout, int sendBufferSize, int dataBufferSize) { 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.udpChannel = createChannel(host, port, timeout, sendBufferSize); HeaderTBaseSerializerPoolFactory headerTBaseSerializerPoolFactory = new HeaderTBaseSerializerPoolFactory(false, dataBufferSize, true); this.serializerPool = new ObjectPool<HeaderTBaseSerializer>(headerTBaseSerializerPoolFactory, 16); this.spanStreamSendDataSerializer = new SpanStreamSendDataSerializer(); this.spanStreamSendDataFactory = new SpanStreamSendDataFactory(dataBufferSize, 16, serializerPool); this.standbySpanStreamDataSendWorker = new StandbySpanStreamDataSendWorker(new FlushHandler(), new StandbySpanStreamDataStorage()); this.standbySpanStreamDataSendWorker.start(); 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 { standbySpanStreamDataSendWorker.stop(); } catch (Exception e) { logger.debug("Failed to stop standbySpanStreamDataSendWorker.", e); } try { udpChannel.close(); } catch (IOException e) { logger.debug("Failed to close udp channel.", e); } executor.stop(); } @Override protected void sendPacket(Object message) { if (logger.isDebugEnabled()) { logger.debug("sendPacket message:{}", message); } if (message instanceof Span) { handleSpan((Span) message); } else if (message instanceof SpanChunk) { handleSpanChunk((SpanChunk) message); } else { logger.info("sendPacket fail. invalid type:{}", messageToString(message)); } } private String messageToString(Object message) { if(message == null) { return null; } return message.getClass().toString(); } private void handleSpan(Span span) { if (span == null) { return; } HeaderTBaseSerializer serializer = serializerPool.getObject(); PartitionedByteBufferLocator partitionedByteBufferLocator = spanStreamSendDataSerializer.serializeSpanStream(serializer, span); if (partitionedByteBufferLocator == null) { serializerPool.returnObject(serializer); return; } doAddAndFlush(partitionedByteBufferLocator, serializer); } // streaming private void handleSpanChunk(SpanChunk spanChunk) { if (spanChunk == null) { return; } HeaderTBaseSerializer serializer = serializerPool.getObject(); PartitionedByteBufferLocator partitionedByteBufferLocator = spanStreamSendDataSerializer.serializeSpanChunkStream(serializer, spanChunk); if (partitionedByteBufferLocator == null) { serializerPool.returnObject(serializer); return; } doAddAndFlush(partitionedByteBufferLocator, serializer); } private void doAddAndFlush(PartitionedByteBufferLocator partitionedByteBufferLocator, HeaderTBaseSerializer serializer) { logger.debug("PartitionedByteBufferLocator {}.", partitionedByteBufferLocator); SpanStreamSendData currentSpanStreamSendData = standbySpanStreamDataSendWorker.getStandbySpanStreamSendData(); if (currentSpanStreamSendData == null) { currentSpanStreamSendData = spanStreamSendDataFactory.create(); } try { if (!currentSpanStreamSendData.addBuffer(partitionedByteBufferLocator.getByteBuffer())) { SendDataPlaner sendDataPlaner = new SpanChunkStreamSendDataPlaner(partitionedByteBufferLocator, spanStreamSendDataFactory); Iterator<SpanStreamSendData> sendDataIterator = sendDataPlaner.getSendDataIterator(currentSpanStreamSendData, serializer); while (sendDataIterator.hasNext()) { SpanStreamSendData sendData = sendDataIterator.next(); if (sendData.getFlushMode() == SpanStreamSendDataMode.FLUSH) { flush(sendData); } else if (sendData.getFlushMode() == SpanStreamSendDataMode.WAIT_BUFFER) { boolean isAdded = standbySpanStreamDataSendWorker.addStandbySpanStreamData(sendData); if (!isAdded) { flush(sendData); } } } } else { boolean isAdded = standbySpanStreamDataSendWorker.addStandbySpanStreamData(currentSpanStreamSendData); if (!isAdded) { flush(currentSpanStreamSendData); } } } catch (IOException e) { logger.warn("UDPChannel write fail.", e); } } private void flush(SpanStreamSendData spanStreamSendData) throws IOException { if (spanStreamSendData == null) { return; } ByteBuffer[] byteBuffers = spanStreamSendData.getSendBuffers(); int remainingLength = ByteBufferUtils.getRemaining(byteBuffers); try { if (remainingLength != 0) { long sentBufferSize = udpChannel.write(byteBuffers); if (remainingLength != sentBufferSize) { logger.warn("sent buffer {}/{}.", sentBufferSize, remainingLength); } else { logger.debug("Data sent. size:{}, {}", sentBufferSize); } } } finally { spanStreamSendData.done(); } } class FlushHandler implements StandbySpanStreamDataFlushHandler { @Override public void handleFlush(SpanStreamSendData spanStreamSendData) { if (spanStreamSendData == null) { return; } try { ByteBuffer[] byteBuffers = spanStreamSendData.getSendBuffers(); int remainingLength = ByteBufferUtils.getRemaining(byteBuffers); if (remainingLength != 0) { long sentBufferSize = udpChannel.write(byteBuffers); if (remainingLength != sentBufferSize) { logger.warn("sent buffer {}/{}.", sentBufferSize, remainingLength); } else { // TODO need check ???? } } } catch (IOException e) { logger.warn("Failed to flush span stream data.", e); } finally { spanStreamSendData.done(); } } @Override public void exceptionCaught(SpanStreamSendData spanStreamSendData, Throwable e) { logger.warn("Failed to flush span stream data.", e); } } }