001: /*
002: * Copyright (c) 2000-2001 Sosnoski Software Solutions, Inc.
003: *
004: * Permission is hereby granted, free of charge, to any person obtaining a copy
005: * of this software and associated documentation files (the "Software"), to deal
006: * in the Software without restriction, including without limitation the rights
007: * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
008: * copies of the Software, and to permit persons to whom the Software is
009: * furnished to do so, subject to the following conditions:
010: *
011: * The above copyright notice and this permission notice shall be included in
012: * all copies or substantial portions of the Software.
013: *
014: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
015: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
016: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
017: * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
018: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
019: * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
020: * IN THE SOFTWARE.
021: */
022:
023: package com.sosnoski.xmlbench;
024:
025: import java.io.*;
026: import java.util.*;
027:
028: import javax.xml.parsers.*;
029:
030: import org.xml.sax.*;
031: import org.xml.sax.helpers.*;
032:
033: /**
034: * Benchmark program for comparing performance of Java XML parsers and
035: * document representations. Performance is measured in terms of both
036: * speed and memory requirement for constructing the document representation
037: * from a SAX parse, and in terms of speed for walking the representation and
038: * generating text output.
039: *
040: * @author Dennis M. Sosnoski
041: * @version 1.2
042: */
043:
044: public class XMLBench {
045: /** Wait time between test documents (ms). */
046: public static final int DELAY_BETWEEN_DOCUMENTS = 400;
047:
048: /** Output line length for brief format. */
049: public static final int BRIEF_LINE_LENGTH = 80;
050:
051: /** Width of each item field in brief format. */
052: public static final int BRIEF_ITEM_WIDTH = 16;
053:
054: /** Number of brief format items per output line. */
055: public static final int BRIEF_PER_LINE = BRIEF_LINE_LENGTH
056: / BRIEF_ITEM_WIDTH;
057:
058: /**
059: * Get test object. Constructs and returns an instance of the appropriate
060: * test class.
061: *
062: * @param name document model name to be tested
063: * @return document model test class instance (null if name not recognized)
064: */
065:
066: private static BenchBase getTestInstance(String name) {
067: if (name.equalsIgnoreCase("SAX")) {
068: return new BenchSAX();
069: } else if (name.equalsIgnoreCase("Crimson")) {
070: return new BenchCrimson();
071: } else if (name.equalsIgnoreCase("JDOM")) {
072: return new BenchJDOM();
073: } else if (name.equalsIgnoreCase("dom4j")) {
074: return new BenchDOM4J();
075: } else if (name.equalsIgnoreCase("Xerces")) {
076: return new BenchXercesBase();
077: } else if (name.equalsIgnoreCase("XercesD")) {
078: return new BenchXercesDeferred();
079: } else if (name.equalsIgnoreCase("Xerces2")) {
080: return new BenchXerces2Base();
081: } else if (name.equalsIgnoreCase("Xerces2D")) {
082: return new BenchXerces2Deferred();
083: } else if (name.equalsIgnoreCase("EXML")) {
084: return new BenchElectric();
085: } else if (name.equalsIgnoreCase("XPP")) {
086: return new BenchXPPBase();
087: } else if (name.equalsIgnoreCase("XPPp")) {
088: return new BenchXPPPull();
089: }
090: return null;
091: }
092:
093: /**
094: * Read contents of file into byte array.
095: *
096: * @param file file to be read
097: * @return array of bytes containing all data from file
098: * @throws IOException on file access error
099: */
100:
101: private static byte[] getFileBytes(File file) throws IOException {
102: int length = (int) file.length();
103: byte[] data = new byte[length];
104: FileInputStream in = new FileInputStream(file);
105: int offset = 0;
106: do {
107: offset += in.read(data, offset, length - offset);
108: } while (offset < data.length);
109: return data;
110: }
111:
112: /**
113: * Load data for file or directory argument. If the supplied path is a
114: * file, the returned array of arrays consists of a single byte array
115: * containing the date from that file. If the path is a directory, the
116: * returned array contains one value array for each file in the
117: * target directory.
118: *
119: * @param path file or directory path
120: * @return array of arrays of bytes containing all data from file(s)
121: * @throws IOException on file access error
122: */
123:
124: private static byte[][] getPathData(String path) throws IOException {
125:
126: // check type of path supplied
127: File file = new File(path);
128: Vector data = new Vector();
129: if (file.isDirectory()) {
130:
131: // get array of files in this directory
132: File[] files = file.listFiles();
133: byte[][] arrays = new byte[files.length][];
134: long[] stamps = new long[files.length];
135: // String[] names = new String[files.length];
136: int count = 0;
137: for (int i = 0; i < files.length; i++) {
138: if (files[i].isFile()) {
139: arrays[count] = getFileBytes(files[i]);
140: // names[count] = files[i].getName();
141: stamps[count++] = files[i].lastModified();
142: }
143: }
144:
145: // (bubble) sort results by modify date of file
146: for (int i = 0; i < count; i++) {
147: for (int j = i + 1; j < count; j++) {
148: if (stamps[j] < stamps[i]) {
149: long stamp = stamps[j];
150: stamps[j] = stamps[i];
151: stamps[i] = stamp;
152: byte[] array = arrays[j];
153: arrays[j] = arrays[i];
154: arrays[i] = array;
155: // String name = names[j];
156: // names[j] = names[i];
157: // names[i] = name;
158: }
159: }
160: // System.out.println(" sorted " + names[i]);
161: }
162:
163: // convert results to sized array
164: byte[][] results = new byte[count][];
165: System.arraycopy(arrays, 0, results, 0, count);
166: return results;
167:
168: } else {
169: byte[][] results = new byte[1][];
170: results[0] = getFileBytes(file);
171: return results;
172: }
173: }
174:
175: /**
176: * Show test file results in brief format. This prints the results with
177: * multiple values per line, using the abbreviated value descriptions.
178: *
179: * @param values test result values (unreported values ignored)
180: * @param descripts value description texts
181: */
182:
183: private static void showBrief(int[] values, String[] descripts) {
184: StringBuffer line = new StringBuffer(BRIEF_LINE_LENGTH);
185: int position = 0;
186: for (int j = 0; j < values.length; j++) {
187: if (values[j] != Integer.MIN_VALUE) {
188: if (position == 0) {
189: if (j > 0) {
190: System.out.println(line);
191: line.setLength(0);
192: }
193: } else {
194: int end = position * BRIEF_ITEM_WIDTH;
195: while (line.length() < end) {
196: line.append(' ');
197: }
198: }
199: line.append(' ');
200: line.append(descripts[j]);
201: line.append('=');
202: line.append(values[j]);
203: position = (position + 1) % BRIEF_PER_LINE;
204: }
205: }
206: System.out.println(line);
207: }
208:
209: /**
210: * Show test file results in full format. This prints the results with
211: * a single value per line, using the detailed value descriptions.
212: *
213: * @param values test result values (unreported values ignored)
214: * @param descripts value description texts
215: */
216:
217: private static void showFull(int[] values, String[] descripts) {
218: for (int j = 0; j < values.length; j++) {
219: if (values[j] != Integer.MIN_VALUE) {
220: System.out.println(' ' + descripts[j] + " = "
221: + values[j]);
222: }
223: }
224: }
225:
226: /**
227: * Build text for showing results in compressed format. This uses an
228: * abbreviated value description followed by the corresponding result
229: * values for all test files in sequence, with comma separators.
230: *
231: * @param values array of test result value arrays
232: * @param descripts value description texts
233: * @param line buffer for output text accumulation
234: */
235:
236: private static void buildCompressed(int[][] values,
237: String[] descripts, StringBuffer line) {
238: int fcnt = values.length;
239: int vcnt = values[0].length;
240: for (int i = 0; i < vcnt; i++) {
241: line.append(',');
242: line.append(descripts[i]);
243: for (int j = 0; j < fcnt; j++) {
244: line.append(',');
245: if (values[j][i] != Integer.MIN_VALUE) {
246: line.append(values[j][i]);
247: }
248: }
249: }
250: }
251:
252: /**
253: * Test driver, just reads the input parameters and executes the test.
254: *
255: * @param argv command line arguments
256: */
257:
258: public static void main(String[] argv) {
259:
260: // clean up argument text (may have CR-LF line ends, confusing Linux)
261: for (int i = 0; i < argv.length; i++) {
262: argv[i] = argv[i].trim();
263: }
264:
265: // parse the leading command line parameters
266: boolean valid = true;
267: boolean briefflag = false;
268: boolean compflag = false;
269: boolean detailflag = false;
270: boolean interpretflag = false;
271: boolean jvmflag = false;
272: boolean summaryflag = false;
273: boolean memflag = false;
274: boolean timeflag = true;
275: int npasses = 10;
276: int nexclude = 1;
277: int anum = 0;
278: parse: while (anum < argv.length && argv[anum].charAt(0) == '-') {
279: String arg = argv[anum++];
280: int cnum = 1;
281: while (cnum < arg.length()) {
282: char option = Character.toLowerCase(arg.charAt(cnum++));
283: switch (option) {
284:
285: case 'b':
286: briefflag = true;
287: break;
288:
289: case 'c':
290: compflag = true;
291: break;
292:
293: case 'd':
294: detailflag = true;
295: break;
296:
297: case 'i':
298: interpretflag = true;
299: break;
300:
301: case 'm':
302: memflag = true;
303: break;
304:
305: case 'n':
306: timeflag = false;
307: break;
308:
309: case 's':
310: summaryflag = true;
311: break;
312:
313: case 'v':
314: jvmflag = true;
315: break;
316:
317: case 'p':
318: case 'x':
319: int value = 0;
320: int nchars = 0;
321: char chr;
322: while (cnum < arg.length()
323: && (chr = arg.charAt(cnum++)) >= '0'
324: && chr <= '9') {
325: if (++nchars > 2) {
326: valid = false;
327: System.err
328: .println("Number value out of range");
329: break parse;
330: } else {
331: value = (value * 10) + (chr - '0');
332: }
333: }
334: if (option == 'p') {
335: if (value < 1) {
336: valid = false;
337: System.err
338: .println("Pass count cannot be 0");
339: break parse;
340: } else {
341: npasses = value;
342: }
343: } else {
344: nexclude = value;
345: }
346: break;
347: }
348: }
349: }
350:
351: // check for invalid pass count and exclude count combination
352: if (npasses <= nexclude) {
353: System.err
354: .println("Pass count must be greater than exclude count");
355: valid = false;
356: }
357:
358: // next parameter should be the model name
359: valid = valid && anum < argv.length;
360: BenchBase bench = null;
361: if (valid) {
362:
363: // construct test instance for requested model
364: bench = getTestInstance(argv[anum++]);
365: if (bench == null) {
366: System.err.println("Unknown model name");
367: valid = false;
368: } else {
369: bench.setPrint(System.out);
370: bench.setShowDocument(summaryflag);
371: bench.setShowPass(detailflag);
372: }
373: }
374:
375: // handle list of files to be used for test
376: if (valid && (memflag || timeflag)) {
377:
378: // read data from all input files into array of arrays
379: int fcnt = argv.length - anum;
380: byte[][][] data = new byte[fcnt][][];
381: try {
382: for (int i = 0; i < fcnt; i++) {
383: data[i] = getPathData(argv[i + anum]);
384: }
385: } catch (IOException ex) {
386: ex.printStackTrace(System.err);
387: return;
388: }
389:
390: // report JVM and parameter information
391: if (jvmflag) {
392: System.out.println("Java version "
393: + System.getProperty("java.version"));
394: String text = System.getProperty("java.vm.name");
395: if (text != null) {
396: System.out.println(text);
397: }
398: text = System.getProperty("java.vm.version");
399: if (text != null) {
400: System.out.println(text);
401: }
402: text = System.getProperty("java.vm.vendor");
403: if (text == null) {
404: text = System.getProperty("java.vendor");
405: }
406: System.out.println(text);
407: }
408:
409: // initialize results accumulation array
410: int tests = (memflag && timeflag) ? 2 : 1;
411: int[][][] results = new int[tests][fcnt][];
412:
413: // check for memory test needed
414: if (memflag) {
415:
416: // execute the test sequence on supplied files
417: for (int i = 0; i < fcnt; i++) {
418:
419: // check if we're printing results immediately
420: if (briefflag | interpretflag) {
421: if (i > 0) {
422: System.out.println();
423: }
424: System.out.print("Running " + bench.getName()
425: + " with " + npasses + " passes on ");
426: if (data[i].length == 1) {
427: System.out.println("file " + argv[i + anum]
428: + " (" + data[i][0].length
429: + " bytes):");
430: } else {
431: int total = 0;
432: for (int j = 0; j < data[i].length; j++) {
433: total += data[i][j].length;
434: }
435: System.out.println("directory "
436: + argv[i + anum] + " ("
437: + data[i].length
438: + " files totalling " + total
439: + " bytes):");
440: }
441: }
442:
443: // collect test results
444: int[] values = bench.runSpaceTest(npasses,
445: nexclude, data[i]);
446: results[0][i] = values;
447:
448: // show results in brief format
449: if (briefflag) {
450: showBrief(values,
451: BenchBase.s_spaceShortDescriptions);
452: }
453:
454: // show results in interpreted format
455: if (interpretflag) {
456: showFull(values,
457: BenchBase.s_spaceFullDescriptions);
458: }
459: }
460: }
461:
462: // check for time test needed
463: if (timeflag) {
464:
465: // add spacer if running both types of tests
466: if (memflag) {
467: System.out.println();
468: }
469:
470: // execute the test sequence on supplied files
471: int index = memflag ? 1 : 0;
472: for (int i = 0; i < fcnt; i++) {
473:
474: // check if we're printing results immediately
475: if (briefflag | interpretflag) {
476: if (i > 0) {
477: System.out.println();
478: }
479: System.out.print("Running " + bench.getName()
480: + " with " + npasses
481: + " passes on file " + argv[i + anum]);
482: if (nexclude == 1) {
483: System.out
484: .print(", first pass excluded from averages");
485: } else if (nexclude > 1) {
486: System.out.print(", first " + nexclude
487: + " passes excluded from averages");
488: }
489: System.out.println(':');
490: }
491:
492: // collect test results
493: int[] values = bench.runTimeTest(npasses, nexclude,
494: data[i]);
495: results[index][i] = values;
496:
497: // show results in brief format
498: if (briefflag) {
499: showBrief(values,
500: BenchBase.s_timeShortDescriptions);
501: }
502:
503: // show results in interpreted format
504: if (interpretflag) {
505: showFull(values,
506: BenchBase.s_timeFullDescriptions);
507: }
508: }
509: }
510:
511: // print compressed results for all files
512: if (compflag) {
513: System.out.println("Compressed results for "
514: + bench.getName() + " with " + npasses
515: + " passes on the following files:");
516: for (int i = 0; i < fcnt; i++) {
517: System.out.println(' ' + argv[i + anum]);
518: }
519: StringBuffer line = new StringBuffer();
520: line.append(bench.getName());
521: if (memflag) {
522: buildCompressed(results[0],
523: BenchBase.s_spaceShortDescriptions, line);
524: }
525: if (timeflag) {
526: int index = memflag ? 1 : 0;
527: buildCompressed(results[index],
528: BenchBase.s_timeShortDescriptions, line);
529: }
530: System.out.println(line);
531: }
532: System.out.println();
533:
534: } else {
535: System.err
536: .println("\nUsage: XMLBench [-options] model file-list\n"
537: + "Options are:\n"
538: + " -b show brief results (with abbreviated captions)\n"
539: + " -c show compressed results (comma-separated value"
540: + " fields with results\n"
541: + " ordered by type and within type by file, useful for"
542: + " spreadsheet import)\n"
543: + " -d show detailed per-pass information\n"
544: + " -i show interpreted results (with full captions)\n"
545: + " -m run memory tests (default is time tests only, when"
546: + "testing both memory tests are run first)\n"
547: + " -n do not run time tests (default is time tests only)\n"
548: + " -pNN run NN passes of each operation on each document,"
549: + " where NN is 1-99\n"
550: + " (default is p10)\n"
551: + " -s show summary information for each document\n"
552: + " -xNN exclude first NN passes of each document from"
553: + " averages, where N is\n"
554: + " 0-99 (default is x1)\n"
555: + " -v show JVM version information\n"
556: + "These options may be concatenated together with a single"
557: + " leading dash.\n\n"
558: + "Model may be any of the following values:\n"
559: + " sax JAXP-compliant SAX2 parser\n"
560: + " crimson Crimson DOM and parser combination\n"
561: + " jdom JDOM with JAXP-compliant SAX2 parser\n"
562: + " dom4j dom4j with JAXP-compliant SAX2 parser\n"
563: + " xerces Xerces DOM and parser combination\n"
564: + " xercesd Xerces deferred DOM and parser combination\n"
565: + " xerces2 Xerces2 DOM and parser combination\n"
566: + " xerces2d Xerces2 deferred DOM and parser combination\n"
567: + " exml Electric XML model and parser combination\n"
568: + " xpp XPP model and parser combination\n"
569: + " xppp XPP pull model and parser combination\n\n"
570: + "The models which support JAXP-compliant SAX2 parsers may "
571: + "use any qualifying\n"
572: + "parser by setting the javax.xml.parsers.SAXParserFactory "
573: + "system property\n"
574: + "to the appropriate class.\n\n"
575: + "The file-list consists of any number of file or directory"
576: + " names, in any"
577: + "combination. If a directory name is given, all files in the"
578: + "directory are\n"
579: + "processed once in each timing pass.\n");
580: }
581: }
582: }
|