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         sink.tableRows(null, false);
188         sink.tableRow();
189         tableHeaderCell(sink, "Name");
190         tableHeaderCell(sink, "Average (sec)");
191         tableHeaderCell(sink, "Measurements");
192         tableHeaderCell(sink, "Minimum (sec)");
193         tableHeaderCell(sink, "Maximum (sec)");
194         tableHeaderCell(sink, "Total");
195         sink.tableRow_();
196         for (Aggregate aggregate : aggregates) {
197             sink.tableRow();
198             tableCell(sink, aggregate.getName());
199             tableCell(sink, decimalFormatter.format((aggregate.getTotal() / aggregate
200                     .getMeasurements()) / 1000.0));
201             tableCell(sink, Long.toString(aggregate.getMeasurements()));
202             tableCell(sink, decimalFormatter.format(aggregate.getMin() / 1000.0));
203             tableCell(sink, decimalFormatter.format(aggregate.getMax() / 1000.0));
204             tableCell(sink, decimalFormatter.format(aggregate.getTotal() / 1000.0));
205             sink.tableRow_();
206         }
207         sink.tableRows_();
208         sink.table_();
209     }
210 
211     /**
212      * Create a table header cell.
213      * 
214      * @param sink
215      *            The {@link Sink} used to render out the header.
216      * @param text
217      *            The text to be printed within the table header.
218      */
219     private void tableHeaderCell(Sink sink, String text) {
220         sink.tableHeaderCell();
221         sink.text(text);
222         sink.tableHeaderCell_();
223     }
224 
225     /**
226      * Create a table cell.
227      * 
228      * @param sink
229      *            The {@link Sink} used to render out the table cell.
230      * @param text
231      *            The text to be written inside the cell.
232      */
233     private void tableCell(Sink sink, String text) {
234         sink.tableCell();
235         sink.text(text);
236         sink.tableCell_();
237     }
238 
239     /**
240      * Get aggregates.
241      * 
242      * @return A {@link Map}. Its keys are the files that contain aggregate
243      *         data; the values are {@link List}s of {@link Aggregate} objects
244      *         representing the timings read within each file.
245      *         <p />
246      *         If a file contains no timing data, it will not be returned in
247      *         this map.
248      * @throws MavenReportException
249      *             If any errors occur while reading the file.
250      */
251     private Map<File, List<Aggregate>> getAggregates() throws MavenReportException {
252         final Map<File, List<Aggregate>> aggregates = new LinkedHashMap<File, List<Aggregate>>();
253         for (File directory : timings) {
254             if (!directory.exists())
255                 continue;
256 
257             for (File file : FileUtils.listFiles(directory, xmlFileFilter, TrueFileFilter.TRUE)) {
258                 final List<Aggregate> aggregateList = new LinkedList<Aggregate>();
259                 FileReader reader;
260                 try {
261                     reader = new FileReader(file);
262                 } catch (FileNotFoundException e) {
263                     throw new MavenReportException("File not found: " + file, e);
264                 }
265                 try {
266                     final Collection<Aggregate> unbound = binder.unbind(reader);
267                     if (unbound.isEmpty())
268                         continue;
269                     aggregateList.addAll(unbound);
270                 } finally {
271                     IOUtils.closeQuietly(reader);
272                 }
273                 aggregates.put(file, aggregateList);
274             }
275         }
276         return aggregates;
277     }
278 
279     /**
280      * Create aggregate summaries.
281      * 
282      * @param aggregates
283      *            A {@link Map} containing the aggregate data to be summarized.
284      * @return A {@link List} of {@link AggregateSummary} objects representing
285      *         the entirety of aggregate data, summarized by name.
286      * @see #getAggregates()
287      */
288     private List<AggregateSummary> aggregate(Map<File, List<Aggregate>> aggregates) {
289         if (aggregates.isEmpty())
290             return Collections.emptyList();
291 
292         final Map<String, AggregateSummary> summaries = new HashMap<String, AggregateSummary>();
293         for (List<Aggregate> aggregateList : aggregates.values())
294             for (Aggregate aggregate : aggregateList) {
295                 final String name = aggregate.getName();
296                 if (!summaries.containsKey(name))
297                     summaries.put(name, new AggregateSummary(name));
298 
299                 final AggregateSummary summary = summaries.get(name);
300                 summary.add(aggregate);
301             }
302 
303         final List<AggregateSummary> summaryList = new ArrayList<AggregateSummary>(summaries.size());
304         for (AggregateSummary summary : summaries.values())
305             summaryList.add(summary);
306 
307         return summaryList;
308     }
309 }