com.yahoo.yolean.trace.TraceNode Maven / Gradle / Ivy
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.yolean.trace;
import com.yahoo.yolean.concurrent.ThreadRobustList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
/**
* This class represents a single node in a tree of TraceNodes
. The trace forms a tree where there is a
* branch for each parallel execution, and a node within such a branch for each traced event. As each TraceNode
* may contain a payload of any type, the trace tree can be used to exchange any thread-safe state between producers and
* consumers in different threads, whether or not the shape of the trace tree is relevant to the particular
* information.
* This class uses a {@link ThreadRobustList} for its children. That list allows multiple threads to inspect the
* hierarchy of a TraceNode
tree while there are other threads concurrently modifying it, without incurring the
* cost of memory synchronization. The only caveat being that for each TraceNode
there can never be more than
* exactly one writer thread. If multiple threads need to mutate a single TraceNode
, then the writer threads
* need to synchronize their access on the TraceNode
.
*
* @author Steinar Knutsen
* @author bratseth
* @since 5.1.15
*/
public class TraceNode {
private final Object payload;
private final long timestamp;
private ThreadRobustList children;
private TraceNode parent;
/**
* Creates a new instance of this class.
*
* @param payload the payload to assign to this, may be null
* @param timestamp the timestamp to assign to this
*/
public TraceNode(Object payload, long timestamp) {
this.payload = payload;
this.timestamp = timestamp;
}
/**
* Adds another TraceNode
as a child to this.
*
* @param child the TraceNode to add
* @return this, to allow chaining
* @throws IllegalArgumentException if child
is not a root TraceNode
* @see #isRoot()
*/
public TraceNode add(TraceNode child) {
if (child.parent != null) {
throw new IllegalArgumentException("Can not add " + child + " to " + this + "; it is not a root.");
}
child.parent = this;
if (children == null) {
children = new ThreadRobustList<>();
}
children.add(child);
return this;
}
/**
* Returns a read-only iterable of all {@link #payload() payloads} that are instances of payloadType
,
* in all its decendants. The payload of this TraceNode
is ignored.
* The payloads are retrieved in depth-first, prefix order.
*
* @param payloadType the type of payloads to retrieve
* @return the payloads, never null
*/
public Iterable descendants(final Class payloadType) {
if (children == null) {
return List.of();
}
return new Iterable() {
@Override
public Iterator iterator() {
return new PayloadIterator<>(TraceNode.this, payloadType);
}
};
}
/**
* Returns the payload of this TraceNode
, or null if none.
*
* @return the payload
*/
public Object payload() {
return payload;
}
/**
* Returns the timestamp of this TraceNode
.
*
* @return the timestamp
*/
public long timestamp() {
return timestamp;
}
/**
* Returns the parent TraceNode
of this.
*
* @return the parent
*/
public TraceNode parent() {
return parent;
}
/**
* Returns the child TraceNodes
of this.
*
* @return the children
*/
public Iterable children() {
if (children == null) {
return List.of();
}
return children;
}
/**
* Returns whether or not this TraceNode
is a root node (i.e. it has no parent).
*
* @return true
if {@link #parent()} returns null
*/
public boolean isRoot() {
return parent == null;
}
/**
* Returns the root TraceNode
of the tree that this TraceNode
belongs to.
*
* @return the root
*/
public TraceNode root() {
TraceNode node = this;
while (node.parent != null) {
node = node.parent;
}
return node;
}
/**
* Visits this TraceNode
and all of its descendants in depth-first, prefix order.
*
* @param visitor The visitor to accept.
* @return The visitor
parameter.
*/
public T accept(T visitor) {
visitor.visit(this);
if (children == null || children.isEmpty()) {
return visitor;
}
visitor.entering(this);
for (TraceNode child : children) {
child.accept(visitor);
}
visitor.leaving(this);
return visitor;
}
@Override
public String toString() {
final StringBuilder out = new StringBuilder("[ ");
accept(new TraceVisitor() {
@Override
public void visit(TraceNode node) {
if (node.payload != null) {
out.append(node.payload).append(" ");
}
}
@Override
public void entering(TraceNode node) {
out.append("[ ");
}
@Override
public void leaving(TraceNode node) {
out.append("] ");
}
});
return out.append("]").toString();
}
private static class PayloadIterator implements Iterator {
final List unexploredNodes = new LinkedList<>();
final Class payloadType;
PAYLOADTYPE next;
PayloadIterator(TraceNode root, Class payloadType) {
payloadType.getClass(); // throws NullPointerException
this.payloadType = payloadType;
unexploredNodes.add(root);
next = advance();
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public boolean hasNext() {
return next != null;
}
@Override
public PAYLOADTYPE next() {
if (next == null) {
throw new NoSuchElementException();
}
PAYLOADTYPE current = next;
next = advance();
return current;
}
PAYLOADTYPE advance() {
// Current node is depleted, find next
while (unexploredNodes.size() > 0) {
// Take the next node
TraceNode node = unexploredNodes.remove(0);
// Add its children to the list of nodes we1'll look at
if (node.children != null) {
int i = 0; // used to fabricate depth-first traversal order
for (TraceNode child : node.children) {
unexploredNodes.add(i++, child);
}
}
Object payload = node.payload();
if (payloadType.isInstance(payload)) {
return payloadType.cast(payload);
}
}
return null;
}
}
}