1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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) {
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)) {
149 List hourSets = CollectionUtil.listOfLists(3);
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)) {
186 List daySets = CollectionUtil.listOfLists(5);
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);
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);
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);
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
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
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
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 {
452 series.add(new Millisecond(new Date(date)), data);
453 } catch (Exception ex) {
454
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
501 }
502 }
503