/**
* Copyright 2011 Intuit Inc. All Rights Reserved
*/
package com.intuit.tank.service.impl.v1.report;
/*
* #%L
* Reporting Rest Service Implementation
* %%
* Copyright (C) 2011 - 2015 Intuit Inc.
* %%
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
* #L%
*/
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.enterprise.event.Event;
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import com.intuit.tank.api.service.v1.report.ReportService;
import com.intuit.tank.dao.JobInstanceDao;
import com.intuit.tank.dao.PeriodicDataDao;
import com.intuit.tank.dao.SummaryDataDao;
import com.intuit.tank.persistence.databases.BucketDataItem;
import com.intuit.tank.persistence.databases.MetricsCalculator;
import com.intuit.tank.project.JobInstance;
import com.intuit.tank.project.PeriodicData;
import com.intuit.tank.project.PeriodicDataBuilder;
import com.intuit.tank.project.SummaryData;
import com.intuit.tank.project.SummaryDataBuilder;
import com.intuit.tank.reporting.api.ResultsReader;
import com.intuit.tank.reporting.factory.ReportingFactory;
import com.intuit.tank.vm.api.enumerated.JobLifecycleEvent;
import com.intuit.tank.vm.common.util.MethodTimer;
import com.intuit.tank.vm.event.JobEvent;
/**
* NotificationRunner
*
* @author dangleton
*
*/
public class SummaryReportRunner implements Runnable {
private static final Logger LOG = LogManager.getLogger(SummaryReportRunner.class);
private static final char NEWLINE = '\n';
private JobEvent jobEvent;
private Event<JobEvent> jobEventProducer;
private String rootUrl;
/**
*
* @param jobEventProducer
* @param jobEvent
*/
public SummaryReportRunner(String rootUrl,
Event<JobEvent> jobEventProducer, JobEvent jobEvent) {
this.jobEvent = jobEvent;
this.jobEventProducer = jobEventProducer;
this.rootUrl = rootUrl;
}
/**
* @{inheritDoc
*/
@Override
public void run() {
String jobId = jobEvent.getJobId();
generateSummary(jobEvent.getJobId());
jobEventProducer.fire(new JobEvent(jobId, getSummaryEventMessage(),
JobLifecycleEvent.SUMMARY_REPORT_FINISHED));
}
/**
* @return
*/
private String getSummaryEventMessage() {
StringBuilder sb = new StringBuilder();
sb.append(
"Summary Timing Data Report for job " + jobEvent.getJobId()
+ " is ready for download.").append(NEWLINE)
.append(NEWLINE);
sb.append("The report can be downloaded at ").append(rootUrl)
.append("/rest").append(ReportService.SERVICE_RELATIVE_PATH)
.append(ReportService.METHOD_TIMING_SUMMARY_CSV).append('/')
.append(jobEvent.getJobId());
return sb.toString();
}
/**
* @param db
* @param tableName
*/
public static void generateSummary(String jobIdString) {
synchronized (jobIdString) {
LOG.info("generateSummary: job " + jobIdString);
ResultsReader resultsReader = ReportingFactory.getResultsReader();
if (resultsReader.hasTimingData(jobIdString)) {
LOG.info("Generating Summary Report for job " + jobIdString + "...");
SummaryDataDao dao = new SummaryDataDao();
int jobId = Integer.parseInt(jobIdString);
if (dao.findByJobId(jobId).size() == 0) {
MethodTimer mt = new MethodTimer(LOG,
SummaryReportRunner.class, "generateSummary");
MetricsCalculator metricsCalculator = new MetricsCalculator();
JobInstance jobInstance = new JobInstanceDao()
.findById(Integer.valueOf(jobIdString));
metricsCalculator.retrieveAndCalculateTimingData(jobIdString,
calculateSteadyStateStart(jobInstance),
calculateSteadyStateEnd(jobInstance));
mt.markAndLog("calculated timing data");
Map<String, DescriptiveStatistics> timingData = metricsCalculator
.getSummaryResults();
if (!timingData.isEmpty()) {
try {
List<SummaryData> toStore = new ArrayList<SummaryData>();
for (Entry<String, DescriptiveStatistics> entry : timingData
.entrySet()) {
SummaryData data = getSummaryData(jobId,
entry.getKey(), entry.getValue());
toStore.add(data);
}
dao.persistCollection(toStore);
LOG.info("Finished Summary Report for job " + jobId);
} catch (Exception t) {
LOG.error("Error adding Summary Items: " + t, t);
}
}
mt.markAndLog("stored " + timingData.size()
+ " summary data items");
// now store buckets
Map<String, Map<Date, BucketDataItem>> bucketItems = metricsCalculator
.getBucketItems();
int countItems = 0;
if (!bucketItems.isEmpty()) {
PeriodicDataDao periodicDataDao = new PeriodicDataDao();
try {
List<PeriodicData> toStore = new ArrayList<PeriodicData>();
for (Entry<String, Map<Date, BucketDataItem>> entry : bucketItems
.entrySet()) {
for (BucketDataItem bucketItem : entry
.getValue().values()) {
PeriodicData data = getBucketData(jobId,
entry.getKey(), bucketItem);
toStore.add(data);
countItems++;
}
}
periodicDataDao.persistCollection(toStore);
LOG.info("Finished Summary Report for job " + jobId);
} catch (Exception t) {
LOG.error("Error adding Summary Items: " + t, t);
}
}
mt.markAndLog("stored " + countItems
+ " periodic data items");
mt.endAndLog();
}
} else {
LOG.info("generateSummary: job " + jobIdString
+ " has no data.");
}
// now delete timing data
resultsReader.deleteTimingForJob(jobIdString, true);
}
}
private static Date calculateSteadyStateEnd(JobInstance jobInstance) {
if (jobInstance.getStartTime() != null
&& jobInstance.getSimulationTime() > 0) {
return new Date(jobInstance.getStartTime().getTime()
+ jobInstance.getSimulationTime());
}
return null;
}
private static Date calculateSteadyStateStart(JobInstance jobInstance) {
if (jobInstance.getStartTime() != null) {
return new Date(jobInstance.getStartTime().getTime()
+ jobInstance.getRampTime());
}
return null;
}
/**
* @param jobId
* @param key
* @param stats
* @return
*/
private static PeriodicData getBucketData(int jobId, String key,
BucketDataItem bucketItem) {
DescriptiveStatistics stats = bucketItem.getStats();
PeriodicData ret = PeriodicDataBuilder.periodicData().withJobId(jobId)
.withMax(stats.getMax()).withMean(stats.getMean())
.withMin(stats.getMin()).withPageId(key)
.withSampleSize((int) stats.getN())
.withPeriod(bucketItem.getPeriod())
.withTimestamp(bucketItem.getStartTime()).build();
return ret;
}
/**
* @param key
* @param value
* @return
*/
private static SummaryData getSummaryData(int jobId, String key,
DescriptiveStatistics stats) {
SummaryData ret = SummaryDataBuilder
.summaryData()
.withJobId(jobId)
.withKurtosis(
!Double.isNaN(stats.getKurtosis()) ? stats
.getKurtosis() : 0)
.withMax(stats.getMax())
.withMean(stats.getMean())
.withMin(stats.getMin())
.withPageId(key)
.withPercentile10(stats.getPercentile(10))
.withPercentile20(stats.getPercentile(20))
.withPercentile30(stats.getPercentile(30))
.withPercentile40(stats.getPercentile(40))
.withPercentile50(stats.getPercentile(50))
.withPercentile60(stats.getPercentile(60))
.withPercentile70(stats.getPercentile(70))
.withPercentile80(stats.getPercentile(80))
.withPercentile90(stats.getPercentile(90))
.withPercentile95(stats.getPercentile(95))
.withPercentile99(stats.getPercentile(99))
.withSampleSize((int) stats.getN())
.withSkewness(
!Double.isNaN(stats.getSkewness()) ? stats
.getSkewness() : 0)
.withSttDev(
!Double.isNaN(stats.getStandardDeviation()) ? stats
.getStandardDeviation() : 0)
.withVarience(
!Double.isNaN(stats.getVariance()) ? stats
.getVariance() : 0).build();
return ret;
}
}