package net.floodlightcontroller.debugcounter; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.TreeMap; import java.util.regex.Pattern; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; /** * A node in the counter hierarchy tree. We use a single tree and "merge" the * counter modules into this single tree. * * <li> Adding modules or counters to the tree must happen at the root of the tree. * <li> The root and the first level of the tree (root and module) are a shim * layers that con't have an actual counter value. We represent this with a * null counter. * * @author gregor */ class CounterNode implements Iterable<DebugCounterImpl> { private static final String QUOTED_SEP = Pattern.quote("/"); private static final Logger log = LoggerFactory.getLogger(CounterNode.class); /** path/hierarchy of this counter without leading /. An empty string * represents the root. A string without a / is a module name */ private final String hierarchy; /** * The path/hierarchy elements split into a list */ private final List<String> hierarchyElements; /** * The actual counter instance for this node. Can be null for * root level and module level. */ private final DebugCounterImpl counter; private final TreeMap<String, CounterNode> children = new TreeMap<>(); /** * convert module name and counter hierarchy into list of * hierarchy elements * @param moduleName * @param counterHierarchy * @return */ static List<String> getHierarchyElements(String moduleName, String counterHierarchy) { DebugCounterServiceImpl.verifyModuleNameSanity(moduleName); List<String> ret = new ArrayList<>(); ret.add(moduleName); if (counterHierarchy == null || counterHierarchy.isEmpty()) { return ret; } for (String element : counterHierarchy.split(QUOTED_SEP)) { ret.add(element); } return ret; } private CounterNode(List<String> hierarchyElements, DebugCounterImpl counter) { super(); this.hierarchyElements = ImmutableList.copyOf(hierarchyElements); this.hierarchy = Joiner.on("/").join(hierarchyElements); this.counter = counter; } /** * Create a new counter hierarchy tree and return the root * @return */ public static CounterNode newTree() { return new CounterNode(ImmutableList.<String>of(), null); } /** verify that this node is the root */ private void verifyIsRoot() { if (hierarchyElements.size() != 0) { throw new IllegalStateException("This is not the root. Can " + "only call addCounter() on the root node. Current node: " + hierarchy); } } /** * return the full hierarchy as string, including module name */ @Nonnull String getHierarchy() { return hierarchy; } /** * @return the list of hierarchy elements for this node */ @Nonnull List<String> getHierarchyElements() { return hierarchyElements; } /** * @return this node's counters */ @Nullable DebugCounterImpl getCounter() { return counter; } /** * Reset this counter all counter below it in the hierarchy */ void resetHierarchy() { for (DebugCounterImpl cur: this) { cur.reset(); } } /** * Return an Iterable over all DebugCounterImpls at and below this * node. Note we return an Iterable<DebugCounterImpls> not * Iterable<IDebugCounter> on purpose. * @return */ Iterable<DebugCounterImpl> getCountersInHierarchy() { return this; } /** * Lookup the CounterNode identified by the hieraryElements if it exists. * Returns null if no such CounterNode is found. Must only be called on * the root of the tree. * @param hierarchyElements * @return */ CounterNode lookup(List<String> hierarchyElements) { CounterNode cur = this; for (String element: hierarchyElements) { cur = cur.children.get(element); if (cur == null) { break; } } return cur; } /** * Remove the CounterNode identified by the hieraryElements if it exists. * Returns null if no such CounterNode is found. Must only be called on * the root of the tree. * @param hierarchyElements * @return */ CounterNode remove(List<String> hierarchyElements) { CounterNode cur = this; /* The last element of the hierarchy is what we want to delete. * remove(len - 1) will shorten the hierarchy list to the parent of the last * descendant. This will be our stopping point. The parent of the last * descendant will contain a TreeMap of all it's "children." The String * key we removed can be used to remove the specific child from the parent's * "children" TreeMap. * * We're directly reusing the shrunken hierarchyElements List and * keyToRemove String, which IMHO is pretty cool how it works out. */ /* * Make sure it's possible to remove something. */ if (hierarchyElements.isEmpty()) { log.error("Cannot remove a CounterNode from an empty list of hierarchy elements. Returning null."); return null; } String keyToRemove = hierarchyElements.remove(hierarchyElements.size() - 1); for (String element: hierarchyElements) { cur = cur.children.get(element); if (cur == null) { break; } } // At this point, if !null, cur will be the parent of the CounterNode we want to remove. CounterNode removed = null; if (cur != null) { removed = cur.children.remove(keyToRemove); } // TODO This will remove the CounterNode from IDebugCounterService, but if any // other modules still have a reference to the IDebugCounter within the CounterNode // we just removed, they might mistakenly query the "dead" counter. return removed; } /** * Add the given moduleName to the tree. Can only be called on the root. * If the module already exists, the all counters of the module will * be reset. * @param moduleName * @return true if the module was newly added, false if the module already * existed */ boolean addModule(@Nonnull String moduleName) { verifyIsRoot(); if (children.containsKey(moduleName)) { children.get(moduleName).resetHierarchy(); return false; } else { CounterNode newNode = new CounterNode(ImmutableList.of(moduleName), null); children.put(moduleName, newNode); return true; } } /** * Add the given Counter to the hierarchy. If the counterHierarcy already * exists, reset the hierarchy * @param counter * @return null if the counterHierarchy is newly registered, otherwise * returns the already registered DebugCounterImpl instance * @throws IllegalArgumentException if the parent of the counter does not * yet exist */ @Nullable DebugCounterImpl addCounter(@Nonnull DebugCounterImpl counter) { verifyIsRoot(); ArrayList<String> path = new ArrayList<>(); path.add(counter.getModuleName()); for (String element: counter.getCounterHierarchy().split(QUOTED_SEP)) { path.add(element); } String newCounterName = path.get(path.size()-1); CounterNode parent = lookup(path.subList(0, path.size()-1)); if (parent == null) { throw new IllegalArgumentException("Missing hierarchy level for " + "counter: " + counter.getModuleName() + " " + counter.getCounterHierarchy()); } if (parent.children.containsKey(newCounterName)) { CounterNode old = parent.children.get(newCounterName); // FIXME: we should check that old and new has the same // description and meta-data, otherwise we should probably thrown // and exception and refuse the operation. old.resetHierarchy(); return old.counter; } else { CounterNode newNode = new CounterNode(path, counter); parent.children.put(newCounterName, newNode); return null; } } /** * Iterator over the counters in the counter hierarchy. * Iteration order is a pre-order tree walk. Children of a node are * visited in sorted order. * @author gregor */ private final static class CounterIterator implements Iterator<DebugCounterImpl> { // NOTE: since some counters ArrayDeque<CounterNode> stack = new ArrayDeque<>(); CounterNode curNode = null; private CounterIterator(CounterNode root) { stack.push(root); gotoNextNode(); } private void gotoNextNode() { while (true) { curNode = null; if (stack.isEmpty()) { break; } curNode = stack.pop(); for (CounterNode child: curNode.children.descendingMap().values()) { stack.push(child); } if (curNode.counter != null) { break; } } } @Override public boolean hasNext() { return curNode != null; } @Override public DebugCounterImpl next() { if (curNode == null) { throw new NoSuchElementException(); } DebugCounterImpl ret = curNode.counter; gotoNextNode(); return ret; } @Override public void remove() { throw new UnsupportedOperationException(); } } @Override public Iterator<DebugCounterImpl> iterator() { return new CounterIterator(this); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((children == null) ? 0 : children.hashCode()); result = prime * result + ((counter == null) ? 0 : counter.hashCode()); result = prime * result + ((hierarchy == null) ? 0 : hierarchy.hashCode()); result = prime * result + ((hierarchyElements == null) ? 0 : hierarchyElements.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; CounterNode other = (CounterNode) obj; if (children == null) { if (other.children != null) return false; } else if (!children.equals(other.children)) return false; if (counter == null) { if (other.counter != null) return false; } else if (!counter.equals(other.counter)) return false; if (hierarchy == null) { if (other.hierarchy != null) return false; } else if (!hierarchy.equals(other.hierarchy)) return false; if (hierarchyElements == null) { if (other.hierarchyElements != null) return false; } else if (!hierarchyElements.equals(other.hierarchyElements)) return false; return true; } @Override public String toString() { return toString(0); } public String toString(int indent) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < indent; i++) { builder.append(" "); } builder.append("hierarchy="); builder.append(hierarchy); builder.append(", counter="); builder.append(counter); builder.append(", children="); builder.append(children.keySet()); builder.append("\n"); for (CounterNode child: children.values()) { builder.append(child.toString(indent + 3)); } return builder.toString(); } }