NIHVIVO-1129 create a utility that will merge and compare the results of tests on different systems.

This commit is contained in:
jeb228 2011-01-13 19:55:58 +00:00
parent 43135eccfe
commit 493da8b667
9 changed files with 638 additions and 0 deletions

View file

@ -48,6 +48,8 @@ report - Just summarize output from previously run tests.
target: properties
- - - - - - - - - - - - - - - - - -->
<target name="properties">
<property name="source.dir" value="src" />
<property name="build.dir" value=".build" />
<property name="tests.dir" value="tests" />
<property name="results.dir" value=".results" />
@ -96,6 +98,7 @@ report - Just summarize output from previously run tests.
target: prepare
- - - - - - - - - - - - - - - - - -->
<target name="prepare" depends="properties">
<mkdir dir="${build.dir}" />
<mkdir dir="${results.dir}" />
</target>
@ -189,7 +192,32 @@ report - Just summarize output from previously run tests.
target: merge
================================= -->
<target name="merge" depends="prepare" description="--> Merge summaries together">
<javac srcdir="${source.dir}"
destdir="${build.dir}"
debug="true"
deprecation="true"
optimize="true"
source="1.6">
</javac>
<property name="merge.output.dir"
location="/Development/JIRA issues/NIHVIVO-1129_Load_testing/mergerFiles/" />
<property name="merge.input.dir"
location="/Development/JIRA issues/NIHVIVO-1129_Load_testing/mergerFiles/results" />
<property name="merge.input.filenames"
value="ver_release1.1.1/SecondTests-rel-1-1-1.html, ver_trunkRdb/SecondTests-rel-1-2.html, ver_trunkSdb/SecondTests-rel-1-2.html" />
<java classname="edu.cornell.mannlib.vitro.utilities.loadtesting.ReportsMerger"
fork="yes"
failonerror="true">
<classpath location="${build.dir}" />
<arg value="${merge.output.dir}" />
<arg value="${merge.input.dir}" />
<arg value="${merge.input.filenames}" />
</java>
<copy file="mergedResults.css"
todir="${merge.output.dir}" />
</target>
</project>

View file

@ -0,0 +1,40 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
table.testData {
}
table.testData td,th {
border: thin solid black;
font-family: sans-serif;
text-align: center;
}
table.testData td.left {
text-align: left;
}
table.testData td.open {
border-right: none;
}
table.testData td.middle {
border-left: none;
border-right: none;
}
table.testData td.close {
border-left: none;
}
table.oneResult td {
border: none;
}
table.oneResult {
border: thin solid black;
width: 100%;
}
table.oneResult td.minmax {
font-size: smaller;
}

View file

