0001 /*
0002 * PRTimeReporter.java
0003 *
0004 * Copyright (c) 2008-2009, Intelius, Inc.
0005 *
0006 * This file is part of GATE (see http://gate.ac.uk/), and is free
0007 * software, licenced under the GNU Library General Public License,
0008 * Version 2, June 1991 (in the distribution as file licence.html,
0009 * and also available at http://gate.ac.uk/gate/licence.html).
0010 *
0011 * Chirag Viradiya & Andrew Borthwick, 30/Sep/2009
0012 *
0013 * $Id$
0014 */
0015
0016 package gate.util.reporting;
0017
0018 import java.io.BufferedWriter;
0019 import java.io.File;
0020 import java.io.FileWriter;
0021 import java.io.IOException;
0022 import java.io.RandomAccessFile;
0023 import java.util.ArrayList;
0024 import java.util.Collections;
0025 import java.util.Comparator;
0026 import java.util.Date;
0027 import java.util.HashSet;
0028 import java.util.Hashtable;
0029 import java.util.Iterator;
0030 import java.util.LinkedHashMap;
0031 import java.util.List;
0032 import java.util.StringTokenizer;
0033 import java.util.Timer;
0034 import java.util.TimerTask;
0035 import java.util.Vector;
0036 import java.util.regex.Matcher;
0037 import java.util.regex.Pattern;
0038
0039 import gate.util.reporting.exceptions.BenchmarkReportExecutionException;
0040 import gate.util.reporting.exceptions.BenchmarkReportInputFileFormatException;
0041 import gnu.getopt.Getopt;
0042
0043 /**
0044 * A reporter class to generate a report on total time taken by each processing
0045 * element across corpus.
0046 */
0047 public class PRTimeReporter implements BenchmarkReportable {
0048 /**
0049 * This string constant when set as print media indicates that the report is
0050 * printed in TEXT format.
0051 */
0052 public static final String MEDIA_TEXT = "text";
0053 /**
0054 * This string constant when set as print media indicates that the report is
0055 * printed in HTML format.
0056 */
0057 public static final String MEDIA_HTML = "html";
0058 /**
0059 * This string constant when set as sort order indicates that the processing
0060 * elements are sorted in the order of their execution.
0061 */
0062 public static final String SORT_EXEC_ORDER = "exec_order";
0063 /**
0064 * This string constant when set as sort order indicates that the processing
0065 * elements are sorted in the descending order of processing time taken by a
0066 * particular element.
0067 */
0068 public static final String SORT_TIME_TAKEN = "time_taken";
0069 /** A Hashtable storing the time taken by each pipeline. */
0070 private Hashtable<String, String> globalTotal = new Hashtable<String, String>();
0071 /** An ArrayList containing the lines to be printed in the final text report. */
0072 private ArrayList<String> printLines = new ArrayList<String>();
0073 /** An OS independent line separator. */
0074 private static final String NL = System.getProperty("line.separator");
0075 /**
0076 * A String containing the HTML code to generate collapsible tree for
0077 * processing elements.
0078 */
0079 private String htmlElementTree =
0080 "<td rowspan=\"112\" width=\"550\">" + NL +
0081 "<div id=\"treemenu\" style=\"margin: 0;\">" + NL +
0082 "<ul id=\"tree\" style=\"margin-left: 0;\">" + NL;
0083 /**
0084 * A String containing the HTML code to generate collapsible tree for time
0085 * taken by each processing elements.
0086 */
0087 private String htmlTimeTree =
0088 "<td rowspan=\"112\" width=\"100\">" + NL +
0089 "<div style=\"margin-top: 0;\"><div>" + NL;
0090 /**
0091 * A String containing the HTML code to generate collapsible tree for time
0092 * taken by each processing elements (in %).
0093 */
0094 private String htmlTimeInPercentTree =
0095 "<td rowspan=\"112\" width=\"100\">" + NL +
0096 "<div style=\"margin-top: 0;\"><div>" + NL;
0097 /** A integer to track tree depth level. */
0098 private int level = 1;
0099 /** Place holder for storing the total time taken by a pipeline. */
0100 private double globalValue = 0;
0101 /** Status flag for normal exit. */
0102 private static final int STATUS_NORMAL = 0;
0103 /** Status flag for error exit. */
0104 private static final int STATUS_ERROR = 1;
0105 /**
0106 * An integer containing the count of total valid log entries present in input
0107 * file provided.
0108 */
0109 public int validEntries = 0;
0110 /** Chunk size in which file will be read. */
0111 private static final int FILE_CHUNK_SIZE = 2000;
0112 /**
0113 * Names of the given pipeline for which the entries are present in given
0114 * benchmark file.
0115 */
0116 public HashSet<String> pipelineNames = new HashSet<String>();
0117
0118 /** A handle to the input benchmark file (benchmark.txt). */
0119 private File benchmarkFile = new File("benchmark.txt");
0120 /** Indicate whether or not to show 0 millisecond entries. */
0121 private boolean suppressZeroTimeEntries = true;
0122 /** Report media. */
0123 private String printMedia = MEDIA_HTML;
0124 /**
0125 * A String specifying the sorting order to be used while displaying the
0126 * report.
0127 */
0128 private String sortOrder = SORT_EXEC_ORDER;
0129 /** Path where to save the report file. */
0130 private File reportFile;
0131 /** A marker indicating the start of current logical run. */
0132 private String logicalStart = null;
0133
0134 /**
0135 * No Argument constructor.
0136 */
0137 public PRTimeReporter() {
0138 }
0139
0140 /**
0141 * A constructor to be used while executing from the command line.
0142 *
0143 * @param args array containing command line arguments.
0144 */
0145 PRTimeReporter(String[] args) {
0146 parseArguments(args);
0147 }
0148
0149 /**
0150 * Stores GATE processing elements and the time taken by them in an in-memory
0151 * data structure for report generation.
0152 *
0153 * @param inputFile
0154 * A File handle of the input log file.
0155 *
0156 * @return An Object of type LinkedHashMap<String, Object> containing the
0157 * processing elements (with time in milliseconds) in hierarchical
0158 * structure. Null if there was an error.
0159 */
0160 public Object store(File inputFile)
0161 throws BenchmarkReportInputFileFormatException {
0162 LinkedHashMap<String, Object> globalStore =
0163 new LinkedHashMap<String, Object>();
0164 long fromPos = 0;
0165 RandomAccessFile in = null;
0166 try {
0167 if (getLogicalStart() != null) {
0168 fromPos = tail(inputFile, FILE_CHUNK_SIZE);
0169 }
0170 in = new RandomAccessFile(inputFile, "r");
0171 if (getLogicalStart() != null) {
0172 in.seek(fromPos);
0173 }
0174 ArrayList<String> startTokens = new ArrayList<String>();
0175 String logEntry;
0176 String docName = null;
0177 Pattern pattern = Pattern.compile("(\\d+) (\\d+) (.*) (.*) \\{(.*)\\}");
0178 while ((logEntry = in.readLine()) != null) {
0179 Matcher matcher = pattern.matcher(logEntry);
0180 // Skip the statistics for the event documentLoaded
0181 if (logEntry.matches(".*documentLoaded.*"))
0182 continue;
0183 if (logEntry.matches(".*START.*")) {
0184 String[] splittedStartEntry = logEntry.split("\\s");
0185 String startToken = (splittedStartEntry.length > 2) ? splittedStartEntry[2]
0186 : null;
0187 if (startToken == null) {
0188 throw new BenchmarkReportInputFileFormatException(
0189 getBenchmarkFile().getAbsolutePath() + " is invalid.");
0190 }
0191 startTokens.add(startToken);
0192 if (startToken.endsWith("Start"))
0193 continue;
0194 organizeEntries(globalStore, startToken.split("\\."), "0");
0195 }
0196
0197 if (matcher != null) {
0198 if (matcher.matches()) {
0199 if (validateLogEntry(matcher.group(3), startTokens)) {
0200 String[] splittedBIDs = matcher.group(3).split("\\.");
0201 if (splittedBIDs.length > 1) {
0202 docName = splittedBIDs[1];
0203 pipelineNames.add(splittedBIDs[0]);
0204 }
0205 organizeEntries(globalStore, (matcher.group(3).replaceFirst(
0206 Pattern.quote(docName) + ".", "")).split("\\."), matcher.group(2));
0207 }
0208 }
0209 }
0210 }
0211
0212 } catch (IOException e) {
0213 e.printStackTrace();
0214 globalStore = null;
0215
0216 } finally {
0217 try {
0218 if (in != null) { in.close(); }
0219 } catch (IOException e) {
0220 e.printStackTrace();
0221 globalStore = null;
0222 }
0223 }
0224
0225 if (validEntries == 0) {
0226 if (logicalStart != null) {
0227 throw new BenchmarkReportInputFileFormatException(
0228 "No valid log entries present in " +
0229 getBenchmarkFile().getAbsolutePath() +
0230 " does not contain a marker named " + logicalStart + ".");
0231 } else {
0232 throw new BenchmarkReportInputFileFormatException(
0233 "No valid log entries present in " +
0234 getBenchmarkFile().getAbsolutePath());
0235 }
0236 }
0237 return globalStore;
0238 }
0239
0240 /**
0241 * Generates a tree like structure made up of LinkedHashMap containing the
0242 * processing elements and time taken by each element totaled at leaf level
0243 * over corpus.
0244 *
0245 * @param store
0246 * An Object of type LinkedHashMap<String, Object> containing the
0247 * processing elements (with time in milliseconds) in hierarchical
0248 * structure.
0249 * @param tokens
0250 * An array consisting of remaining benchmarkID tokens except the one
0251 * being processed.
0252 * @param bTime
0253 * time(in milliseconds) of the benchmarkID token being processed.
0254 */
0255 private void organizeEntries(LinkedHashMap<String, Object> store,
0256 String[] tokens, String bTime) {
0257 if (tokens.length > 0 && store.containsKey(tokens[0])) {
0258 if (tokens.length > 1) {
0259 String[] tempArr = new String[tokens.length - 1];
0260 System.arraycopy(tokens, 1, tempArr, 0, tokens.length - 1);
0261 if (store.get(tokens[0]) instanceof LinkedHashMap) {
0262 organizeEntries((LinkedHashMap<String, Object>) (store
0263 .get(tokens[0])), tempArr, bTime);
0264 } else {
0265 if (store.get(tokens[0]) != null) {
0266 store.put(tokens[0], new LinkedHashMap<String, Object>());
0267 } else {
0268 store.put(tokens[0], bTime);
0269 }
0270 }
0271 } else {
0272 if (store.get(tokens[0]) != null) {
0273 if (!(store.get(tokens[0]) instanceof LinkedHashMap)) {
0274 int total = Integer.parseInt((String) (store.get(tokens[0])))
0275 + Integer.parseInt(bTime);
0276 store.put(tokens[0], Integer.toString(total));
0277 } else {
0278 int total = Integer.parseInt(bTime);
0279 if (((java.util.LinkedHashMap<String, Object>) (store
0280 .get(tokens[0]))).get("systotal") != null) {
0281 total = total
0282 + Integer
0283 .parseInt((String) ((java.util.LinkedHashMap<String, Object>) (store
0284 .get(tokens[0]))).get("systotal"));
0285 }
0286 ((java.util.LinkedHashMap<String, Object>) (store.get(tokens[0])))
0287 .put("systotal", Integer.toString(total));
0288 }
0289 }
0290 }
0291 } else {
0292 if (tokens.length - 1 == 0) {
0293 store.put(tokens[0], bTime);
0294 } else {
0295 store.put(tokens[0], new LinkedHashMap<String, Object>());
0296 String[] tempArr = new String[tokens.length - 1];
0297 System.arraycopy(tokens, 1, tempArr, 0, tokens.length - 1);
0298 organizeEntries(
0299 (LinkedHashMap<String, Object>) (store.get(tokens[0])), tempArr,
0300 bTime);
0301 }
0302 }
0303 }
0304
0305 /**
0306 * Sorts the processing element entries inside tree like structure made up of
0307 * LinkedHashMap. Entries will be sorted in descending order of time taken.
0308 *
0309 * @param gStore
0310 * An Object of type LinkedHashMap<String, Object> containing the
0311 * processing elements (with time in milliseconds) in hierarchical
0312 * structure.
0313 *
0314 * @return An Object of type LinkedHashMap<String, Object> containing the
0315 * processing elements sorted in descending order of processing time
0316 * taken.
0317 */
0318 private LinkedHashMap<String, Object> sortReport(
0319 LinkedHashMap<String, Object> gStore) {
0320 Iterator<String> i = gStore.keySet().iterator();
0321 LinkedHashMap<String, Object> sortedReport = new LinkedHashMap<String, Object>();
0322 LinkedHashMap<String, Object> mapperReport = new LinkedHashMap<String, Object>();
0323 LinkedHashMap<String, String> unsortedReport = new LinkedHashMap<String, String>();
0324 while (i.hasNext()) {
0325 Object key = i.next();
0326 if (gStore.get(key) instanceof LinkedHashMap) {
0327 int systotal = 0;
0328 if (((LinkedHashMap<String, Object>) (gStore.get(key)))
0329 .get("systotal") != null) {
0330 systotal = Integer
0331 .parseInt((String) ((LinkedHashMap<String, Object>) (gStore
0332 .get(key))).get("systotal"));
0333 }
0334 if (systotal >= 0) {
0335 unsortedReport.put((String) key, Integer.toString(systotal));
0336 }
0337 mapperReport.put((String) key,
0338 sortReport((LinkedHashMap<String, Object>) (gStore.get(key))));
0339
0340 } else {
0341 if (!(key.equals("total") || key.equals("systotal"))) {
0342 if (Integer.parseInt((String) (gStore.get(key))) >= 0) {
0343 unsortedReport.put((String) key, new Integer((String) gStore
0344 .get(key)).toString());
0345 }
0346 }
0347 }
0348 }
0349 LinkedHashMap<String, String> tempOutLHM =
0350 sortHashMapByValues(unsortedReport);
0351
0352 Iterator<String> itr = tempOutLHM.keySet().iterator();
0353 while (itr.hasNext()) {
0354 Object tempKey = itr.next();
0355 sortedReport.put((String) tempKey, tempOutLHM.get(tempKey));
0356 if (mapperReport.containsKey(tempKey)) {
0357 sortedReport
0358 .put((String) tempKey, mapperReport.get((String) tempKey));
0359 }
0360 }
0361 sortedReport.put("total", gStore.get("total"));
0362 if (gStore.get("systotal") != null) {
0363 sortedReport.put("systotal", gStore.get("systotal"));
0364 }
0365 return sortedReport;
0366 }
0367
0368 /**
0369 * Sorts LinkedHashMap by its values(natural descending order). keeps the
0370 * duplicates as it is.
0371 *
0372 * @param passedMap
0373 * An Object of type LinkedHashMap to be sorted by its values.
0374 *
0375 * @return An Object containing the sorted LinkedHashMap.
0376 */
0377 private LinkedHashMap sortHashMapByValues(LinkedHashMap passedMap) {
0378 List mapKeys = new ArrayList(passedMap.keySet());
0379 List mapValues = new ArrayList(passedMap.values());
0380
0381 Collections.sort(mapValues, new ValueComparator());
0382 Collections.sort(mapKeys);
0383 Collections.reverse(mapValues);
0384 LinkedHashMap sortedMap = new LinkedHashMap();
0385
0386 Iterator<Integer> valueIt = mapValues.iterator();
0387 while (valueIt.hasNext()) {
0388 Object val = valueIt.next();
0389 Iterator<String> keyIt = mapKeys.iterator();
0390 while (keyIt.hasNext()) {
0391 Object key = keyIt.next();
0392 String comp1 = passedMap.get(key).toString();
0393 String comp2 = val.toString();
0394
0395 if (comp1.equals(comp2)) {
0396 passedMap.remove(key);
0397 mapKeys.remove(key);
0398 sortedMap.put(key, val);
0399 break;
0400 }
0401 }
0402 }
0403 return sortedMap;
0404 }
0405
0406 /**
0407 * Calculates the sub totals at each level.
0408 *
0409 * @param reportContainer
0410 * An Object of type LinkedHashMap<String, Object> containing the
0411 * processing elements (with time in milliseconds) in hierarchical
0412 * structure.
0413 *
0414 * @return An Object containing modified hierarchical structure of processing
0415 * elements with totals and All others embedded in it.
0416 */
0417 public Object calculate(Object reportContainer) {
0418 LinkedHashMap<String, Object> globalStore =
0419 (LinkedHashMap<String, Object>) reportContainer;
0420 Iterator<String> iter = globalStore.keySet().iterator();
0421 int total = 0;
0422 while (iter.hasNext()) {
0423 String key = iter.next();
0424 total = getTotal((LinkedHashMap<String, Object>) (globalStore.get(key)));
0425 globalTotal.put(key, Integer.toString(total));
0426 }
0427 return globalStore;
0428 }
0429
0430 /**
0431 * Calculates the total of the time taken by processing element at each leaf
0432 * level. Also calculates the difference between the actual time taken by the
0433 * resources and system noted time.
0434 *
0435 * @param reportContainer
0436 * An Object of type LinkedHashMap<String, Object> containing the
0437 * processing elements (with time in milliseconds) in hierarchical
0438 * structure.
0439 *
0440 * @return An integer containing the sub total.
0441 */
0442
0443 private int getTotal(LinkedHashMap<String, Object> reportContainer) {
0444 int total = 0;
0445 int diff = 0;
0446 int systotal = 0;
0447 int subLevelTotal = 0;
0448 Iterator<String> i = reportContainer.keySet().iterator();
0449 while (i.hasNext()) {
0450 Object key = i.next();
0451
0452 if (reportContainer.get(key) instanceof LinkedHashMap) {
0453 subLevelTotal = getTotal((LinkedHashMap<String, Object>) (reportContainer
0454 .get(key)));
0455 total = total + subLevelTotal;
0456 } else {
0457 if (!key.equals("systotal")) {
0458 total = total
0459 + Integer.parseInt((String) (reportContainer.get(key)));
0460 }
0461 }
0462 }
0463 if (reportContainer.get("systotal") != null) {
0464 systotal = Integer.parseInt((String) (reportContainer.get("systotal")));
0465 }
0466 diff = systotal - total;
0467 reportContainer.put("total", Integer.toString(total));
0468 reportContainer.put("All others", Integer.toString(diff));
0469 total += diff;
0470 return total;
0471 }
0472
0473 /**
0474 * Prints a report as per the value provided for print media option.
0475 *
0476 * @param reportSource
0477 * An Object of type LinkedHashMap<String, Object> containing the
0478 * processing elements (with time in milliseconds) in hierarchical
0479 * structure.
0480 * @param outputFile
0481 * Path where to save the report.
0482 */
0483 public void printReport(Object reportSource, File outputFile) {
0484 if (printMedia.equalsIgnoreCase(MEDIA_TEXT)) {
0485 printToText(reportSource, outputFile, suppressZeroTimeEntries);
0486 } else if (printMedia.equalsIgnoreCase(MEDIA_HTML)) {
0487 printToHTML((LinkedHashMap<String, Object>) reportSource,
0488 outputFile, suppressZeroTimeEntries);
0489 }
0490 }
0491
0492 /**
0493 * Prints benchmark report in text format.
0494 *
0495 * @param reportContainer
0496 * An Object of type LinkedHashMap<String, Object> containing the
0497 * processing elements (with time in milliseconds) in hierarchical
0498 * structure.
0499 * @param outputFile
0500 * An object of type File representing the output report file.
0501 * @param suppressZeroTimeEntries
0502 * Indicate whether or not to show 0 millisecond entries.
0503 */
0504 private void printToText(Object reportContainer, File outputFile,
0505 boolean suppressZeroTimeEntries) {
0506 LinkedHashMap<String, Object> globalStore =
0507 (LinkedHashMap<String, Object>) reportContainer;
0508 prettyPrint(globalStore, "\t", suppressZeroTimeEntries);
0509 BufferedWriter out = null;
0510 try {
0511 out = new BufferedWriter(new FileWriter(outputFile));
0512 for (String line : printLines) {
0513 out.write(line);
0514 out.newLine();
0515 }
0516 } catch (IOException e) {
0517 e.printStackTrace();
0518 } finally {
0519 try {
0520 if (out != null) { out.close(); }
0521 } catch (IOException e) {
0522 e.printStackTrace();
0523 }
0524 }
0525 }
0526
0527 /**
0528 * Prints a processing elements structure in a tree like format with time
0529 * taken by each element in milliseconds and in %.
0530 *
0531 * @param gStore
0532 * An Object of type LinkedHashMap<String, Object> containing the
0533 * processing elements (with time in milliseconds) in hierarchical
0534 * structure.
0535 * @param separator
0536 * A String separator to indent the processing elements in tree like
0537 * structure.
0538 * @param suppressZeroTimeEntries
0539 * Indicate whether or not to show 0 millisecond entries.
0540 */
0541 private void prettyPrint(LinkedHashMap<String, Object> gStore,
0542 String separator, boolean suppressZeroTimeEntries) {
0543
0544 Iterator<String> i = gStore.keySet().iterator();
0545 while (i.hasNext()) {
0546 Object key = i.next();
0547 if (globalTotal.containsKey(key))
0548 globalValue = Integer.parseInt(globalTotal.get(key));
0549 if (gStore.get(key) instanceof LinkedHashMap) {
0550 int systotal = 0;
0551 if (((LinkedHashMap<String, Object>) gStore.get(key))
0552 .containsKey("systotal")) {
0553 systotal = Integer
0554 .parseInt((String) ((LinkedHashMap<String, Object>) (gStore
0555 .get(key))).get("systotal"));
0556 }
0557 if (suppressZeroTimeEntries) {
0558 if (systotal > 0)
0559 printLines.add(separator + key + " (" + systotal / 1000.0 + ") ["
0560 + Math.round(((systotal / globalValue) * 100) * 10) / 10.0
0561 + "%]");
0562 } else {
0563 printLines
0564 .add(separator + key + " (" + systotal / 1000.0 + ") ["
0565 + Math.round(((systotal / globalValue) * 100) * 10) / 10.0
0566 + "%]");
0567 }
0568
0569 prettyPrint((LinkedHashMap<String, Object>) (gStore.get(key)),
0570 separator + "\t", suppressZeroTimeEntries);
0571 } else {
0572 if (!(key.equals("total") || key.equals("systotal"))) {
0573 if (suppressZeroTimeEntries) {
0574 if (Integer.parseInt((String) (gStore.get(key))) != 0) {
0575 printLines
0576 .add(separator
0577 + key
0578 + " ("
0579 + Integer.parseInt((String) (gStore.get(key)))
0580 / 1000.0
0581 + ") ["
0582 + Math
0583 .round(((Integer.parseInt((String) (gStore.get(key))) / globalValue) * 100) * 10)
0584 / 10.0 + "%]");
0585 }
0586 } else {
0587 printLines
0588 .add(separator
0589 + key
0590 + " ("
0591 + Integer.parseInt((String) (gStore.get(key)))
0592 / 1000.0
0593 + ") ["
0594 + Math
0595 .round(((Integer.parseInt((String) (gStore.get(key))) / globalValue) * 100) * 10)
0596 / 10.0 + "%]");
0597 }
0598 }
0599 }
0600 }
0601 }
0602
0603 /**
0604 * Prints a report in HTML format. The output report will be represented as
0605 * collapsible ul/li structure.
0606 *
0607 * @param gStore
0608 * An Object of type LinkedHashMap<String, Object> containing the
0609 * processing elements (with time in milliseconds) in hierarchical
0610 * structure.
0611 * @param outputFile
0612 * An object of type File representing the output report file to
0613 * which the HTML report is to be written.
0614 * @param suppressZeroTimeEntries
0615 * Indicate whether or not to show 0 millisecond entries.
0616 */
0617 private void printToHTML(LinkedHashMap<String, Object> gStore,
0618 File outputFile, boolean suppressZeroTimeEntries) {
0619 String htmlPipelineNames = "<ul>";
0620 for (String pipeline : pipelineNames) {
0621 htmlPipelineNames += "<li><b>" + pipeline + "</b></li>" + NL;
0622 }
0623 htmlPipelineNames += "</ul>" + NL;
0624 String htmlReport =
0625 "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"" + NL +
0626 "\"http://www.w3.org/TR/html4/loose.dtd\">" + NL +
0627 "<html><head><title>Benchmarking Report</title>" + NL +
0628 "<meta http-equiv=\"Content-Type\"" +
0629 " content=\"text/html; charset=utf-8\">" + NL +
0630 "<style type=\"text/css\">" + NL +
0631 "#treemenu li { list-style: none; }" + NL +
0632 "#treemenu li A { font-family: monospace; text-decoration: none; }" + NL +
0633 "</style>" + NL +
0634 "<script type=\"text/javascript\" language=\"javascript\">" + NL +
0635 "function expandCollapseTree(obj){" + NL +
0636 " var li = obj.parentNode;" + NL +
0637 " var uls = li.getElementsByTagName('ul');" + NL +
0638 " var id = li.id;" + NL +
0639 " var ul = uls[0];" + NL +
0640 " var objTimeBranch = document.getElementById(id + '.1');" + NL +
0641 " var divs = objTimeBranch.getElementsByTagName('div');" + NL +
0642 " var div = divs[0];" + NL +
0643 " var objperBranch = document.getElementById(id + '.2');" + NL +
0644 " var perdivs = objperBranch.getElementsByTagName('div');" + NL +
0645 " var perdiv = perdivs[0];" + NL + NL +
0646 " if (ul.style.display == 'none' || ul.style.display == '') {" + NL +
0647 " ul.style.display = 'block';" + NL +
0648 " obj.innerHTML = '[—]';" + NL +
0649 " } else {" + NL +
0650 " ul.style.display = 'none';" + NL +
0651 " obj.innerHTML = '[+]';" + NL +
0652 " }" + NL +
0653 " if (div.style.display == 'block') {" + NL +
0654 " div.style.display = 'none';" + NL +
0655 " } else {" + NL +
0656 " div.style.display = 'block';" + NL +
0657 " }" + NL +
0658 " if (perdiv.style.display == 'block') {" + NL +
0659 " perdiv.style.display = 'none';" + NL +
0660 " } else {" + NL +
0661 " perdiv.style.display = 'block';" + NL +
0662 " }" + NL +
0663 "}" + NL + NL +
0664 "</script>" + NL +
0665 "</head>" + NL +
0666 "<body style=\"font-family:Verdana; color:navy;\">" + NL +
0667 "<table><tr bgcolor=\"#eeeeff\">" + NL +
0668 "<td><b>Processing elements of following pipelines</b>" + NL +
0669 htmlPipelineNames + NL + "</td>" + NL +
0670 "<td><b>Time in seconds</b></td>" + NL +
0671 "<td><b>% time taken</b></td></tr><tr>";
0672 generateCollapsibleHTMLTree(gStore, suppressZeroTimeEntries);
0673 htmlElementTree += "</ul></div></td>" + NL;
0674 htmlTimeTree += "</div></div></td>" + NL;
0675 htmlTimeInPercentTree += "</div></div></td>" + NL;
0676 htmlReport += htmlElementTree + htmlTimeTree + htmlTimeInPercentTree +
0677 "</tr></table>" + NL +
0678 "</body></html>";
0679 // write the html string in the specified output html file
0680 BufferedWriter out = null;
0681 try {
0682 out = new BufferedWriter(new FileWriter(outputFile));
0683 out.write(htmlReport);
0684 } catch (IOException e) {
0685 e.printStackTrace();
0686 } finally {
0687 try {
0688 if (out != null) { out.close(); }
0689 } catch (IOException e) {
0690 e.printStackTrace();
0691 }
0692 }
0693 }
0694
0695 /**
0696 * Creates three tree like ul/li structures 1. A tree to represent processing
0697 * elements 2. A tree to represent time taken by processing elements 3. A tree
0698 * to represent time taken by processing elements in %.
0699 *
0700 * @param gStore
0701 * An Object of type LinkedHashMap<String, Object> containing the
0702 * processing elements (with time in milliseconds) in hierarchical
0703 * structure.
0704 * @param suppressZeroTimeEntries
0705 * Indicate whether or not to show 0 millisecond entries.
0706 */
0707 private void generateCollapsibleHTMLTree(LinkedHashMap<String, Object> gStore,
0708 boolean suppressZeroTimeEntries) {
0709 Iterator<String> i = gStore.keySet().iterator();
0710 while (i.hasNext()) {
0711 Object key = i.next();
0712 if (globalTotal.containsKey(key))
0713 globalValue = Integer.parseInt(globalTotal.get(key));
0714
0715 if (gStore.get(key) instanceof LinkedHashMap) {
0716 int systotal = 0;
0717 if (((LinkedHashMap<String, Object>) gStore.get(key))
0718 .containsKey("systotal")) {
0719 systotal = Integer
0720 .parseInt((String) ((LinkedHashMap<String, Object>) (gStore
0721 .get(key))).get("systotal"));
0722 }
0723
0724 if (suppressZeroTimeEntries) {
0725 if (systotal > 0) {
0726 htmlElementTree += "<li id=\"level" + level + "\">" +
0727 "<a href=\"#\" onclick=\"expandCollapseTree(this)\">[+]</a>" +
0728 " " + key + "<ul style=\"display:none\">" + NL;
0729 htmlTimeTree += "<div id=level" + level + ".1>" + NL +
0730 systotal / 1000.0 + NL + "<div style=\"display:none\">" + NL;
0731 htmlTimeInPercentTree += "<div id=level" + level + ".2>" + NL
0732 + Math.round(((systotal / globalValue) * 100) * 10) / 10.0
0733 + "<div style=\"display:none\">" + NL;
0734 level++;
0735 generateCollapsibleHTMLTree((LinkedHashMap<String, Object>) (gStore
0736 .get(key)), suppressZeroTimeEntries);
0737 htmlElementTree += "</ul></li>" + NL;
0738 htmlTimeTree += "</div></div>" + NL;
0739 htmlTimeInPercentTree += "</div></div>" + NL;
0740 }
0741 } else {
0742 htmlElementTree += "<li id=level" + level + ">" +
0743 "<a href=\"#\" onclick=\"expandCollapseTree(this)\">[+]</a>" +
0744 " " + key + "<ul style=\"display:none\">" + NL;
0745 htmlTimeTree += "<div id=level" + level + ".1>" + NL +
0746 systotal / 1000.0 + "<div style=\"display:none\">" + NL;
0747 htmlTimeInPercentTree += "<div id=level" + level + ".2>" + NL
0748 + Math.round(((systotal / globalValue) * 100) * 10) / 10.0
0749 + "<div style=\"display:none\">" + NL;
0750 level++;
0751 generateCollapsibleHTMLTree((LinkedHashMap<String, Object>) (gStore
0752 .get(key)), suppressZeroTimeEntries);
0753 htmlElementTree += "</ul></li>" + NL;
0754 htmlTimeTree += "</div></div>" + NL;
0755 htmlTimeInPercentTree += "</div></div>" + NL;
0756 }
0757 } else {
0758 if (!(key.equals("total") || key.equals("systotal"))) {
0759 if (suppressZeroTimeEntries) {
0760 if (Integer.parseInt((String) (gStore.get(key))) != 0) {
0761 htmlElementTree += "<li> " + key + "</li>" + NL;
0762 htmlTimeTree += "<div>" + NL
0763 + Integer.parseInt((String) (gStore.get(key))) / 1000.0
0764 + "</div>" + NL;
0765 htmlTimeInPercentTree += "<div>" + NL
0766 + Math.round(((Integer.parseInt((String) (gStore.get(key))) / globalValue) * 100) * 10)
0767 / 10.0 + "</div>" + NL;
0768 }
0769 } else {
0770 htmlElementTree += "<li> " + key + "</li>" + NL;
0771 htmlTimeTree += "<div>" + NL
0772 + Integer.parseInt((String) (gStore.get(key))) / 1000.0
0773 + "</div>" + NL;
0774 htmlTimeInPercentTree += "<div>" + NL
0775 + Math.round(((Integer.parseInt((String) (gStore.get(key))) / globalValue) * 100) * 10)
0776 / 10.0 + "</div>" + NL;
0777 }
0778 }
0779 }
0780 }
0781 }
0782
0783 /**
0784 * Ensures that the required line is read from the given file part.
0785 *
0786 * @param bytearray
0787 * A part of a file being read upside down.
0788 * @param lastNlines
0789 * A vector containing the lines extracted from file part.
0790 * @return true if marker indicating the logical start of run is found; false
0791 * otherwise.
0792 */
0793 private boolean parseLinesFromLast(byte[] bytearray,
0794 Vector<String> lastNlines) {
0795 String lastNChars = new String(bytearray);
0796 StringBuffer sb = new StringBuffer(lastNChars);
0797 lastNChars = sb.reverse().toString();
0798 StringTokenizer tokens = new StringTokenizer(lastNChars, NL);
0799 while (tokens.hasMoreTokens()) {
0800 StringBuffer sbLine = new StringBuffer(tokens.nextToken());
0801 lastNlines.add(sbLine.reverse().toString());
0802 if ((lastNlines.get(lastNlines.size() - 1)).trim().endsWith(
0803 getLogicalStart())) {
0804 return true;
0805 }
0806 }
0807 return false; // indicates didn't read 'lineCount' lines
0808 }
0809
0810 /**
0811 * Reads the given file upside down.
0812 *
0813 * @param fileToBeRead
0814 * An object of type File representing the file to be read.
0815 * @param chunkSize
0816 * An integer specifying the size of the chunks in which file will be
0817 * read.
0818 * @return A long value pointing to the start position of the given file
0819 * chunk.
0820 * @throws BenchmarkReportInputFileFormatException
0821 */
0822 private long tail(File fileToBeRead, int chunkSize)
0823 throws BenchmarkReportInputFileFormatException {
0824 try {
0825 RandomAccessFile raf = new RandomAccessFile(fileToBeRead, "r");
0826 Vector<String> lastNlines = new Vector<String>();
0827 int delta = 0;
0828 long curPos = raf.length() - 1;
0829 long fromPos;
0830 byte[] bytearray;
0831 while (true) {
0832 fromPos = curPos - chunkSize;
0833 if (fromPos <= 0) {
0834 raf.seek(0);
0835 bytearray = new byte[(int) curPos];
0836 raf.readFully(bytearray);
0837 if (parseLinesFromLast(bytearray, lastNlines)) {
0838 if (fromPos < 0)
0839 fromPos = 0;
0840 }
0841 break;
0842 } else {
0843 raf.seek(fromPos);
0844 bytearray = new byte[chunkSize];
0845 raf.readFully(bytearray);
0846 if (parseLinesFromLast(bytearray, lastNlines)) {
0847 break;
0848 }
0849 delta = (lastNlines.get(lastNlines.size() - 1)).length();
0850 lastNlines.remove(lastNlines.size() - 1);
0851 curPos = fromPos + delta;
0852 }
0853 }
0854 if (fromPos < 0)
0855 throw new BenchmarkReportInputFileFormatException(getBenchmarkFile()
0856 .getAbsolutePath()
0857 + " does not contain a marker named "
0858 + getLogicalStart()
0859 + " indicating logical start of a run.");
0860 return fromPos;
0861
0862 } catch (IOException e) {
0863 e.printStackTrace();
0864 return -1;
0865 }
0866 }
0867
0868 /**
0869 * Ignores the inconsistent log entries from the benchmark file. Entries from
0870 * modules like pronominal coreferencer which have not been converted to new
0871 * benchmarking conventions are ignored.
0872 *
0873 * @param benchmarkIDChain
0874 * The chain of benchmark ids. This is the third token in the
0875 * benchmark file.
0876 * @param startTokens
0877 * An array of first tokens in the benchmark id chain.
0878 *
0879 * @return true if valid log entry; false otherwise.
0880 */
0881 private boolean validateLogEntry(String benchmarkIDChain,
0882 ArrayList<String> startTokens) {
0883 String startTokenRegExp = "(";
0884 for (int i = 0; i < startTokens.size(); i++) {
0885 if ((benchmarkIDChain.split("\\.")).length == 1
0886 && benchmarkIDChain.equals(startTokens.get(i))) {
0887 startTokens.remove(i);
0888 validEntries += 1;
0889 return true;
0890 }
0891 startTokenRegExp += startTokens.get(i) + "|";
0892 }
0893 if (startTokenRegExp.length() > 1) {
0894 startTokenRegExp = startTokenRegExp.substring(0, startTokenRegExp
0895 .length() - 1);
0896 }
0897 startTokenRegExp += ")";
0898 if (benchmarkIDChain.matches(startTokenRegExp + "\\.doc_.*?\\.pr_.*")) {
0899 validEntries += 1;
0900 return true;
0901 } else {
0902 return false;
0903 }
0904 }
0905
0906 /**
0907 * Parses the report arguments.
0908 *
0909 * @param args
0910 * A string array containing the command line arguments.
0911 */
0912 public void parseArguments(String[] args) {
0913 Getopt g = new Getopt("gate.util.reporting.PRTimeReporter", args,
0914 "i:m:z:s:o:l:h");
0915 int choice;
0916 String argSuppressZeroTimeEntries = null;
0917 while ((choice = g.getopt()) != -1) {
0918 switch (choice) {
0919 // -i inputFile
0920 case 'i':
0921 String argInPath = g.getOptarg();
0922 if (argInPath != null) {
0923 setBenchmarkFile(new File(argInPath));
0924 }
0925 break;
0926 // -m printMedia
0927 case 'm':
0928 String argPrintMedia = g.getOptarg();
0929 if (argPrintMedia != null) {
0930 setPrintMedia(argPrintMedia);
0931 } else {
0932 setPrintMedia(printMedia);
0933 }
0934 break;
0935 // -z suppressZeroTimeEntries
0936 case 'z':
0937 argSuppressZeroTimeEntries = g.getOptarg();
0938 if (argSuppressZeroTimeEntries == null) {
0939 setSuppressZeroTimeEntries(suppressZeroTimeEntries);
0940 }
0941 break;
0942 // -s sortOrder
0943 case 's':
0944 String argSortOrder = g.getOptarg();
0945 if (argSortOrder != null) {
0946 setSortOrder(argSortOrder);
0947 } else {
0948 setSortOrder(sortOrder);
0949 }
0950 break;
0951
0952 // -o ReportFile
0953 case 'o':
0954 String argOutPath = g.getOptarg();
0955 if (argOutPath != null) {
0956 setReportFile(new File(argOutPath));
0957 }
0958 break;
0959 // -l logical start
0960 case 'l':
0961 String argLogicalStart = g.getOptarg();
0962 if (argLogicalStart != null) {
0963 setLogicalStart(argLogicalStart);
0964 }
0965 break;
0966 // -h
0967 case 'h':
0968 case '?':
0969 usage();
0970 System.exit(STATUS_NORMAL);
0971 break;
0972
0973 default:
0974 usage();
0975 System.exit(STATUS_ERROR);
0976 break;
0977 } // getopt switch
0978 }
0979 if (argSuppressZeroTimeEntries != null) {
0980 if (argSuppressZeroTimeEntries.trim().equalsIgnoreCase("true")) {
0981 setSuppressZeroTimeEntries(true);
0982 } else if (argSuppressZeroTimeEntries.trim().equalsIgnoreCase("false")) {
0983 setSuppressZeroTimeEntries(false);
0984 } else {
0985 System.err.println("Suppress Zero Time Entries: parameter value" + NL +
0986 " passed is invalid. Please provide true or false as value.");
0987 usage();
0988 System.exit(STATUS_ERROR);
0989 }
0990 }
0991 }
0992
0993 /**
0994 * Display a usage message
0995 */
0996 public static void usage() {
0997 System.out.println(
0998 "Usage: java gate.util.reporting.PRTimeReporter [Options]" + NL
0999 + "\t Options:" + NL
1000 + "\t -i input file path (default: benchmark.txt in the execution directory)" + NL
1001 + "\t -m print media - html/text (default: html)" + NL
1002 + "\t -z suppressZeroTimeEntries - true/false (default: true)" + NL
1003 + "\t -s sorting order - exec_order/time_taken (default: exec_order)" + NL
1004 + "\t -o output file path (default: report.html/txt in the system temporary directory)" + NL
1005 + "\t -l logical start (not set by default)" + NL
1006 + "\t -h show help" + NL);
1007 } // usage()
1008
1009 /**
1010 * A main method which acts as a entry point while executing a report via
1011 * command line.
1012 *
1013 * @param args
1014 * A string array containing the command line arguments.
1015 */
1016 public static void main(String[] args)
1017 throws BenchmarkReportInputFileFormatException {
1018 // process command-line options
1019 PRTimeReporter reportOne = new PRTimeReporter(args);
1020 reportOne.generateReport();
1021 }
1022
1023 /**
1024 * Calls store, calculate and printReport for generating the actual report.
1025 */
1026 private void generateReport()
1027 throws BenchmarkReportInputFileFormatException {
1028 Timer timer = null;
1029 try {
1030 TimerTask task = new FileWatcher(getBenchmarkFile()) {
1031 protected void onChange(File file) throws BenchmarkReportExecutionException {
1032 throw new BenchmarkReportExecutionException(getBenchmarkFile()
1033 + " file has been modified while generating the report.");
1034 }
1035 };
1036 timer = new Timer();
1037 // repeat the check every second
1038 timer.schedule(task, new Date(), 1000);
1039
1040 Object report1Container1 = store(getBenchmarkFile());
1041 Object report1Container2 = calculate(report1Container1);
1042 if (getSortOrder().equalsIgnoreCase("time_taken")) {
1043 report1Container2 = sortReport(
1044 (LinkedHashMap<String, Object>) report1Container2);
1045 }
1046 if (reportFile == null) {
1047 reportFile = new File(System.getProperty("java.io.tmpdir"),
1048 "report." + ((printMedia.equals(MEDIA_HTML)) ? "html" : "txt"));
1049 }
1050 printReport(report1Container2, reportFile);
1051 } finally {
1052 if (timer != null) { timer.cancel(); }
1053 }
1054 }
1055
1056 /*
1057 * (non-Javadoc)
1058 *
1059 * @see gate.util.reporting.BenchmarkReportable#executeReport()
1060 */
1061 public void executeReport() throws BenchmarkReportInputFileFormatException {
1062 generateReport();
1063 }
1064
1065 /**
1066 * Returns the flag indicating whether or not to suppress the processing
1067 * elements from the report which took 0 milliseconds.
1068 *
1069 * @return suppressZeroTimeEntries A boolean indicating whether or not to
1070 * suppress zero time entries.
1071 */
1072 public boolean isSuppressZeroTimeEntries() {
1073 return suppressZeroTimeEntries;
1074 }
1075
1076 /**
1077 * Allow to suppress the processing elements from the report which
1078 * took 0 milliseconds.
1079 *
1080 * @param suppressZeroTimeEntries if true suppress zero time entries.
1081 * This Parameter is ignored if SortOrder specified is
1082 * <code>SORT_TIME_TAKEN</code>. True by default.
1083 */
1084 public void setSuppressZeroTimeEntries(boolean suppressZeroTimeEntries) {
1085 this.suppressZeroTimeEntries = suppressZeroTimeEntries;
1086 }
1087
1088 /**
1089 * Returns the name of the media on which report will be generated. e.g. text,
1090 * HTML.
1091 *
1092 * @return printMedia A String containing the name of the media on which
1093 * report will be generated.
1094 */
1095 public String getPrintMedia() {
1096 return printMedia;
1097 }
1098
1099 /**
1100 * Sets the media on which report will be generated.
1101 *
1102 * @param printMedia Type of media on which the report will be generated.
1103 * Must be MEDIA_TEXT or MEDIA_HTML.
1104 * The default is MEDIA_HTML.
1105 */
1106 public void setPrintMedia(String printMedia) {
1107 if (!printMedia.equals(MEDIA_HTML)
1108 && !printMedia.equals(MEDIA_TEXT)) {
1109 throw new IllegalArgumentException("Illegal argument: " + printMedia);
1110 }
1111 this.printMedia = printMedia.trim();
1112 }
1113
1114 /**
1115 * Returns the sorting order specified for the report (EXEC_ORDER or
1116 * TIME_TAKEN).
1117 *
1118 * @return sortOrder A String containing the sorting order.
1119 */
1120 public String getSortOrder() {
1121 return sortOrder;
1122 }
1123
1124 /**
1125 * Sets the sorting order of the report.
1126 *
1127 * @param sortOrder Sorting order of the report.
1128 * Must be SORT_EXEC_ORDER or SORT_TIME_TAKEN.
1129 * Default is <code>SORT_EXEC_ORDER</code>.
1130 *
1131 */
1132 public void setSortOrder(String sortOrder) {
1133 if (!sortOrder.equals(SORT_EXEC_ORDER)
1134 && !sortOrder.equals(SORT_TIME_TAKEN)) {
1135 throw new IllegalArgumentException("Illegal argument: " + printMedia);
1136 }
1137 this.sortOrder = sortOrder.trim();
1138 }
1139
1140 /**
1141 * Returns the marker indicating logical start of a run.
1142 *
1143 * @return logicalStart A String containing the marker indicating logical
1144 * start of a run.
1145 */
1146 public String getLogicalStart() {
1147 return logicalStart;
1148 }
1149
1150 /**
1151 * Sets optionally a string indicating the logical start of a run.
1152 *
1153 * @param logicalStart A String indicating the logical start of a run.
1154 * Useful when you you have marked different runs in
1155 * your benchmark file with this string at their start.
1156 * By default the value is null.
1157 */
1158 public void setLogicalStart(String logicalStart) {
1159 this.logicalStart = logicalStart.trim();
1160 }
1161
1162 /**
1163 * @return benchmarkFile path to input benchmark file.
1164 * @see #setBenchmarkFile(java.io.File)
1165 */
1166 public File getBenchmarkFile() {
1167 return benchmarkFile;
1168 }
1169
1170 /**
1171 * Sets the input benchmark file from which the report is generated.
1172 * By default use the file named "benchmark.txt" from the application
1173 * execution directory.
1174 *
1175 * @param benchmarkFile Input benchmark file.
1176 */
1177 public void setBenchmarkFile(File benchmarkFile) {
1178 this.benchmarkFile = benchmarkFile;
1179 }
1180
1181 /**
1182 * @return reportFile file path where the report file is written.
1183 * @see #setReportFile(java.io.File)
1184 */
1185 public File getReportFile() {
1186 return reportFile;
1187 }
1188
1189 /**
1190 * If not set, the default is the file name "report.txt/html"
1191 * in the system temporary directory.
1192 *
1193 * @param reportFile file path to the report file to write.
1194 */
1195 public void setReportFile(File reportFile) {
1196 this.reportFile = reportFile;
1197 }
1198
1199 }
1200
1201 /**
1202 * A Comparator class to compare the values of the LinkedHashMaps containing
1203 * processing elements and time taken by them.
1204 */
1205 class ValueComparator implements Comparator {
1206 /**
1207 * Provides the comparison logic between the processing time taken by
1208 * processing elements
1209 *
1210 * @param obj1
1211 * An integer value in form of string to be compared
1212 * @param obj2
1213 * An integer value in form of string to be compared
1214 *
1215 * @return An integer representing difference (0 if both are equal, positive
1216 * if obj1 is greater then obj2, negative if obj2 is greater then
1217 * obj1)
1218 */
1219 public int compare(Object obj1, Object obj2) {
1220 int i1 = Integer.parseInt((String) obj1);
1221 int i2 = Integer.parseInt((String) obj2);
1222 return i1 - i2;
1223 }
1224 }
|