co.elastic.otel.profiler.CallTree Maven / Gradle / Ivy
Show all versions of inferred-spans Show documentation
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you 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 co.elastic.otel.profiler;
import static java.util.logging.Level.FINE;
import static java.util.logging.Level.WARNING;
import co.elastic.otel.common.ElasticAttributes;
import co.elastic.otel.common.util.HexUtils;
import co.elastic.otel.profiler.collections.LongHashSet;
import co.elastic.otel.profiler.pooling.ObjectPool;
import co.elastic.otel.profiler.pooling.Recyclable;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.SpanBuilder;
import io.opentelemetry.api.trace.SpanContext;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Context;
import io.opentelemetry.semconv.incubating.CodeIncubatingAttributes;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import javax.annotation.Nullable;
/**
* Converts a sequence of stack traces into a tree structure of method calls.
*
*
* count
* b b a 4
* aaaa ──► ├─b 1
* └─b 1
*
*
* It also stores information about which span is the parent of a particular call tree node,
* based on which span has been active at that time.
*
*
This allows to infer spans from the call tree which have the correct parent/child
* relationships with the regular spans.
*/
@SuppressWarnings("javadoc")
public class CallTree implements Recyclable {
private static final int INITIAL_CHILD_SIZE = 2;
private static final Attributes CHILD_LINK_ATTRIBUTES =
Attributes.builder().put(ElasticAttributes.IS_CHILD, true).build();
@Nullable private CallTree parent;
protected int count;
private List children = new ArrayList<>(INITIAL_CHILD_SIZE);
@Nullable private StackFrame frame;
protected long start;
private long lastSeen;
private boolean ended;
private long activationTimestamp = -1;
/**
* The context of the transaction or span which is the direct parent of this call tree node. Used
* in {@link #spanify} to override the parent.
*/
@Nullable private TraceContext activeContextOfDirectParent;
private long deactivationTimestamp = -1;
private boolean isSpan;
private int depth;
@Nullable private ChildList childIds;
@Nullable private ChildList maybeChildIds;
public CallTree() {}
public void set(@Nullable CallTree parent, StackFrame frame, long nanoTime) {
this.parent = parent;
this.frame = frame;
this.start = nanoTime;
if (parent != null) {
this.depth = parent.depth + 1;
}
}
public boolean isSuccessor(CallTree parent) {
if (depth > parent.depth) {
return getNthParent(depth - parent.depth) == parent;
}
return false;
}
@Nullable
public CallTree getNthParent(int n) {
CallTree parent = this;
for (int i = 0; i < n; i++) {
if (parent != null) {
parent = parent.parent;
} else {
return null;
}
}
return parent;
}
public void activation(TraceContext traceContext, long activationTimestamp) {
this.activeContextOfDirectParent = traceContext;
this.activationTimestamp = activationTimestamp;
}
protected void handleDeactivation(
TraceContext deactivatedSpan, long activationTimestamp, long deactivationTimestamp) {
if (deactivatedSpan.idEquals(activeContextOfDirectParent)) {
this.deactivationTimestamp = deactivationTimestamp;
} else {
CallTree lastChild = getLastChild();
if (lastChild != null) {
lastChild.handleDeactivation(deactivatedSpan, activationTimestamp, deactivationTimestamp);
}
}
// if an actual child span is deactivated after this call tree node has ended
// it means that this node has actually ended at least at the same point, if not after, the
// actual span has been deactivated
//
// [a(inferred)] ─► [a(inferred) ] ← set end timestamp to timestamp of deactivation of b
// └─[b(actual) ] └─[b(actual) ]
// see also CallTreeTest::testDectivationAfterEnd
if (happenedDuring(activationTimestamp) && happenedAfter(deactivationTimestamp)) {
lastSeen = deactivationTimestamp;
}
}
private boolean happenedDuring(long timestamp) {
return start <= timestamp && timestamp <= lastSeen;
}
private boolean happenedAfter(long timestamp) {
return lastSeen < timestamp;
}
public static CallTree.Root createRoot(
ObjectPool rootPool, byte[] traceContext, long nanoTime) {
CallTree.Root root = rootPool.createInstance();
root.set(traceContext, nanoTime);
return root;
}
/**
* Adds a single stack trace to the call tree which either updates the {@link #lastSeen} timestamp
* of an existing call tree node, {@linkplain #end ends} a node, or {@linkplain #addChild adds a
* new child}.
*
* @param stackFrames the stack trace which is iterated over in reverse order
* @param index the current index of {@code stackFrames}
* @param activeSpan the trace context of the currently active span
* @param activationTimestamp the timestamp of when {@code traceContext} has been activated
* @param nanoTime the timestamp of when this stack trace has been recorded
*/
protected CallTree addFrame(
List stackFrames,
int index,
@Nullable TraceContext activeSpan,
long activationTimestamp,
long nanoTime,
ObjectPool callTreePool,
long minDurationNs,
Root root) {
count++;
lastSeen = nanoTime;
// c ee ← traceContext not set - they are not a child of the active span but the frame
// below them
// bbb dd ← traceContext set
// ------ ← all new CallTree during this period should have the traceContext set
// a aaaaaa a
// | |
// active deactive
// this branch is already aware of the activation
// this means the provided activeSpan is not a direct parent of new child nodes
if (activeSpan != null
&& this.activeContextOfDirectParent != null
&& this.activeContextOfDirectParent.idEquals(activeSpan)) {
activeSpan = null;
}
// non-last children are already ended by definition
CallTree lastChild = getLastChild();
// if the frame corresponding to the last child is not in the stack trace
// it's assumed to have ended one tick ago
CallTree topOfStack = this;
boolean endChild = true;
if (index >= 1) {
final StackFrame frame = stackFrames.get(--index);
if (lastChild != null) {
if (!lastChild.isEnded() && frame.equals(lastChild.frame)) {
topOfStack =
lastChild.addFrame(
stackFrames,
index,
activeSpan,
activationTimestamp,
nanoTime,
callTreePool,
minDurationNs,
root);
endChild = false;
} else {
topOfStack =
addChild(
frame,
stackFrames,
index,
activeSpan,
activationTimestamp,
nanoTime,
callTreePool,
minDurationNs,
root);
}
} else {
topOfStack =
addChild(
frame,
stackFrames,
index,
activeSpan,
activationTimestamp,
nanoTime,
callTreePool,
minDurationNs,
root);
}
}
if (lastChild != null && !lastChild.isEnded() && endChild) {
lastChild.end(callTreePool, minDurationNs, root);
}
transferMaybeChildIdsToChildIds();
return topOfStack;
}
/**
* This method is called when we know for sure that the maybe child ids are actually belonging to
* this call tree. This is the case after we've seen another frame represented by this call tree.
*
* @see #addMaybeChildId(long)
*/
private void transferMaybeChildIdsToChildIds() {
if (maybeChildIds != null) {
if (childIds == null) {
childIds = maybeChildIds;
maybeChildIds = null;
} else {
childIds.addAll(maybeChildIds);
maybeChildIds.clear();
}
}
}
private CallTree addChild(
StackFrame frame,
List stackFrames,
int index,
@Nullable TraceContext traceContext,
long activationTimestamp,
long nanoTime,
ObjectPool callTreePool,
long minDurationNs,
Root root) {
CallTree callTree = callTreePool.createInstance();
callTree.set(this, frame, nanoTime);
if (traceContext != null) {
callTree.activation(traceContext, activationTimestamp);
}
children.add(callTree);
return callTree.addFrame(
stackFrames, index, null, activationTimestamp, nanoTime, callTreePool, minDurationNs, root);
}
long getDurationUs() {
return getDurationNs() / 1000;
}
private long getDurationNs() {
return lastSeen - start;
}
public int getCount() {
return count;
}
@Nullable
public StackFrame getFrame() {
return frame;
}
public List getChildren() {
return children;
}
protected void end(ObjectPool pool, long minDurationNs, Root root) {
ended = true;
// if the parent span has already been deactivated before this call tree node has ended
// it means that this node is actually the parent of the already deactivated span
// make b parent of a and pre-date the start of b to the activation of a
// [a(inferred) ] [a(inferred) ]
// [1 ] ──┐ [b(inferred) ]
// └[b(inferred)] │ [c(inferred)]
// [c(infer.) ] └► [1 ]
// └─[d(i.)] └──[d(i.)]
// see also CallTreeTest::testDeactivationBeforeEnd
if (deactivationHappenedBeforeEnd()) {
start = Math.min(activationTimestamp, start);
if (parent != null) {
// we know there's always exactly one activation in the parent's childIds
// that needs to be transferred to this call tree node
// in the above example, 1's child id would be first transferred from a to b and then from b
// to c
// this ensures that the UI knows that c is the parent of 1
parent.giveLastChildIdTo(this);
}
List callTrees = getChildren();
for (int i = 0, size = callTrees.size(); i < size; i++) {
CallTree child = callTrees.get(i);
child.activation(activeContextOfDirectParent, activationTimestamp);
child.deactivationTimestamp = deactivationTimestamp;
// re-run this logic for all children, even if they have already ended
child.end(pool, minDurationNs, root);
}
activeContextOfDirectParent = null;
activationTimestamp = -1;
deactivationTimestamp = -1;
}
if (parent != null && isTooFast(minDurationNs)) {
root.previousTopOfStack = parent;
parent.removeChild(pool, this);
} else {
CallTree lastChild = getLastChild();
if (lastChild != null && !lastChild.isEnded()) {
lastChild.end(pool, minDurationNs, root);
}
}
}
private boolean isTooFast(long minDurationNs) {
return count == 1 || isFasterThan(minDurationNs);
}
private void removeChild(ObjectPool pool, CallTree child) {
children.remove(child);
child.recursiveGiveChildIdsTo(this);
child.recycle(pool);
}
private boolean isFasterThan(long minDurationNs) {
return getDurationNs() < minDurationNs;
}
private boolean deactivationHappenedBeforeEnd() {
return activeContextOfDirectParent != null
&& deactivationTimestamp > -1
&& lastSeen > deactivationTimestamp;
}
public boolean isLeaf() {
return children.isEmpty();
}
/**
* Returns {@code true} if this node has just one child and no self time.
*
*
* c
* b ← b is a pillar
* aaa
*
*/
private boolean isPillar() {
return children.size() == 1 && children.get(0).count == count;
}
@Nullable
public CallTree getLastChild() {
return children.size() > 0 ? children.get(children.size() - 1) : null;
}
public boolean isEnded() {
return ended;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
try {
toString(sb);
} catch (IOException e) {
throw new RuntimeException(e);
}
return sb.toString();
}
private void toString(Appendable out) throws IOException {
toString(out, 0);
}
private void toString(Appendable out, int level) throws IOException {
for (int i = 0; i < level; i++) {
out.append(" ");
}
out.append(frame != null ? frame.getClassName() : "null")
.append('.')
.append(frame != null ? frame.getMethodName() : "null")
.append(' ')
.append(Integer.toString(count))
.append('\n');
for (CallTree node : children) {
node.toString(out, level + 1);
}
}
int spanify(
CallTree.Root root,
@Nullable Span parentSpan,
TraceContext parentContext,
SpanAnchoredClock clock,
StringBuilder tempBuilder,
Tracer tracer) {
int createdSpans = 0;
if (activeContextOfDirectParent != null) {
parentSpan = null;
parentContext = activeContextOfDirectParent;
}
Span span = null;
if (!isPillar() || isLeaf()) {
createdSpans++;
span = asSpan(root, parentSpan, parentContext, tracer, clock, tempBuilder);
this.isSpan = true;
}
List children = getChildren();
for (int i = 0, size = children.size(); i < size; i++) {
createdSpans +=
children
.get(i)
.spanify(
root,
span != null ? span : parentSpan,
parentContext,
clock,
tempBuilder,
tracer);
}
return createdSpans;
}
protected Span asSpan(
Root root,
@Nullable Span parentSpan,
TraceContext parentContext,
Tracer tracer,
SpanAnchoredClock clock,
StringBuilder tempBuilder) {
Context parentOtelCtx;
if (parentSpan != null) {
parentOtelCtx = Context.root().with(parentSpan);
} else {
tempBuilder.setLength(0);
parentOtelCtx = Context.root().with(Span.wrap(parentContext.toOtelSpanContext(tempBuilder)));
}
tempBuilder.setLength(0);
String classFqn = frame.getClassName();
if (classFqn != null) {
tempBuilder.append(classFqn, frame.getSimpleClassNameOffset(), classFqn.length());
} else {
tempBuilder.append("null");
}
tempBuilder.append("#");
tempBuilder.append(frame.getMethodName());
transferMaybeChildIdsToChildIds();
SpanBuilder spanBuilder =
tracer
.spanBuilder(tempBuilder.toString())
.setParent(parentOtelCtx)
.setAttribute(ElasticAttributes.IS_INFERRED, true)
.setStartTimestamp(
clock.toEpochNanos(parentContext.getClockAnchor(), this.start),
TimeUnit.NANOSECONDS);
insertChildIdLinks(
spanBuilder, Span.fromContext(parentOtelCtx).getSpanContext(), parentContext, tempBuilder);
// we're not interested in the very bottom of the stack which contains things like accepting and
// handling connections
if (parentSpan != null || !root.rootContext.idEquals(parentContext)) {
// we're never spanifying the root
assert this.parent != null;
tempBuilder.setLength(0);
this.parent.fillStackTrace(tempBuilder);
spanBuilder.setAttribute(CodeIncubatingAttributes.CODE_STACKTRACE, tempBuilder.toString());
}
Span span = spanBuilder.startSpan();
span.end(
clock.toEpochNanos(parentContext.getClockAnchor(), this.start + getDurationNs()),
TimeUnit.NANOSECONDS);
return span;
}
private void insertChildIdLinks(
SpanBuilder span,
SpanContext parentContext,
TraceContext nonInferredParent,
StringBuilder tempBuilder) {
if (childIds == null || childIds.isEmpty()) {
return;
}
for (int i = 0; i < childIds.getSize(); i++) {
// to avoid cycles, we only insert child-ids if the parent of the child is also
// the parent of the stack of inferred spans inserted
if (nonInferredParent.getSpanId() == childIds.getParentId(i)) {
tempBuilder.setLength(0);
HexUtils.appendLongAsHex(childIds.getId(i), tempBuilder);
SpanContext spanContext =
SpanContext.create(
parentContext.getTraceId(),
tempBuilder.toString(),
parentContext.getTraceFlags(),
parentContext.getTraceState());
span.addLink(spanContext, CHILD_LINK_ATTRIBUTES);
}
}
}
/** Fill in the stack trace up to the parent span */
private void fillStackTrace(StringBuilder resultBuilder) {
if (parent != null && !this.isSpan) {
if (resultBuilder.length() > 0) {
resultBuilder.append('\n');
}
resultBuilder
.append("at ")
.append(frame.getClassName())
.append('.')
.append(frame.getMethodName())
.append('(');
frame.appendFileName(resultBuilder);
resultBuilder.append(')');
parent.fillStackTrace(resultBuilder);
}
}
/**
* Recycles this subtree to the provided pool recursively. Note that this method ends by recycling
* {@code this} node (i.e. - this subtree root), which means that the caller of this method
* should make sure that no reference to this object is held anywhere.
*
* ALSO NOTE: MAKE SURE NOT TO CALL THIS METHOD FOR {@link CallTree.Root} INSTANCES.
*
* @param pool the pool to which all subtree nodes are to be recycled
*/
public final void recycle(ObjectPool pool) {
assert !(this instanceof Root);
List children = this.children;
for (int i = 0, size = children.size(); i < size; i++) {
children.get(i).recycle(pool);
}
pool.recycle(this);
}
@Override
public void resetState() {
parent = null;
count = 0;
frame = null;
start = 0;
lastSeen = 0;
ended = false;
activationTimestamp = -1;
activeContextOfDirectParent = null;
deactivationTimestamp = -1;
isSpan = false;
childIds = null;
maybeChildIds = null;
depth = 0;
if (children.size() > INITIAL_CHILD_SIZE) {
// the overwhelming majority of call tree nodes has either one or two children
// don't let outliers grow all lists in the pool over time
children = new ArrayList<>(INITIAL_CHILD_SIZE);
} else {
children.clear();
}
}
/**
* When a regular span is activated, we want it's {@code span.id} to be added to the call tree
* that represents the {@linkplain CallTree.Root#topOfStack top of the stack} to ensure correct
* parent/child relationships via re-parenting.
*
* However, the {@linkplain CallTree.Root#topOfStack current top of the stack} may turn out to
* not be the right target. Consider this example:
*
*
* bb
* aa aa
* 1 1 ← activation
*
*
* We would add the id of span {@code 1} to {@code b}'s {@link #maybeChildIds}. But after
* seeing the next frame, we realize the {@code b} has already ended and that we should {@link
* #giveMaybeChildIdsTo} from {@code b} and give it to {@code a}. This logic is implemented in
* {@link CallTree.Root#addStackTrace}. After seeing another frame of {@code a}, we know that
* {@code 1} is really the child of {@code a}, so we {@link #transferMaybeChildIdsToChildIds()}.
*
* @param id the child span id to add to this call tree element
*/
public void addMaybeChildId(long id, long parentId) {
if (maybeChildIds == null) {
maybeChildIds = new ChildList();
}
maybeChildIds.add(id, parentId);
}
public void addChildId(long id, long parentId) {
if (childIds == null) {
childIds = new ChildList();
}
childIds.add(id, parentId);
}
public boolean hasChildIds() {
return (maybeChildIds != null && maybeChildIds.getSize() > 0)
|| (childIds != null && childIds.getSize() > 0);
}
public void recursiveGiveChildIdsTo(CallTree giveTo) {
for (int i = 0, childrenSize = children.size(); i < childrenSize; i++) {
children.get(i).recursiveGiveChildIdsTo(giveTo);
}
giveChildIdsTo(giveTo);
giveMaybeChildIdsTo(giveTo);
}
void giveChildIdsTo(CallTree giveTo) {
if (this.childIds == null) {
return;
}
if (giveTo.childIds == null) {
giveTo.childIds = this.childIds;
} else {
giveTo.childIds.addAll(this.childIds);
}
this.childIds = null;
}
void giveLastChildIdTo(CallTree giveTo) {
if (childIds != null && !childIds.isEmpty()) {
int size = childIds.getSize();
long id = childIds.getId(size - 1);
long parentId = childIds.getParentId(size - 1);
giveTo.addChildId(id, parentId);
childIds.removeLast();
}
}
void giveMaybeChildIdsTo(CallTree giveTo) {
if (this.maybeChildIds == null) {
return;
}
if (giveTo.maybeChildIds == null) {
giveTo.maybeChildIds = this.maybeChildIds;
} else {
giveTo.maybeChildIds.addAll(this.maybeChildIds);
}
this.maybeChildIds = null;
}
public int getDepth() {
return depth;
}
/**
* A special kind of a {@link CallTree} node which represents the root of the call tree. This acts
* as the interface to the outside to add new nodes to the tree or to update existing ones by
* {@linkplain #addStackTrace adding stack traces}.
*/
public static class Root extends CallTree implements Recyclable {
private static final Logger logger = Logger.getLogger(Root.class.getName());
private static final StackFrame ROOT_FRAME = new StackFrame("root", "root");
/**
* The context of the thread root, mostly a transaction or a span which got activated in an
* auxiliary thread
*/
protected TraceContext rootContext;
/**
* The context of the transaction or span which is currently active. This is lazily deserialized
* from {@link #activeSpanSerialized} if there's an actual {@linkplain #addStackTrace stack
* trace} for this activation.
*/
@Nullable private TraceContext activeSpan;
/** The timestamp of when {@link #activeSpan} got activated */
private long activationTimestamp = -1;
/**
* The context of the transaction or span which is currently active, in its {@linkplain
* TraceContext#serialize serialized} form.
*/
private byte[] activeSpanSerialized = new byte[TraceContext.SERIALIZED_LENGTH];
@Nullable private CallTree previousTopOfStack;
@Nullable private CallTree topOfStack;
private final LongHashSet activeSet = new LongHashSet();
public Root() {
this.rootContext = new TraceContext();
}
private void set(byte[] traceContext, long nanoTime) {
super.set(null, ROOT_FRAME, nanoTime);
this.rootContext.deserialize(traceContext);
setActiveSpan(traceContext, nanoTime);
}
public void setActiveSpan(byte[] activeSpanSerialized, long timestamp) {
activationTimestamp = timestamp;
System.arraycopy(
activeSpanSerialized, 0, this.activeSpanSerialized, 0, activeSpanSerialized.length);
this.activeSpan = null;
}
public void onActivation(byte[] active, long timestamp) {
setActiveSpan(active, timestamp);
if (topOfStack != null) {
long spanId = TraceContext.getSpanId(active);
activeSet.add(spanId);
if (!isNestedActivation(topOfStack)) {
topOfStack.addMaybeChildId(spanId, TraceContext.getParentId(active));
}
}
}
private boolean isNestedActivation(CallTree topOfStack) {
return isAnyActive(topOfStack.childIds) || isAnyActive(topOfStack.maybeChildIds);
}
private boolean isAnyActive(@Nullable ChildList spanIds) {
if (spanIds == null) {
return false;
}
for (int i = 0, size = spanIds.getSize(); i < size; i++) {
if (activeSet.contains(spanIds.getId(i))) {
return true;
}
}
return false;
}
public void onDeactivation(byte[] deactivated, byte[] active, long timestamp) {
if (logger.isLoggable(FINE) && !Arrays.equals(activeSpanSerialized, deactivated)) {
logger.log(WARNING, "Illegal state: deactivating span that is not active");
}
if (activeSpan != null) {
handleDeactivation(activeSpan, activationTimestamp, timestamp);
}
// else: activeSpan has not been materialized because no stack traces were added during this
// activation
setActiveSpan(active, timestamp);
// we're not interested in tracking nested activations that happen before we see the first
// stack trace
// that's because isNestedActivation is only called if topOfStack != null
// this optimizes for the case where we have no stack traces for a fast executing transaction
if (topOfStack != null) {
long spanId = TraceContext.getSpanId(deactivated);
activeSet.remove(spanId);
}
}
public void addStackTrace(
List stackTrace,
long nanoTime,
ObjectPool callTreePool,
long minDurationNs) {
// only "materialize" trace context if there's actually an associated stack trace to the
// activation
// avoids allocating a TraceContext for very short activations which have no effect on the
// CallTree anyway
boolean firstFrameAfterActivation = false;
if (activeSpan == null) {
firstFrameAfterActivation = true;
activeSpan = new TraceContext();
activeSpan.deserialize(activeSpanSerialized);
}
previousTopOfStack = topOfStack;
topOfStack =
addFrame(
stackTrace,
stackTrace.size(),
activeSpan,
activationTimestamp,
nanoTime,
callTreePool,
minDurationNs,
this);
// After adding the first frame after an activation, we can check if we added the child ids to
// the correct CallTree
// If the new top of stack is not a successor (a different branch vs just added nodes on the
// same branch)
// we have to transfer the child ids of not yet deactivated spans to the new top of the stack.
// See also CallTreeTest.testActivationAfterMethodEnds and following tests.
if (firstFrameAfterActivation
&& previousTopOfStack != topOfStack
&& previousTopOfStack != null
&& previousTopOfStack.hasChildIds()) {
if (!topOfStack.isSuccessor(previousTopOfStack)) {
CallTree commonAncestor = findCommonAncestor(previousTopOfStack, topOfStack);
CallTree newParent = commonAncestor != null ? commonAncestor : topOfStack;
if (newParent.count > 1) {
previousTopOfStack.giveMaybeChildIdsTo(newParent);
} else if (previousTopOfStack.maybeChildIds != null) {
previousTopOfStack.maybeChildIds.clear();
}
}
}
}
@Nullable
private CallTree findCommonAncestor(CallTree previousTopOfStack, CallTree topOfStack) {
int maxDepthOfCommonAncestor = Math.min(previousTopOfStack.getDepth(), topOfStack.getDepth());
CallTree commonAncestor = null;
// i = 1 avoids considering the CallTree.Root node which is always the same
for (int i = 1; i <= maxDepthOfCommonAncestor; i++) {
CallTree ancestor1 = previousTopOfStack.getNthParent(previousTopOfStack.getDepth() - i);
CallTree ancestor2 = topOfStack.getNthParent(topOfStack.getDepth() - i);
if (ancestor1 == ancestor2) {
commonAncestor = ancestor1;
} else {
break;
}
}
return commonAncestor;
}
/**
* Creates spans for call tree nodes if they are either not a {@linkplain #isPillar() pillar} or
* are a {@linkplain #isLeaf() leaf}. Nodes which are not converted to {@link Span}s are part of
* the span stackframes for the nodes which do get converted to a span.
*
* Parent/child relationships with the regular spans are maintained. One exception is that an
* inferred span can't be the parent of a regular span. That is because the regular spans have
* already been reported once the inferred spans are created. In the future, we might make it
* possible to update the parent ID of a regular span so that it correctly reflects being a
* child of an inferred span.
*/
public int spanify(SpanAnchoredClock clock, Tracer tracer) {
StringBuilder tempBuilder = new StringBuilder();
int createdSpans = 0;
List callTrees = getChildren();
for (int i = 0, size = callTrees.size(); i < size; i++) {
createdSpans +=
callTrees.get(i).spanify(this, null, rootContext, clock, tempBuilder, tracer);
}
return createdSpans;
}
public TraceContext getRootContext() {
return rootContext;
}
/**
* Recycles this tree to the provided pools. First, all child subtrees are recycled recursively
* to the children pool. Then, {@code this} root node is recycled to the root pool. This means
* that the caller of this method should make sure that no reference to this root object is
* held anywhere.
*
* @param childrenPool object pool for all non-root nodes
* @param rootPool object pool for root nodes
*/
public void recycle(ObjectPool childrenPool, ObjectPool rootPool) {
List children = getChildren();
for (int i = 0, size = children.size(); i < size; i++) {
children.get(i).recycle(childrenPool);
}
rootPool.recycle(this);
}
public void end(ObjectPool pool, long minDurationNs) {
end(pool, minDurationNs, this);
}
@Override
public void resetState() {
super.resetState();
rootContext.resetState();
activeSpan = null;
activationTimestamp = -1;
Arrays.fill(activeSpanSerialized, (byte) 0);
previousTopOfStack = null;
topOfStack = null;
activeSet.clear();
}
}
}