package org.skywalking.apm.agent.core.queue;
import com.lmax.disruptor.EventHandler;
import com.lmax.disruptor.RingBuffer;
import com.lmax.disruptor.dsl.Disruptor;
import com.lmax.disruptor.util.DaemonThreadFactory;
import org.skywalking.apm.agent.core.conf.Config;
import org.skywalking.apm.agent.core.boot.StatusBootService;
import org.skywalking.apm.agent.core.context.TracerContext;
import org.skywalking.apm.agent.core.context.TracerContextListener;
import org.skywalking.apm.logging.ILog;
import org.skywalking.apm.logging.LogManager;
import org.skywalking.apm.trace.TraceSegment;
import java.util.LinkedList;
import java.util.List;
/**
* {@link TraceSegmentProcessQueue} is a proxy of {@link Disruptor}, High Performance Inter-Thread MQ.
* <p>
* {@see https://github.com/LMAX-Exchange/disruptor}
* <p>
* Created by wusheng on 2017/2/17.
*/
public class TraceSegmentProcessQueue extends StatusBootService implements TracerContextListener, EventHandler<TraceSegmentHolder> {
private static final ILog logger = LogManager.getLogger(TraceSegmentProcessQueue.class);
private Disruptor<TraceSegmentHolder> disruptor;
private RingBuffer<TraceSegmentHolder> buffer;
private TraceSegment[] secondLevelCache;
private volatile int cacheIndex;
public TraceSegmentProcessQueue() {
disruptor = new Disruptor<TraceSegmentHolder>(TraceSegmentHolder.Factory.INSTANCE, Config.Buffer.SIZE, DaemonThreadFactory.INSTANCE);
secondLevelCache = new TraceSegment[Config.Buffer.SIZE];
cacheIndex = 0;
disruptor.handleEventsWith(this);
buffer = disruptor.getRingBuffer();
}
@Override
protected void bootUpWithStatus() {
TracerContext.ListenerManager.add(this);
disruptor.start();
}
/**
* Append the given traceSegment to the queue, wait for sending to Collector.
*
* @param traceSegment finished {@link TraceSegment}
*/
@Override
public void afterFinished(TraceSegment traceSegment) {
if (isStarted() && traceSegment.isSampled()) {
long sequence = this.buffer.next(); // Grab the next sequence
try {
TraceSegmentHolder data = this.buffer.get(sequence);
data.setValue(traceSegment);
} finally {
this.buffer.publish(sequence);
}
}
}
@Override
public void onEvent(TraceSegmentHolder event, long sequence, boolean endOfBatch) throws Exception {
TraceSegment traceSegment = event.getValue();
try {
if (secondLevelCache[cacheIndex] == null) {
secondLevelCache[cacheIndex] = traceSegment;
} else {
/**
* If your application has very high throughput(also called tps/qps),
* this log message will be output in very high frequency.
* And this is not suppose to happen. Disable log.warn or expend {@link Config.Disruptor.BUFFER_SIZE}
*/
logger.warn("TraceSegmentProcessQueue has data conflict. Discard the new TraceSegment.");
}
/**
* increase the {@link #cacheIndex}, if it is out of range, reset it.
*/
cacheIndex++;
if (cacheIndex == secondLevelCache.length) {
cacheIndex = 0;
}
} finally {
event.clear();
}
}
public List<TraceSegment> getCachedTraceSegments() {
List<TraceSegment> segmentList = new LinkedList<TraceSegment>();
for (int i = 0; i < secondLevelCache.length; i++) {
TraceSegment segment = secondLevelCache[i];
if (segment != null) {
segmentList.add(segment);
secondLevelCache[i] = null;
}
}
return segmentList;
}
}