/*
* Sun Public License
*
* The contents of this file are subject to the Sun Public License Version
* 1.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is available at http://www.sun.com/
*
* The Original Code is the SLAMD Distributed Load Generation Engine.
* The Initial Developer of the Original Code is Neil A. Wilson.
* Portions created by Neil A. Wilson are Copyright (C) 2004-2010.
* Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc.
* All Rights Reserved.
*
* Contributor(s): Neil A. Wilson
*/
package com.slamd.report;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.slamd.admin.RequestInfo;
import com.slamd.common.Constants;
import com.slamd.job.Job;
import com.slamd.job.JobItem;
import com.slamd.job.OptimizationAlgorithm;
import com.slamd.job.OptimizingJob;
import com.slamd.parameter.BooleanParameter;
import com.slamd.parameter.Parameter;
import com.slamd.parameter.ParameterList;
import com.slamd.stat.StatTracker;
/**
* This class provides an implementation of a SLAMD report generator that will
* write the report information to a plain text file.
*
*
* @author Neil A. Wilson
*/
public class TextReportGenerator
implements ReportGenerator
{
/**
* The end-of-line string that will be used.
*/
public static final String EOL = Constants.EOL;
/**
* The name of the configuration parameter that indicates whether to include
* detailed statistical information in the report.
*/
public static final String PARAM_INCLUDE_DETAILED_STATISTICS =
"include_detailed_stats";
/**
* The name of the configuration parameter that indicates whether to include
* resource monitor statistics in the report.
*/
public static final String PARAM_INCLUDE_MONITOR_STATS =
"include_monitor_config";
/**
* The name of the configuration parameter that indicates whether to include
* the job-specific configuration in the report.
*/
public static final String PARAM_INCLUDE_JOB_CONFIG = "include_job_config";
/**
* The name of the configuration parameter that indicates whether to include
* the schedule configuration in the report.
*/
public static final String PARAM_INCLUDE_SCHEDULE_CONFIG =
"include_schedule_config";
/**
* The name of the configuration parameter that indicates whether to include
* job statistics in the report.
*/
public static final String PARAM_INCLUDE_STATS = "include_stats";
/**
* The name of the configuration parameter that indicates whether to only
* include jobs that contain statistics in the generated report.
*/
public static final String PARAM_REQUIRE_STATS = "require_stats";
/**
* The name of the configuration parameter that indicates whether to
* summarize the individual iterations of an optimizing job.
*/
public static final String PARAM_SUMMARIZE_OPTIMIZING_ITERATIONS =
"summarize_optimizing_iterations";
// The list used to hold the jobs and optimizing jobs that will be included
// in the generated report.
private ArrayList<JobItem> jobList;
// Indicates whether to include detailed statistics for the jobs included in
// the report.
private boolean includeDetailedStats;
// Indicates whether to include the job-specific configuration in the
// generated report.
private boolean includeJobConfig;
// Indicates whether to include resource monitor statistics in the generated
// report.
private boolean includeMonitorStats;
// Indicates whether to include the schedule configuration in the generated
// report.
private boolean includeScheduleConfig;
// Indicates whether to include job statistics in the generated report.
private boolean includeStats;
// Indicates whether to only include jobs that have statistics.
private boolean requireStats;
// Indicates whether to only provide summary information for individual
// iterations of an optimizing job.
private boolean summarizeOptimizingIterations;
// The decimal format that will be used to format floating-point values.
private DecimalFormat decimalFormat;
// The date formatter that will be used when writing out dates.
private SimpleDateFormat dateFormat;
/**
* Creates a new text report generator.
*/
public TextReportGenerator()
{
jobList = new ArrayList<JobItem>();
dateFormat = new SimpleDateFormat(Constants.DISPLAY_DATE_FORMAT);
decimalFormat = new DecimalFormat("0.000");
includeScheduleConfig = true;
includeJobConfig = true;
includeStats = true;
includeMonitorStats = true;
includeDetailedStats = false;
requireStats = true;
summarizeOptimizingIterations = true;
}
/**
* Retrieves a user-friendly name that can be used to indicate the type of
* report that will be generated.
*
* @return The user-friendly name that can be used to indicate the type of
* report that will be generated.
*/
public String getReportGeneratorName()
{
return "Plain Text Report Generator";
}
/**
* Retrieves a new instance of this report generator initialized with the
* default configuration.
*
* @return A new instance of this report generator initialized with the
* default configuration.
*/
public ReportGenerator newInstance()
{
return new TextReportGenerator();
}
/**
* Retrieves a set of parameters that can be used to allow the user to
* configure the way that the report is generated.
*
* @return A set of parameters that can be used to allow the user to
* configure the way that the report is generated.
*/
public ParameterList getReportParameterStubs()
{
BooleanParameter includeScheduleConfigParameter =
new BooleanParameter(PARAM_INCLUDE_SCHEDULE_CONFIG,
"Include Schedule Configuration",
"Indicates whether the schedule configuration " +
"information should be included in the report.",
includeScheduleConfig);
BooleanParameter includeJobConfigParameter =
new BooleanParameter(PARAM_INCLUDE_JOB_CONFIG,
"Include Job-Specific Configuration",
"Indicates whether the job-specific " +
"configuration information should be included " +
"in the report.", includeJobConfig);
BooleanParameter includeStatsParameter =
new BooleanParameter(PARAM_INCLUDE_STATS, "Include Job Statistics",
"Indicates whether the statistics collected " +
"from job execution should be included in the " +
"report.", includeStats);
BooleanParameter includeMonitorParameter =
new BooleanParameter(PARAM_INCLUDE_MONITOR_STATS,
"Include Resource Monitor Statistics",
"Indicates whether the statistics collected " +
"from job execution should be included in the " +
"report.", includeMonitorStats);
BooleanParameter includeDetailParameter =
new BooleanParameter(PARAM_INCLUDE_DETAILED_STATISTICS,
"Include Detailed Statistical Information",
"Indicates whether to include a detailed " +
"report of the statistics collected",
includeDetailedStats);
BooleanParameter requireStatsParameter =
new BooleanParameter(PARAM_REQUIRE_STATS,
"Only Include Jobs with Statistics",
"Indicates whether to only include jobs that " +
"have statistics available.", requireStats);
BooleanParameter summarizeOptimizingParameter =
new BooleanParameter(PARAM_SUMMARIZE_OPTIMIZING_ITERATIONS,
"Summarize Optimizing Job Iterations",
"Indicates whether to only provide summary " +
"data for optimizing job iterations.",
summarizeOptimizingIterations);
Parameter[] parameters = new Parameter[]
{
includeScheduleConfigParameter,
includeJobConfigParameter,
includeStatsParameter,
includeMonitorParameter,
includeDetailParameter,
requireStatsParameter,
summarizeOptimizingParameter,
};
return new ParameterList(parameters);
}
/**
* Initializes this reporter based on the parameters customized by the end
* user.
*
* @param reportParameters The set of parameters provided by the end user
* that should be used to customize the report.
*/
public void initializeReporter(ParameterList reportParameters)
{
BooleanParameter bp =
reportParameters.getBooleanParameter(PARAM_INCLUDE_SCHEDULE_CONFIG);
if (bp != null)
{
includeScheduleConfig = bp.getBooleanValue();
}
bp = reportParameters.getBooleanParameter(PARAM_INCLUDE_JOB_CONFIG);
if (bp != null)
{
includeJobConfig = bp.getBooleanValue();
}
bp = reportParameters.getBooleanParameter(PARAM_INCLUDE_STATS);
if (bp != null)
{
includeStats = bp.getBooleanValue();
}
bp = reportParameters.getBooleanParameter(PARAM_INCLUDE_MONITOR_STATS);
if (bp != null)
{
includeMonitorStats = bp.getBooleanValue();
}
bp = reportParameters.getBooleanParameter(
PARAM_INCLUDE_DETAILED_STATISTICS);
if (bp != null)
{
includeDetailedStats = bp.getBooleanValue();
}
bp = reportParameters.getBooleanParameter(PARAM_REQUIRE_STATS);
if (bp != null)
{
requireStats = bp.getBooleanValue();
}
bp = reportParameters.getBooleanParameter(
PARAM_SUMMARIZE_OPTIMIZING_ITERATIONS);
if (bp != null)
{
summarizeOptimizingIterations = bp.getBooleanValue();
}
}
/**
* Indicates that information about the provided job should be included in the
* report.
*
* @param job The job about which to include information in the report.
*/
public void addJobReport(Job job)
{
if (requireStats && (! job.hasStats()))
{
return;
}
jobList.add(job);
}
/**
* Indicates that information about the provided optimizing job should be
* included in the report.
*
* @param optimizingJob The optimizing job about which to include
* information in the report.
*/
public void addOptimizingJobReport(OptimizingJob optimizingJob)
{
if (requireStats && (! optimizingJob.hasStats()))
{
return;
}
jobList.add(optimizingJob);
}
/**
* Generates the report and sends it to the user over the provided servlet
* response.
*
* @param requestInfo State information about the request being processed.
*/
public void generateReport(RequestInfo requestInfo)
{
StringBuilder buffer = new StringBuilder();
addHeader(requestInfo, buffer);
String separator = "----------------------------------------------------" +
"-----------------------" + EOL;
boolean addSeparator = true;
for (int i=0; i < jobList.size(); i++)
{
if (addSeparator)
{
buffer.append(EOL);
buffer.append(separator);
buffer.append(EOL);
}
Object element = jobList.get(i);
if (element instanceof Job)
{
Job job = (Job) element;
addSeparator = addJob(job, buffer);
}
else if (element instanceof OptimizingJob)
{
OptimizingJob optimizingJob = (OptimizingJob) element;
addSeparator = addOptimizingJob(optimizingJob, buffer);
}
}
HttpServletResponse response = requestInfo.getResponse();
response.setContentType("text/plain");
response.addHeader("Content-Disposition",
"filename=\"slamd_data_report.txt\"");
try
{
PrintWriter writer = response.getWriter();
writer.println(buffer.toString());
}
catch (IOException ioe)
{
// Not much we can do about this.
System.err.println("Unable to generate report: " + ioe);
}
}
/**
* Adds a header to the report.
*
* @param requestInfo State information for the request being processed.
* @param buffer The string buffer to which the report will be added.
*/
private void addHeader(RequestInfo requestInfo, StringBuilder buffer)
{
String title = "SLAMD Generated Report";
if (jobList.size() == 1)
{
Object element = jobList.get(0);
if (element instanceof Job)
{
Job job = (Job) element;
title += " for Job " + job.getJobID();
}
else if (element instanceof OptimizingJob)
{
OptimizingJob optimizingJob = (OptimizingJob) element;
title += " for Optimizing Job " + optimizingJob.getOptimizingJobID();
}
}
buffer.append(title + EOL);
buffer.append("Generated " + dateFormat.format(new Date()) + EOL);
HttpServletRequest request = requestInfo.getRequest();
String serverURL = request.getScheme() + "://" + request.getServerName() +
':' + request.getServerPort() +
requestInfo.getServletBaseURI();
buffer.append("Generated from data at " + serverURL + EOL);
}
/**
* Adds information about the provided job to the report, if appropriate.
*
* @param job The job to include in the report.
* @param buffer The buffer to which the job information should be written.
*
* @return <CODE>true</CODE> if information about the job was actually
* included in the report, or <CODE>false</CODE> if not.
*/
private boolean addJob(Job job, StringBuilder buffer)
{
// Check to see if this is an optimizing job iteration that we should skip.
String optimizingJobID = job.getOptimizingJobID();
if (summarizeOptimizingIterations && (optimizingJobID != null) &&
(optimizingJobID.length() > 0))
{
for (int i=0; i < jobList.size(); i++)
{
Object element = jobList.get(i);
if (element instanceof OptimizingJob)
{
OptimizingJob optimizingJob = (OptimizingJob) element;
if (optimizingJob.getOptimizingJobID().equalsIgnoreCase(
optimizingJobID))
{
return false;
}
}
}
}
// Write information to the buffer that will always be included.
buffer.append(job.getJobClass().getJobName() + " Job " + job.getJobID() +
EOL);
buffer.append("Job ID: " + job.getJobID() + EOL);
String description = job.getJobDescription();
if ((description != null) && (description.length() > 0))
{
buffer.append("Job Description: " + description + EOL);
}
if ((optimizingJobID != null) && (optimizingJobID.length() > 0))
{
buffer.append("Optimizing Job ID: " + optimizingJobID + EOL);
}
buffer.append("Job Type: " + job.getJobClass().getJobName() + EOL);
buffer.append("Job Class: " + job.getJobClassName() + EOL);
buffer.append("Job State: " +
Constants.jobStateToString(job.getJobState()) + EOL);
// Write information to the buffer about the schedule configuration.
if (includeScheduleConfig)
{
buffer.append("----- Job Schedule Information -----" + EOL);
Date scheduledStartTime = job.getStartTime();
String startTimeStr;
if (scheduledStartTime == null)
{
startTimeStr = "(Not Available)";
}
else
{
startTimeStr = dateFormat.format(scheduledStartTime);
}
buffer.append("Scheduled Start Time: " + startTimeStr + EOL);
Date scheduledStopTime = job.getStopTime();
String stopTimeStr;
if (scheduledStopTime == null)
{
stopTimeStr = "(Not Specified)";
}
else
{
stopTimeStr = dateFormat.format(scheduledStopTime);
}
buffer.append("Scheduled Stop Time: " + stopTimeStr + EOL);
int scheduledDuration = job.getDuration();
String durationStr;
if (scheduledDuration > 0)
{
durationStr = scheduledDuration + " seconds";
}
else
{
durationStr = "(Not Specified)";
}
buffer.append("Scheduled Duration: " + durationStr + EOL);
int numClients = job.getNumberOfClients();
buffer.append("Number of Clients: " + numClients + EOL);
String[] requestedClients = job.getRequestedClients();
if ((requestedClients != null) && (requestedClients.length > 0))
{
buffer.append("Requested Clients: " + requestedClients[0]);
for (int i=1; i < requestedClients.length; i++)
{
buffer.append(',' + EOL);
buffer.append(" " + requestedClients[i] + EOL);
}
buffer.append(EOL);
}
int numThreads = job.getThreadsPerClient();
buffer.append("Number of Threads per Client: " + numThreads + EOL);
int startupDelay = job.getThreadStartupDelay();
buffer.append("Thread Startup Delay: " + startupDelay + EOL);
int collectionInterval = job.getCollectionInterval();
buffer.append("Statistics Collection Interval: " + collectionInterval +
" seconds" + EOL);
}
// Write information to the buffer about the job-specific configuration.
if (includeJobConfig)
{
buffer.append("----- Job-Specific Configuration -----" + EOL);
Parameter[] params = job.getParameterList().getParameters();
for (int i=0; i < params.length; i++)
{
buffer.append(params[i].getDisplayName() + ": " +
params[i].getDisplayValue() + EOL);
}
}
// Write information to the buffer about the job statistics.
if (includeStats && job.hasStats())
{
buffer.append("----- General Statistical Information -----" + EOL);
Date actualStartTime = job.getActualStartTime();
String startTimeStr;
if (actualStartTime == null)
{
startTimeStr = "(Not Available)";
}
else
{
startTimeStr = dateFormat.format(actualStartTime);
}
buffer.append("Actual Start Time: " + startTimeStr + EOL);
Date actualStopTime = job.getActualStopTime();
String stopTimeStr;
if (actualStopTime == null)
{
stopTimeStr = "(Not Available)";
}
else
{
stopTimeStr = dateFormat.format(actualStopTime);
}
buffer.append("Actual Stop Time: " + stopTimeStr + EOL);
int actualDuration = job.getActualDuration();
String durationStr;
if (actualDuration > 0)
{
durationStr = actualDuration + " seconds";
}
else
{
durationStr = "(Not Available)";
}
buffer.append("Actual Duration: " + durationStr + EOL);
String[] clients = job.getStatTrackerClientIDs();
if ((clients != null) && (clients.length > 0))
{
buffer.append("Clients Used: " + clients[0]);
for (int i=1; i < clients.length; i++)
{
buffer.append(',' + EOL);
buffer.append(" " + clients[i]);
}
buffer.append(EOL);
}
String[] statNames = job.getStatTrackerNames();
for (int i=0; i < statNames.length; i++)
{
StatTracker[] trackers = job.getStatTrackers(statNames[i]);
if ((trackers == null) || (trackers.length == 0))
{
continue;
}
StatTracker tracker = trackers[0].newInstance();
tracker.aggregate(trackers);
buffer.append("----- " + statNames[i] + " Statistics -----" + EOL);
if (includeDetailedStats)
{
buffer.append(tracker.getDetailString());
}
else
{
buffer.append(tracker.getSummaryString() + EOL);
}
}
}
// Write information to the buffer about the resource monitor statistics.
if (includeMonitorStats && job.hasResourceStats())
{
buffer.append("----- Resource Monitor Statistics -----" + EOL);
String[] statNames = job.getResourceStatTrackerNames();
for (int i=0; i < statNames.length; i++)
{
StatTracker[] trackers = job.getResourceStatTrackers(statNames[i]);
if ((trackers == null) || (trackers.length == 0))
{
continue;
}
StatTracker tracker = trackers[0].newInstance();
tracker.aggregate(trackers);
buffer.append("----- " + statNames[i] +
" Resource Monitor Statistics -----" + EOL);
if (includeDetailedStats)
{
buffer.append(tracker.getDetailString());
}
else
{
buffer.append(tracker.getSummaryString() + EOL);
}
}
}
return true;
}
/**
* Adds information about the provided optimizing job to the report,
* if appropriate.
*
* @param optimizingJob The optimizing job to include in the report.
* @param buffer The buffer to which the optimizing job information
* should be written.
*
* @return <CODE>true</CODE> if information about the optimizing job was
* actually included in the report, or <CODE>false</CODE> if not.
*/
private boolean addOptimizingJob(OptimizingJob optimizingJob,
StringBuilder buffer)
{
// Get the optimization algorithm and set of parameters.
OptimizationAlgorithm optimizationAlgorithm =
optimizingJob.getOptimizationAlgorithm();
ParameterList paramList =
optimizationAlgorithm.getOptimizationAlgorithmParameters();
Parameter[] optimizationParams = paramList.getParameters();
// Write information to the buffer that will always be included.
buffer.append("Optimizing " + optimizingJob.getJobClass().getJobName() +
" Job " + optimizingJob.getOptimizingJobID() +
EOL);
buffer.append("Optimizing Job ID: " + optimizingJob.getOptimizingJobID() +
EOL);
String description = optimizingJob.getDescription();
if ((description != null) && (description.length() > 0))
{
buffer.append("Job Description: " + description + EOL);
}
buffer.append("Job Type: " + optimizingJob.getJobClass().getJobName() +
EOL);
buffer.append("Job Class: " + optimizingJob.getJobClassName() + EOL);
buffer.append("Job State: " +
Constants.jobStateToString(optimizingJob.getJobState()) +
EOL);
// Write information to the buffer about the schedule configuration.
if (includeScheduleConfig)
{
buffer.append("----- Job Schedule Information -----" + EOL);
Date scheduledStartTime = optimizingJob.getStartTime();
String startTimeStr;
if (scheduledStartTime == null)
{
startTimeStr = "(Not Available)";
}
else
{
startTimeStr = dateFormat.format(scheduledStartTime);
}
buffer.append("Scheduled Start Time: " + startTimeStr + EOL);
int scheduledDuration = optimizingJob.getDuration();
String durationStr;
if (scheduledDuration > 0)
{
durationStr = scheduledDuration + " seconds";
}
else
{
durationStr = "(Not Specified)";
}
buffer.append("Scheduled Duration: " + durationStr + EOL);
int numClients = optimizingJob.getNumClients();
buffer.append("Number of Clients: " + numClients);
String[] requestedClients = optimizingJob.getRequestedClients();
if ((requestedClients != null) && (requestedClients.length > 0))
{
buffer.append("Requested Clients: " + requestedClients[0]);
for (int i=1; i < requestedClients.length; i++)
{
buffer.append(',' + EOL);
buffer.append(" " + requestedClients[i] + EOL);
}
buffer.append(EOL);
}
int minThreads = optimizingJob.getMinThreads();
buffer.append("Minimum Number of Threads: " + minThreads + EOL);
int maxThreads = optimizingJob.getMaxThreads();
String maxStr;
if (maxThreads > 0)
{
maxStr = String.valueOf(maxThreads);
}
else
{
maxStr = "(Not Specified)";
}
buffer.append("Maximum Number of Threads: " + maxStr + EOL);
int threadIncrement = optimizingJob.getThreadIncrement();
buffer.append("Thread Increment Between Iterations: " + threadIncrement +
EOL);
int collectionInterval = optimizingJob.getCollectionInterval();
buffer.append("Statistics Collection Interval: " + collectionInterval +
" seconds" + EOL);
for (int i=0; i < optimizationParams.length; i++)
{
buffer.append(optimizationParams[i].getDisplayName());
buffer.append(": ");
buffer.append(optimizationParams[i].getDisplayValue());
buffer.append(EOL);
}
int maxNonImproving = optimizingJob.getMaxNonImproving();
buffer.append("Maximum Consecutive Non-Improving Iterations: " +
maxNonImproving + EOL);
boolean reRun = optimizingJob.reRunBestIteration();
buffer.append("Re-Run Best Iteration: " + String.valueOf(reRun) + EOL);
int reRunDuration = optimizingJob.getReRunDuration();
if (reRunDuration > 0)
{
durationStr = reRunDuration + " seconds";
}
else
{
durationStr = "(Not Specified)";
}
buffer.append("Re-Run Duration: " + durationStr + EOL);
}
// Write information to the buffer about the job-specific configuration.
if (includeJobConfig)
{
buffer.append("----- Job-Specific Configuration -----" + EOL);
Parameter[] params = optimizingJob.getParameters().getParameters();
for (int i=0; i < params.length; i++)
{
buffer.append(params[i].getDisplayName() + ": " +
params[i].getDisplayValue() + EOL);
}
}
// Write information to the buffer about the job statistics.
if (includeStats && optimizingJob.hasStats())
{
buffer.append("----- General Statistical Information -----" + EOL);
Date actualStartTime = optimizingJob.getActualStartTime();
String startTimeStr;
if (actualStartTime == null)
{
startTimeStr = "(Not Available)";
}
else
{
startTimeStr = dateFormat.format(actualStartTime);
}
buffer.append("Actual Start Time: " + startTimeStr + EOL);
Date actualStopTime = optimizingJob.getActualStopTime();
String stopTimeStr;
if (actualStopTime == null)
{
stopTimeStr = "(Not Available)";
}
else
{
stopTimeStr = dateFormat.format(actualStopTime);
}
buffer.append("Actual Stop Time: " + stopTimeStr + EOL);
Job[] iterations = optimizingJob.getAssociatedJobs();
if ((iterations != null) && (iterations.length > 0))
{
buffer.append("Job Iterations Completed: " + iterations.length + EOL);
int optimalThreads = optimizingJob.getOptimalThreadCount();
buffer.append("Optimal Thread Count: " + optimalThreads + EOL);
double optimalValue = optimizingJob.getOptimalValue();
buffer.append("Optimal Value: " + decimalFormat.format(optimalValue) +
EOL);
Job reRunIteration = optimizingJob.getReRunIteration();
if (reRunIteration != null)
{
String valueStr;
try
{
double value =
optimizationAlgorithm.getIterationOptimizationValue(
reRunIteration);
valueStr = decimalFormat.format(value);
}
catch (Exception e)
{
valueStr = "N/A";
}
buffer.append("Re-Run Value: " + valueStr + EOL);
}
buffer.append("----- Optimizing Job Iterations -----" + EOL);
for (int i=0; i < iterations.length; i++)
{
String valueStr;
try
{
double value =
optimizationAlgorithm.getIterationOptimizationValue(
iterations[i]);
valueStr = decimalFormat.format(value);
}
catch (Exception e)
{
valueStr = "N/A";
}
buffer.append(iterations[i].getJobID() + ": " + valueStr + EOL);
}
}
}
return true;
}
}