View Javadoc

1   package com.google.code.jetm.maven;
2   
3   import java.io.File;
4   import java.io.FileNotFoundException;
5   import java.io.FileReader;
6   import java.text.DecimalFormat;
7   import java.util.ArrayList;
8   import java.util.Collection;
9   import java.util.Collections;
10  import java.util.HashMap;
11  import java.util.LinkedHashMap;
12  import java.util.LinkedList;
13  import java.util.List;
14  import java.util.Locale;
15  import java.util.Map;
16  import java.util.Map.Entry;
17  
18  import org.apache.commons.io.FileUtils;
19  import org.apache.commons.io.IOUtils;
20  import org.apache.commons.io.filefilter.TrueFileFilter;
21  import org.apache.maven.doxia.sink.Sink;
22  import org.apache.maven.doxia.siterenderer.Renderer;
23  import org.apache.maven.project.MavenProject;
24  import org.apache.maven.reporting.AbstractMavenReport;
25  import org.apache.maven.reporting.MavenReportException;
26  
27  import com.google.code.jetm.maven.data.AggregateSummary;
28  import com.google.code.jetm.maven.util.XmlIOFileFilter;
29  import com.google.code.jetm.reporting.AggregateBinder;
30  import com.google.code.jetm.reporting.xml.XmlAggregateBinder;
31  
32  import etm.core.aggregation.Aggregate;
33  
34  /**
35   * A mojo used to create a report that displays the collective JETM timings that
36   * were collected and rendered using an {@link XmlAggregateBinder}.
37   * 
38   * @author jrh3k5
39   * @goal timing-report
40   * @phase site
41   */
42  
43  public class TimingReportMojo extends AbstractMavenReport {
44      /**
45       * The directories containing the timing report XML files.
46       * 
47       * @parameter
48       * @required
49       */
50      private File[] timings;
51  
52      /**
53       * Directory where reports will go.
54       * 
55       * @parameter expression="${project.reporting.outputDirectory}"
56       * @required
57       * @readonly
58       */
59      private String outputDirectory;
60  
61      /**
62       * @parameter default-value="${project}"
63       * @required
64       * @readonly
65       */
66      private MavenProject project;
67  
68      /**
69       * @component
70       * @required
71       * @readonly
72       */
73      private Renderer siteRenderer;
74  
75      /**
76       * {@inheritDoc}
77       */
78      public String getOutputName() {
79          return "jetm-timing-report";
80      }
81  
82      /**
83       * {@inheritDoc}
84       */
85      public String getName(Locale locale) {
86          return "JETM Timing Report";
87      }
88  
89      /**
90       * {@inheritDoc}
91       */
92      public String getDescription(Locale locale) {
93          return "A collective report of all JETM timings that were collected and rendered.";
94      }
95  
96      /**
97       * {@inheritDoc}
98       */
99      protected Renderer getSiteRenderer() {
100         return siteRenderer;
101     }
102 
103     /**
104      * {@inheritDoc}
105      */
106     protected String getOutputDirectory() {
107         return outputDirectory;
108     }
109 
110     /**
111      * {@inheritDoc}
112      */
113     protected MavenProject getProject() {
114         return project;
115     }
116 
117     private DecimalFormat decimalFormatter = new DecimalFormat("0.00");
118     private final AggregateBinder binder = new XmlAggregateBinder();
119     private final XmlIOFileFilter xmlFileFilter = new XmlIOFileFilter();
120 
121     /**
122      * {@inheritDoc}
123      */
124     protected void executeReport(Locale locale) throws MavenReportException {
125         final Map<File, List<Aggregate>> aggregates = getAggregates();
126         final List<AggregateSummary> summaries = aggregate(aggregates);
127         Collections.sort(summaries);
128 
129         final Sink sink = getSink();
130         try {
131             sink.head();
132             sink.title();
133             sink.text(getName(locale));
134             sink.title_();
135             sink.head_();
136 
137             sink.body();
138             sink.sectionTitle1();
139             sink.text(getName(locale));
140             sink.sectionTitle1_();
141 
142             if (summaries.isEmpty()) {
143                 sink.text(" There are no JETM timings available for reporting.");
144                 return;
145             }
146 
147             sink.sectionTitle2();
148             sink.text("Summary");
149             sink.sectionTitle2_();
150 
151             sink.text("This is a summary, by measurement name, of the measurements taken.");
152 
153             print(sink, summaries);
154 
155             sink.sectionTitle2();
156             sink.text("File Breakdown");
157             sink.sectionTitle2_();
158 
159             sink.text("This is a list of, per XML file, the measurements taken.");
160 
161             for (Entry<File, List<Aggregate>> entry : aggregates.entrySet()) {
162                 sink.sectionTitle3();
163                 sink.text(entry.getKey().getName());
164                 sink.sectionTitle3_();
165 
166                 print(sink, entry.getValue());
167             }
168         } finally {
169             sink.body_();
170 
171             sink.flush();
172             sink.close();
173         }
174     }
175 
176     /**
177      * Print a table containing information within a given set of aggregates.
178      * 
179      * @param sink
180      *            The {@link Sink} used to render out the table.
181      * @param aggregates
182      *            A {@link Collection} of {@link Aggregate} objects representing
183      *            the data to be written out.
184      */
185     private void print(Sink sink, Collection<? extends Aggregate> aggregates) {
186         sink.table();
187         tableHeaderCell(sink, "Name");
188         tableHeaderCell(sink, "Average (sec)");
189         tableHeaderCell(sink, "Measurements");
190         tableHeaderCell(sink, "Minimum (sec)");
191         tableHeaderCell(sink, "Maximum (sec)");
192         tableHeaderCell(sink, "Total");
193         for (Aggregate aggregate : aggregates) {
194             sink.tableRow();
195             tableCell(sink, aggregate.getName());
196             tableCell(sink, decimalFormatter.format((aggregate.getTotal() / aggregate
197                     .getMeasurements()) / 1000.0));
198             tableCell(sink, Long.toString(aggregate.getMeasurements()));
199             tableCell(sink, decimalFormatter.format(aggregate.getMin() / 1000.0));
200             tableCell(sink, decimalFormatter.format(aggregate.getMax() / 1000.0));
201             tableCell(sink, decimalFormatter.format(aggregate.getTotal() / 1000.0));
202             sink.tableRow_();
203         }
204         sink.table_();
205     }
206 
207     /**
208      * Create a table header cell.
209      * 
210      * @param sink
211      *            The {@link Sink} used to render out the header.
212      * @param text
213      *            The text to be printed within the table header.
214      */
215     private void tableHeaderCell(Sink sink, String text) {
216         sink.tableHeaderCell();
217         sink.text(text);
218         sink.tableHeaderCell_();
219     }
220 
221     /**
222      * Create a table cell.
223      * 
224      * @param sink
225      *            The {@link Sink} used to render out the table cell.
226      * @param text
227      *            The text to be written inside the cell.
228      */
229     private void tableCell(Sink sink, String text) {
230         sink.tableCell();
231         sink.text(text);
232         sink.tableCell_();
233     }
234 
235     /**
236      * Get aggregates.
237      * 
238      * @return A {@link Map}. Its keys are the files that contain aggregate
239      *         data; the values are {@link List}s of {@link Aggregate} objects
240      *         representing the timings read within each file.
241      *         <p />
242      *         If a file contains no timing data, it will not be returned in
243      *         this map.
244      * @throws MavenReportException
245      *             If any errors occur while reading the file.
246      */
247     private Map<File, List<Aggregate>> getAggregates() throws MavenReportException {
248         final Map<File, List<Aggregate>> aggregates = new LinkedHashMap<File, List<Aggregate>>();
249         for (File directory : timings) {
250             if (!directory.exists())
251                 continue;
252 
253             for (File file : FileUtils.listFiles(directory, xmlFileFilter, TrueFileFilter.TRUE)) {
254                 final List<Aggregate> aggregateList = new LinkedList<Aggregate>();
255                 FileReader reader;
256                 try {
257                     reader = new FileReader(file);
258                 } catch (FileNotFoundException e) {
259                     throw new MavenReportException("File not found: " + file, e);
260                 }
261                 try {
262                     final Collection<Aggregate> unbound = binder.unbind(reader);
263                     if (unbound.isEmpty())
264                         continue;
265                     aggregateList.addAll(unbound);
266                 } finally {
267                     IOUtils.closeQuietly(reader);
268                 }
269                 aggregates.put(file, aggregateList);
270             }
271         }
272         return aggregates;
273     }
274 
275     /**
276      * Create aggregate summaries.
277      * 
278      * @param aggregates
279      *            A {@link Map} containing the aggregate data to be summarized.
280      * @return A {@link List} of {@link AggregateSummary} objects representing
281      *         the entirety of aggregate data, summarized by name.
282      * @see #getAggregates()
283      */
284     private List<AggregateSummary> aggregate(Map<File, List<Aggregate>> aggregates) {
285         if (aggregates.isEmpty())
286             return Collections.emptyList();
287 
288         final Map<String, AggregateSummary> summaries = new HashMap<String, AggregateSummary>();
289         for (List<Aggregate> aggregateList : aggregates.values())
290             for (Aggregate aggregate : aggregateList) {
291                 final String name = aggregate.getName();
292                 if (!summaries.containsKey(name))
293                     summaries.put(name, new AggregateSummary(name));
294 
295                 final AggregateSummary summary = summaries.get(name);
296                 summary.add(aggregate);
297             }
298 
299         final List<AggregateSummary> summaryList = new ArrayList<AggregateSummary>(summaries.size());
300         for (AggregateSummary summary : summaries.values())
301             summaryList.add(summary);
302 
303         return summaryList;
304     }
305 }