@ -0,0 +1,142 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.loadtesting;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
/**
* Write the merged data to an HTML page.
*/
public class OutputMarshaller {
private final List<TestResultsFileData> reportData;
private final PrintWriter w;
private final List<String> testNames;
public OutputMarshaller(List<TestResultsFileData> reportData, PrintWriter w) {
this.reportData = reportData;
this.w = w;
this.testNames = assembleListOfTestNames();
}
public void marshall() {
writePageHeader();
writeTestDataTable();
writePageFooter();
}
private List<String> assembleListOfTestNames() {
Set<String> names = new TreeSet<String>();
for (TestResultsFileData filedata : reportData) {
names.addAll(filedata.getTestMap().keySet());
}
return new ArrayList<String>(names);
}
private void writePageHeader() {
w.println("<html>");
w.println("<head>");
w.println(" <link REL='STYLESHEET' TYPE='text/css' HREF='./mergedResults.css'>");
w.println("</head>");
w.println("<body>");
}
private void writeTestDataTable() {
w.println("<table class='testData' cellspacing='0'>");
writeTestDataHeader();
for (String testName : testNames) {
writeTestDataRow(testName);
}
w.println("</table>");
}
private void writeTestDataHeader() {
w.println(" <tr>");
w.println(" <th>&nbsp;</th>");
for (TestResultsFileData fileData : reportData) {
w.println(" <th colspan='3'>" + fileData.getVivoVersion()
+ "<br/>" + fileData.getResultsFilename() + "<br/>"
+ formatDate(fileData.getCreated()) + "</th>");
}
w.println(" </tr>");
w.println(" <tr>");
w.println(" <th>Test Name</th>");
for (TestResultsFileData fileData : reportData) {
w.println(" <th>iterations</th>");
w.println(" <th>time (min/max)</th>");
w.println(" <th>ratio</th>");
}
w.println(" </tr>");
}
private void writeTestDataRow(String testName) {
w.println(" <tr>");
w.println(" <td class='left'>" + testName + "</td>");
for (TestResultsFileData fileData : reportData) {
writeTestDataCellForFile(fileData, testName);
}
w.println(" </tr>");
}
private void writeTestDataCellForFile(TestResultsFileData fileData,
String testName) {
TestResultInfo testData = fileData.getTestMap().get(testName);
TestResultInfo baselineTestData = reportData.get(0).getTestMap()
.get(testName);
String count = (testData == null) ? "&nbsp;" : ("" + testData
.getCount());
String averageTime = (testData == null) ? "&nbsp;"
: ("" + formatTime(testData.getAverageTime()));
String minTime = (testData == null) ? "&nbsp;"
: ("" + formatTime(testData.getMinTime()));
String maxTime = (testData == null) ? "&nbsp;"
: ("" + formatTime(testData.getMaxTime()));
String ratioWithBaseline = "&nbsp";
if ((testData != null) && (baselineTestData != null)) {
ratioWithBaseline = percentage(testData.getAverageTime(),
baselineTestData.getAverageTime());
}
w.println(" <td class='open'>" + count + "</td>");
w.println(" <td>");
w.println(" <table class='oneResult middle' cellspacing=0>");
w.println(" <tr>");
w.println(" <td rowspan='2'>" + averageTime + "</td>");
w.println(" <td class='minmax'>" + minTime + "</td>");
w.println(" </tr>");
w.println(" <tr>");
w.println(" <td class='minmax'>" + maxTime + "</td>");
w.println(" </tr>");
w.println(" </table>");
w.println(" <td class='close'>" + ratioWithBaseline + "</td>");
w.println(" </td>");
}
private String percentage(float value, float baseline) {
float ratio = value / baseline;
return String.format("%1$8.2f%%", ratio * 100.0);
}
public String formatTime(float time) {
return String.format("%1$8.3f", time);
}
public String formatDate(long date) {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
.format(new Date(date));
}
private void writePageFooter() {
w.println("</body>");
w.println("</html>");
}
}

View file

@ -0,0 +1,70 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.loadtesting;
import java.io.File;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
/**
* Take two or more reports from JMeter's test results, and merge them into a
* unified HTML report.
*/
public class ReportsMerger {
/**
* Start with list of filenames in command line For each contributing file,
* heading is from parsing the filename. Get the one after LoadTesting, and
* the last one, minus the extension.
*
* For each file, build a structure with header info and a LinkedMap of the
* desired info, testname -> info structure Build a list of these. Build a
* unified list of testnames.
*
* List<TestResultsFile>
*
* TestResultsFile: String version; String filename; Date timestamp;
* LinkedMap<String, TestResultInfo> testMap;
*
* TestResultInfo: boolean success; int count; float averageTime; float
* maxTime; float minTime;
*/
private final ReportsMergerParameters parms;
private List<TestResultsFileData> reportData;
public ReportsMerger(ReportsMergerParameters parms) {
this.parms = parms;
}
private void parseReports() {
List<TestResultsFileData> reportData = new ArrayList<TestResultsFileData>();
for (File reportFile : parms.getReportFiles()) {
TestResultsFileData fileData = new TestResultsParser(reportFile)
.parse();
System.out.println("File data: " + fileData);
reportData.add(fileData);
}
this.reportData = reportData;
}
private void produceOutput() {
PrintWriter writer = parms.getOutputWriter();
new OutputMarshaller(reportData, writer).marshall();
writer.flush();
writer.close();
}
/**
* @param args
*/
public static void main(String[] args) {
ReportsMergerParameters parms = ReportsMergerParameters
.getInstance(args);
ReportsMerger rm = new ReportsMerger(parms);
rm.parseReports();
rm.produceOutput();
}
}

View file

