// 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.Collections; 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 Collections.emptyList(); } 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 Collections.emptyList(); } 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; } } }