PRTimeReporter.java
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 > && 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 - == 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(keyinstanceof 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((Stringkey, Integer.toString(systotal));
0336         }
0337         mapperReport.put((Stringkey,
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((Stringkey, new Integer((StringgStore
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((StringtempKey, tempOutLHM.get(tempKey));
0356       if (mapperReport.containsKey(tempKey)) {
0357         sortedReport
0358             .put((StringtempKey, mapperReport.get((StringtempKey));
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(keyinstanceof 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(keyinstanceof 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 / globalValue1001010.0
0561                 "%]");
0562         else {
0563           printLines
0564               .add(separator + key + " (" + systotal / 1000.0 ") ["
0565                   + Math.round(((systotal / globalValue1001010.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))) / globalValue10010)
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))) / globalValue10010)
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 = '[&mdash;]';" + 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(keyinstanceof 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               "&nbsp;" + 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 / globalValue1001010.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             "&nbsp;" + 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 / globalValue1001010.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>&nbsp;&nbsp;&nbsp;" + 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))) / globalValue10010)
0767                   10.0 "</div>" + NL;
0768             }
0769           else {
0770             htmlElementTree += "<li>&nbsp;&nbsp;&nbsp;" + 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))) / globalValue10010)
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[(intcurPos];
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 filethrows 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((Stringobj1);
1221     int i2 = Integer.parseInt((Stringobj2);
1222     return i1 - i2;
1223   }
1224 }