-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Here are some Reduced ReportGenerator performance Code #4790
Comments
@vlsi (migrated from Bugzilla): Do you have profiling results that quantify the impact of the issues you mention? |
Allen (migrated from Bugzilla):
I use the default condition generated csv result file as a test case file, which is greater than 900MB, and my local CPU is i7-6600U, memory is 16GB, window test command is : Measure-Command {.\jmeter.bat-g D: \case.csv -o D:\output01}. you can observe the code is different, generate test report time will change. In my optimization proposal, except that the modifications of the CSVSaveService and NormalizerSampleConsumer are more radical, other suggestions are based on the language features of the Java code itself. For the modifications of the CSVSaveService class and the NormalizerSampleConsumer class, I only tested the csv file generated from the default conditions. I believe that the code you write is so complicated and there must be a reason, so I can't be 100% certain that my changes cover all cases. So I just suggest that there is a csv file that can be judged to be generated by default. If it is a simple csv file, then use a simple test report generation program. If you need a very comprehensive test basis to prove that the modifications of the CSVSaveService class and the NormalizerSampleConsumer class are harmless, I really don't have this. Because the csv test file I generated cannot cover all the scenarios and conditions. |
@FSchumacher (migrated from Bugzilla):
This would be possible and probably faster.
String#format is slow, but simple concatenation doesn't help here, as the labels are used for localization. We probably would need a pre-compiled String#format mechanism, that would be initialized ones and reused afterwards. Sadly, that is not possible with the standard implementation of String#format.
This one is needed, as the CSV file may contain new lines inside of quoted text. If we remove it, we can not read in all of our self generated csv files.
Do you have transaction samplers in your csv sample files? |
@vlsi (migrated from Bugzilla): I agree SampleMetadata.index should be changed from TreeMap to HashMap, however it should not be that important though. The field type should be just Map interface, not the class type. Regarding the other changes, I would like to see profiling results. I'm inclined to decline "performance" fixes unless there's justification.
I agree, there should be a test CSV file that is used to benchmark report generator. However, I do not have such a file. |
@FSchumacher (migrated from Bugzilla): Created attachment generate_tests.py: Script to generate a jtl file that could be used for performance testing generate_tests.py#!/usr/bin/python
import csv
with open('tests.jtl', 'w') as f:
w = csv.writer(f)
w.writerow(['timeStamp','elapsed','label','responseCode','responseMessage','threadName','dataType','success','failureMessage','bytes','sentBytes','grpThreads','allThreads','Latency','IdleTime','Connect'])
label = 'HTTP Request'
responseCode = 200
responseMessage = 'OK'
dataType = 'text'
success = 'true'
failureMessage = ''
bytesReceived = 4320
bytesSent = 121
grpThreads = 100
allThreads = 100
timestamp = 1529430552272
elapsed = 144
latency = 143
idleTime = 0
connectTime = 69
for i in range(50000):
timestamp += elapsed
for t in range(allThreads):
threadName = 'Thread Group %d-%d' % (t, allThreads)
w.writerow([timestamp, elapsed, label, responseCode, responseMessage, threadName, dataType, success, failureMessage, bytesReceived, bytesSent, grpThreads, allThreads, latency, idleTime, connectTime]) |
@FSchumacher (migrated from Bugzilla): It is about 10 times faster than String#format, but slower than concatenation. Benchmark Mode Cnt Score Error Units Created attachment 0001-Use-pre-compiled-text-formatter-instead-of-String-fo.patch: Simple replacement for String#format |
@FSchumacher (migrated from Bugzilla): Created attachment 0002-Convert-data-eagerly-to-optimize-report-generation.patch: Eager initialization of fields in sample 0002-Convert-data-eagerly-to-optimize-report-generation.patchFrom de68e0cf25ebd335b8ab0bcfecbb389a639a02dd Mon Sep 17 00:00:00 2001
From: Felix Schumacher <felix.schumacher@debeka.de>
Date: Tue, 19 Jun 2018 13:47:41 +0200
Subject: [PATCH 2/2] Convert data eagerly to optimize report generation
---
src/core/org/apache/jmeter/report/core/Sample.java | 44 +++++++++++++---------
1 file changed, 26 insertions(+), 18 deletions(-)
diff --git a/src/core/org/apache/jmeter/report/core/Sample.java b/src/core/org/apache/jmeter/report/core/Sample.java
index fdf8ab15a..9a0f799cf 100644
--- a/src/core/org/apache/jmeter/report/core/Sample.java
+++ b/src/core/org/apache/jmeter/report/core/Sample.java
@@ -35,13 +35,21 @@ public class Sample {
private static final String ERROR_ON_SAMPLE = "Error in sample at line:";
private static final String CONTROLLER_PATTERN = "Number of samples in transaction";
-
+
private static final String EMPTY_CONTROLLER_PATTERN = "Number of samples in transaction : 0";
private final boolean storesStartTimeStamp;
private final SampleMetadata metadata;
private final String[] data;
private final long row;
+ private final long elapsedTime;
+ private final long timestamp;
+ private final long latency;
+ private final long connectTime;
+ private final long receivedBytes;
+ private final long sentBytes;
+ private final int groupThreads;
+ private final boolean success;
/**
* Build a sample from a string array
@@ -59,6 +67,14 @@ public class Sample {
this.metadata = metadata;
this.data = data;
this.storesStartTimeStamp = JMeterUtils.getPropDefault("sampleresult.timestamp.start", false);
+ this.elapsedTime = getData(long.class, CSVSaveService.CSV_ELAPSED).longValue();
+ this.timestamp = getData(long.class, CSVSaveService.TIME_STAMP).longValue();
+ this.latency = getData(long.class, CSVSaveService.CSV_LATENCY).longValue();
+ this.connectTime = metadata.indexOf(CSVSaveService.CSV_CONNECT_TIME) >= 0 ? getData(long.class, CSVSaveService.CSV_CONNECT_TIME).longValue() : 0L;
+ this.success = getData(boolean.class, CSVSaveService.SUCCESSFUL).booleanValue();
+ this.receivedBytes = getData(long.class, CSVSaveService.CSV_BYTES).longValue();
+ this.sentBytes = metadata.indexOf(CSVSaveService.CSV_SENT_BYTES) >= 0 ? getData(long.class, CSVSaveService.CSV_SENT_BYTES).longValue() : 0L;
+ this.groupThreads = getData(int.class, CSVSaveService.CSV_THREAD_COUNT1).intValue();
}
/**
@@ -148,7 +164,7 @@ public class Sample {
* @return the time stamp
*/
public long getTimestamp() {
- return getData(long.class, CSVSaveService.TIME_STAMP).longValue();
+ return this.timestamp;
}
/**
@@ -157,7 +173,7 @@ public class Sample {
* @return the elapsed time stored in the sample
*/
public long getElapsedTime() {
- return getData(long.class, CSVSaveService.CSV_ELAPSED).longValue();
+ return this.elapsedTime;
}
/**
@@ -242,20 +258,16 @@ public class Sample {
* @return the latency stored in the sample
*/
public long getLatency() {
- return getData(long.class, CSVSaveService.CSV_LATENCY).longValue();
+ return this.latency;
}
-
+
/**
* Gets the connect time stored in the sample.
*
* @return the connect time stored in the sample or 0 is column is not in results
*/
public long getConnectTime() {
- if(metadata.indexOf(CSVSaveService.CSV_CONNECT_TIME) >= 0) {
- return getData(long.class, CSVSaveService.CSV_CONNECT_TIME).longValue();
- } else {
- return 0L;
- }
+ return this.connectTime;
}
/**
@@ -264,7 +276,7 @@ public class Sample {
* @return the success status stored in the sample
*/
public boolean getSuccess() {
- return getData(boolean.class, CSVSaveService.SUCCESSFUL).booleanValue();
+ return this.success;
}
/**
@@ -273,7 +285,7 @@ public class Sample {
* @return the number of received bytes stored in the sample
*/
public long getReceivedBytes() {
- return getData(long.class, CSVSaveService.CSV_BYTES).longValue();
+ return this.receivedBytes;
}
/**
@@ -282,11 +294,7 @@ public class Sample {
* @return the number of sent bytes stored in the sample
*/
public long getSentBytes() {
- if(metadata.indexOf(CSVSaveService.CSV_SENT_BYTES) >= 0) {
- return getData(long.class, CSVSaveService.CSV_SENT_BYTES).longValue();
- } else {
- return 0L;
- }
+ return this.sentBytes;
}
/**
@@ -295,7 +303,7 @@ public class Sample {
* @return the number of threads in the group of this sample
*/
public int getGroupThreads() {
- return getData(int.class, CSVSaveService.CSV_THREAD_COUNT1).intValue();
+ return this.groupThreads;
}
/**
--
2.13.7
|
@FSchumacher (migrated from Bugzilla): It should be noted, that report generation is single threaded. It might be interesting to see, if decoupling reading of csv and aggregating them into the different report classes into different threads might help. |
@pmouawad (migrated from Bugzilla):
Nice !
Yes, maybe we should see which step is the most consuming. |
@FSchumacher (migrated from Bugzilla): URL: http://svn.apache.org/viewvc?rev=1835351&view=rev Compute things that don't change, but get used often eagerly. Modified: |
Allen (Bug 62426):
Importance from high to low:
1.
public class TransactionsPerSecondGraphConsumer extends
AbstractOverTimeGraphConsumer
@OverRide
public Iterable<String> select(Sample sample) {
String label = String.format(STATUS_SERIES_FORMAT,
sample.getName(),
sample.getSuccess() ? SUCCESS_SERIES_SUFFIX
: FAILURE_SERIES_SUFFIX);
return Arrays.asList(label);
}
String.format can replace to "" + "";
public class SyntheticResponseTimeDistributionGraphConsumer extends
AbstractGraphConsumer {
if(elapsedTime<=getSatisfiedThreshold()) {
return Arrays.asList(SATISFIED_LABEL.format(new Object[] {Long.valueOf(getSatisfiedThreshold())}));
} else if(elapsedTime <= getToleratedThreshold()) {
return Arrays.asList(TOLERATED_LABEL.format(new Object[] {Long.valueOf(getSatisfiedThreshold()), Long.valueOf(getToleratedThreshold())}));
} else {
return Arrays.asList(UNTOLERATED_LABEL.format(new Object[] {Long.valueOf(getToleratedThreshold())}));
}
The same: String.format to slow.
public final class CSVSaveService {
public static String[] csvReadFile(BufferedReader infile, char delim)
throws IOException {
why not use like this:
String str = infile.readLine();
if (str == null) {
return new String[]{};
}
return str.split("" + delim);
the code:
if ((ch == '\n' || ch == '\r') && state != ParserState.QUOTED)
Consumes a lot of CPU time
public class NormalizerSampleConsumer extends AbstractSampleConsumer {
public void consume(Sample s, int channel) {
I just return
super.produce(s, 0);
public class SampleMetadata {
private TreeMap<String, Integer> index = new TreeMap<>();
why not use
private HashMap<String, Integer> index = new HashMap<>();
throws ConvertException {
why not just return like bellow:
f (clazz.isAssignableFrom(String.class)) {
return (T) value;
}
if (clazz.isAssignableFrom(Long.class) || clazz.isAssignableFrom(long.class)) {
return (T) Long.valueOf(value);
}
if (clazz.isAssignableFrom(Boolean.class) || clazz.isAssignableFrom(boolean.class)) {
return (T) Boolean.valueOf(value);
}
if (clazz.isAssignableFrom(Integer.class) || clazz.isAssignableFrom(int.class)) {
return (T) Integer.valueOf(value);
}
if (clazz.isAssignableFrom(Double.class) || clazz.isAssignableFrom(double.class)) {
return (T) Double.valueOf(value);
}
if (clazz.isAssignableFrom(Float.class) || clazz.isAssignableFrom(float.class)) {
return (T) Float.valueOf(value);
}
if (clazz.isAssignableFrom(Character.class) || clazz.isAssignableFrom(char.class)) {
return (T) Character.valueOf(value.charAt(0));
}
else {
StringConverter<T> converter = Converters.getConverter(clazz);
if (converter == null) {
throw new ConvertException(value, clazz.getName());
}
result = converter.convert(value);
}
My performance can be improved by at least 50%
The original code may be considered for special situations, but the default generated report file, I have no difference between the test before and after the modification
OS: All
Duplicates:
The text was updated successfully, but these errors were encountered: