![JAR search and dependency download from the Maven repository](/logo.png)
com.bigdata.counters.CounterSet Maven / Gradle / Ivy
/*
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
[email protected]
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Mar 13, 2008
*/
package com.bigdata.counters;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.log4j.Logger;
import org.xml.sax.SAXException;
import com.bigdata.util.StackInfoReport;
import cutthecrap.utils.striterators.Expander;
import cutthecrap.utils.striterators.Filter;
import cutthecrap.utils.striterators.IStriterator;
import cutthecrap.utils.striterators.SingleValueIterator;
import cutthecrap.utils.striterators.Sorter;
import cutthecrap.utils.striterators.Striterator;
/**
* A set of counters arranged in a hierarchy, much like a file system. Each node
* has a name and a path. The name is a local and immutable label. The path is
* the {separator, name} sequence reading down from the root to a given node.
* The "root" is the top-most node in the hierarchy - it always has an empty
* name and its path is /
. The direct children of a root are
* typically fully qualified host names. E.g., /www.bigdata.com
.
*
* Nodes are always created as children of an existing root. Once created, any
* non-root node may be attached as a child of any other node, including a root
* node, as long as cycles would not be formed. When a node is attached as a
* child of another node, the path of the child and all of its children are
* updated recursively. E.g., if /Memory
is attached to
* /www.bigdata.com
then its path becomes
* /www.bigdata.com/Memory
.
*
* Children are either {@link CounterSet}s or individual {@link Counter}s.
* Counter sets and counters are declared in the namespace and their names must
* be distinct.
*
* @author Bryan Thompson
* @version $Id$
*
* @todo The CounterSet should perhaps obtain a lock on the node(s) to be
* modified rather than the root for better concurrency.
*
* @todo the syntax "." and ".." are not recognized.
*
* @todo should declare the units and the counter description with the counter
* but only propagate the description once (alternatively, specify a
* counter description interface and pass that along). The more difficult
* question is how to limit the transfer of the full description. Perhaps
* by having the LBS query for it?
*/
public class CounterSet extends AbstractCounterSet implements ICounterSet {
static private final Logger log = Logger.getLogger(CounterSet.class);
// private String pathx;
private final Map children = new ConcurrentHashMap();
/**
* Ctor for a root node.
*/
public CounterSet() {
this("",null);
}
/**
* Used to add a child.
*
* @param name
* The name of the child.
*/
private CounterSet(final String name, final CounterSet parent) {
super(name,parent);
// this.path = computePath();
}
// /**
// * Updates the {@link #path} on this {@link CounterSet} and then recursively
// * on all of its children.
// */
// private void updatePath() {
//
// this.path = computePath();
//
// Iterator itr = children.values().iterator();
//
// while(itr.hasNext()) {
//
// CounterSet child = (CounterSet)itr.next();
//
// child.updatePath();
//
// }
//
// }
//
// private String computePath() {
//
// if (parent == null || parent.isAbsoluteRoot()) {
//
// return pathSeparator + name;
//
// }
//
// final ICounterSet[] a = getPathComponents();
//
// final StringBuilder sb = new StringBuilder();
//
// for(ICounterSet x : a) {
//
// sb.append(pathSeparator);
//
// sb.append(x.getName());
//
// }
//
// return sb.toString();
//
// }
/**
* Return true
iff there are no children.
*/
public boolean isLeaf() {
return children.isEmpty();
}
/**
* Attaches a {@link CounterSet} as a child of this node. If child
* is a root, then all children of the child are attached instead.
* If a {@link CounterSet} already exists then its children are attached. If
* a {@link Counter}s already exists then it is overwritten. During
* recursive attach if we encounter a node that already exists then just
* copy its children. If there is a conflict (trying to copy a counter over
* a counter set or visa-versa), then a warning is logged and we ignore the
* conflicting node.
*
* @param src
* The child counter set.
*
* @throws IllegalArgumentException
* if child is null
* @throws IllegalStateException
* if child is either this node or any parent of this
* node since a cycle would be formed.
*/
synchronized public void attach(final ICounterNode src) {
attach(src, false/* replace */);
}
synchronized public void attach(final ICounterNode src, final boolean replace) {
// FIXME detect cycles.
if(src.isRoot()) {
/*
* If the child is a root then we attach its children.
*/
final Iterator itr = ((CounterSet) src).children
.values().iterator();
while (itr.hasNext()) {
attach2(itr.next(), replace);
}
} else {
attach2(src, replace);
}
}
private void attach2(final ICounterNode src, final boolean replace) {
if (src == null)
throw new IllegalArgumentException();
if (children.containsKey(src.getName())) {
/*
* There is an existing node with the same path as [src].
*/
// the existing node with the same path as [src].
final ICounterNode existingChild = getChild(src.getName());
if (existingChild.isCounter() && !replace) {
if(log.isInfoEnabled())
log.info("Will not replace existing counter: "
+ existingChild.getPath());
return;
} else if (src.isCounterSet()) {
/*
* If the [src] is a counter set, then attach its children
* instead.
*/
final Iterator itr = ((CounterSet) src).children
.values().iterator();
while (itr.hasNext()) {
((CounterSet) existingChild).attach2(itr.next(), replace);
}
return;
}
// fall through.
}
/*
* Attach or replace the counter.
*/
synchronized(src) {
final String name = src.getName();
final CounterSet oldParent = (CounterSet) src.getParent();
assert oldParent != null;
if (oldParent.children.remove(name) == null) {
throw new AssertionError();
}
if(src.isCounterSet()) {
((CounterSet)src).parent = this;
} else {
((Counter>)src).parent = this;
}
children.put(name, src);
// /*
// * update the path on the child (and recursively on its children) to
// * reflect its location in the hierarchy. the counters on the child
// * use a dynamic path so that is not a problem.
// */
// child.updatePath();
}
}
/**
* Detaches and returns the node having that path.
*
* @param path
* The path.
* @return The node -or- null
if there is no node with that
* path.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
synchronized public ICounterNode detach(final String path) {
final ICounterNode node = getPath(path);
if(node != null && !node.isRoot() ) {
final CounterSet p = (CounterSet)node.getParent();
p.children.remove(node.getName());
if(node.isCounterSet()) {
((CounterSet)node).parent = null;
} else {
((Counter)node).parent = null;
}
}
return node;
}
/**
* Visits direct child counters matching the optional filter.
*
* Note: Since the filter does NOT have to be anchored at the root, the only
* place we can apply a filter that is NOT anchored at the root is when
* checking a fully qualified counter name.
*
* @todo optimize for patterns that are anchored by filtering the child
* {@link ICounterSet}.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public Iterator counterIterator(final Pattern filter) {
final IStriterator src = new Striterator(directChildIterator(
true/* sorted */, ICounter.class));
if (filter != null) {
src.addFilter(
new Filter() {
private static final long serialVersionUID = 1L;
@Override
public boolean isValid(Object val) {
final ICounter counter = (ICounter) val;
final String path = counter.getPath();
final Matcher matcher = filter.matcher(path);
final boolean matched = matcher.matches();
return matched;
}
});
}
return src;
}
/**
* All spanned nodes.
*
* @param filter An optional filter.
*
* @return
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public Iterator getNodes(final Pattern filter) {
IStriterator src = ((IStriterator) postOrderIterator())
.addFilter(new Expander() {
private static final long serialVersionUID = 1L;
@Override
protected Iterator expand(Object val) {
CounterSet c = (CounterSet)val;
return new Striterator(new SingleValueIterator(c)).append(c.counterIterator(filter));
}
});
return src;
}
@Override
@SuppressWarnings({ "unchecked", "rawtypes" })
public Iterator getCounters(final Pattern filter) {
IStriterator src = ((IStriterator) postOrderIterator())
.addFilter(new Expander() {
private static final long serialVersionUID = 1L;
@Override
protected Iterator expand(Object val) {
CounterSet c = (CounterSet)val;
return c.counterIterator(filter);
}
});
return src;
}
/**
* Iterator visits all directly attached children.
*
* @param sorted
* When true
the children will be visited in order
* by their name.
*
* @param type
* An optional type filter - specify either {@link ICounterSet}
* or {@link ICounter} you want to be visited by the iterator.
* When null
all directly attached children
* (counters and counter sets) are visited.
*/
@SuppressWarnings("rawtypes")
public Iterator directChildIterator(final boolean sorted,
final Class extends ICounterNode> type) {
/*
* Note: In order to avoid concurrent modification problems under
* traversal I am currently creating a snapshot of the set of child
* references and then using the sorterator over that stable snapshot.
*
* @todo consider using linked list or insertion sort rather than hash
* map and runtime sort.
*/
final ICounterNode[] a;
synchronized(this) {
a = (ICounterNode[])children.values().toArray(new ICounterNode[]{});
}
final IStriterator itr = new Striterator( Arrays.asList(a).iterator() );
if (type != null) {
itr.addTypeFilter(type);
}
if (sorted) {
itr.addFilter(new Sorter() {
private static final long serialVersionUID = 1L;
@Override
public int compare(Object arg0, Object arg1) {
return ((ICounterNode) arg0).getName().compareTo(
((ICounterNode) arg1).getName());
}
});
}
return itr;
}
/**
* Iterator visits the directly attached {@link ICounterSet} children.
*/
@SuppressWarnings("unchecked")
public Iterator counterSetIterator() {
return directChildIterator(true/*sorted*/,ICounterSet.class);
}
/**
* Iterator visits {@link ICounterSet} children recursively expanding each
* child with a post-order traversal of its children and finally visits this
* node itself.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public Iterator postOrderIterator() {
/*
* Appends this node to the iterator in the post-order position.
*/
return new Striterator(postOrderIterator1())
.append(new SingleValueIterator(this));
}
/**
* Iterator visits this node recursively expanding each {@link ICounterSet}
* child with a pre-order traversal of its children and finally visits this
* node itself.
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public Iterator preOrderIterator() {
/*
* Appends this node to the iterator in the pre-order position.
*/
return new Striterator(new SingleValueIterator(this))
.append(preOrderIterator1());
}
/**
* Visits the {@link ICounterSet} children (recursively) using pre-order
* traversal, but does NOT visit this node.
*/
@SuppressWarnings("unchecked")
private Iterator preOrderIterator1() {
/*
* Iterator visits the direct children, expanding them in turn with a
* recursive application of the pre-order iterator.
*/
return new Striterator(counterSetIterator()).addTypeFilter(
ICounterSet.class).addFilter(new Expander() {
private static final long serialVersionUID = 1L;
/*
* Expand each child in turn.
*/
@Override
@SuppressWarnings("rawtypes")
protected Iterator expand(final Object childObj) {
/*
* A child of this node.
*/
final ICounterSet child = (ICounterSet) childObj;
// append this node in pre-order position.
final Striterator itr = new Striterator(
new SingleValueIterator(child));
itr.append(((CounterSet) child).preOrderIterator1());
return itr;
}
});
}
/**
* Visits the {@link ICounterSet} children (recursively) using post-order
* traversal, but does NOT visit this node.
*/
@SuppressWarnings("unchecked")
private Iterator postOrderIterator1() {
/*
* Iterator visits the direct children, expanding them in turn with a
* recursive application of the post-order iterator.
*/
return new Striterator(counterSetIterator()).addTypeFilter(
ICounterSet.class).addFilter(new Expander() {
private static final long serialVersionUID = 1L;
/*
* Expand each child in turn.
*/
@Override
@SuppressWarnings("rawtypes")
protected Iterator expand(final Object childObj) {
/*
* A child of this node.
*/
final ICounterSet child = (ICounterSet) childObj;
final Striterator itr = new Striterator(((CounterSet) child)
.postOrderIterator1());
// append this node in post-order position.
itr.append(new SingleValueIterator(child));
return itr;
}
});
}
@Override
public ICounterNode getChild(final String name) {
if (name == null)
throw new IllegalArgumentException();
return children.get(name);
}
/**
* Adds any necessary {@link CounterSet}s described in the path (ala
* mkdirs).
*
* @param path
* The path.
*
* @return The {@link CounterSet} described by the path.
*/
@Override
synchronized public CounterSet makePath(String path) {
if (path == null) {
throw new IllegalArgumentException();
}
if(path.length()==0) {
throw new IllegalArgumentException();
}
if (path.equals(pathSeparator)) {
// Handles: "/"
return (CounterSet) getRoot();
}
if( path.contains("//")) {
/*
* Empty path names are not allowed.
*/
throw new IllegalArgumentException(path);
}
/*
* Normalize to a path relative to the node on which we evaluate the
* path. If the path is absolute, then we drop off the leading '/' and
* evaluate against the root (so the path is now relative to the root).
* Otherwise the path is already relative to this node and we evaluate
* it here.
*/
if (path.startsWith(pathSeparator)) {
// drop off the leading '/'
path = path.substring(1);
// start at the root
if (parent != null) {
return (CounterSet)getRoot().makePath(path);
}
}
final String[] a = path.split(pathSeparator);
CounterSet p = this;
for (int i = 0; i < a.length; i++) {
String name = a[i];
ICounterNode t = p.getChild(name);
if (t == null) {
// node does not exist, so create it now.
t = new CounterSet(name, p);
p.children.put(name, t);
} else if (t instanceof ICounter) {
// path names a counter.
throw new IllegalArgumentException("path identifies a counter");
}
p = (CounterSet) t;
}
return p;
}
/**
* Add a counter.
*
* @param path
* The path of the counter (absolute or relative).
*
* @param instrument
* The object that is used to take the measurements from which
* the counter's value will be determined.
*/
@SuppressWarnings("rawtypes")
synchronized public ICounter addCounter(final String path,
final IInstrument instrument) {
if (path == null) {
throw new IllegalArgumentException();
}
final int indexOf = path.lastIndexOf(pathSeparator);
if (indexOf == -1) {
return addCounter2(path, instrument);
}
final String name = path.substring(indexOf + 1, path.length());
final String ppath = path.substring(0, indexOf);
final CounterSet parent = (CounterSet) makePath(ppath);
return parent.addCounter2(name, instrument);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private ICounter addCounter2(final String name, final IInstrument instrument) {
if (name == null)
throw new IllegalArgumentException();
if (instrument == null)
throw new IllegalArgumentException();
{
final ICounterNode counter = children.get(name);
if (counter != null) {
if(counter instanceof ICounter ) {
// counter exists for that path.
log.error(new StackInfoReport("Exists: path=" + getPath() + ", name=" + name));
// return existing counter for path @todo vs replace.
return (ICounter)counter;
} else {
// a counter set exists for that path(not a counter).
throw new IllegalStateException("Node exists: path="
+ getPath() + ", name=" + name);
}
}
}
// if (children.containsKey(name)) {
//
// throw new IllegalStateException("Exists: path=" + getPath()
// + ", name=" + name);
//
// }
final ICounter counter = new Counter(this, name, instrument);
if (log.isInfoEnabled())
log.info("parent=" + getPath()+", name="+name);
children.put(name, counter);
return counter;
}
/**
* Per {@link #asXML(OutputStream, String, Pattern)} but does not write out
* the header declaring the encoding.
*
* @param w
* The XML will be written on this object.
* @param filter
* The optional filter.
*
* @throws IOException
*/
@Override
public void asXML(Writer w, Pattern filter) throws IOException {
XMLUtility.INSTANCE.writeXML(this, w, filter);
}
@Override
public void readXML(final InputStream is,
final IInstrumentFactory instrumentFactory, final Pattern filter)
throws IOException, ParserConfigurationException, SAXException {
XMLUtility.INSTANCE.readXML(this, is, instrumentFactory, filter);
}
}