[Bug 62195] Save Responses to a file : Improve component and UI

Contributed by UbikLoadPack
Bugzilla Id: 62195

git-svn-id: https://svn.apache.org/repos/asf/jmeter/trunk@1827348 13f79535-47bb-0310-9956-ffa450edef68

Former-commit-id: aac159779d
This commit is contained in:
Philippe Mouawad 2018-03-20 19:23:19 +00:00
parent 85aa886e0e
commit 12190d932a
6 changed files with 227 additions and 88 deletions

View File

@ -28,6 +28,7 @@ import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.apache.jmeter.control.TransactionController;
import org.apache.jmeter.engine.util.NoThreadClone;
import org.apache.jmeter.samplers.SampleEvent;
import org.apache.jmeter.samplers.SampleListener;
@ -36,6 +37,7 @@ import org.apache.jmeter.services.FileServer;
import org.apache.jmeter.testelement.AbstractTestElement;
import org.apache.jmeter.testelement.TestStateListener;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jorphan.util.JMeterStopTestNowException;
import org.apache.jorphan.util.JOrphanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -73,6 +75,8 @@ public class ResultSaver extends AbstractTestElement implements NoThreadClone, S
public static final String ADD_TIMESTAMP = "FileSaver.addTimstamp"; // $NON-NLS-1$
public static final String NUMBER_PAD_LENGTH = "FileSaver.numberPadLen"; // $NON-NLS-1$
public static final String IGNORE_TC = "FileSaver.ignoreTC"; // $NON-NLS-1$
//- JMX property names
@ -150,7 +154,7 @@ public class ResultSaver extends AbstractTestElement implements NoThreadClone, S
*/
@Override
public void sampleOccurred(SampleEvent e) {
processSample(e.getResult(), new Counter());
processSample(e.getResult(), new Counter());
}
/**
@ -172,15 +176,11 @@ public class ResultSaver extends AbstractTestElement implements NoThreadClone, S
* @param num number to append to variable (if >0)
*/
private void saveSample(SampleResult s, int num) {
// Should we save the sample?
if (s.isSuccessful()){
if (getErrorsOnly()){
return;
}
} else {
if (getSuccessOnly()){
return;
if(ignoreSampler(s)) {
if (log.isDebugEnabled()) {
log.debug("Ignoring SampleResult from Sampler {}", s.getSampleLabel());
}
return;
}
String fileName = makeFileName(s.getContentType(), getSkipAutoNumber(), getSkipSuffix());
@ -198,6 +198,7 @@ public class ResultSaver extends AbstractTestElement implements NoThreadClone, S
JMeterContextService.getContext().getVariables().put(variable, fileName);
}
File out = new File(fileName);
createFoldersIfNeeded(out.getParentFile());
try (FileOutputStream fos = new FileOutputStream(out);
BufferedOutputStream bos = new BufferedOutputStream(fos)){
JOrphanUtils.write(s.getResponseData(), bos); // chunk the output if necessary
@ -208,6 +209,34 @@ public class ResultSaver extends AbstractTestElement implements NoThreadClone, S
}
}
/**
* @param s {@link SamplerResult}
* @return true if we should ignore SampleResult
*/
private boolean ignoreSampler(SampleResult s) {
if(getIgnoreTC() && TransactionController.isFromTransactionController(s)) {
return true;
}
// Should we save the sample?
return ((s.isSuccessful() && getErrorsOnly()) ||
(!s.isSuccessful() && getSuccessOnly()));
}
/**
* Create path hierarchy to parentFile
* @param parentFile
*/
private void createFoldersIfNeeded(File parentFile) {
if (!parentFile.exists()) {
log.debug("Creating path hierarchy for folder {}", parentFile.getAbsolutePath());
if(!parentFile.mkdirs()) {
throw new JMeterStopTestNowException("Cannot create path hierarchy for folder "+ parentFile.getAbsolutePath());
}
} else {
log.debug("Folder {} already exists", parentFile.getAbsolutePath());
}
}
/**
* @param contentType Content type
* @param skipAutoNumber Skip auto number
@ -263,37 +292,49 @@ public class ResultSaver extends AbstractTestElement implements NoThreadClone, S
// not used
}
private String getFilename() {
public String getFilename() {
return getPropertyAsString(FILENAME);
}
private String getVariableName() {
public String getVariableName() {
return getPropertyAsString(VARIABLE_NAME,""); // $NON-NLS-1$
}
private boolean getErrorsOnly() {
public boolean getErrorsOnly() {
return getPropertyAsBoolean(ERRORS_ONLY);
}
private boolean getSkipAutoNumber() {
public boolean getSkipAutoNumber() {
return getPropertyAsBoolean(SKIP_AUTO_NUMBER);
}
private boolean getSkipSuffix() {
public boolean getSkipSuffix() {
return getPropertyAsBoolean(SKIP_SUFFIX);
}
private boolean getSuccessOnly() {
public boolean getSuccessOnly() {
return getPropertyAsBoolean(SUCCESS_ONLY);
}
private boolean getAddTimeStamp() {
public boolean getAddTimeStamp() {
return getPropertyAsBoolean(ADD_TIMESTAMP);
}
private int getNumberPadLen() {
public int getNumberPadLen() {
return getPropertyAsInt(NUMBER_PAD_LENGTH, 0);
}
public boolean getIgnoreTC() {
return getPropertyAsBoolean(IGNORE_TC, true);
}
public void setIgnoreTC(boolean value) {
setProperty(IGNORE_TC, value, true);
}
public void setFilename(String value) {
setProperty(FILENAME, value);
}
// Mutable int to keep track of sample count
private static class Counter{
@ -315,4 +356,32 @@ public class ResultSaver extends AbstractTestElement implements NoThreadClone, S
public boolean equals(Object obj) {
return super.equals(obj);
}
public void setAddTimestamp(boolean selected) {
setProperty(ADD_TIMESTAMP, selected, false);
}
public void setVariableName(String value) {
setProperty(VARIABLE_NAME, value,""); //$NON-NLS-1$
}
public void setNumberPadLength(String text) {
setProperty(ResultSaver.NUMBER_PAD_LENGTH, text,""); //$NON-NLS-1$
}
public void setErrorsOnly(boolean selected) {
setProperty(ResultSaver.ERRORS_ONLY, selected);
}
public void setSuccessOnly(boolean selected) {
setProperty(ResultSaver.SUCCESS_ONLY, selected);
}
public void setSkipSuffix(boolean selected) {
setProperty(ResultSaver.SKIP_SUFFIX, selected);
}
public void setSkipAutoNumber(boolean selected) {
setProperty(ResultSaver.SKIP_AUTO_NUMBER, selected);
}
}

View File

@ -19,16 +19,19 @@
package org.apache.jmeter.reporters.gui;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JTextField;
import org.apache.jmeter.reporters.ResultSaver;
import org.apache.jmeter.samplers.Clearable;
import org.apache.jmeter.testelement.AbstractTestElement;
import org.apache.jmeter.testelement.TestElement;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.visualizers.gui.AbstractListenerGui;
@ -39,26 +42,28 @@ import org.apache.jorphan.gui.JLabeledTextField;
* of files
*
*/
public class ResultSaverGui extends AbstractListenerGui implements Clearable {
public class ResultSaverGui extends AbstractListenerGui implements Clearable { // NOSONAR Ignore inheritance rule
private static final long serialVersionUID = 240L;
private static final long serialVersionUID = 241L;
private JTextField filename;
private JLabeledTextField filename;
private JTextField variableName;
private JLabeledTextField variableName;
private JLabeledTextField numberPadLength;
private JCheckBox errorsOnly;
private JCheckBox successOnly;
private JCheckBox ignoreTC;
private JCheckBox skipAutoNumber;
private JCheckBox skipSuffix;
private JCheckBox addTimestamp;
private JLabeledTextField numberPadLength;
public ResultSaverGui() {
super();
init();
@ -78,14 +83,17 @@ public class ResultSaverGui extends AbstractListenerGui implements Clearable {
@Override
public void configure(TestElement el) {
super.configure(el);
filename.setText(el.getPropertyAsString(ResultSaver.FILENAME));
errorsOnly.setSelected(el.getPropertyAsBoolean(ResultSaver.ERRORS_ONLY));
successOnly.setSelected(el.getPropertyAsBoolean(ResultSaver.SUCCESS_ONLY));
skipAutoNumber.setSelected(el.getPropertyAsBoolean(ResultSaver.SKIP_AUTO_NUMBER));
skipSuffix.setSelected(el.getPropertyAsBoolean(ResultSaver.SKIP_SUFFIX));
variableName.setText(el.getPropertyAsString(ResultSaver.VARIABLE_NAME,""));
addTimestamp.setSelected(el.getPropertyAsBoolean(ResultSaver.ADD_TIMESTAMP));
numberPadLength.setText(el.getPropertyAsString(ResultSaver.NUMBER_PAD_LENGTH,""));
ResultSaver resultSaver = (ResultSaver) el;
filename.setText(resultSaver.getFilename());
errorsOnly.setSelected(resultSaver.getErrorsOnly());
successOnly.setSelected(resultSaver.getSuccessOnly());
ignoreTC.setSelected(resultSaver.getIgnoreTC());
skipAutoNumber.setSelected(resultSaver.getSkipAutoNumber());
skipSuffix.setSelected(resultSaver.getSkipSuffix());
variableName.setText(resultSaver.getVariableName());
addTimestamp.setSelected(resultSaver.getAddTimeStamp());
numberPadLength.setText(resultSaver.getNumberPadLen() == 0 ?
"" : Integer.toString(resultSaver.getNumberPadLen()));
}
/**
@ -106,15 +114,16 @@ public class ResultSaverGui extends AbstractListenerGui implements Clearable {
@Override
public void modifyTestElement(TestElement te) {
super.configureTestElement(te);
te.setProperty(ResultSaver.FILENAME, filename.getText());
te.setProperty(ResultSaver.ERRORS_ONLY, errorsOnly.isSelected());
te.setProperty(ResultSaver.SKIP_AUTO_NUMBER, skipAutoNumber.isSelected());
te.setProperty(ResultSaver.SKIP_SUFFIX, skipSuffix.isSelected());
te.setProperty(ResultSaver.SUCCESS_ONLY, successOnly.isSelected());
te.setProperty(ResultSaver.ADD_TIMESTAMP, addTimestamp.isSelected(), false);
AbstractTestElement at = (AbstractTestElement) te;
at.setProperty(ResultSaver.VARIABLE_NAME, variableName.getText(),""); //$NON-NLS-1$
at.setProperty(ResultSaver.NUMBER_PAD_LENGTH, numberPadLength.getText(),""); //$NON-NLS-1$
ResultSaver resultSaver = (ResultSaver) te;
resultSaver.setFilename(filename.getText());
resultSaver.setErrorsOnly(errorsOnly.isSelected());
resultSaver.setSuccessOnly(successOnly.isSelected());
resultSaver.setSkipSuffix(skipSuffix.isSelected());
resultSaver.setSkipAutoNumber(skipAutoNumber.isSelected());
resultSaver.setIgnoreTC(ignoreTC.isSelected());
resultSaver.setAddTimestamp(addTimestamp.isSelected());
resultSaver.setVariableName(variableName.getText());
resultSaver.setNumberPadLength(numberPadLength.getText());
}
/**
@ -129,6 +138,7 @@ public class ResultSaverGui extends AbstractListenerGui implements Clearable {
filename.setText(""); //$NON-NLS-1$
errorsOnly.setSelected(false);
successOnly.setSelected(false);
ignoreTC.setSelected(true);
addTimestamp.setSelected(false);
variableName.setText(""); //$NON-NLS-1$
numberPadLength.setText(""); //$NON-NLS-1$
@ -137,57 +147,107 @@ public class ResultSaverGui extends AbstractListenerGui implements Clearable {
private void init() { // WARNING: called from ctor so must not be overridden (i.e. must be private or final)
setLayout(new BorderLayout());
setBorder(makeBorder());
Box box = Box.createVerticalBox();
box.add(makeTitlePanel());
box.add(createFilenamePrefixPanel());
box.add(createVariableNamePanel());
errorsOnly = new JCheckBox(JMeterUtils.getResString("resultsaver_errors")); // $NON-NLS-1$
box.add(errorsOnly);
successOnly = new JCheckBox(JMeterUtils.getResString("resultsaver_success")); // $NON-NLS-1$
box.add(successOnly);
skipAutoNumber = new JCheckBox(JMeterUtils.getResString("resultsaver_skipautonumber")); // $NON-NLS-1$
box.add(skipAutoNumber);
skipSuffix = new JCheckBox(JMeterUtils.getResString("resultsaver_skipsuffix")); // $NON-NLS-1$
box.add(skipSuffix);
addTimestamp = new JCheckBox(JMeterUtils.getResString("resultsaver_addtimestamp")); // $NON-NLS-1$
box.add(addTimestamp);
numberPadLength = new JLabeledTextField(JMeterUtils.getResString("resultsaver_numberpadlen"));// $NON-NLS-1$
box.add(numberPadLength);
box.add(createSaveConditionsPanel());
box.add(createSaveFormatPanel());
add(box, BorderLayout.NORTH);
}
private JPanel createFilenamePrefixPanel()
{
JLabel label = new JLabel(JMeterUtils.getResString("resultsaver_prefix")); // $NON-NLS-1$
filename = new JTextField(10);
private Component createSaveFormatPanel() {
filename = new JLabeledTextField(JMeterUtils.getResString("resultsaver_prefix"));
filename.setName(ResultSaver.FILENAME);
label.setLabelFor(filename);
JPanel filenamePanel = new JPanel(new BorderLayout(5, 0));
filenamePanel.add(label, BorderLayout.WEST);
filenamePanel.add(filename, BorderLayout.CENTER);
return filenamePanel;
}
numberPadLength = new JLabeledTextField(JMeterUtils.getResString("resultsaver_numberpadlen"));// $NON-NLS-1$
numberPadLength.setName(ResultSaver.NUMBER_PAD_LENGTH);
skipAutoNumber = new JCheckBox(JMeterUtils.getResString("resultsaver_skipautonumber")); // $NON-NLS-1$
skipSuffix = new JCheckBox(JMeterUtils.getResString("resultsaver_skipsuffix")); // $NON-NLS-1$
addTimestamp = new JCheckBox(JMeterUtils.getResString("resultsaver_addtimestamp")); // $NON-NLS-1$
private JPanel createVariableNamePanel()
{
JLabel label = new JLabel(JMeterUtils.getResString("resultsaver_variable")); // $NON-NLS-1$
variableName = new JTextField(10);
variableName = new JLabeledTextField(JMeterUtils.getResString("resultsaver_variable"));
variableName.setName(ResultSaver.VARIABLE_NAME);
label.setLabelFor(variableName);
JPanel filenamePanel = new JPanel(new BorderLayout(5, 0));
filenamePanel.add(label, BorderLayout.WEST);
filenamePanel.add(variableName, BorderLayout.CENTER);
return filenamePanel;
JPanel panel = new JPanel(new GridBagLayout());
panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("resultsaver_save_format"))); //$NON-NLS-1$
GridBagConstraints gbc = new GridBagConstraints();
initConstraints(gbc);
addField(panel, variableName, gbc);
resetContraints(gbc);
addField(panel, filename, gbc);
resetContraints(gbc);
addField(panel, skipAutoNumber, gbc);
resetContraints(gbc);
addField(panel, skipSuffix, gbc);
resetContraints(gbc);
addField(panel, addTimestamp, gbc);
resetContraints(gbc);
addField(panel, numberPadLength, gbc);
resetContraints(gbc);
return panel;
}
private Component createSaveConditionsPanel() {
successOnly = new JCheckBox(JMeterUtils.getResString("resultsaver_success")); // $NON-NLS-1$
errorsOnly = new JCheckBox(JMeterUtils.getResString("resultsaver_errors")); // $NON-NLS-1$
ignoreTC = new JCheckBox(JMeterUtils.getResString("resultsaver_ignore_tc")); // $NON-NLS-1$
JPanel panel = new JPanel(new GridBagLayout());
panel.setBorder(BorderFactory.createTitledBorder(JMeterUtils.getResString("resultsaver_save_conditions"))); //$NON-NLS-1$
GridBagConstraints gbc = new GridBagConstraints();
initConstraints(gbc);
addField(panel, successOnly, gbc);
resetContraints(gbc);
addField(panel, errorsOnly, gbc);
resetContraints(gbc);
addField(panel, ignoreTC, gbc);
resetContraints(gbc);
return panel;
}
// Needed to avoid Class cast error in Clear.java
@Override
public void clearData() {
// NOOP
}
private void addField(JPanel panel, JCheckBox field, GridBagConstraints gbc) {
gbc.weightx = 2;
gbc.fill=GridBagConstraints.HORIZONTAL;
panel.add(field, gbc.clone());
}
private void addField(JPanel panel, JLabeledTextField field, GridBagConstraints gbc) {
List<JComponent> item = field.getComponentList();
panel.add(item.get(0), gbc.clone());
gbc.gridx++;
gbc.weightx = 1;
gbc.fill=GridBagConstraints.HORIZONTAL;
panel.add(item.get(1), gbc.clone());
}
// Next line
private void resetContraints(GridBagConstraints gbc) {
gbc.gridx = 0;
gbc.gridy++;
gbc.weightx = 0;
gbc.fill=GridBagConstraints.NONE;
}
private void initConstraints(GridBagConstraints gbc) {
gbc.anchor = GridBagConstraints.NORTHWEST;
gbc.fill = GridBagConstraints.NONE;
gbc.gridheight = 1;
gbc.gridwidth = 1;
gbc.gridx = 0;
gbc.gridy = 0;
gbc.weightx = 0;
gbc.weighty = 0;
}
}

View File

@ -939,13 +939,16 @@ restart=Restart
resultaction_title=Result Status Action Handler
resultsaver_addtimestamp=Add timestamp
resultsaver_errors=Save Failed Responses only
resultsaver_numberpadlen=Minimum Length of sequence number
resultsaver_prefix=Filename prefix\:
resultsaver_ignore_tc=Don't save Transaction Controller SampleResult
resultsaver_numberpadlen=Minimum Length of sequence number \:
resultsaver_prefix=Filename prefix (can include folders) \:
resultsaver_save_conditions=Save conditions
resultsaver_save_format=Save details
resultsaver_skipautonumber=Don't add number to prefix
resultsaver_skipsuffix=Don't add suffix
resultsaver_skipsuffix=Don't add content type suffix
resultsaver_success=Save Successful Responses only
resultsaver_title=Save Responses to a file
resultsaver_variable=Variable Name:
resultsaver_variable=Variable Name containing saved file name \:
result_function=The result of the function is
retobj=Return object
return_code_config_box_title=Return Code Configuration

View File

@ -929,8 +929,11 @@ result_function=Le r\u00E9sultat de la fonction est \:
resultaction_title=Op\u00E9rateur R\u00E9sultats Action
resultsaver_addtimestamp=Ajouter un timestamp
resultsaver_errors=Enregistrer seulement les r\u00E9ponses en \u00E9checs
resultsaver_ignore_tc=Ne pas enregistrer l''\u00E9chantillon Transaction Controller
resultsaver_numberpadlen=Taille minimale du num\u00E9ro de s\u00E9quence
resultsaver_prefix=Pr\u00E9fixe du nom de fichier \:
resultsaver_prefix=Pr\u00E9fixe du nom de fichier \:
resultsaver_save_conditions=Conditions d''enregistrement
resultsaver_save_format=D\u00E9tails de l''enregistrement
resultsaver_skipautonumber=Ne pas ajouter de nombre au pr\u00E9fixe
resultsaver_skipsuffix=Ne pas ajouter de suffixe
resultsaver_success=Enregistrer seulement les r\u00E9ponses en succ\u00E8s

View File

@ -98,6 +98,7 @@ this behaviour, set <code>httpclient.reset_state_on_thread_group_iteration=false
<h3>Listeners</h3>
<ul>
<li><bug>62195</bug>Save Responses to a file : Improve component and UI. Contributed by Ubik Load Pack (support at ubikloadpack.com)</li>
</ul>
<h3>Timers, Assertions, Config, Pre- &amp; Post-Processors</h3>

View File

@ -3178,23 +3178,26 @@ i.e. 30.0 requests/minute is saved as <code>0.5</code>.
</description>
<properties>
<property name="Name" required="No">Descriptive name for this element that is shown in the tree.</property>
<property name="Filename Prefix" required="Yes">Prefix for the generated file names; this can include a directory name.
<property name="Filename Prefix (can include folders)" required="Yes">Prefix for the generated file names; this can include a directory name.
Relative paths are resolved relative to the current working directory (which defaults to the <code>bin/</code> directory).
JMeter also supports paths relative to the directory containing the current test plan (JMX file).
If the path name begins with "<code>~/</code>" (or whatever is in the <code>jmeter.save.saveservice.base_prefix</code> JMeter property),
then the path is assumed to be relative to the JMX file location.
then the path is assumed to be relative to the JMX file location. <br/>
If parent folders in prefix do not exists, JMeter will create them and stop test if it fails.
</property>
<property name="Variable Name" required="No">
<property name="Variable Name containing saved file name" required="No">
Name of a variable in which to save the generated file name (so it can be used later in the test plan).
If there are sub-samples then a numeric suffix is added to the variable name.
E.g. if the variable name is <code>FILENAME</code>, then the parent sample file name is saved in the variable <code>FILENAME</code>,
and the filenames for the child samplers are saved in <code>FILENAME1</code>, <code>FILENAME2</code> etc.
</property>
<property name="Minimum Length of sequence number" required="No">If "<code>Don't add number to prefix</code>" is not checked, then numbers added to prefix will be padded by <code>0</code> so that prefix is has size of this value. Defaults to <code>0</code>.</property>
<property name="Save Failed Responses only" required="Yes">If selected, then only failed responses are saved</property>
<property name="Save Successful Responses only" required="Yes">If selected, then only successful responses are saved</property>
<property name="Don't add number to prefix" required="Yes">If selected, then no number is added to the prefix. If you select this option, make sure that the prefix is unique or the file may be overwritten.</property>
<property name="Don't add suffix" required="Yes">If selected, then no suffix is added. If you select this option, make sure that the prefix is unique or the file may be overwritten.</property>
<property name="Minimum Length of sequence number" required="No">If "<code>Don't add number to prefix</code>" is not checked, then numbers added to prefix will be padded by <code>0</code> so that prefix is has size of this value. Defaults to <code>0</code>.</property>
<property name="Don't add content type suffix" required="Yes">If selected, then no suffix is added. If you select this option, make sure that the prefix is unique or the file may be overwritten.</property>
<property name="Add timestamp" required="Yes">If selected, then date will be included in file suffix following format <code>yyyyMMdd-HHmm_</code></property>
<property name="Don't Save Transaction Controller SampleResult" required="Yes">If selected, then SamplerResult generated by Transaction Controller will be ignored</property>
</properties>
</component>