/* * 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.test; import java.io.PrintStream; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.apache.thrift.TBase; import com.navercorp.pinpoint.profiler.context.Span; import com.navercorp.pinpoint.profiler.context.SpanEvent; /** * @author Jongho Moon */ public class OrderedSpanRecorder implements ListenableDataSender.Listener, Iterable<TBase<?, ?>> { private static final int ROOT_SEQUENCE = -1; private static final int ASYNC_ID_NOT_SET = -1; private static final int ASYNC_SEQUENCE_NOT_SET = -1; private final List<Item> list = new ArrayList<Item>(); private static final class Item implements Comparable<Item> { private final TBase<?, ?> value; private final long time; private final long spanId; private final int sequence; private final int asyncId; private final int asyncSequence; public Item(TBase<?, ?> value, long time, long spanId, int sequence) { this(value, time, spanId, sequence, ASYNC_ID_NOT_SET, ASYNC_SEQUENCE_NOT_SET); } public Item(TBase<?, ?> value, long time, long spanId, int sequence, int asyncId, int asyncSequence) { this.value = value; this.time = time; this.spanId = spanId; this.sequence = sequence; this.asyncId = asyncId; this.asyncSequence = asyncSequence; } @Override public int compareTo(Item o) { if (this.asyncId == ASYNC_ID_NOT_SET && o.asyncId == ASYNC_ID_NOT_SET) { return compareItems(this, o); } else if (this.asyncId != ASYNC_ID_NOT_SET && o.asyncId != ASYNC_ID_NOT_SET) { return compareAsyncItems(this, o); } else { if (this.asyncId == ASYNC_ID_NOT_SET) { return -1; } else { return 1; } } } private static int compareItems(Item lhs, Item rhs) { if (lhs.time < rhs.time) { return -1; } else if (lhs.time > rhs.time) { return 1; } else { if (lhs.spanId < rhs.spanId) { return -1; } else if (lhs.spanId > rhs.spanId) { return 1; } else { if (lhs.sequence < rhs.sequence) { return -1; } else if (lhs.sequence > rhs.sequence) { return 1; } else { return compareHashes(lhs, rhs); } } } } private static int compareAsyncItems(Item lhs, Item rhs) { if (lhs.asyncId < rhs.asyncId) { return -1; } else if (lhs.asyncId > rhs.asyncId) { return 1; } else { if (lhs.asyncSequence < rhs.asyncSequence) { return -1; } else if (lhs.asyncSequence > rhs.asyncSequence) { return 1; } else { if (lhs.sequence < rhs.sequence) { return -1; } else if (lhs.sequence > rhs.sequence) { return 1; } else { return compareHashes(lhs, rhs); } } } } private static int compareHashes(Item lhs, Item rhs) { int h1 = System.identityHashCode(lhs.value); int h2 = System.identityHashCode(rhs.value); return h1 < h2 ? -1 : (h1 > h2 ? 1 : 0); } } @Override public synchronized boolean handleSend(TBase<?, ?> data) { if (data instanceof Span) { insertSpan((Span) data); return true; } else if (data instanceof SpanEvent) { handleSpanEvent((SpanEvent) data); return true; } return false; } private void insertSpan(Span span) { long startTime = span.getStartTime(); long spanId = span.getSpanId(); insertItem(new Item(span, startTime, spanId, ROOT_SEQUENCE)); } private void insertItem(Item item) { int pos = Collections.binarySearch(list, item); if (pos >= 0) { throw new IllegalArgumentException("Duplicated?? list: " + list + ", item: " + item); } int index = -(pos + 1); list.add(index, item); } private void handleSpanEvent(SpanEvent event) { Span span = event.getSpan(); int asyncId = event.isSetAsyncId() ? event.getAsyncId() : ASYNC_ID_NOT_SET; int asyncSequence = event.isSetAsyncSequence() ? event.getAsyncSequence() : ASYNC_SEQUENCE_NOT_SET; insertItem(new Item(event, span.getStartTime() + event.getStartElapsed(), span.getSpanId(), event.getSequence(), asyncId, asyncSequence)); } public synchronized TBase<?, ?> pop() { if (list.isEmpty()) { return null; } return list.remove(0).value; } public synchronized void print(PrintStream out) { out.println("TRACES(" + list.size() + "):"); for (TBase<?, ?> obj : this) { out.println(obj); } } public synchronized void clear() { list.clear(); } public synchronized int size() { return list.size(); } @Override public synchronized Iterator<TBase<?, ?>> iterator() { return new RecorderIterator(); } private final class RecorderIterator implements Iterator<TBase<?, ?>> { private int current = -1; private int index = 0; @Override public boolean hasNext() { synchronized (OrderedSpanRecorder.this) { return index < list.size(); } } @Override public TBase<?, ?> next() { synchronized (OrderedSpanRecorder.this) { current = index; index++; return list.get(current).value; } } @Override public void remove() { synchronized (OrderedSpanRecorder.this) { if (current == -1) { throw new IllegalStateException(); } list.remove(current); current = -1; index--; } } } @Override public String toString() { return "OrderedSpanRecorder{" + "list=" + list + '}'; } }