Coverage Report - com.google.code.jetm.maven.TimingReportMojo
 
Classes in this File Line Coverage Branch Coverage Complexity
TimingReportMojo
0%
0/117
0%
0/30
2.059
 
 1  
 package com.google.code.jetm.maven;
 2  
 
 3  
 import java.io.File;
 4  
 import java.io.FileInputStream;
 5  
 import java.io.FileNotFoundException;
 6  
 import java.io.InputStreamReader;
 7  
 import java.nio.charset.Charset;
 8  
 import java.text.DecimalFormat;
 9  
 import java.util.ArrayList;
 10  
 import java.util.Collection;
 11  
 import java.util.Collections;
 12  
 import java.util.HashMap;
 13  
 import java.util.LinkedHashMap;
 14  
 import java.util.LinkedList;
 15  
 import java.util.List;
 16  
 import java.util.Locale;
 17  
 import java.util.Map;
 18  
 import java.util.Map.Entry;
 19  
 
 20  
 import org.apache.commons.io.FileUtils;
 21  
 import org.apache.commons.io.IOUtils;
 22  
 import org.apache.commons.io.filefilter.TrueFileFilter;
 23  
 import org.apache.maven.doxia.sink.Sink;
 24  
 import org.apache.maven.doxia.siterenderer.Renderer;
 25  
 import org.apache.maven.project.MavenProject;
 26  
 import org.apache.maven.reporting.AbstractMavenReport;
 27  
 import org.apache.maven.reporting.MavenReportException;
 28  
 import org.codehaus.plexus.util.StringUtils;
 29  
 
 30  
 import com.google.code.jetm.maven.data.AggregateSummary;
 31  
 import com.google.code.jetm.maven.data.TimeUnit;
 32  
 import com.google.code.jetm.maven.util.AggregateComparator;
 33  
 import com.google.code.jetm.maven.util.XmlIOFileFilter;
 34  
 import com.google.code.jetm.reporting.AggregateBinder;
 35  
 import com.google.code.jetm.reporting.xml.XmlAggregateBinder;
 36  
 
 37  
 import etm.core.aggregation.Aggregate;
 38  
 
 39  
 /**
 40  
  * A mojo used to create a report that displays the collective JETM timings that
 41  
  * were collected and rendered using an {@link XmlAggregateBinder}.
 42  
  * 
 43  
  * @author jrh3k5
 44  
  * @goal timing-report
 45  
  * @phase site
 46  
  */
 47  
 
 48  0
 public class TimingReportMojo extends AbstractMavenReport {
 49  
     /**
 50  
      * The directories containing the timing report XML files. If not set, then a default of "${project.build.directory}/jetm" will be used instead.
 51  
      * 
 52  
      * @parameter
 53  
      */
 54  
     private File[] timings;
 55  
 
 56  
     /**
 57  
      * The encoding by which the XML files will be read. If not specified, this defaults to platform encoding.
 58  
      * 
 59  
      * @parameter default-value="${project.build.sourceEncoding}"
 60  
      * @required
 61  
      */
 62  
     private String inputEncoding;
 63  
 
 64  
     /**
 65  
      * The unit of time in which the report is to express its recorded timings. Supported values are:
 66  
      * <ul>
 67  
      * <li>SECS: the report will display times in seconds</li>
 68  
      * <li>MILLIS: the report will display times in milliseconds</li>
 69  
      * </ul>
 70  
      * 
 71  
      * @parameter default-value="SECS"
 72  
      * @required
 73  
      */
 74  
     private String timeUnit;
 75  
 
 76  
     /**
 77  
      * Directory where reports will go.
 78  
      * 
 79  
      * @parameter expression="${project.reporting.outputDirectory}"
 80  
      * @required
 81  
      * @readonly
 82  
      */
 83  
     private String outputDirectory;
 84  
 
 85  
     /**
 86  
      * @parameter default-value="${project}"
 87  
      * @required
 88  
      * @readonly
 89  
      */
 90  
     private MavenProject project;
 91  
 
 92  
     /**
 93  
      * @component
 94  
      * @required
 95  
      * @readonly
 96  
      */
 97  
     private Renderer siteRenderer;
 98  
 
 99  
     /**
 100  
      * The build directory for the Maven project.
 101  
      * 
 102  
      * @parameter default-value="${project.build.directory}"
 103  
      * @required
 104  
      * @readonly
 105  
      */
 106  
     private File buildDirectory;
 107  
 
 108  0
     private final AggregateBinder binder = new XmlAggregateBinder();
 109  
 
 110  0
     private DecimalFormat decimalFormatter = new DecimalFormat("0.00");
 111  
 
 112  0
     private final XmlIOFileFilter xmlFileFilter = new XmlIOFileFilter();
 113  
 
 114  
     @Override
 115  
     public boolean canGenerateReport() {
 116  0
         return !getTimingFiles().isEmpty();
 117  
     }
 118  
 
 119  
     /**
 120  
      * {@inheritDoc}
 121  
      */
 122  
     public String getOutputName() {
 123  0
         return "jetm-timing-report";
 124  
     }
 125  
 
 126  
     /**
 127  
      * {@inheritDoc}
 128  
      */
 129  
     public String getName(Locale locale) {
 130  0
         return "JETM Timing Report";
 131  
     }
 132  
 
 133  
     /**
 134  
      * {@inheritDoc}
 135  
      */
 136  
     public String getDescription(Locale locale) {
 137  0
         return "A collective report of all JETM timings that were collected and rendered.";
 138  
     }
 139  
 
 140  
     /**
 141  
      * {@inheritDoc}
 142  
      */
 143  
     protected void executeReport(Locale locale) throws MavenReportException {
 144  0
         final Map<File, List<Aggregate>> aggregates = getAggregates();
 145  0
         final List<AggregateSummary> summaries = aggregate(aggregates);
 146  0
         Collections.sort(summaries);
 147  
     
 148  0
         final Sink sink = getSink();
 149  
         try {
 150  0
             sink.head();
 151  0
             sink.title();
 152  0
             sink.text(getName(locale));
 153  0
             sink.title_();
 154  0
             sink.head_();
 155  
     
 156  0
             sink.body();
 157  0
             sink.sectionTitle1();
 158  0
             sink.text(getName(locale));
 159  0
             sink.sectionTitle1_();
 160  
     
 161  0
             if (summaries.isEmpty()) {
 162  0
                 sink.text(" There are no JETM timings available for reporting.");
 163  
                 return;
 164  
             }
 165  
     
 166  0
             sink.sectionTitle2();
 167  0
             sink.text("Summary");
 168  0
             sink.sectionTitle2_();
 169  
     
 170  0
             sink.text("This is a summary, by measurement name, of the measurements taken.");
 171  
     
 172  0
             print(sink, summaries);
 173  
     
 174  0
             sink.sectionTitle2();
 175  0
             sink.text("File Breakdown");
 176  0
             sink.sectionTitle2_();
 177  
     
 178  0
             sink.text("This is a list of, per XML file, the measurements taken.");
 179  
     
 180  0
             for (Entry<File, List<Aggregate>> entry : aggregates.entrySet()) {
 181  0
                 sink.sectionTitle3();
 182  0
                 sink.text(entry.getKey().getName());
 183  0
                 sink.sectionTitle3_();
 184  
     
 185  0
                 print(sink, entry.getValue());
 186  
             }
 187  
         } finally {
 188  0
             sink.body_();
 189  
     
 190  0
             sink.flush();
 191  0
             sink.close();
 192  0
         }
 193  0
     }
 194  
 
 195  
     /**
 196  
      * {@inheritDoc}
 197  
      */
 198  
     protected String getOutputDirectory() {
 199  0
         return outputDirectory;
 200  
     }
 201  
 
 202  
     /**
 203  
      * {@inheritDoc}
 204  
      */
 205  
     protected MavenProject getProject() {
 206  0
         return project;
 207  
     }
 208  
 
 209  
     /**
 210  
      * {@inheritDoc}
 211  
      */
 212  
     protected Renderer getSiteRenderer() {
 213  0
         return siteRenderer;
 214  
     }
 215  
 
 216  
     /**
 217  
      * Create aggregate summaries.
 218  
      * 
 219  
      * @param aggregates
 220  
      *            A {@link Map} containing the aggregate data to be summarized.
 221  
      * @return A {@link List} of {@link AggregateSummary} objects representing
 222  
      *         the entirety of aggregate data, summarized by name.
 223  
      * @see #getAggregates()
 224  
      */
 225  
     private List<AggregateSummary> aggregate(Map<File, List<Aggregate>> aggregates) {
 226  0
         if (aggregates.isEmpty())
 227  0
             return Collections.emptyList();
 228  
     
 229  0
         final Map<String, AggregateSummary> summaries = new HashMap<String, AggregateSummary>();
 230  0
         for (List<Aggregate> aggregateList : aggregates.values())
 231  0
             for (Aggregate aggregate : aggregateList) {
 232  0
                 final String name = aggregate.getName();
 233  0
                 if (!summaries.containsKey(name))
 234  0
                     summaries.put(name, new AggregateSummary(name));
 235  
     
 236  0
                 final AggregateSummary summary = summaries.get(name);
 237  0
                 summary.add(aggregate);
 238  0
             }
 239  
     
 240  0
         final List<AggregateSummary> summaryList = new ArrayList<AggregateSummary>(summaries.size());
 241  0
         for (AggregateSummary summary : summaries.values())
 242  0
             summaryList.add(summary);
 243  
     
 244  0
         return summaryList;
 245  
     }
 246  
 
 247  
     /**
 248  
      * Get aggregates.
 249  
      * 
 250  
      * @return A {@link Map}. Its keys are the files that contain aggregate
 251  
      *         data; the values are {@link List}s of {@link Aggregate} objects
 252  
      *         representing the timings read within each file.
 253  
      *         <p />
 254  
      *         If a file contains no timing data, it will not be returned in
 255  
      *         this map.
 256  
      * @throws MavenReportException
 257  
      *             If any errors occur while reading the file.
 258  
      */
 259  
     private Map<File, List<Aggregate>> getAggregates() throws MavenReportException {
 260  0
         final Map<File, List<Aggregate>> aggregates = new LinkedHashMap<File, List<Aggregate>>();
 261  0
         for (File file : getTimingFiles()) {
 262  0
             final List<Aggregate> aggregateList = new LinkedList<Aggregate>();
 263  
             InputStreamReader reader;
 264  
             try {
 265  0
                 reader = new InputStreamReader(new FileInputStream(file), getInputCharset());
 266  0
             } catch (FileNotFoundException e) {
 267  0
                 throw new MavenReportException("File not found: " + file, e);
 268  0
             }
 269  
             try {
 270  0
                 final Collection<Aggregate> unbound = binder.unbind(reader);
 271  0
                 if (unbound.isEmpty())
 272  
                     continue;
 273  0
                 aggregateList.addAll(unbound);
 274  
             } finally {
 275  0
                 IOUtils.closeQuietly(reader);
 276  0
             }
 277  0
             aggregates.put(file, aggregateList);
 278  0
         }
 279  0
         return aggregates;
 280  
     }
 281  
 
 282  
     /**
 283  
      * Get the file encoding to be used to read the XML files.
 284  
      * 
 285  
      * @return A {@link Charset} representing the configured character set.
 286  
      */
 287  
     private Charset getInputCharset() {
 288  0
         return StringUtils.isBlank(inputEncoding) ? Charset.defaultCharset() : Charset.forName(inputEncoding);
 289  
     }
 290  
 
 291  
     /**
 292  
      * Get the time unit to be used when rendering the report.
 293  
      * 
 294  
      * @return A {@link TimeUnit} enum corresponding to the configured time unit.
 295  
      */
 296  
     private TimeUnit getTimeUnit() {
 297  0
         return TimeUnit.fromMojoAbbreviation(timeUnit);
 298  
     }
 299  
 
 300  
     /**
 301  
      * Get the timings directories.
 302  
      * 
 303  
      * @return An array of {@link File} objects representing the configured timing directories; if none are configured, then "${project.build.directory}/jetm" will be used as a default.
 304  
      */
 305  
     private File[] getTimingDirectories() {
 306  0
         return timings == null ? new File[] { new File(buildDirectory, "jetm") } : timings;
 307  
     }
 308  
 
 309  
     /**
 310  
      * Get all files available for reading as timings.
 311  
      * 
 312  
      * @return A {@link List} of {@link File} objects representing the files to be read as timing data.
 313  
      */
 314  
     private List<File> getTimingFiles() {
 315  0
         final List<File> timingFiles = new ArrayList<File>();
 316  0
         for (File timingDirectory : getTimingDirectories()) {
 317  0
             if (!timingDirectory.exists())
 318  0
                 continue;
 319  
 
 320  0
             timingFiles.addAll(FileUtils.listFiles(timingDirectory, xmlFileFilter, TrueFileFilter.TRUE));
 321  
         }
 322  
 
 323  0
         return timingFiles;
 324  
     }
 325  
 
 326  
     /**
 327  
      * Print a table containing information within a given set of aggregates.
 328  
      * 
 329  
      * @param sink
 330  
      *            The {@link Sink} used to render out the table.
 331  
      * @param aggregates
 332  
      *            A {@link Collection} of {@link Aggregate} objects representing
 333  
      *            the data to be written out.
 334  
      */
 335  
     private void print(Sink sink, Collection<? extends Aggregate> aggregates) {
 336  0
         final TimeUnit timeUnit = getTimeUnit();
 337  
 
 338  0
         sink.table();
 339  0
         sink.tableRows(null, false);
 340  0
         sink.tableRow();
 341  0
         tableHeaderCell(sink, "Name");
 342  0
         tableHeaderCell(sink, "Average (" + timeUnit.getDisplayName() + ")");
 343  0
         tableHeaderCell(sink, "Measurements");
 344  0
         tableHeaderCell(sink, "Minimum (" + timeUnit.getDisplayName() + ")");
 345  0
         tableHeaderCell(sink, "Maximum (" + timeUnit.getDisplayName() + ")");
 346  0
         tableHeaderCell(sink, "Total (" + timeUnit.getDisplayName() + ")");
 347  0
         sink.tableRow_();
 348  
 
 349  0
         final List<? extends Aggregate> sortedAggregates = new ArrayList<Aggregate>(aggregates);
 350  0
         Collections.sort(sortedAggregates, new AggregateComparator());
 351  
 
 352  0
         for (Aggregate aggregate : sortedAggregates) {
 353  0
             sink.tableRow();
 354  0
             tableCell(sink, aggregate.getName());
 355  0
             tableCell(sink, decimalFormatter.format(timeUnit.fromMilliseconds(aggregate.getTotal() / aggregate.getMeasurements())));
 356  0
             tableCell(sink, Long.toString(aggregate.getMeasurements()));
 357  0
             tableCell(sink, decimalFormatter.format(timeUnit.fromMilliseconds(aggregate.getMin())));
 358  0
             tableCell(sink, decimalFormatter.format(timeUnit.fromMilliseconds(aggregate.getMax())));
 359  0
             tableCell(sink, decimalFormatter.format(timeUnit.fromMilliseconds(aggregate.getTotal())));
 360  0
             sink.tableRow_();
 361  
         }
 362  0
         sink.tableRows_();
 363  0
         sink.table_();
 364  0
     }
 365  
 
 366  
     /**
 367  
      * Create a table header cell.
 368  
      * 
 369  
      * @param sink
 370  
      *            The {@link Sink} used to render out the header.
 371  
      * @param text
 372  
      *            The text to be printed within the table header.
 373  
      */
 374  
     private void tableHeaderCell(Sink sink, String text) {
 375  0
         sink.tableHeaderCell();
 376  0
         sink.text(text);
 377  0
         sink.tableHeaderCell_();
 378  0
     }
 379  
 
 380  
     /**
 381  
      * Create a table cell.
 382  
      * 
 383  
      * @param sink
 384  
      *            The {@link Sink} used to render out the table cell.
 385  
      * @param text
 386  
      *            The text to be written inside the cell.
 387  
      */
 388  
     private void tableCell(Sink sink, String text) {
 389  0
         sink.tableCell();
 390  0
         sink.text(text);
 391  0
         sink.tableCell_();
 392  0
     }
 393  
 }