@ -0,0 +1,21 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.loadtesting;
import java.io.File;
import java.io.PrintWriter;
import java.util.List;
/**
* Parse the command-line parameters for the ReportManager
*/
public abstract class ReportsMergerParameters {
public static ReportsMergerParameters getInstance(String[] args) {
return new ReportsMergerParametersImpl(args);
}
public abstract List<File> getReportFiles();
public abstract PrintWriter getOutputWriter();
}

View file

@ -0,0 +1,58 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.loadtesting;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* A base implementation of ReportsMergerParameters
*/
public class ReportsMergerParametersImpl extends ReportsMergerParameters {
private final String outputDirectoryPath;
private final String inputDirectoryPath;
private final List<String> inputFilenames;
/**
* The first arg is the output directory. The second arg is an input
* directory. The third arg is a comma-separated list of input filenames.
*/
public ReportsMergerParametersImpl(String[] args) {
this.outputDirectoryPath = args[0];
this.inputDirectoryPath = args[1];
this.inputFilenames = Arrays.asList(args[2].split("[, ]+"));
}
@Override
public List<File> getReportFiles() {
List<File> files = new ArrayList<File>();
for (String filename : inputFilenames) {
files.add(new File(inputDirectoryPath, filename));
}
// files.add(new File(
// "/Development/JIRA issues/NIHVIVO-1129_Load_testing/mergerFiles/LoadTesting/release1.1.1/SecondTests-rel-1-1-1.html"));
// files.add(new File(
// "/Development/JIRA issues/NIHVIVO-1129_Load_testing/mergerFiles/LoadTesting/trunkNoSdb/SecondTests-rel-1-2.html"));
// files.add(new File(
// "/Development/JIRA issues/NIHVIVO-1129_Load_testing/mergerFiles/LoadTesting/trunkSdb/SecondTests-rel-1-2.html"));
return files;
}
@Override
public PrintWriter getOutputWriter() {
try {
File outputFile = new File(outputDirectoryPath,
"mergedResults.html");
return new PrintWriter(new FileWriter(outputFile));
} catch (IOException e) {
throw new RuntimeException("Can't open the output writer.", e);
}
}
}

View file

@ -0,0 +1,56 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.loadtesting;
/**
* Info about the executions of a single test in a single file.
*/
public class TestResultInfo {
private final String testName;
private final boolean success;
private final int count;
private final float averageTime;
private final float maxTime;
private final float minTime;
public TestResultInfo(String testName, boolean success, int count,
float averageTime, float maxTime, float minTime) {
this.testName = testName;
this.success = success;
this.count = count;
this.averageTime = averageTime;
this.maxTime = maxTime;
this.minTime = minTime;
}
public String getTestName() {
return testName;
}
public boolean isSuccess() {
return success;
}
public int getCount() {
return count;
}
public float getAverageTime() {
return averageTime;
}
public float getMaxTime() {
return maxTime;
}
public float getMinTime() {
return minTime;
}
@Override
public String toString() {
return "TestResultInfo[testName=" + testName + ", success=" + success
+ ", count=" + count + ", averageTime=" + averageTime
+ ", maxTime=" + maxTime + ", minTime=" + minTime + "]";
}
}

View file

@ -0,0 +1,55 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.loadtesting;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Holds the data that was parsed from a single results file.
*/
public class TestResultsFileData {
private final String vivoVersion;
private final String resultsFilename;
private final long created;
private final LinkedHashMap<String, TestResultInfo> testMap;
public TestResultsFileData(String vivoVersion, String resultsFilename,
long created, Map<String, TestResultInfo> testMap) {
this.vivoVersion = vivoVersion;
this.resultsFilename = resultsFilename;
this.created = created;
this.testMap = new LinkedHashMap<String, TestResultInfo>(testMap);
}
public String getTimeStamp() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(
created));
}
public String getVivoVersion() {
return vivoVersion;
}
public String getResultsFilename() {
return resultsFilename;
}
public long getCreated() {
return created;
}
public LinkedHashMap<String, TestResultInfo> getTestMap() {
return testMap;
}
@Override
public String toString() {
return "TestResultsFileData[vivoVersion=" + vivoVersion
+ ", resultsFilename=" + resultsFilename + ", created="
+ getTimeStamp() + ", testMap=" + testMap + "]";
}
}

View file

