http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/AndOp.java
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/AndOp.java
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/AndOp.java
new file mode 100644
index 0000000..581bdaa
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/AndOp.java
@@ -0,0 +1,33 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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 org.apache.zookeeper.graph.filterops;
+
+import org.apache.zookeeper.graph.LogEntry;
+import org.apache.zookeeper.graph.FilterOp;
+import org.apache.zookeeper.graph.FilterException;
+
+public class AndOp extends FilterOp {
+    public boolean matches(LogEntry entry) throws FilterException {
+       for (FilterOp f : subOps) {
+           if (!f.matches(entry)) {
+               return false;
+           }
+       }
+       return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/Arg.java
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/Arg.java
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/Arg.java
new file mode 100644
index 0000000..4fda3cf
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/Arg.java
@@ -0,0 +1,36 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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 org.apache.zookeeper.graph.filterops;
+
+import org.apache.zookeeper.graph.FilterOp.*;
+
+public class Arg<T> {
+    private ArgType type;
+    protected T value;
+    
+    protected Arg(ArgType type) {
+       this.type = type;
+    }
+    
+    public ArgType getType() { return type; }
+    public T getValue() { return value; }
+
+    public String toString() {
+       return "[" + type + ":" + value + "]";
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/EqualsOp.java
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/EqualsOp.java
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/EqualsOp.java
new file mode 100644
index 0000000..409815a
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/EqualsOp.java
@@ -0,0 +1,44 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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 org.apache.zookeeper.graph.filterops;
+
+import org.apache.zookeeper.graph.LogEntry;
+import org.apache.zookeeper.graph.FilterOp;
+import org.apache.zookeeper.graph.FilterException;
+
+public class EqualsOp extends FilterOp {
+    public boolean matches(LogEntry entry) throws FilterException {
+
+       Object last = null;
+       for (Arg a : args) {
+           Object v = a.getValue();
+           if (a.getType() == FilterOp.ArgType.SYMBOL) {
+               String key = (String)a.getValue();
+               v = entry.getAttribute(key);
+           }
+
+           if (last != null
+               && !last.equals(v)) {
+               return false;
+           }
+           last = v;
+       }
+
+       return true;
+    }
+}    

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/GreaterThanOp.java
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/GreaterThanOp.java
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/GreaterThanOp.java
new file mode 100644
index 0000000..244dd3d
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/GreaterThanOp.java
@@ -0,0 +1,70 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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 org.apache.zookeeper.graph.filterops;
+
+import org.apache.zookeeper.graph.LogEntry;
+import org.apache.zookeeper.graph.FilterOp;
+import org.apache.zookeeper.graph.FilterException;
+
+public class GreaterThanOp extends FilterOp {
+    public boolean matches(LogEntry entry) throws FilterException {
+       Arg first = args.get(0);
+       
+       if (first != null) {
+           FilterOp.ArgType type = first.getType();
+           if (type == FilterOp.ArgType.SYMBOL) {
+               String key = (String)first.getValue();
+               Object v = entry.getAttribute(key);
+               if (v instanceof String) {
+                   type = FilterOp.ArgType.STRING;
+               } else if (v instanceof Double || v instanceof Long || v 
instanceof Integer || v instanceof Short) {
+                   type = FilterOp.ArgType.NUMBER;
+               } else {
+                   throw new FilterException("LessThanOp: Invalid argument, 
first argument resolves to neither a String nor a Number");
+               }
+           }
+           
+           Object last = null;
+           for (Arg a : args) {
+               Object v = a.getValue();
+               if (a.getType() == FilterOp.ArgType.SYMBOL) {
+                   String key = (String)a.getValue();
+                   v = entry.getAttribute(key);
+               }
+
+               if (last != null) {
+                   if (type == FilterOp.ArgType.STRING) {
+                       if (((String)last).compareTo((String)v) <= 0) {
+                           return false;
+                       }
+                   } else if (type == FilterOp.ArgType.NUMBER) {
+                       //                      System.out.println("last[" + 
((Number)last).longValue() + "] v["+ ((Number)v).longValue() + "]");
+                       if (((Number)last).longValue() <= 
((Number)v).longValue()) {
+                           return false;
+                       }
+                   }
+               }
+               last = v;
+           }
+           return true;
+       } else { 
+           return true; 
+       }
+    }
+       
+}    

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/LessThanOp.java
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/LessThanOp.java
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/LessThanOp.java
new file mode 100644
index 0000000..b7d9e09
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/LessThanOp.java
@@ -0,0 +1,69 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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 org.apache.zookeeper.graph.filterops;
+
+import org.apache.zookeeper.graph.LogEntry;
+import org.apache.zookeeper.graph.FilterOp;
+import org.apache.zookeeper.graph.FilterException;
+
+public class LessThanOp extends FilterOp {
+    public boolean matches(LogEntry entry) throws FilterException {
+       Arg first = args.get(0);
+       
+       if (first != null) {
+           FilterOp.ArgType type = first.getType();
+           if (type == FilterOp.ArgType.SYMBOL) {
+               String key = (String)first.getValue();
+               Object v = entry.getAttribute(key);
+               if (v instanceof String) {
+                   type = FilterOp.ArgType.STRING;
+               } else if (v instanceof Double || v instanceof Long || v 
instanceof Integer || v instanceof Short) {
+                   type = FilterOp.ArgType.NUMBER;
+               } else {
+                   throw new FilterException("LessThanOp: Invalid argument, 
first argument resolves to neither a String nor a Number");
+               }
+           }
+           
+           Object last = null;
+           for (Arg a : args) {
+               Object v = a.getValue();
+               if (a.getType() == FilterOp.ArgType.SYMBOL) {
+                   String key = (String)a.getValue();
+                   v = entry.getAttribute(key);
+               }
+
+               if (last != null) {
+                   if (type == FilterOp.ArgType.STRING) {
+                       if (((String)last).compareTo((String)v) >= 0) {
+                           return false;
+                       }
+                   } else if (type == FilterOp.ArgType.NUMBER) {
+                       if (((Number)last).doubleValue() >= 
((Number)v).doubleValue()) {
+                           return false;
+                       }
+                   }
+               }
+               last = v;
+           }
+           return true;
+       } else { 
+           return true; 
+       }
+    }
+       
+}    

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/NotOp.java
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/NotOp.java
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/NotOp.java
new file mode 100644
index 0000000..d8ed757
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/NotOp.java
@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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 org.apache.zookeeper.graph.filterops;
+
+import org.apache.zookeeper.graph.LogEntry;
+import org.apache.zookeeper.graph.FilterOp;
+import org.apache.zookeeper.graph.FilterException;
+
+public class NotOp extends FilterOp {
+    public boolean matches(LogEntry entry) throws FilterException {
+       if (subOps.size() != 1) {
+           throw new FilterException("Not operation can only take one 
argument");
+       }
+       return !subOps.get(0).matches(entry);
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/NumberArg.java
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/NumberArg.java
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/NumberArg.java
new file mode 100644
index 0000000..d6b584d
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/NumberArg.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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 org.apache.zookeeper.graph.filterops;
+
+import org.apache.zookeeper.graph.FilterOp.*;
+
+public class NumberArg extends Arg<Long> {
+    public NumberArg(Long value) {
+       super(ArgType.NUMBER);
+       this.value = value;
+    }
+};
+

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/OrOp.java
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/OrOp.java
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/OrOp.java
new file mode 100644
index 0000000..d681589
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/OrOp.java
@@ -0,0 +1,33 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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 org.apache.zookeeper.graph.filterops;
+
+import org.apache.zookeeper.graph.LogEntry;
+import org.apache.zookeeper.graph.FilterOp;
+import org.apache.zookeeper.graph.FilterException;
+
+public class OrOp extends FilterOp {
+    public boolean matches(LogEntry entry) throws FilterException {
+       for (FilterOp f : subOps) {
+           if (f.matches(entry)) {
+               return true;
+           }
+       }
+       return false;
+    }
+}    

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/StringArg.java
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/StringArg.java
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/StringArg.java
new file mode 100644
index 0000000..7345d3c
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/StringArg.java
@@ -0,0 +1,28 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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 org.apache.zookeeper.graph.filterops;
+
+import org.apache.zookeeper.graph.FilterOp.*;
+
+public class StringArg extends Arg<String> {
+    public StringArg(String value) {
+       super(ArgType.STRING);
+           this.value = value;
+    }
+};
+

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/SymbolArg.java
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/SymbolArg.java
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/SymbolArg.java
new file mode 100644
index 0000000..077553b
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/SymbolArg.java
@@ -0,0 +1,27 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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 org.apache.zookeeper.graph.filterops;
+
+import org.apache.zookeeper.graph.FilterOp.*;
+
+public class SymbolArg extends Arg<String> {
+    public SymbolArg(String value) {
+       super(ArgType.SYMBOL);
+       this.value = value;
+    }
+};

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/XorOp.java
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/XorOp.java
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/XorOp.java
new file mode 100644
index 0000000..9e778b1
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/filterops/XorOp.java
@@ -0,0 +1,40 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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 org.apache.zookeeper.graph.filterops;
+
+import org.apache.zookeeper.graph.LogEntry;
+import org.apache.zookeeper.graph.FilterOp;
+import org.apache.zookeeper.graph.FilterException;
+
+public class XorOp extends FilterOp {
+    public boolean matches(LogEntry entry) throws FilterException {
+       int count = 0;
+       for (FilterOp f : subOps) {
+           if (f.matches(entry)) {
+               count++;
+               if (count > 1) {
+                   return false;
+               }
+           }
+       }
+       if (count == 1) {
+           return true;
+       }
+       return false;
+    }
+}    

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/FileLoader.java
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/FileLoader.java
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/FileLoader.java
new file mode 100644
index 0000000..67e8945
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/FileLoader.java
@@ -0,0 +1,60 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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 org.apache.zookeeper.graph.servlets;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.FileNotFoundException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.JSONValue;
+
+import org.apache.zookeeper.graph.*;
+
+public class FileLoader extends JsonServlet
+{
+    private MergedLogSource source = null;
+    
+    public FileLoader(MergedLogSource src) throws Exception {
+       source = src;
+    }
+
+    String handleRequest(JsonRequest request) throws Exception
+    {
+       String output = "";
+               
+       String file = request.getString("path", "/");
+       JSONObject o = new JSONObject();
+       try {
+           this.source.addSource(file);
+           o.put("status", "OK");
+       
+       } catch (Exception e) {
+           o.put("status", "ERR");
+           o.put("error",  e.toString());
+       }
+       
+       return JSONValue.toJSONString(o);
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/Fs.java
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/Fs.java
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/Fs.java
new file mode 100644
index 0000000..e5b1a01
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/Fs.java
@@ -0,0 +1,69 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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 org.apache.zookeeper.graph.servlets;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.FileNotFoundException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.JSONValue;
+import java.util.Arrays;
+import java.util.Comparator;
+
+public class Fs extends JsonServlet
+{
+    String handleRequest(JsonRequest request) throws Exception
+    {
+       String output = "";
+       JSONArray filelist = new JSONArray();
+
+       File base = new File(request.getString("path", "/"));
+       if (!base.exists() || !base.isDirectory()) {
+           throw new FileNotFoundException("Couldn't find [" + request + "]");
+       }
+       File[] files = base.listFiles();
+       Arrays.sort(files, new Comparator<File>() { 
+               public int compare(File o1, File o2) {
+                   if (o1.isDirectory() != o2.isDirectory()) {
+                       if (o1.isDirectory()) {
+                           return -1;
+                       } else {
+                           return 1;
+                       }
+                   }
+                   return o1.getName().compareToIgnoreCase(o2.getName());
+               } 
+           });
+       
+       for (File f : files) {
+           JSONObject o = new JSONObject();
+           o.put("file", f.getName());
+           o.put("type", f.isDirectory() ? "D" : "F");
+           o.put("path", f.getCanonicalPath());
+           filelist.add(o);
+       }
+       return JSONValue.toJSONString(filelist);
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/GraphData.java
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/GraphData.java
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/GraphData.java
new file mode 100644
index 0000000..fc10eb1
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/GraphData.java
@@ -0,0 +1,85 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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 org.apache.zookeeper.graph.servlets;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.FileNotFoundException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.JSONValue;
+
+import org.apache.zookeeper.graph.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class GraphData extends JsonServlet
+{
+    private static final Logger LOG = LoggerFactory.getLogger(GraphData.class);
+    private static final int DEFAULT_PERIOD = 1000;
+
+    private LogSource source = null;
+
+    public GraphData(LogSource src) throws Exception {
+       this.source = src; 
+    }
+
+    String handleRequest(JsonRequest request) throws Exception {
+       
+
+       long starttime = 0;
+       long endtime = 0;
+       long period = 0;
+       FilterOp fo = null;
+
+       starttime = request.getNumber("start", 0);
+       endtime = request.getNumber("end", 0);
+       period = request.getNumber("period", 0);
+       String filterstr = request.getString("filter", "");
+
+       if (filterstr.length() > 0) {
+           fo = new FilterParser(filterstr).parse();
+       }
+       
+       if (starttime == 0) { starttime = source.getStartTime(); }
+       if (endtime == 0) { 
+           if (period > 0) {
+               endtime = starttime + period;
+           } else {
+               endtime = starttime + DEFAULT_PERIOD; 
+           }
+       }
+
+       if (LOG.isDebugEnabled()) {
+           LOG.debug("handle(start= " + starttime + ", end=" + endtime + ", 
period=" + period + ")");
+       }
+       
+       LogIterator iterator = (fo != null) ? 
+           source.iterator(starttime, endtime, fo) : 
source.iterator(starttime, endtime);
+       return new JsonGenerator(iterator).toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/JsonServlet.java
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/JsonServlet.java
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/JsonServlet.java
new file mode 100644
index 0000000..910d44f
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/JsonServlet.java
@@ -0,0 +1,85 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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 org.apache.zookeeper.graph.servlets;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.json.simple.JSONObject;
+import org.json.simple.JSONValue;
+
+import java.util.Map;
+
+abstract public class JsonServlet extends HttpServlet {
+    abstract String handleRequest(JsonRequest request) throws Exception;
+
+    protected class JsonRequest {
+       private Map map;
+
+       public JsonRequest(ServletRequest request) {
+           map = request.getParameterMap();
+       }
+       
+       public long getNumber(String name, long defaultnum) {
+           String[] vals = (String[])map.get(name);
+           if (vals == null || vals.length == 0) {
+               return defaultnum;
+           }
+
+           try {
+               return Long.valueOf(vals[0]);
+           } catch (NumberFormatException e) {
+               return defaultnum;
+           }
+       }
+       
+       public String getString(String name, String defaultstr) {
+           String[] vals = (String[])map.get(name);
+           if (vals == null || vals.length == 0) {
+               return defaultstr;
+           } else {
+               return vals[0];
+           }
+       }
+    }
+
+    protected void doGet(HttpServletRequest request, HttpServletResponse 
response) throws ServletException, IOException
+    {
+        response.setContentType("text/plain;charset=utf-8");
+        response.setStatus(HttpServletResponse.SC_OK);
+       
+       try {
+           String req = 
request.getRequestURI().substring(request.getServletPath().length());
+
+           response.getWriter().println(handleRequest(new 
JsonRequest(request)));
+       } catch (Exception e) {
+           JSONObject o = new JSONObject();
+           o.put("error", e.toString());
+           response.getWriter().println(JSONValue.toJSONString(o));
+       } catch (java.lang.OutOfMemoryError oom) {
+           JSONObject o = new JSONObject();
+           o.put("error", "Out of memory. Perhaps you've requested too many 
logs. Try narrowing you're filter criteria.");
+           response.getWriter().println(JSONValue.toJSONString(o));
+       }
+    }
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/NumEvents.java
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/NumEvents.java
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/NumEvents.java
new file mode 100644
index 0000000..5961a12
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/NumEvents.java
@@ -0,0 +1,88 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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 org.apache.zookeeper.graph.servlets;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.FileNotFoundException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.JSONValue;
+
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+import org.apache.zookeeper.graph.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class NumEvents extends JsonServlet
+{
+    private static final Logger LOG = LoggerFactory.getLogger(NumEvents.class);
+    private static final int DEFAULT_PERIOD = 1000;
+
+    private LogSource source = null;
+
+    public NumEvents(LogSource src) throws Exception {
+       this.source = src;
+    }
+
+    String handleRequest(JsonRequest request) throws Exception {
+       String output = "";
+
+       long starttime = 0;
+       long endtime = 0;
+       long period = 0;
+
+       starttime = request.getNumber("start", 0);
+       endtime = request.getNumber("end", 0);
+       period = request.getNumber("period", 0);
+
+       if (starttime == 0) { starttime = source.getStartTime(); }
+       if (endtime == 0) { 
+           if (period > 0) {
+               endtime = starttime + period;
+           } else {
+               endtime = source.getEndTime(); 
+           }
+       }
+       
+       LogIterator iter = source.iterator(starttime, endtime);
+       JSONObject data = new JSONObject();
+       data.put("startTime", starttime);
+       data.put("endTime", endtime);
+       long size = 0;
+       
+       size = iter.size();
+       
+       data.put("numEntries",  size);
+       if (LOG.isDebugEnabled()) {
+           LOG.debug("handle(start= " + starttime + ", end=" + endtime + ", 
numEntries=" + size +")");
+       }
+       iter.close();
+       return JSONValue.toJSONString(data);
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/StaticContent.java
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/StaticContent.java
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/StaticContent.java
new file mode 100644
index 0000000..d91acb6
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/StaticContent.java
@@ -0,0 +1,53 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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 org.apache.zookeeper.graph.servlets;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.BufferedReader;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class StaticContent extends HttpServlet {
+    protected void doGet(HttpServletRequest request, HttpServletResponse 
response) throws ServletException, IOException
+    {
+       String path = 
request.getRequestURI().substring(request.getServletPath().length());
+
+       InputStream resource = 
ClassLoader.getSystemResourceAsStream("org/apache/zookeeper/graph/resources" + 
path);      
+       if (resource == null) {
+           response.getWriter().println(path + " not found!");
+           response.setStatus(HttpServletResponse.SC_NOT_FOUND);
+           return;
+       }
+       try {
+         while (resource.available() > 0) {
+           response.getWriter().write(resource.read());
+         }
+       } finally {
+         resource.close();
+       }
+       //        response.setContentType("text/plain;charset=utf-8");
+        response.setStatus(HttpServletResponse.SC_OK);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/Throughput.java
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/Throughput.java
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/Throughput.java
new file mode 100644
index 0000000..80ed1dc
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/java/org/apache/zookeeper/graph/servlets/Throughput.java
@@ -0,0 +1,126 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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 org.apache.zookeeper.graph.servlets;
+
+import java.io.IOException;
+import java.io.BufferedOutputStream;
+import java.io.FileOutputStream;
+import java.io.DataOutputStream;
+import java.io.PrintStream;
+
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Set;
+
+import org.apache.zookeeper.graph.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.JSONValue;
+
+
+public class Throughput extends JsonServlet
+{
+    private static final int MS_PER_SEC = 1000;
+    private static final int MS_PER_MIN = MS_PER_SEC*60;
+    private static final int MS_PER_HOUR = MS_PER_MIN*60;
+
+    private LogSource source = null;
+
+    public Throughput(LogSource src) throws Exception {
+       this.source = src; 
+    }
+
+    public String handleRequest(JsonRequest request) throws Exception {
+       long starttime = 0;
+       long endtime = 0;
+       long period = 0;
+       long scale = 0;
+       
+       starttime = request.getNumber("start", 0);
+       endtime = request.getNumber("end", 0);
+       period = request.getNumber("period", 0);
+       
+
+       if (starttime == 0) { starttime = source.getStartTime(); }
+       if (endtime == 0) { 
+           if (period > 0) {
+               endtime = starttime + period;
+           } else {
+               endtime = source.getEndTime(); 
+           }
+       }
+       
+       String scalestr = request.getString("scale", "minutes");
+       if (scalestr.equals("seconds")) {
+           scale = MS_PER_SEC;
+       } else if (scalestr.equals("hours")) {
+           scale = MS_PER_HOUR;
+       } else {
+           scale = MS_PER_MIN;
+       }       
+       
+       LogIterator iter = source.iterator(starttime, endtime);
+       
+       long current = 0;
+       long currentms = 0;
+       Set<Long> zxids_ms = new HashSet<Long>();
+       long zxidcount = 0;
+
+       JSONArray events = new JSONArray();
+       while (iter.hasNext()) {
+           LogEntry e = iter.next();
+           if (e.getType() != LogEntry.Type.TXN) {
+               continue;
+           }
+
+           TransactionEntry cxn = (TransactionEntry)e;
+           
+           long ms = cxn.getTimestamp();
+           long inscale = ms/scale;
+
+           if (currentms != ms && currentms != 0) {
+               zxidcount += zxids_ms.size();
+               zxids_ms.clear();
+           }
+
+           if (inscale != current && current != 0) {
+               JSONObject o = new JSONObject();
+               o.put("time", current*scale);
+               o.put("count", zxidcount);
+               events.add(o);
+               zxidcount = 0;
+           }
+           current = inscale;
+           currentms = ms;
+
+           zxids_ms.add(cxn.getZxid());
+       }
+       JSONObject o = new JSONObject();
+       o.put("time", current*scale);
+       o.put("count", zxidcount);
+       events.add(o);
+
+       iter.close();
+       
+       return JSONValue.toJSONString(events);
+    }
+
+};

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/resources/loggraph-dev.sh
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/resources/loggraph-dev.sh
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/resources/loggraph-dev.sh
new file mode 100755
index 0000000..e04434e
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/resources/loggraph-dev.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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.
+
+make_canonical () {
+    cd $1; pwd;
+}
+
+SCRIPTDIR=`dirname $0`
+BUILDDIR=`make_canonical $SCRIPTDIR/../../../../../build/contrib/loggraph`
+LIBDIR=`make_canonical $BUILDDIR/lib`
+WEBDIR=`make_canonical $SCRIPTDIR/../web`
+ZKDIR=`make_canonical $SCRIPTDIR/../../../../../build/`
+
+if [ ! -x $BUILDDIR ]; then
+    echo "\n\n*** You need to build loggraph before running it ***\n\n";
+    exit;
+fi
+
+for i in `ls $LIBDIR`; do 
+    CLASSPATH=$LIBDIR/$i:$CLASSPATH
+done
+
+for i in $ZKDIR/zookeeper-*.jar; do
+    CLASSPATH="$i:$CLASSPATH"
+done
+
+CLASSPATH=$BUILDDIR/classes:$WEBDIR:$CLASSPATH
+echo $CLASSPATH
+java -Dlog4j.configuration=org/apache/zookeeper/graph/log4j.properties -Xdebug 
-Xrunjdwp:transport=dt_socket,address=4444,server=y,suspend=n -cp $CLASSPATH 
org.apache.zookeeper.graph.LogServer $*

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/resources/loggraph.sh
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/resources/loggraph.sh 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/resources/loggraph.sh
new file mode 100755
index 0000000..0259dc6
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/resources/loggraph.sh
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF 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.
+
+make_canonical () {
+    cd $1; pwd;
+}
+
+SCRIPTDIR=`dirname $0`
+BUILDDIR=`make_canonical $SCRIPTDIR/../../../../../build/contrib/loggraph`
+LIBDIR=`make_canonical $BUILDDIR/lib`
+ZKDIR=`make_canonical $SCRIPTDIR/../../../../../build/`
+
+if [ ! -x $BUILDDIR ]; then
+    echo "\n\n*** You need to build loggraph before running it ***\n\n";
+    exit;
+fi
+
+for i in `ls $LIBDIR`; do 
+    CLASSPATH=$LIBDIR/$i:$CLASSPATH
+done
+
+for i in `ls $BUILDDIR/*.jar`; do 
+    CLASSPATH=$i:$CLASSPATH
+done
+
+for i in $ZKDIR/zookeeper-*.jar; do
+    CLASSPATH="$i:$CLASSPATH"
+done
+
+java -cp $CLASSPATH org.apache.zookeeper.graph.LogServer $*
+
+
+
+

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/webapp/org/apache/zookeeper/graph/log4j.properties
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/webapp/org/apache/zookeeper/graph/log4j.properties
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/webapp/org/apache/zookeeper/graph/log4j.properties
new file mode 100644
index 0000000..ab8960b
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/webapp/org/apache/zookeeper/graph/log4j.properties
@@ -0,0 +1,11 @@
+log4j.rootLogger=TRACE, CONSOLE
+
+# Print the date in ISO 8601 format
+log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
+log4j.appender.CONSOLE.Threshold=TRACE
+log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
+log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} - %-5p 
[%t:%C{1}@%L] - %m%n
+
+log4j.logger.org.apache.zookeeper.graph.LogSkipList=off
+log4j.logger.org.apache.zookeeper.graph.RandomAccessFileReader=off
+#log4j.logger.org.apache.zookeeper.graph.Log4JSource=off
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/webapp/org/apache/zookeeper/graph/resources/date.format.js
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/webapp/org/apache/zookeeper/graph/resources/date.format.js
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/webapp/org/apache/zookeeper/graph/resources/date.format.js
new file mode 100644
index 0000000..5515009
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/webapp/org/apache/zookeeper/graph/resources/date.format.js
@@ -0,0 +1,126 @@
+/*
+ * Date Format 1.2.3
+ * (c) 2007-2009 Steven Levithan <stevenlevithan.com>
+ * MIT license
+ *
+ * Includes enhancements by Scott Trenda <scott.trenda.net>
+ * and Kris Kowal <cixar.com/~kris.kowal/>
+ *
+ * Accepts a date, a mask, or a date and a mask.
+ * Returns a formatted version of the given date.
+ * The date defaults to the current date/time.
+ * The mask defaults to dateFormat.masks.default.
+ */
+
+var dateFormat = function () {
+       var     token = 
/d{1,4}|m{1,4}|yy(?:yy)?|([HhMsTt])\1?|[LloSZ]|"[^"]*"|'[^']*'/g,
+               timezone = 
/\b(?:[PMCEA][SDP]T|(?:Pacific|Mountain|Central|Eastern|Atlantic) 
(?:Standard|Daylight|Prevailing) Time|(?:GMT|UTC)(?:[-+]\d{4})?)\b/g,
+               timezoneClip = /[^-+\dA-Z]/g,
+               pad = function (val, len) {
+                       val = String(val);
+                       len = len || 2;
+                       while (val.length < len) val = "0" + val;
+                       return val;
+               };
+
+       // Regexes and supporting functions are cached through closure
+       return function (date, mask, utc) {
+               var dF = dateFormat;
+
+               // You can't provide utc if you skip other args (use the "UTC:" 
mask prefix)
+               if (arguments.length == 1 && 
Object.prototype.toString.call(date) == "[object String]" && !/\d/.test(date)) {
+                       mask = date;
+                       date = undefined;
+               }
+
+               // Passing date through Date applies Date.parse, if necessary
+               date = date ? new Date(date) : new Date;
+               if (isNaN(date)) throw SyntaxError("invalid date");
+
+               mask = String(dF.masks[mask] || mask || dF.masks["default"]);
+
+               // Allow setting the utc argument via the mask
+               if (mask.slice(0, 4) == "UTC:") {
+                       mask = mask.slice(4);
+                       utc = true;
+               }
+
+               var     _ = utc ? "getUTC" : "get",
+                       d = date[_ + "Date"](),
+                       D = date[_ + "Day"](),
+                       m = date[_ + "Month"](),
+                       y = date[_ + "FullYear"](),
+                       H = date[_ + "Hours"](),
+                       M = date[_ + "Minutes"](),
+                       s = date[_ + "Seconds"](),
+                       L = date[_ + "Milliseconds"](),
+                       o = utc ? 0 : date.getTimezoneOffset(),
+                       flags = {
+                               d:    d,
+                               dd:   pad(d),
+                               ddd:  dF.i18n.dayNames[D],
+                               dddd: dF.i18n.dayNames[D + 7],
+                               m:    m + 1,
+                               mm:   pad(m + 1),
+                               mmm:  dF.i18n.monthNames[m],
+                               mmmm: dF.i18n.monthNames[m + 12],
+                               yy:   String(y).slice(2),
+                               yyyy: y,
+                               h:    H % 12 || 12,
+                               hh:   pad(H % 12 || 12),
+                               H:    H,
+                               HH:   pad(H),
+                               M:    M,
+                               MM:   pad(M),
+                               s:    s,
+                               ss:   pad(s),
+                               l:    pad(L, 3),
+                               L:    pad(L > 99 ? Math.round(L / 10) : L),
+                               t:    H < 12 ? "a"  : "p",
+                               tt:   H < 12 ? "am" : "pm",
+                               T:    H < 12 ? "A"  : "P",
+                               TT:   H < 12 ? "AM" : "PM",
+                               Z:    utc ? "UTC" : 
(String(date).match(timezone) || [""]).pop().replace(timezoneClip, ""),
+                               o:    (o > 0 ? "-" : "+") + 
pad(Math.floor(Math.abs(o) / 60) * 100 + Math.abs(o) % 60, 4),
+                               S:    ["th", "st", "nd", "rd"][d % 10 > 3 ? 0 : 
(d % 100 - d % 10 != 10) * d % 10]
+                       };
+
+               return mask.replace(token, function ($0) {
+                       return $0 in flags ? flags[$0] : $0.slice(1, $0.length 
- 1);
+               });
+       };
+}();
+
+// Some common format strings
+dateFormat.masks = {
+       "default":      "ddd mmm dd yyyy HH:MM:ss",
+       shortDate:      "m/d/yy",
+       mediumDate:     "mmm d, yyyy",
+       longDate:       "mmmm d, yyyy",
+       fullDate:       "dddd, mmmm d, yyyy",
+       shortTime:      "h:MM TT",
+       mediumTime:     "h:MM:ss TT",
+       longTime:       "h:MM:ss TT Z",
+       isoDate:        "yyyy-mm-dd",
+       isoTime:        "HH:MM:ss",
+       isoDateTime:    "yyyy-mm-dd'T'HH:MM:ss",
+       isoUtcDateTime: "UTC:yyyy-mm-dd'T'HH:MM:ss'Z'"
+};
+
+// Internationalization strings
+dateFormat.i18n = {
+       dayNames: [
+               "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat",
+               "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", 
"Friday", "Saturday"
+       ],
+       monthNames: [
+               "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", 
"Oct", "Nov", "Dec",
+               "January", "February", "March", "April", "May", "June", "July", 
"August", "September", "October", "November", "December"
+       ]
+};
+
+// For convenience...
+Date.prototype.format = function (mask, utc) {
+       return dateFormat(this, mask, utc);
+};
+

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/webapp/org/apache/zookeeper/graph/resources/g.bar.js
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/webapp/org/apache/zookeeper/graph/resources/g.bar.js
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/webapp/org/apache/zookeeper/graph/resources/g.bar.js
new file mode 100644
index 0000000..2f7212a
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/webapp/org/apache/zookeeper/graph/resources/g.bar.js
@@ -0,0 +1,385 @@
+/*
+ * g.Raphael 0.4 - Charting library, based on Raphaël
+ *
+ * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 
license.
+ */
+Raphael.fn.g.barchart = function (x, y, width, height, values, opts) {
+    opts = opts || {};
+    var type = {round: "round", sharp: "sharp", soft: "soft"}[opts.type] || 
"square",
+        gutter = parseFloat(opts.gutter || "20%"),
+        chart = this.set(),
+        bars = this.set(),
+        covers = this.set(),
+        covers2 = this.set(),
+        total = Math.max.apply(Math, values),
+        stacktotal = [],
+        paper = this,
+        multi = 0,
+        colors = opts.colors || this.g.colors,
+        len = values.length;
+    if (this.raphael.is(values[0], "array")) {
+        total = [];
+        multi = len;
+        len = 0;
+        for (var i = values.length; i--;) {
+            bars.push(this.set());
+            total.push(Math.max.apply(Math, values[i]));
+            len = Math.max(len, values[i].length);
+        }
+        if (opts.stacked) {
+            for (var i = len; i--;) {
+                var tot = 0;
+                for (var j = values.length; j--;) {
+                    tot +=+ values[j][i] || 0;
+                }
+                stacktotal.push(tot);
+            }
+        }
+        for (var i = values.length; i--;) {
+            if (values[i].length < len) {
+                for (var j = len; j--;) {
+                    values[i].push(0);
+                }
+            }
+        }
+        total = Math.max.apply(Math, opts.stacked ? stacktotal : total);
+    }
+    
+    total = (opts.to) || total;
+    var barwidth = width / (len * (100 + gutter) + gutter) * 100,
+        barhgutter = barwidth * gutter / 100,
+        barvgutter = opts.vgutter == null ? 20 : opts.vgutter,
+        stack = [],
+        X = x + barhgutter,
+        Y = (height - 2 * barvgutter) / total;
+    if (!opts.stretch) {
+        barhgutter = Math.round(barhgutter);
+        barwidth = Math.floor(barwidth);
+    }
+    !opts.stacked && (barwidth /= multi || 1);
+    for (var i = 0; i < len; i++) {
+        stack = [];
+        for (var j = 0; j < (multi || 1); j++) {
+            var h = Math.round((multi ? values[j][i] : values[i]) * Y),
+                top = y + height - barvgutter - h,
+                bar = this.g.finger(Math.round(X + barwidth / 2), top + h, 
barwidth, h, true, type).attr({stroke: colors[multi ? j : i], fill: 
colors[multi ? j : i]});
+            if (multi) {
+                bars[j].push(bar);
+            } else {
+                bars.push(bar);
+            }
+            bar.y = top;
+            bar.x = Math.round(X + barwidth / 2);
+            bar.w = barwidth;
+            bar.h = h;
+            bar.value = multi ? values[j][i] : values[i];
+            if (!opts.stacked) {
+                X += barwidth;
+            } else {
+                stack.push(bar);
+            }
+        }
+        if (opts.stacked) {
+            var cvr;
+            covers2.push(cvr = this.rect(stack[0].x - stack[0].w / 2, y, 
barwidth, height).attr(this.g.shim));
+            cvr.bars = this.set();
+            var size = 0;
+            for (var s = stack.length; s--;) {
+                stack[s].toFront();
+            }
+            for (var s = 0, ss = stack.length; s < ss; s++) {
+                var bar = stack[s],
+                    cover,
+                    h = (size + bar.value) * Y,
+                    path = this.g.finger(bar.x, y + height - barvgutter - 
!!size * .5, barwidth, h, true, type, 1);
+                cvr.bars.push(bar);
+                size && bar.attr({path: path});
+                bar.h = h;
+                bar.y = y + height - barvgutter - !!size * .5 - h;
+                covers.push(cover = this.rect(bar.x - bar.w / 2, bar.y, 
barwidth, bar.value * Y).attr(this.g.shim));
+                cover.bar = bar;
+                cover.value = bar.value;
+                size += bar.value;
+            }
+            X += barwidth;
+        }
+        X += barhgutter;
+    }
+    covers2.toFront();
+    X = x + barhgutter;
+    if (!opts.stacked) {
+        for (var i = 0; i < len; i++) {
+            for (var j = 0; j < (multi || 1); j++) {
+                var cover;
+                covers.push(cover = this.rect(Math.round(X), y + barvgutter, 
barwidth, height - barvgutter).attr(this.g.shim));
+                cover.bar = multi ? bars[j][i] : bars[i];
+                cover.value = cover.bar.value;
+                X += barwidth;
+            }
+            X += barhgutter;
+        }
+    }
+    chart.label = function (labels, isBottom) {
+        labels = labels || [];
+        this.labels = paper.set();
+        var L, l = -Infinity;
+        if (opts.stacked) {
+            for (var i = 0; i < len; i++) {
+                var tot = 0;
+                for (var j = 0; j < (multi || 1); j++) {
+                    tot += multi ? values[j][i] : values[i];
+                    if (j == multi - 1) {
+                        var label = paper.g.labelise(labels[i], tot, total);
+                        L = paper.g.text(bars[i * (multi || 1) + j].x, y + 
height - barvgutter / 2, label).insertBefore(covers[i * (multi || 1) + j]);
+                        var bb = L.getBBox();
+                        if (bb.x - 7 < l) {
+                            L.remove();
+                        } else {
+                            this.labels.push(L);
+                            l = bb.x + bb.width;
+                        }
+                    }
+                }
+            }
+        } else {
+            for (var i = 0; i < len; i++) {
+                for (var j = 0; j < (multi || 1); j++) {
+                    var label = paper.g.labelise(multi ? labels[j] && 
labels[j][i] : labels[i], multi ? values[j][i] : values[i], total);
+                    L = paper.g.text(bars[i * (multi || 1) + j].x, isBottom ? 
y + height - barvgutter / 2 : bars[i * (multi || 1) + j].y - 10, 
label).insertBefore(covers[i * (multi || 1) + j]);
+                    var bb = L.getBBox();
+                    if (bb.x - 7 < l) {
+                        L.remove();
+                    } else {
+                        this.labels.push(L);
+                        l = bb.x + bb.width;
+                    }
+                }
+            }
+        }
+        return this;
+    };
+    chart.hover = function (fin, fout) {
+        covers2.hide();
+        covers.show();
+        covers.mouseover(fin).mouseout(fout);
+        return this;
+    };
+    chart.hoverColumn = function (fin, fout) {
+        covers.hide();
+        covers2.show();
+        fout = fout || function () {};
+        covers2.mouseover(fin).mouseout(fout);
+        return this;
+    };
+    chart.click = function (f) {
+        covers2.hide();
+        covers.show();
+        covers.click(f);
+        return this;
+    };
+    chart.each = function (f) {
+        if (!Raphael.is(f, "function")) {
+            return this;
+        }
+        for (var i = covers.length; i--;) {
+            f.call(covers[i]);
+        }
+        return this;
+    };
+    chart.eachColumn = function (f) {
+        if (!Raphael.is(f, "function")) {
+            return this;
+        }
+        for (var i = covers2.length; i--;) {
+            f.call(covers2[i]);
+        }
+        return this;
+    };
+    chart.clickColumn = function (f) {
+        covers.hide();
+        covers2.show();
+        covers2.click(f);
+        return this;
+    };
+    chart.push(bars, covers, covers2);
+    chart.bars = bars;
+    chart.covers = covers;
+    return chart;
+};
+Raphael.fn.g.hbarchart = function (x, y, width, height, values, opts) {
+    opts = opts || {};
+    var type = {round: "round", sharp: "sharp", soft: "soft"}[opts.type] || 
"square",
+        gutter = parseFloat(opts.gutter || "20%"),
+        chart = this.set(),
+        bars = this.set(),
+        covers = this.set(),
+        covers2 = this.set(),
+        total = Math.max.apply(Math, values),
+        stacktotal = [],
+        paper = this,
+        multi = 0,
+        colors = opts.colors || this.g.colors,
+        len = values.length;
+    if (this.raphael.is(values[0], "array")) {
+        total = [];
+        multi = len;
+        len = 0;
+        for (var i = values.length; i--;) {
+            bars.push(this.set());
+            total.push(Math.max.apply(Math, values[i]));
+            len = Math.max(len, values[i].length);
+        }
+        if (opts.stacked) {
+            for (var i = len; i--;) {
+                var tot = 0;
+                for (var j = values.length; j--;) {
+                    tot +=+ values[j][i] || 0;
+                }
+                stacktotal.push(tot);
+            }
+        }
+        for (var i = values.length; i--;) {
+            if (values[i].length < len) {
+                for (var j = len; j--;) {
+                    values[i].push(0);
+                }
+            }
+        }
+        total = Math.max.apply(Math, opts.stacked ? stacktotal : total);
+    }
+    
+    total = (opts.to) || total;
+    var barheight = Math.floor(height / (len * (100 + gutter) + gutter) * 100),
+        bargutter = Math.floor(barheight * gutter / 100),
+        stack = [],
+        Y = y + bargutter,
+        X = (width - 1) / total;
+    !opts.stacked && (barheight /= multi || 1);
+    for (var i = 0; i < len; i++) {
+        stack = [];
+        for (var j = 0; j < (multi || 1); j++) {
+            var val = multi ? values[j][i] : values[i],
+                bar = this.g.finger(x, Y + barheight / 2, Math.round(val * X), 
barheight - 1, false, type).attr({stroke: colors[multi ? j : i], fill: 
colors[multi ? j : i]});
+            if (multi) {
+                bars[j].push(bar);
+            } else {
+                bars.push(bar);
+            }
+            bar.x = x + Math.round(val * X);
+            bar.y = Y + barheight / 2;
+            bar.w = Math.round(val * X);
+            bar.h = barheight;
+            bar.value = +val;
+            if (!opts.stacked) {
+                Y += barheight;
+            } else {
+                stack.push(bar);
+            }
+        }
+        if (opts.stacked) {
+            var cvr = this.rect(x, stack[0].y - stack[0].h / 2, width, 
barheight).attr(this.g.shim);
+            covers2.push(cvr);
+            cvr.bars = this.set();
+            var size = 0;
+            for (var s = stack.length; s--;) {
+                stack[s].toFront();
+            }
+            for (var s = 0, ss = stack.length; s < ss; s++) {
+                var bar = stack[s],
+                    cover,
+                    val = Math.round((size + bar.value) * X),
+                    path = this.g.finger(x, bar.y, val, barheight - 1, false, 
type, 1);
+                cvr.bars.push(bar);
+                size && bar.attr({path: path});
+                bar.w = val;
+                bar.x = x + val;
+                covers.push(cover = this.rect(x + size * X, bar.y - bar.h / 2, 
bar.value * X, barheight).attr(this.g.shim));
+                cover.bar = bar;
+                size += bar.value;
+            }
+            Y += barheight;
+        }
+        Y += bargutter;
+    }
+    covers2.toFront();
+    Y = y + bargutter;
+    if (!opts.stacked) {
+        for (var i = 0; i < len; i++) {
+            for (var j = 0; j < multi; j++) {
+                var cover = this.rect(x, Y, width, 
barheight).attr(this.g.shim);
+                covers.push(cover);
+                cover.bar = bars[j][i];
+                Y += barheight;
+            }
+            Y += bargutter;
+        }
+    }
+    chart.label = function (labels, isRight) {
+        labels = labels || [];
+        this.labels = paper.set();
+        for (var i = 0; i < len; i++) {
+            for (var j = 0; j < multi; j++) {
+                var  label = paper.g.labelise(multi ? labels[j] && 
labels[j][i] : labels[i], multi ? values[j][i] : values[i], total);
+                var X = isRight ? bars[i * (multi || 1) + j].x - barheight / 2 
+ 3 : x + 5,
+                    A = isRight ? "end" : "start",
+                    L;
+                this.labels.push(L = paper.g.text(X, bars[i * (multi || 1) + 
j].y, label).attr({"text-anchor": A}).insertBefore(covers[0]));
+                if (L.getBBox().x < x + 5) {
+                    L.attr({x: x + 5, "text-anchor": "start"});
+                } else {
+                    bars[i * (multi || 1) + j].label = L;
+                }
+            }
+        }
+        return this;
+    };
+    chart.hover = function (fin, fout) {
+        covers2.hide();
+        covers.show();
+        fout = fout || function () {};
+        covers.mouseover(fin).mouseout(fout);
+        return this;
+    };
+    chart.hoverColumn = function (fin, fout) {
+        covers.hide();
+        covers2.show();
+        fout = fout || function () {};
+        covers2.mouseover(fin).mouseout(fout);
+        return this;
+    };
+    chart.each = function (f) {
+        if (!Raphael.is(f, "function")) {
+            return this;
+        }
+        for (var i = covers.length; i--;) {
+            f.call(covers[i]);
+        }
+        return this;
+    };
+    chart.eachColumn = function (f) {
+        if (!Raphael.is(f, "function")) {
+            return this;
+        }
+        for (var i = covers2.length; i--;) {
+            f.call(covers2[i]);
+        }
+        return this;
+    };
+    chart.click = function (f) {
+        covers2.hide();
+        covers.show();
+        covers.click(f);
+        return this;
+    };
+    chart.clickColumn = function (f) {
+        covers.hide();
+        covers2.show();
+        covers2.click(f);
+        return this;
+    };
+    chart.push(bars, covers, covers2);
+    chart.bars = bars;
+    chart.covers = covers;
+    return chart;
+};

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/webapp/org/apache/zookeeper/graph/resources/g.dot.js
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/webapp/org/apache/zookeeper/graph/resources/g.dot.js
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/webapp/org/apache/zookeeper/graph/resources/g.dot.js
new file mode 100644
index 0000000..2821e62
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/webapp/org/apache/zookeeper/graph/resources/g.dot.js
@@ -0,0 +1,110 @@
+/*
+ * g.Raphael 0.4 - Charting library, based on Raphaël
+ *
+ * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 
license.
+ */
+Raphael.fn.g.dotchart = function (x, y, width, height, valuesx, valuesy, size, 
opts) {
+    function drawAxis(ax) {
+        +ax[0] && (ax[0] = paper.g.axis(x + gutter, y + gutter, width - 2 * 
gutter, minx, maxx, opts.axisxstep || Math.floor((width - 2 * gutter) / 20), 2, 
opts.axisxlabels || null, opts.axisxtype || "t"));
+        +ax[1] && (ax[1] = paper.g.axis(x + width - gutter, y + height - 
gutter, height - 2 * gutter, miny, maxy, opts.axisystep || Math.floor((height - 
2 * gutter) / 20), 3, opts.axisylabels || null, opts.axisytype || "t"));
+        +ax[2] && (ax[2] = paper.g.axis(x + gutter, y + height - gutter + 
maxR, width - 2 * gutter, minx, maxx, opts.axisxstep || Math.floor((width - 2 * 
gutter) / 20), 0, opts.axisxlabels || null, opts.axisxtype || "t"));
+        +ax[3] && (ax[3] = paper.g.axis(x + gutter - maxR, y + height - 
gutter, height - 2 * gutter, miny, maxy, opts.axisystep || Math.floor((height - 
2 * gutter) / 20), 1, opts.axisylabels || null, opts.axisytype || "t"));
+    }
+    opts = opts || {};
+    var xdim = this.g.snapEnds(Math.min.apply(Math, valuesx), 
Math.max.apply(Math, valuesx), valuesx.length - 1),
+        minx = xdim.from,
+        maxx = xdim.to,
+        gutter = opts.gutter || 10,
+        ydim = this.g.snapEnds(Math.min.apply(Math, valuesy), 
Math.max.apply(Math, valuesy), valuesy.length - 1),
+        miny = ydim.from,
+        maxy = ydim.to,
+        len = Math.max(valuesx.length, valuesy.length, size.length),
+        symbol = this.g.markers[opts.symbol] || "disc",
+        res = this.set(),
+        series = this.set(),
+        max = opts.max || 100,
+        top = Math.max.apply(Math, size),
+        R = [],
+        paper = this,
+        k = Math.sqrt(top / Math.PI) * 2 / max;
+
+    for (var i = 0; i < len; i++) {
+        R[i] = Math.min(Math.sqrt(size[i] / Math.PI) * 2 / k, max);
+    }
+    gutter = Math.max.apply(Math, R.concat(gutter));
+    var axis = this.set(),
+        maxR = Math.max.apply(Math, R);
+    if (opts.axis) {
+        var ax = (opts.axis + "").split(/[,\s]+/);
+        drawAxis(ax);
+        var g = [], b = [];
+        for (var i = 0, ii = ax.length; i < ii; i++) {
+            var bb = ax[i].all ? ax[i].all.getBBox()[["height", "width"][i % 
2]] : 0;
+            g[i] = bb + gutter;
+            b[i] = bb;
+        }
+        gutter = Math.max.apply(Math, g.concat(gutter));
+        for (var i = 0, ii = ax.length; i < ii; i++) if (ax[i].all) {
+            ax[i].remove();
+            ax[i] = 1;
+        }
+        drawAxis(ax);
+        for (var i = 0, ii = ax.length; i < ii; i++) if (ax[i].all) {
+            axis.push(ax[i].all);
+        }
+        res.axis = axis;
+    }
+    var kx = (width - gutter * 2) / ((maxx - minx) || 1),
+        ky = (height - gutter * 2) / ((maxy - miny) || 1);
+    for (var i = 0, ii = valuesy.length; i < ii; i++) {
+        var sym = this.raphael.is(symbol, "array") ? symbol[i] : symbol,
+            X = x + gutter + (valuesx[i] - minx) * kx,
+            Y = y + height - gutter - (valuesy[i] - miny) * ky;
+        sym && R[i] && series.push(this.g[sym](X, Y, R[i]).attr({fill: 
opts.heat ? this.g.colorValue(R[i], maxR) : Raphael.fn.g.colors[0], 
"fill-opacity": opts.opacity ? R[i] / max : 1, stroke: "none"}));
+    }
+    var covers = this.set();
+    for (var i = 0, ii = valuesy.length; i < ii; i++) {
+        var X = x + gutter + (valuesx[i] - minx) * kx,
+            Y = y + height - gutter - (valuesy[i] - miny) * ky;
+        covers.push(this.circle(X, Y, maxR).attr(this.g.shim));
+        opts.href && opts.href[i] && covers[i].attr({href: opts.href[i]});
+        covers[i].r = +R[i].toFixed(3);
+        covers[i].x = +X.toFixed(3);
+        covers[i].y = +Y.toFixed(3);
+        covers[i].X = valuesx[i];
+        covers[i].Y = valuesy[i];
+        covers[i].value = size[i] || 0;
+        covers[i].dot = series[i];
+    }
+    res.covers = covers;
+    res.series = series;
+    res.push(series, axis, covers);
+    res.hover = function (fin, fout) {
+        covers.mouseover(fin).mouseout(fout);
+        return this;
+    };
+    res.click = function (f) {
+        covers.click(f);
+        return this;
+    };
+    res.each = function (f) {
+        if (!Raphael.is(f, "function")) {
+            return this;
+        }
+        for (var i = covers.length; i--;) {
+            f.call(covers[i]);
+        }
+        return this;
+    };
+    res.href = function (map) {
+        var cover;
+        for (var i = covers.length; i--;) {
+            cover = covers[i];
+            if (cover.X == map.x && cover.Y == map.y && cover.value == 
map.value) {
+                cover.attr({href: map.href});
+            }
+        }
+    };
+    return res;
+};

http://git-wip-us.apache.org/repos/asf/zookeeper/blob/b0df8fe1/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/webapp/org/apache/zookeeper/graph/resources/g.line.js
----------------------------------------------------------------------
diff --git 
a/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/webapp/org/apache/zookeeper/graph/resources/g.line.js
 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/webapp/org/apache/zookeeper/graph/resources/g.line.js
new file mode 100644
index 0000000..eb56e59
--- /dev/null
+++ 
b/zookeeper-contrib/zookeeper-contrib-loggraph/src/main/webapp/org/apache/zookeeper/graph/resources/g.line.js
@@ -0,0 +1,230 @@
+/*
+ * g.Raphael 0.4 - Charting library, based on Raphaël
+ *
+ * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 
license.
+ */
+Raphael.fn.g.linechart = function (x, y, width, height, valuesx, valuesy, 
opts) {
+    function shrink(values, dim) {
+        var k = values.length / dim,
+            j = 0,
+            l = k,
+            sum = 0,
+            res = [];
+        while (j < values.length) {
+            l--;
+            if (l < 0) {
+                sum += values[j] * (1 + l);
+                res.push(sum / k);
+                sum = values[j++] * -l;
+                l += k;
+            } else {
+                sum += values[j++];
+            }
+        }
+        return res;
+    }
+    opts = opts || {};
+    if (!this.raphael.is(valuesx[0], "array")) {
+        valuesx = [valuesx];
+    }
+    if (!this.raphael.is(valuesy[0], "array")) {
+        valuesy = [valuesy];
+    }
+    var allx = Array.prototype.concat.apply([], valuesx),
+        ally = Array.prototype.concat.apply([], valuesy),
+        xdim = this.g.snapEnds(Math.min.apply(Math, allx), 
Math.max.apply(Math, allx), valuesx[0].length - 1),
+        minx = xdim.from,
+        maxx = xdim.to,
+        gutter = opts.gutter || 10,
+        kx = (width - gutter * 2) / (maxx - minx),
+        ydim = this.g.snapEnds(Math.min.apply(Math, ally), 
Math.max.apply(Math, ally), valuesy[0].length - 1),
+        miny = ydim.from,
+        maxy = ydim.to,
+        ky = (height - gutter * 2) / (maxy - miny),
+        len = Math.max(valuesx[0].length, valuesy[0].length),
+        symbol = opts.symbol || "",
+        colors = opts.colors || Raphael.fn.g.colors,
+        that = this,
+        columns = null,
+        dots = null,
+        chart = this.set(),
+        path = [];
+
+    for (var i = 0, ii = valuesy.length; i < ii; i++) {
+        len = Math.max(len, valuesy[i].length);
+    }
+    var shades = this.set();
+    for (var i = 0, ii = valuesy.length; i < ii; i++) {
+        if (opts.shade) {
+            shades.push(this.path().attr({stroke: "none", fill: colors[i], 
opacity: opts.nostroke ? 1 : .3}));
+        }
+        if (valuesy[i].length > width - 2 * gutter) {
+            valuesy[i] = shrink(valuesy[i], width - 2 * gutter);
+            len = width - 2 * gutter;
+        }
+        if (valuesx[i] && valuesx[i].length > width - 2 * gutter) {
+            valuesx[i] = shrink(valuesx[i], width - 2 * gutter);
+        }
+    }
+    var axis = this.set();
+    if (opts.axis) {
+        var ax = (opts.axis + "").split(/[,\s]+/);
+        +ax[0] && axis.push(this.g.axis(x + gutter, y + gutter, width - 2 * 
gutter, minx, maxx, opts.axisxstep || Math.floor((width - 2 * gutter) / 20), 2, 
opts.northlabels));
+        +ax[1] && axis.push(this.g.axis(x + width - gutter, y + height - 
gutter, height - 2 * gutter, miny, maxy, opts.axisystep || Math.floor((height - 
2 * gutter) / 20), 3, opts.eastlabels));
+        +ax[2] && axis.push(this.g.axis(x + gutter, y + height - gutter, width 
- 2 * gutter, minx, maxx, opts.axisxstep || Math.floor((width - 2 * gutter) / 
20), 0, opts.southlabels));
+        +ax[3] && axis.push(this.g.axis(x + gutter, y + height - gutter, 
height - 2 * gutter, miny, maxy, opts.axisystep || Math.floor((height - 2 * 
gutter) / 20), 1, opts.westlabels));
+    }
+    if (opts.northAxisLabel) {
+       this.g.text(x + gutter + width/2, gutter, opts.northAxisLabel);
+    }
+    if (opts.southAxisLabel) {
+       this.g.text(x + gutter + width/2, y + height + 20, opts.southAxisLabel);
+    }
+    if (opts.westAxisLabel) {
+       this.g.text(gutter, y + gutter + height/2, 
opts.westAxisLabel).attr({rotation: -90});
+    }
+    if (opts.eastAxisLabel) {
+       this.g.text(x + gutter + width + 20, y + gutter + height/2, 
opts.eastAxisLabel).attr({rotation: 90});
+    }
+
+    var lines = this.set(),
+        symbols = this.set(),
+        line;
+    for (var i = 0, ii = valuesy.length; i < ii; i++) {
+        if (!opts.nostroke) {
+            lines.push(line = this.path().attr({
+                stroke: colors[i],
+                "stroke-width": opts.width || 2,
+                "stroke-linejoin": "round",
+                "stroke-linecap": "round",
+                "stroke-dasharray": opts.dash || ""
+            }));
+        }
+        var sym = this.raphael.is(symbol, "array") ? symbol[i] : symbol,
+            symset = this.set();
+        path = [];
+        for (var j = 0, jj = valuesy[i].length; j < jj; j++) {
+            var X = x + gutter + ((valuesx[i] || valuesx[0])[j] - minx) * kx;
+            var Y = y + height - gutter - (valuesy[i][j] - miny) * ky;
+            (Raphael.is(sym, "array") ? sym[j] : sym) && 
symset.push(this.g[Raphael.fn.g.markers[this.raphael.is(sym, "array") ? sym[j] 
: sym]](X, Y, (opts.width || 2) * 3).attr({fill: colors[i], stroke: "none"}));
+            path = path.concat([j ? "L" : "M", X, Y]);
+        }
+        symbols.push(symset);
+        if (opts.shade) {
+            shades[i].attr({path: path.concat(["L", X, y + height - gutter, 
"L",  x + gutter + ((valuesx[i] || valuesx[0])[0] - minx) * kx, y + height - 
gutter, "z"]).join(",")});
+        }
+        !opts.nostroke && line.attr({path: path.join(",")});
+    }
+    function createColumns(f) {
+        // unite Xs together
+        var Xs = [];
+        for (var i = 0, ii = valuesx.length; i < ii; i++) {
+            Xs = Xs.concat(valuesx[i]);
+        }
+        Xs.sort();
+        // remove duplicates
+        var Xs2 = [],
+            xs = [];
+        for (var i = 0, ii = Xs.length; i < ii; i++) {
+            Xs[i] != Xs[i - 1] && Xs2.push(Xs[i]) && xs.push(x + gutter + 
(Xs[i] - minx) * kx);
+        }
+        Xs = Xs2;
+        ii = Xs.length;
+        var cvrs = f || that.set();
+        for (var i = 0; i < ii; i++) {
+            var X = xs[i] - (xs[i] - (xs[i - 1] || x)) / 2,
+                w = ((xs[i + 1] || x + width) - xs[i]) / 2 + (xs[i] - (xs[i - 
1] || x)) / 2,
+                C;
+            f ? (C = {}) : cvrs.push(C = that.rect(X - 1, y, Math.max(w + 1, 
1), height).attr({stroke: "none", fill: "#000", opacity: 0}));
+            C.values = [];
+            C.symbols = that.set();
+            C.y = [];
+            C.x = xs[i];
+            C.axis = Xs[i];
+            for (var j = 0, jj = valuesy.length; j < jj; j++) {
+                Xs2 = valuesx[j] || valuesx[0];
+                for (var k = 0, kk = Xs2.length; k < kk; k++) {
+                    if (Xs2[k] == Xs[i]) {
+                        C.values.push(valuesy[j][k]);
+                        C.y.push(y + height - gutter - (valuesy[j][k] - miny) 
* ky);
+                        C.symbols.push(chart.symbols[j][k]);
+                    }
+                }
+            }
+            f && f.call(C);
+        }
+        !f && (columns = cvrs);
+    }
+    function createDots(f) {
+        var cvrs = f || that.set(),
+            C;
+        for (var i = 0, ii = valuesy.length; i < ii; i++) {
+            for (var j = 0, jj = valuesy[i].length; j < jj; j++) {
+                var X = x + gutter + ((valuesx[i] || valuesx[0])[j] - minx) * 
kx,
+                    nearX = x + gutter + ((valuesx[i] || valuesx[0])[j ? j - 1 
: 1] - minx) * kx,
+                    Y = y + height - gutter - (valuesy[i][j] - miny) * ky;
+                f ? (C = {}) : cvrs.push(C = that.circle(X, Y, Math.abs(nearX 
- X) / 2).attr({stroke: "none", fill: "#000", opacity: 0}));
+                C.x = X;
+                C.y = Y;
+                C.value = valuesy[i][j];
+                C.line = chart.lines[i];
+                C.shade = chart.shades[i];
+                C.symbol = chart.symbols[i][j];
+                C.symbols = chart.symbols[i];
+                C.axis = (valuesx[i] || valuesx[0])[j];
+                f && f.call(C);
+            }
+        }
+        !f && (dots = cvrs);
+    }
+    chart.push(lines, shades, symbols, axis, columns, dots);
+    chart.lines = lines;
+    chart.shades = shades;
+    chart.symbols = symbols;
+    chart.axis = axis;
+    chart.hoverColumn = function (fin, fout) {
+        !columns && createColumns();
+        columns.mouseover(fin).mouseout(fout);
+        return this;
+    };
+    chart.clickColumn = function (f) {
+        !columns && createColumns();
+        columns.click(f);
+        return this;
+    };
+    chart.hrefColumn = function (cols) {
+        var hrefs = that.raphael.is(arguments[0], "array") ? arguments[0] : 
arguments;
+        if (!(arguments.length - 1) && typeof cols == "object") {
+            for (var x in cols) {
+                for (var i = 0, ii = columns.length; i < ii; i++) if 
(columns[i].axis == x) {
+                    columns[i].attr("href", cols[x]);
+                }
+            }
+        }
+        !columns && createColumns();
+        for (var i = 0, ii = hrefs.length; i < ii; i++) {
+            columns[i] && columns[i].attr("href", hrefs[i]);
+        }
+        return this;
+    };
+    chart.hover = function (fin, fout) {
+        !dots && createDots();
+        dots.mouseover(fin).mouseout(fout);
+        return this;
+    };
+    chart.click = function (f) {
+        !dots && createDots();
+        dots.click(f);
+        return this;
+    };
+    chart.each = function (f) {
+        createDots(f);
+        return this;
+    };
+    chart.eachColumn = function (f) {
+        createColumns(f);
+        return this;
+    };
+    return chart;
+};

Reply via email to