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