Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add a performance-metric system in contrib, as well as hamlet.

  • Loading branch information...
commit 8b719c86913398ace8e197b6de145b33d9d300bb 1 parent e270fd0
@rolfl rolfl authored
View
379 contrib/src/java/org/jdom2/contrib/perf/PerfDoc.java
@@ -0,0 +1,379 @@
+package org.jdom2.contrib.perf;
+
+import java.io.CharArrayReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+import org.jdom2.*;
+import org.jdom2.filter.Filters;
+import org.jdom2.input.SAXBuilder;
+import org.jdom2.input.SAXHandler;
+import org.jdom2.output.Format;
+import org.jdom2.output.SAXOutputter;
+import org.jdom2.output.XMLOutputter;
+import org.jdom2.xpath.XPath;
+
+public class PerfDoc {
+
+ private class LoadRunnable implements TimeRunnable {
+ private final char[] data;
+
+ LoadRunnable(char[] indata) {
+ this.data = indata;
+ }
+
+ @Override
+ public void run() throws Exception {
+ subload(data);
+ }
+ }
+
+ private static final Writer devnull = new Writer() {
+
+ @Override
+ public void write(char[] cbuf, int off, int len) throws IOException {
+ // do nothing
+ }
+
+ @Override
+ public void flush() throws IOException {
+ // do nothing
+ }
+
+ @Override
+ public void close() throws IOException {
+ // do nothing
+ }
+
+ @Override
+ public void write(int c) throws IOException {
+ // do nothing
+ }
+
+ @Override
+ public void write(char[] cbuf) throws IOException {
+ // do nothing
+ }
+
+ @Override
+ public void write(String str) throws IOException {
+ // do nothing
+ }
+
+ @Override
+ public void write(String str, int off, int len) throws IOException {
+ // do nothing
+ }
+
+ @Override
+ public Writer append(CharSequence csq) throws IOException {
+ return this;
+ }
+
+ @Override
+ public Writer append(CharSequence csq, int start, int end) throws IOException {
+ return this;
+ }
+
+ @Override
+ public Writer append(char c) throws IOException {
+ return this;
+ }
+
+ };
+
+ private final File infile;
+
+ private long loadTime = -1L;
+ private long loadMem = -1L;
+
+ private long dumpTime = -1L;
+ private long xpathTime = -1L;
+ private long dupeTime = -1L;
+ private long scanTime = -1L;
+ private long checkedTime = -1L;
+ private long uncheckedTime = -1L;
+
+
+ private Document document = null;
+
+ public PerfDoc(File file) {
+ infile = file;
+ }
+
+ public File getFile() {
+ return infile;
+ }
+
+ public long load() throws Exception {
+ char[] chars = new char[(int)infile.length()];
+ int len = 0;
+ int cnt = 0;
+ FileReader fr = null;
+ try {
+ fr = new FileReader(infile);
+ while ((cnt = fr.read(chars, len, chars.length - len)) >= 0) {
+ if (cnt == 0 && len == chars.length) {
+ chars = Arrays.copyOf(chars, chars.length + 10240);
+ }
+ len += cnt;
+ }
+ chars = Arrays.copyOf(chars, len);
+ } finally {
+ if (fr != null) {
+ fr.close();
+ }
+ fr = null;
+ }
+ final long startmem = PerfTest.usedMem();
+ loadTime = PerfTest.timeRun( new LoadRunnable(chars) );
+ loadMem = PerfTest.usedMem() - startmem;
+ for (char c : chars) {
+ // need to keep chars in memory until after the usedMem test.
+ if (c == (char)0) {
+ throw new IllegalStateException();
+ }
+ }
+ return loadTime;
+ }
+
+ public long subload(char[] chars) throws IOException, JDOMException {
+
+ CharArrayReader car = new CharArrayReader(chars);
+ SAXBuilder sax = new SAXBuilder();
+ sax.setValidation(false);
+ PerfTest.usedMem();
+ long start = System.nanoTime();
+ document = sax.build(car);
+ return System.nanoTime() - start;
+ }
+
+ public int recurse(Element emt) {
+ int cnt = 1;
+ for (Element kid : emt.getChildren()) {
+ cnt += recurse(kid);
+ }
+ return cnt;
+ }
+
+ public long scan() throws Exception {
+ scanTime = PerfTest.timeRun(new TimeRunnable() {
+ @Override
+ public void run() {
+ int elements = 0;
+ Iterator<Content> it = document.getDescendants();
+ while (it.hasNext()) {
+ if (it.next() instanceof Element) {
+ elements++;
+ }
+ }
+
+ int felements = 0;
+ Iterator<Element> et = document.getDescendants(Filters.element());
+ while (et.hasNext()) {
+ et.next();
+ felements++;
+ }
+ if (felements != elements) {
+ System.out.printf("Different counts Descendants=%d Elements=%d\n", elements, felements);
+ }
+
+ int rcnt = recurse(document.getRootElement());
+ if (rcnt != elements) {
+ System.out.printf("Different counts Descendants=%d Recurse=%d\n", elements, rcnt);
+ }
+ }
+ });
+ return scanTime;
+ }
+
+ public long dump () throws Exception {
+ dumpTime = PerfTest.timeRun(new TimeRunnable() {
+
+ @Override
+ public void run() throws Exception {
+ dump(Format.getCompactFormat());
+ dump(Format.getPrettyFormat());
+ dump(Format.getRawFormat());
+ }
+ });
+ return dumpTime;
+ }
+
+ private long dump(Format format) throws IOException {
+ XMLOutputter xout = new XMLOutputter(format);
+ long start = System.nanoTime();
+ xout.output(document, devnull);
+ return System.nanoTime() - start;
+ }
+
+ public long xpath() throws Exception {
+
+ xpathTime = PerfTest.timeRun(new TimeRunnable() {
+ @Override
+ public void run() throws Exception {
+ XPath patha = XPath.newInstance("//@null");
+ patha.selectNodes(document);
+ // select everything
+ XPath pathb = XPath.newInstance("//node()");
+ pathb.selectNodes(document);
+ }
+ });
+ return xpathTime;
+ }
+
+ private Collection<Content>duplicateContent(List<? extends Content> content) {
+ ArrayList<Content>ret = new ArrayList<Content>(content.size());
+ for (Content c : content) {
+ if (c instanceof Element) {
+ Element emt = (Element)c;
+ Element ne = new Element(emt.getName(), emt.getNamespacePrefix(), emt.getNamespaceURI());
+ for (Attribute att : emt.getAttributes()) {
+ Attribute a = new Attribute(att.getName(), att.getValue(),
+ att.getAttributeType(),
+ Namespace.getNamespace(att.getNamespacePrefix(), att.getNamespaceURI()));
+ emt.setAttribute(a);
+ }
+ for (Namespace ns : emt.getAdditionalNamespaces()) {
+ ne.addNamespaceDeclaration(ns);
+ }
+ ne.addContent(duplicateContent(emt.getContent()));
+ ret.add(ne);
+ } else if (c instanceof CDATA) {
+ ret.add(new CDATA(((CDATA)c).getText()));
+ } else if (c instanceof Comment) {
+ ret.add(new Comment(((Comment)c).getText()));
+ } else if (c instanceof EntityRef) {
+ EntityRef er = (EntityRef)c;
+ ret.add(new EntityRef(er.getName(), er.getPublicID(), er.getSystemID()));
+ } else if (c instanceof Text) {
+ ret.add(new Text(((Text)c).getText()));
+ } else if (c instanceof ProcessingInstruction) {
+ ProcessingInstruction pi = (ProcessingInstruction)c;
+ ret.add(new ProcessingInstruction(pi.getTarget(), pi.getData()));
+ } else if (c instanceof DocType) {
+ DocType dt = (DocType)c;
+ DocType ndt = new DocType(dt.getElementName(), dt.getPublicID(), dt.getSystemID());
+ if (dt.getInternalSubset() != null) {
+ ndt.setInternalSubset(dt.getInternalSubset());
+ }
+ ret.add(ndt);
+ } else {
+ throw new IllegalStateException("Unknown content " + c);
+ }
+ }
+ return ret;
+ }
+
+ public long duplicate() throws Exception {
+ final XMLOutputter xout = new XMLOutputter(Format.getRawFormat());
+ final String orig = xout.outputString(document);
+ dupeTime = PerfTest.timeRun(new TimeRunnable() {
+ @Override
+ public void run() throws Exception {
+ // select nothing.
+ Document doc = new Document();
+ doc.addContent(duplicateContent(document.getContent()));
+ String dupe = xout.outputString(doc);
+ if (!orig.equals(dupe)) {
+ throw new IllegalStateException("Bad clone!");
+ }
+ }
+ });
+ return dupeTime;
+ }
+
+ public long checkedParse() throws Exception {
+ final XMLOutputter xout = new XMLOutputter(Format.getRawFormat());
+ final String orig = xout.outputString(document);
+ checkedTime = PerfTest.timeRun(new TimeRunnable() {
+ @Override
+ public void run() throws Exception {
+ // select nothing.
+ SAXHandler handler = new SAXHandler(new DefaultJDOMFactory());
+ SAXOutputter saxout = new SAXOutputter(handler, handler, handler, handler, handler);
+ saxout.output(document);
+ Document doc = handler.getDocument();
+ String dupe = xout.outputString(doc);
+ if (!orig.equals(dupe)) {
+ throw new IllegalStateException("Bad clone!");
+ }
+ }
+ });
+ return checkedTime;
+ }
+
+ public long unCheckedParse() throws Exception {
+ final XMLOutputter xout = new XMLOutputter(Format.getRawFormat());
+ final String orig = xout.outputString(document);
+ uncheckedTime = PerfTest.timeRun(new TimeRunnable() {
+ @Override
+ public void run() throws Exception {
+ // select nothing.
+ SAXHandler handler = new SAXHandler(new UncheckedJDOMFactory());
+ SAXOutputter saxout = new SAXOutputter(handler, handler, handler, handler, handler);
+ saxout.output(document);
+ Document doc = handler.getDocument();
+ String dupe = xout.outputString(doc);
+ if (!orig.equals(dupe)) {
+ throw new IllegalStateException("Bad clone!");
+ }
+ }
+ });
+ return uncheckedTime;
+ }
+
+ public long getLoadTime() {
+ return loadTime;
+ }
+
+
+ public long getLoadMem() {
+ return loadMem;
+ }
+
+
+ public long getDumpTime() {
+ return dumpTime;
+ }
+
+ public long getDupeTime() {
+ return dupeTime;
+ }
+
+ public long getXpathTime() {
+ return xpathTime;
+ }
+
+ public long getCheckedTime() {
+ return checkedTime;
+ }
+
+ public long getUncheckedTime() {
+ return uncheckedTime;
+ }
+
+
+ public long getScanTime() {
+ return scanTime;
+ }
+
+
+ public Document getDocument() {
+ return document;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("PerfDoc %s mem=%d load=%d scan=%d xpath=%d dump=%d",
+ infile.getPath(), loadMem, loadTime, scanTime, xpathTime, dumpTime);
+ }
+
+}
View
271 contrib/src/java/org/jdom2/contrib/perf/PerfTest.java
@@ -0,0 +1,271 @@
+package org.jdom2.contrib.perf;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class PerfTest {
+
+ public static final long timeRun(TimeRunnable torun) throws Exception {
+ long min = Long.MAX_VALUE;
+ long max = Long.MIN_VALUE;
+ long sum = 0L;
+ final int cnt = 6;
+ for (int i = 0 ; i < cnt; i++) {
+ System.gc();
+ long time = System.nanoTime();
+ torun.run();
+ time = System.nanoTime() - time;
+ if (time > max) {
+ max = time;
+ }
+ if (time < min) {
+ min = time;
+ }
+ sum += time;
+ }
+ return (sum - (min + max)) / (cnt - 2);
+ }
+
+
+ public static final long usedMem() {
+ long mem = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ boolean stable = false;
+ Thread gct = new Thread("GCPrompt") {
+ @Override
+ public void run() {
+ System.gc();
+ }
+ };
+ gct.setDaemon(true);
+ try {
+ gct.start();
+ Thread.sleep(100);
+ gct.join();
+ } catch (Exception e) {
+ // ignore;
+ }
+ do {
+ int cnt = 3;
+ while (--cnt >= 0) {
+ System.gc();
+ }
+ long tmpmem = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ long diff = mem - tmpmem;
+ mem = tmpmem;
+ if (diff >= 0 && diff < 128) {
+ stable = true;
+ }
+ } while (!stable);
+ return mem;
+ }
+
+ private static final String[] suffix = new String[]{"Bytes", "KiB", "MiB", "GiB", "TiB" };
+
+ private static final String formatMem(long mem) {
+ double frac = mem;
+ int cnt = 0;
+ while (frac > 1024.0) {
+ frac /= 1024.0;
+ cnt++;
+ }
+ return String.format("%.2f%s", frac, suffix[cnt]);
+ }
+
+ /**
+ * @param args
+ */
+ public static void main(String[] args) {
+ ArrayList<File> infiles = new ArrayList<File>();
+
+ final FileFilter filefilter = new FileFilter() {
+ @Override
+ public boolean accept(File path) {
+ return path.isFile() && path.getName().toLowerCase().endsWith(".xml");
+ }
+ };
+
+ for (String arg : args) {
+ File f = new File(arg);
+ if (f.exists()) {
+ if (f.isFile() && f.getName().toLowerCase().endsWith(".xml")) {
+ infiles.add(f);
+ } else if (f.isDirectory()) {
+ File[] files = f.listFiles(filefilter);
+ if (files.length > 0) {
+ for (File sf : files) {
+ infiles.add(sf);
+ }
+ } else {
+ System.err.println("Ignoring File " + f + ": Directory has no XML files.");
+ }
+ } else {
+ System.err.println("Ignoring File " + f + ": not .xml and not Directory.");
+ }
+ } else {
+ System.err.println("Ignoring File " + f + ": does not exist.");
+ }
+ }
+
+ if (infiles.isEmpty()) {
+ File tryhamlet = new File("contrib/src/resources/hamlet.xml");
+ if (tryhamlet.exists()) {
+ System.out.println("Using default file " + tryhamlet);
+ infiles.add(tryhamlet);
+ }
+ }
+
+ System.out.printf("Processing %d files.\n", infiles.size());
+
+ StringBuilder html = new StringBuilder();
+
+ perfloop(infiles, html);
+ System.out.println("Ignore the above warm-up result!\n\n");
+
+ html.setLength(0);
+ html.append("\n\t<hr/>\n\t<p/>\n\tDescription - change me\n\t<br />\n\t<table border=\"1\">\n\t\t<tr>");
+ for (String h : new String[] {"Input", "JDOM", "Parse", "Scan", "Dump",
+ "Dupe", "XPath", "Checked", "UnChecked"}) {
+ html.append("<th>").append(h).append("</th>");
+ }
+ html.append("</tr>\n");
+
+ for (int i = 0; i < 5; i++) {
+ perfloop(infiles, html);
+ }
+
+ html.append("\t</table>\n");
+
+ System.out.println(html.toString());
+ }
+
+ private static final void perfloop(List<File> infiles, StringBuilder html) {
+
+ double mstime = 1000000.0;
+
+ PerfDoc[] docs = new PerfDoc[infiles.size()];
+ int cnt = 0;
+ long bytecnt = 0L;
+ for (File f : infiles) {
+ docs[cnt++] = new PerfDoc(f);
+ bytecnt += f.length();
+ }
+
+ long loadmem = 0L;
+ long loadtime = 0L;
+
+
+ long startmem = usedMem();
+ for (PerfDoc pd : docs) {
+ try {
+ pd.load();
+ loadmem += pd.getLoadMem();
+ loadtime += pd.getLoadTime();
+// System.out.println("Loaded " + pd.getFile());
+ } catch (Exception e) {
+ System.err.println("Failed to load " + pd);
+ e.printStackTrace();
+ }
+ }
+
+ long actloadmem = usedMem() - startmem;
+ double pctdiff = ((actloadmem - loadmem) * 100.0) / actloadmem;
+ if (pctdiff < 0.0) {
+ pctdiff = -1.0 * pctdiff;
+ }
+ if (pctdiff > 1.0) {
+ throw new IllegalStateException(
+ String.format("Memory calculations do not add up direct=%s sum=%s.",
+ formatMem(actloadmem), formatMem(loadmem)));
+ }
+
+ long scantime = 0L;
+ for (PerfDoc pd : docs) {
+ try {
+ scantime += pd.scan();
+ } catch (Exception e) {
+ System.err.println("Failed to scan " + pd);
+ e.printStackTrace();
+ }
+ }
+
+ usedMem();
+
+ long dumptime = 0L;
+ for (PerfDoc pd : docs) {
+ try {
+ dumptime += pd.dump();
+ } catch (Exception e) {
+ System.err.println("Failed to dump " + pd);
+ e.printStackTrace();
+ }
+ }
+
+ usedMem();
+
+ long dupetime = 0L;
+ for (PerfDoc pd : docs) {
+ try {
+ dupetime += pd.duplicate();
+ } catch (Exception e) {
+ System.err.println("Failed to dupe " + pd);
+ e.printStackTrace();
+ }
+ }
+
+ usedMem();
+
+ long xpathtime = 0L;
+ for (PerfDoc pd : docs) {
+ try {
+ xpathtime += pd.xpath();
+ } catch (Exception e) {
+ System.err.println("Failed to xpath " + pd);
+ e.printStackTrace();
+ }
+ }
+
+ usedMem();
+
+ long checkedtime = 0L;
+ for (PerfDoc pd : docs) {
+ try {
+ checkedtime += pd.checkedParse();
+ } catch (Exception e) {
+ System.err.println("Failed to checked-parse " + pd);
+ e.printStackTrace();
+ }
+ }
+
+ long uncheckedtime = 0L;
+ for (PerfDoc pd : docs) {
+ try {
+ uncheckedtime += pd.unCheckedParse();
+ } catch (Exception e) {
+ System.err.println("Failed to unchecked-parse " + pd);
+ e.printStackTrace();
+ }
+ }
+
+ System.out.printf ("PERF: loadbytes=%s loadmem=%s parse=%.2fb " +
+ "scan=%.2fb dump=%.2fb dupe=%.2fb xpath=%.2fb checked=%.2fb unchecked=%.2fb \n",
+ formatMem(bytecnt), formatMem(loadmem), loadtime / mstime,
+ scantime / mstime, dumptime / mstime, dupetime / mstime,
+ xpathtime / mstime, checkedtime / mstime, uncheckedtime / mstime);
+
+ html.append("\t\t<tr><td>").append(formatMem(bytecnt)).append("</td><td>")
+ .append(formatMem(loadmem)).append("</td>");
+
+ long[] times = new long[] {loadtime, scantime, dumptime, dupetime,
+ xpathtime, checkedtime, uncheckedtime};
+ for (long t : times) {
+ html.append("<td>").append(String.format("%.2fms", t / mstime)).append("</td>");
+ }
+ html.append("</tr>\n");
+
+
+ }
+
+}
View
5 contrib/src/java/org/jdom2/contrib/perf/TimeRunnable.java
@@ -0,0 +1,5 @@
+package org.jdom2.contrib.perf;
+
+interface TimeRunnable {
+ void run() throws Exception;
+}
View
9,151 contrib/src/resources/hamlet.xml
9,151 additions, 0 deletions not shown
Please sign in to comment.
Something went wrong with that request. Please try again.