View Javadoc

1   /*
2    * The contents of this file are subject to the terms 
3    * of the Common Development and Distribution License 
4    * (the "License").  You may not use this file except 
5    * in compliance with the License.
6    * 
7    * You can obtain a copy of the license at 
8    * http://www.sun.com/cddl/cddl.html. 
9    * See the License for the specific language governing 
10   * permissions and limitations under the License.
11   * 
12   * When distributing Covered Code, include this CDDL 
13   * HEADER in each file and include the License file at 
14   * license.txt.  If applicable, add the following below 
15   * this CDDL HEADER, with the fields enclosed by brackets 
16   * "[]" replaced with your own identifying information: 
17   * Portions Copyright [yyyy] [name of copyright owner]
18   * 
19   * Portions Copyright 2004 eBay, Inc.
20   */
21  package com.ebay.carad.os.vitalsigns.listeners;
22  
23  import java.awt.Color;
24  import java.io.File;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.Date;
28  import java.util.Iterator;
29  import java.util.List;
30  
31  import org.apache.log4j.Logger;
32  import org.jfree.chart.ChartFactory;
33  import org.jfree.chart.JFreeChart;
34  import org.jfree.data.time.Millisecond;
35  import org.jfree.data.time.TimeSeries;
36  import org.jfree.data.time.TimeSeriesCollection;
37  
38  import com.ebay.carad.os.vitalsigns.DataPoint;
39  import com.ebay.carad.os.vitalsigns.IDashboardAgent;
40  import com.ebay.carad.os.vitalsigns.IDashboardReport;
41  import com.ebay.carad.os.vitalsigns.util.ChartUtil;
42  import com.ebay.carad.os.vitalsigns.util.CollectionUtil;
43  import com.ebay.carad.os.vitalsigns.util.ITimeConstants;
44  import com.ebay.carad.os.vitalsigns.util.ReportUtil;
45  
46  /***
47   * <p>Generates time series line graphs based on report data.</p>
48   * 
49   * <p>TODO : go over with fine tooth comb to make sure that it was not changed by port to OS - VS, especially since this is primary report output format</p>
50   * 
51   * @author Jeremy Thomerson
52   * @author Jeremy Kraybill
53   * @version $Id$
54   */
55  public class LineGraphImageCreationListener extends AbstractReportingListener implements IReportingListener {
56  
57      private static final Logger LOGGER = Logger.getLogger(LineGraphImageCreationListener.class);
58      
59      private String yAxisLabel = "";
60      
61      /***
62       * Returns the label of the graph's Y-axis.
63       * 
64       * @return the label of the graph's Y-axis.
65       */
66      protected String getYAxisLabel() {  
67          return yAxisLabel;
68      }   
69      
70      /***
71       * Sets the label of the graph's Y-axis.
72       * 
73       * @param label the label to use
74       */
75      public void setYAxisLabel(String label) {
76          yAxisLabel = label;
77      }
78      
79      private static final long ERROR_MARGIN = 0;
80      private long now;
81      private long oneHourAgo;
82      private long oneDayAgo;
83      private long twoDaysAgo;
84      private long oneWeekAgo;
85      private long oneWeekAndHourAgo;
86      private long eightDaysAgo;
87      private long twoWeeksAgo;
88      private long oneMonthAgo;
89      private long oneMonthAndHourAgo;
90      private long oneMonthAndDayAgo;
91      private long oneMonthAndWeekAgo;
92      private long twoMonthsAgo;
93      private long fiftyTwoWksAgo;
94      private long fiftyTwoWksAndDayAgo;
95      private long fiftyThreeWksAgo;
96      private long fiftySixWksAgo;
97      private long hundredFourWeeksAgo;
98      
99      private void setTimes() {
100         now = System.currentTimeMillis() - ERROR_MARGIN;
101         oneHourAgo = now - (ITimeConstants.HOUR);
102         oneDayAgo = now - ITimeConstants.DAY;
103         twoDaysAgo = oneDayAgo - ITimeConstants.DAY;
104         oneWeekAgo = now - ITimeConstants.WEEK;
105         oneWeekAndHourAgo = oneWeekAgo - ITimeConstants.HOUR;
106         eightDaysAgo = oneWeekAgo - ITimeConstants.DAY;
107         twoWeeksAgo = oneWeekAgo - ITimeConstants.WEEK;
108         oneMonthAgo = now - ITimeConstants.MONTH;
109         oneMonthAndHourAgo = oneMonthAgo - ITimeConstants.HOUR;
110         oneMonthAndDayAgo = oneMonthAgo - ITimeConstants.DAY;
111         oneMonthAndWeekAgo = oneMonthAgo - ITimeConstants.WEEK;
112         twoMonthsAgo = oneMonthAgo - ITimeConstants.MONTH;
113         fiftyTwoWksAgo = now - ITimeConstants.FIFTYTWOWEEKS;
114         fiftyTwoWksAndDayAgo = fiftyTwoWksAgo - ITimeConstants.DAY;
115         fiftyThreeWksAgo = fiftyTwoWksAgo - ITimeConstants.WEEK;
116         fiftySixWksAgo = fiftyTwoWksAgo - ITimeConstants.MONTH;
117         hundredFourWeeksAgo = fiftyTwoWksAgo - ITimeConstants.FIFTYTWOWEEKS;        
118     }
119     
120     /***
121      * Takes an iterator over a list of Map objects, which have a key for <code>LOGTIME</code>,
122      * and go back until the current value of <code>LOGTIME</code> is less than or equal to the
123      * target given date.
124      * 
125      * @param iterator the <code>Iterator</code> to navigate back with
126      * @param row the current row
127      * @param target the date to stop when at or before
128      * @return the row
129      */
130     private DataPoint goBackTo(Iterator it, DataPoint current, long target) {
131         DataPoint row = current;
132         long date = row.getLogTime();
133         while (it.hasNext() && date >= target) { // go back to target
134             row = (DataPoint) it.next();
135             date = row.getLogTime();
136         }
137         return row;
138     }
139 
140     /***
141      * Builds the hour chart.
142      * 
143      * @param report the report we are building for
144      * @param chartBase the base destination filename for saved files
145      * @param data the data to chart
146      */
147     private void buildHourChart(IDashboardReport report, String chartBase, DataPoint[] data) {
148         if (includeHourChart(report)) { // build the hour chart.
149             List hourSets = CollectionUtil.listOfLists(3); // current hour, one week ago, one month ago
150             Iterator it = new ArrayList(Arrays.asList(data)).iterator();
151             if (it.hasNext()) {
152                 DataPoint row = (DataPoint) it.next();
153                 
154                 while (it.hasNext() && row.getLogTime() >= oneHourAgo) {
155                     ((List) hourSets.get(0)).add(row);
156                     row = (DataPoint) it.next();
157                 }
158                 
159                 row = goBackTo(it, row, oneWeekAgo);
160                 while (it.hasNext() && row.getLogTime() >= oneWeekAndHourAgo) {
161                     DataPoint weekRow = ReportUtil.shiftTime(row, ITimeConstants.WEEK);
162                     ((List) hourSets.get(1)).add(weekRow);
163                     row = (DataPoint) it.next();
164                 }
165                 row = goBackTo(it, row, oneMonthAgo);
166                 while (it.hasNext() && row.getLogTime() >= oneMonthAndHourAgo) {
167                     DataPoint monthRow = ReportUtil.shiftTime(row, ITimeConstants.MONTH);
168                     ((List) hourSets.get(2)).add(monthRow);
169                     row = (DataPoint) it.next();
170                 }
171             }
172     
173             buildCharts(chartBase, "hour", hourSets, ChartUtil.HOURLEGEND, report.getIncludeInSummary());
174         }
175     }
176     
177     /***
178      * Builds the day chart.
179      * 
180      * @param report the report we are building for
181      * @param chartBase the base destination filename for saved files
182      * @param data the list of row data to chart
183      */
184     private void buildDayChart(IDashboardReport report, String chartBase, DataPoint[] data) {
185         if (includeDayChart(report)) { // do the day chart.
186             List daySets = CollectionUtil.listOfLists(5); // current day, one day ago, one week ago, one month ago, one year ago
187             Iterator it = new ArrayList(Arrays.asList(data)).iterator();
188             if (it.hasNext()) {
189                 DataPoint row = (DataPoint) it.next();
190                 
191                 while (it.hasNext() && row.getLogTime() >= oneDayAgo) {
192                     ((List) daySets.get(0)).add(row);
193                     row = (DataPoint) it.next();
194                 }
195                 while (it.hasNext() && row.getLogTime() >= twoDaysAgo) {
196                     ((List) daySets.get(1)).add(ReportUtil.shiftTime(row, ITimeConstants.DAY));
197                     row = (DataPoint) it.next();
198                 }
199                 row = goBackTo(it, row, oneWeekAgo);
200                 while (it.hasNext() && row.getLogTime() >= eightDaysAgo) {
201                     ((List) daySets.get(2)).add(ReportUtil.shiftTime(row, ITimeConstants.WEEK));
202                     row = (DataPoint) it.next();
203                 }
204                 row = goBackTo(it, row, oneMonthAgo);
205                 while (it.hasNext() && row.getLogTime() >= oneMonthAndDayAgo) {
206                     DataPoint monthRow = ReportUtil.shiftTime(row, ITimeConstants.MONTH);
207                     ((List) daySets.get(3)).add(monthRow);
208                     row = (DataPoint) it.next();
209                 }
210                 row = goBackTo(it, row, fiftyTwoWksAgo);
211                 while (it.hasNext() && row.getLogTime() >= fiftyTwoWksAndDayAgo) {
212                     ((List) daySets.get(4)).add(ReportUtil.shiftTime(row, ITimeConstants.FIFTYTWOWEEKS));
213                     row = (DataPoint) it.next();
214                 }
215             }
216             buildCharts(chartBase, "day", daySets, ChartUtil.DAYLEGEND, report.getIncludeInSummary());
217         }
218     }
219     
220     /***
221      * Builds the week chart.
222      * 
223      * @param report the report we are building for
224      * @param chartBase the base destination filename for saved files
225      * @param data the list of row data to chart
226      */
227     private void buildWeekChart(IDashboardReport report, String chartBase, DataPoint[] data) {
228         if (includeWeekChart(report)) {
229             List weekSets = CollectionUtil.listOfLists(4); // current week, one week ago, one month ago, one year ago
230             Iterator it = new ArrayList(Arrays.asList(data)).iterator();
231             if (it.hasNext()) {
232                 DataPoint row = (DataPoint) it.next();
233                 
234                 while (it.hasNext() && row.getLogTime() >= oneWeekAgo) {
235                     ((List) weekSets.get(0)).add(row);
236                     row = (DataPoint) it.next();
237                 }
238                 while (it.hasNext() && row.getLogTime() >= twoWeeksAgo) {
239                     DataPoint weekRow = ReportUtil.shiftTime(row, ITimeConstants.WEEK);
240                     ((List) weekSets.get(1)).add(weekRow);
241                     row = (DataPoint) it.next();
242                 }
243                 row = goBackTo(it, row, oneMonthAgo);
244                 while (it.hasNext() && row.getLogTime() >= oneMonthAndWeekAgo) {
245                     ((List) weekSets.get(2)).add(ReportUtil.shiftTime(row, ITimeConstants.MONTH));
246                     row = (DataPoint) it.next();
247                 }
248                 row = goBackTo(it, row, fiftyTwoWksAgo);
249                 while (it.hasNext() && row.getLogTime() >= fiftyThreeWksAgo) {
250                     DataPoint yearRow = ReportUtil.shiftTime(row, ITimeConstants.FIFTYTWOWEEKS);
251                     ((List) weekSets.get(3)).add(yearRow);
252                     row = (DataPoint) it.next();
253                 }
254             }
255             buildCharts(chartBase, "week", weekSets, ChartUtil.WEEKLEGEND, report.getIncludeInSummary());
256         }       
257     }
258     /***
259      * Builds the month chart.
260      * 
261      * @param report the report we are building for
262      * @param chartBase the base destination file name for saved files
263      * @param data the list of row data to chart
264      */
265     private void buildMonthChart(IDashboardReport report, String chartBase, DataPoint[] data) {
266         if (includeMonthChart(report)) {
267             List monthSets = CollectionUtil.listOfLists(3); // current month, one month ago, one year ago
268             Iterator it = new ArrayList(Arrays.asList(data)).iterator();
269             if (it.hasNext()) {
270                 DataPoint row = (DataPoint) it.next();
271                 
272                 while (it.hasNext() && row.getLogTime() >= oneMonthAgo) {
273                     ((List) monthSets.get(0)).add(row);
274                     row = (DataPoint) it.next();
275                 }
276                 while (it.hasNext() && row.getLogTime() >= twoMonthsAgo) {
277                     ((List) monthSets.get(1)).add(ReportUtil.shiftTime(row, ITimeConstants.MONTH));
278                     row = (DataPoint) it.next();
279                 }
280                 row = goBackTo(it, row, fiftyTwoWksAgo);
281                 while (it.hasNext() && row.getLogTime() >= fiftySixWksAgo) {
282                     ((List) monthSets.get(2)).add(ReportUtil.shiftTime(row, ITimeConstants.FIFTYTWOWEEKS));
283                     row = (DataPoint) it.next();
284                 }
285             }
286             buildCharts(chartBase, "month", monthSets, ChartUtil.MONTHLEGEND, report.getIncludeInSummary());
287         }       
288     }
289 
290     /***
291      * Builds the year chart.
292      * 
293      * @param report the report we are building for
294      * @param chartBase the base destination filename for saved files
295      * @param data the list of row data to chart
296      */
297     private void buildYearChart(IDashboardReport report, String chartBase, DataPoint[] data) {
298         if (includeYearChart(report)) {
299 
300             List yearSets = CollectionUtil.listOfLists(2); // current year, one year ago
301             Iterator it = new ArrayList(Arrays.asList(data)).iterator();
302             if (it.hasNext()) {
303                 DataPoint row = (DataPoint) it.next();
304                 while (it.hasNext() && row.getLogTime() >= fiftyTwoWksAgo) {
305                     ((List) yearSets.get(0)).add(row);
306                     row = (DataPoint) it.next();
307                 }
308                 while (it.hasNext() && row.getLogTime() >= hundredFourWeeksAgo) {
309                     ((List) yearSets.get(1)).add(ReportUtil.shiftTime(row, ITimeConstants.FIFTYTWOWEEKS));
310                     row = (DataPoint) it.next();
311                 }
312             }
313             buildCharts(chartBase, "year", yearSets, ChartUtil.YEARLEGEND, report.getIncludeInSummary());
314         }
315     }
316     
317     /***
318      * Returns the week/month/year deltas for the given data set.
319      * 
320      * @param data the list of row data to chart
321      * @return a float array with percentage deltas for each timeframe (week/month/year)
322      */
323     private float[] getDeltas(DataPoint[] data) {
324         Iterator it = new ArrayList(Arrays.asList(data)).iterator();
325         float[] reportChanges = new float[3];
326         if (it.hasNext()) {
327             DataPoint row = (DataPoint) it.next();
328             
329             float curValue = 0;
330             float thisValue = 0;
331             boolean foundVal = false;
332             
333             while (it.hasNext() && row.getLogTime() >= oneWeekAgo) {
334                 thisValue = row.getData();
335                 if (!foundVal/* && (row.getData() != null)*/) {
336                     curValue = thisValue;
337                     foundVal = true;
338                 }
339                 if (thisValue != 0.0f) {
340                     reportChanges[0] = (curValue / thisValue) - 1;
341                 }
342                 row = (DataPoint) it.next();
343             }
344             while (it.hasNext() && row.getLogTime() >= oneMonthAgo) {
345                 // now one month ago.
346                 thisValue = row.getData();
347                 if (thisValue != 0.0f) {
348                     reportChanges[1] = (curValue / thisValue) - 1;
349                 }
350                 row = (DataPoint) it.next();
351             }
352             while (it.hasNext() && row.getLogTime() >= fiftyTwoWksAgo) {
353                 // now one year ago
354                 thisValue = row.getData();
355                 if (thisValue != 0.0f) {
356                     reportChanges[2] = (curValue / thisValue) - 1;
357                 }
358                 row = (DataPoint) it.next();
359             }
360         }
361 
362         return reportChanges;
363     }
364 
365     /***
366      * Creates a main and mini chart for a given data set, and saves them.
367      * 
368      * @param filebase the base file name for the charts, to which "-" + <code>name</code> will be appended.
369      * @param name the name of the chart for saving purposes, like "month" or "year"
370      * @param data the list of row data to chart
371      * @param legend the legend for the chart (a list of strings)
372      */
373     private void buildCharts(String filebase, String name, List data, List legend, boolean createMini) {
374         JFreeChart chart = createChart(getYAxisLabel(), data, legend, true);
375         ChartUtil.saveChart(chart, filebase + "-" + name + ".jpg", 800, 200);
376         if (createMini) {
377             chart = createChart(getYAxisLabel(), data, legend, false);
378             ChartUtil.saveChart(chart, filebase + "-" + name + "-mini.jpg", 200, 150);
379         }       
380     }
381     
382     /***
383      * Returns whether this report builds an hourly report.
384      * 
385      * @return true to display a last-hour report
386      */
387     protected boolean includeHourChart(IDashboardReport rept) {
388         return rept.getFrequencyInMinutes() <= 30;
389     }
390     
391     /***
392      * Returns whether this report builds a daily report.
393      * 
394      * @return true to display a last-day report
395      */
396     protected boolean includeDayChart(IDashboardReport rept) {
397         return rept.getFrequencyInMinutes() <= 60;
398     }
399     
400     /***
401      * Returns whether this report builds a weekly report.
402      * 
403      * @return true to display a last-week report
404      */
405     protected boolean includeWeekChart(IDashboardReport rept) {
406         return rept.getFrequencyInMinutes() <= 1440;
407     }
408     
409     /***
410      * Returns whether this report builds a monthly report.
411      * 
412      * @return true to display a last-month report
413      */
414     protected boolean includeMonthChart(IDashboardReport rept) {
415         return rept.getFrequencyInMinutes() <= 10080;
416     }
417     
418     /***
419      * Returns whether this report builds a yearly report.
420      * 
421      * @return true to display a last-year report
422      */
423     protected boolean includeYearChart(IDashboardReport rept) {
424         return rept.getFrequencyInMinutes() <= 40320;
425     }
426         
427     /***
428      * Basic chart creation method. Sets up an empty time-series chart, colors, etc.
429      * 
430      * @param yLabel the label for the Y-axis
431      * @param sets a list of lists -- each one a list of <code>Map</code> objects, mapping LOGTIME and DATA to millisecond 
432      * time and data columns, respectively. Each list becomes a series in the graph.
433      * @param legend an array of labels for the data series (a list of strings)
434      * @param showDetail if true, shows both the legend and the Y-axis label, and multi series; otherwise just shows no labels and first series.
435      * @return a chart
436      */
437     public static JFreeChart createChart(String yLabel, List sets, List legend, boolean showDetail) {
438         if (sets.size() != legend.size()) {
439             throw new IllegalArgumentException("A list of " + sets.size() + " datasets but this does not match the size of the legend set (" + legend.size() + ")");
440         }
441         TimeSeriesCollection dataset = new TimeSeriesCollection();
442         int errors = 0;
443         Iterator legendIt = legend.iterator();
444         for (Iterator setI = sets.iterator(); setI.hasNext(); ) {
445             TimeSeries series = new TimeSeries((String) legendIt.next(), Millisecond.class);
446             List rows = (List) setI.next();
447             for (Iterator i = rows.iterator(); i.hasNext(); ) {
448                 DataPoint row = (DataPoint) i.next();
449                 long date = row.getLogTime();
450                 float data = row.getData();
451                 try { // this catches 
452                     series.add(new Millisecond(new Date(date)), data);
453                 } catch (Exception ex) {
454                     //LOGGER.warn("error while adding data to time series [logtime: " + date + "; data: " + data + "]: " + ex.getMessage(), ex);
455                     errors++;
456                 }
457             }
458             dataset.addSeries(series);
459             if (!showDetail) {
460                 break;
461             }
462         }
463         if (errors > 0) {
464             LOGGER.warn(errors + " duplicate data points found in graph for " + yLabel);
465         }
466         JFreeChart chart = ChartFactory.createTimeSeriesChart(
467             null,
468             null, 
469             showDetail ? yLabel : null,
470             dataset,
471             showDetail,
472             false,
473             false
474         );
475         chart.setBackgroundPaint(Color.WHITE);
476         return chart;
477     }
478 
479     public void reportRan(IDashboardAgent agent, IDashboardReport report) {
480     	LOGGER.info("generating images for report: " + report.getID() + " [" + report.getTitle() + "]");
481         setTimes();
482         String dest = agent.getDestinationPath(); 
483         DataPoint[] data = agent.getDefaultDataDAO().getData(report);
484         
485         new File(dest).mkdirs();
486         String relativePathBaseWithoutExtension = "report" + report.getID();
487 		String chartBase = dest + relativePathBaseWithoutExtension;
488 
489         buildHourChart(report, chartBase, data);
490         buildDayChart(report, chartBase, data);
491         buildWeekChart(report, chartBase, data);
492         buildMonthChart(report, chartBase, data);
493         buildYearChart(report, chartBase, data);
494         
495         float[] reportChanges = getDeltas(data);
496         doAfterImagesCreated(reportChanges, report, agent);
497     }
498 
499     protected void doAfterImagesCreated(float[] reportChanges, IDashboardReport report, IDashboardAgent agent) {
500         // extension point for subclasses
501     }
502 }
503