@ -0,0 +1,168 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.utilities.loadtesting;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Parse a single test results file.
*/
public class TestResultsParser {
/** Find the "Pages" table in the file. */
private static final String PATTERN_PAGES_TABLE = "<h2>Pages</h2><table[^>]*>(.*?)</table>";
/** Find a row in the table. */
private static final String PATTERN_TABLE_ROW = "<tr[^>]*>(.*?)</tr>";
private final File reportFile;
private final String filePath;
public TestResultsParser(File reportFile) {
this.reportFile = reportFile;
this.filePath = reportFile.getAbsolutePath();
}
/**
* Get info from the file path, the create date, and the contents of the
* file.
*/
public TestResultsFileData parse() {
String[] pathInfo = parseInfoFromFilePath();
Map<String, TestResultInfo> testMap = extractTestMap();
return new TestResultsFileData(pathInfo[0], pathInfo[1],
reportFile.lastModified(), testMap);
}
private String[] parseInfoFromFilePath() {
String vivoVersion = "--";
String[] pathParts = filePath.split(Pattern.quote(File.separator));
for (int i = 0; i < pathParts.length; i++) {
if (pathParts[i].startsWith("ver_")) {
vivoVersion = pathParts[i].substring(4);
}
}
String trimmedFilename = reportFile.getName().substring(0,
reportFile.getName().lastIndexOf('.'));
return new String[] { vivoVersion, trimmedFilename };
}
/**
* Scan through the contents of the file for info
*/
private Map<String, TestResultInfo> extractTestMap() {
String contents = readEntireFileWithoutLineFeeds();
String pagesTable = findPagesTableInFile(contents);
// System.out.println("PagesTable: " + pagesTable);
return parsePagesTable(pagesTable);
}
private String readEntireFileWithoutLineFeeds() {
StringBuilder result = new StringBuilder();
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(reportFile));
String line;
while (null != (line = reader.readLine())) {
result.append(line);
}
return result.toString();
} catch (FileNotFoundException e) {
throw new RuntimeException(
"File doesn't exist: '" + filePath + "'", e);
} catch (IOException e) {
throw new RuntimeException("Failed to read the file: '" + filePath
+ "'", e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private String findPagesTableInFile(String contents) {
Pattern p = Pattern.compile(PATTERN_PAGES_TABLE);
Matcher m = p.matcher(contents);
if (m.find()) {
return m.group(1);
}
throw new RuntimeException("Failed to find the 'Pages' "
+ "table in file: '" + filePath + "'");
}
private Map<String, TestResultInfo> parsePagesTable(String pagesTable) {
Map<String, TestResultInfo> map = new LinkedHashMap<String, TestResultInfo>();
Pattern p = Pattern.compile(PATTERN_TABLE_ROW);
Matcher m = p.matcher(pagesTable);
discardHeaderRowFromPagesTable(m);
while (m.find()) {
TestResultInfo info = parseTestRowFromPagesTable(m.group(1));
map.put(info.getTestName(), info);
}
return map;
}
private void discardHeaderRowFromPagesTable(Matcher m) {
if (!m.find()) {
throw new RuntimeException("Failed to find a header row "
+ "in the 'Pages' table, in file: '" + filePath + "'");
}
}
private TestResultInfo parseTestRowFromPagesTable(String tableRow) {
// System.out.println("Table Row: " + tableRow);
List<String> cells = new ArrayList<String>();
Pattern p = Pattern.compile("<td.*?>(.*?)</td>");
Matcher m = p.matcher(tableRow);
while (m.find()) {
cells.add(m.group(1));
}
// System.out.println("Cells: " + cells);
if (cells.size() < 7) {
throw new RuntimeException("Only " + cells.size()
+ " cells in this table row: '" + tableRow
+ "', in file: '" + filePath + "'");
}
String testName = cells.get(0);
int count = Integer.parseInt(cells.get(1));
boolean success = cells.get(2).equals("0");
float averageTime = parseTimeFromCell(cells.get(4));
float minTime = parseTimeFromCell(cells.get(5));
float maxTime = parseTimeFromCell(cells.get(6));
return new TestResultInfo(testName, success, count, averageTime,
maxTime, minTime);
}
private float parseTimeFromCell(String cell) {
String[] parts = cell.split(" ");
return Integer.parseInt(parts[0]) / 1000.0F;
}
}