Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions bin/jmeter.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1217,6 +1217,15 @@ view.results.tree.renderers_order=.RenderAsText,.RenderAsRegexp,.RenderAsBoundar
# Set to 0 to store all results (might consume a lot of memory)
#view.results.tree.max_results=500

# Set to true to enable by default the checkbox for auto scrolling.
#view.results.tree.autoscroll=false

# Set to true to enable by default the checkbox for stop auto scrolling on 1st error.
#view.results.tree.autostop=true

# Default width of the scroll view
#view.results.tree.width=250

# Maximum size of Document that can be parsed by Tika engine; default=10 * 1024 * 1024 (10 MB)
# Set to 0 to disable the size check
#document.max_size=0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public abstract class SamplerResultTab implements ResultRenderer {
private static final Logger LOGGER = LoggerFactory.getLogger(SamplerResultTab.class);
// N.B. these are not multi-threaded, so don't make it static
private final DateTimeFormatter dateFormat = DateTimeFormatter
.ofPattern("yyyy-MM-dd HH:mm:ss z") // ISO format $NON-NLS-1$
.ofPattern("yyyy-MM-dd HH:mm:ss.SSS O") // ISO format $NON-NLS-1$
.withZone(ZoneId.systemDefault());

private static final String NL = "\n"; // $NON-NLS-1$
Expand Down Expand Up @@ -265,6 +265,10 @@ public void setupTabPane() {
.append(JMeterUtils
.getResString("view_results_thread_name")).append(SPACE) //$NON-NLS-1$
.append(sampleResult.getThreadName()).append(NL);
statsBuff
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be mentioned in the title of this PR. Was this intended to be included?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes, this was an later add-on. Purpose is: dealing with a long samplers listing in a View Results Tree one can easily lose relation between scroll on left side and selected sampler on right side.
Having the sampler name included can help in tracking between the 2 sources of information.
I will update PR title.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More changes included

.append(JMeterUtils
.getResString("view_results_sample_name")) //$NON-NLS-1$
.append(sampleResult.getSampleLabel()).append(NL);
String startTime = dateFormat
.format(Instant.ofEpochMilli(sampleResult.getStartTime()));
statsBuff
Expand Down Expand Up @@ -371,6 +375,10 @@ public void setupTabPane() {
resultModel.addRow(new RowResult(
JMeterUtils.getParsedLabel("view_results_thread_name"), //$NON-NLS-1$
sampleResult.getThreadName()));
resultModel.addRow(new RowResult(
JMeterUtils.getParsedLabel(
"view_results_sample_name"), //$NON-NLS-1$
sampleResult.getSampleLabel()));
resultModel.addRow(new RowResult(
JMeterUtils.getParsedLabel("view_results_sample_start"), //$NON-NLS-1$
startTime));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
Expand All @@ -43,8 +45,10 @@
import javax.swing.ComboBoxModel;
import javax.swing.DefaultComboBoxModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTabbedPane;
Expand Down Expand Up @@ -117,6 +121,15 @@ public class ViewResultsFullVisualizer extends AbstractVisualizer
private static final String VIEWERS_ORDER =
JMeterUtils.getPropDefault("view.results.tree.renderers_order", ""); // $NON-NLS-1$ //$NON-NLS-2$

//default scroll checkbox status
private static final boolean SCROLL_CHECKBOX = JMeterUtils.getPropDefault("view.results.tree.autoscroll", false);

//default uncheck if failed scroll checkbox status
private static final boolean SCROLL_STOP_CHECKBOX = JMeterUtils.getPropDefault("view.results.tree.autostop", true);

// default tree scroll width
private static final int SCROLL_WIDTH = JMeterUtils.getPropDefault("view.results.tree.width", 250); // $NON-NLS-1$

private static final int REFRESH_PERIOD = JMeterUtils.getPropDefault("jmeter.gui.refresh_period", 500);

private static final ImageIcon imageSuccess = JMeterUtils.getImage(
Expand All @@ -138,9 +151,10 @@ public class ViewResultsFullVisualizer extends AbstractVisualizer
private ResultRenderer resultsRender = null;
private Object resultsObject = null;
private TreeSelectionEvent lastSelectionEvent;
private JCheckBox autoScrollCB;
private JCheckBox autoScrollCB, scrollStopCB;
private final Queue<SampleResult> buffer;
private boolean dataChanged;
private SampleResult failedSampler;

/**
* Constructor
Expand All @@ -163,13 +177,16 @@ public void add(final SampleResult sample) {
synchronized (buffer) {
buffer.add(sample);
dataChanged = true;
if (!sample.isSuccessful() && failedSampler == null)
failedSampler = sample;
}
}

/**
* Update the visualizer with new data.
*/
private void updateGui() {
int failedSamplerPosition = 0;
TreePath selectedPath = null;
Object oldSelectedElement;
Set<Object> oldExpandedElements;
Expand All @@ -184,20 +201,19 @@ private void updateGui() {
oldSelectedElement = getSelectedObject();
root.removeAllChildren();
for (SampleResult sampler: buffer) {
SampleResult res = sampler;
// Add sample
DefaultMutableTreeNode currNode = new SearchableTreeNode(res, treeModel);
DefaultMutableTreeNode currNode = new SearchableTreeNode(sampler, treeModel);
treeModel.insertNodeInto(currNode, root, root.getChildCount());
List<TreeNode> path = new ArrayList<>(Arrays.asList(root, currNode));
selectedPath = checkExpandedOrSelected(path,
res, oldSelectedElement,
sampler, oldSelectedElement,
oldExpandedElements, newExpandedPaths, selectedPath);
TreePath potentialSelection = addSubResults(currNode, res, path, oldSelectedElement, oldExpandedElements, newExpandedPaths);
TreePath potentialSelection = addSubResults(currNode, sampler, path, oldSelectedElement, oldExpandedElements, newExpandedPaths);
if (potentialSelection != null) {
selectedPath = potentialSelection;
}
// Add any assertion that failed as children of the sample node
AssertionResult[] assertionResults = res.getAssertionResults();
AssertionResult[] assertionResults = sampler.getAssertionResults();
int assertionIndex = currNode.getChildCount();
for (AssertionResult assertionResult : assertionResults) {
if (assertionResult.isFailure() || assertionResult.isError()) {
Expand All @@ -209,6 +225,9 @@ private void updateGui() {
assertionNode);
}
}

if (sampler == failedSampler)
failedSamplerPosition = root.getChildCount() - 1;
}
treeModel.nodeStructureChanged(root);
dataChanged = false;
Expand All @@ -221,10 +240,14 @@ private void updateGui() {
if (selectedPath != null) {
jTree.setSelectionPath(selectedPath);
}

if (autoScrollCB.isSelected() && root.getChildCount() > 1) {
jTree.scrollPathToVisible(new TreePath(new Object[] { root,
treeModel.getChild(root, root.getChildCount() - 1) }));
int pos = (scrollStopCB.isSelected() && failedSampler != null) ? failedSamplerPosition : root.getChildCount() - 1;
jTree.scrollPathToVisible(new TreePath(new Object[] { root, treeModel.getChild(root, pos) }));
}
//if session not running, reset the error reference
if (!JMeterUtils.isTestRunning())
failedSampler = null;
}

private Object getSelectedObject() {
Expand Down Expand Up @@ -330,6 +353,7 @@ public void clearData() {
}
resultsRender.clearData();
resultsObject = null;
failedSampler = null;
}

/** {@inheritDoc} */
Expand Down Expand Up @@ -359,7 +383,7 @@ private void init() { // WARNING: called from ctor so must not be overridden (i
searchAndMainSP.setOneTouchExpandable(true);
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, makeTitlePanel(), searchAndMainSP);
splitPane.setOneTouchExpandable(true);
splitPane.setBorder(BorderFactory.createEmptyBorder());
splitPane.setBorder(null);
add(splitPane);

// init right side with first render
Expand Down Expand Up @@ -392,6 +416,44 @@ private void valueChanged(TreeSelectionEvent e, boolean forceRendering) {
}
Object userObject = node.getUserObject();
resultsRender.setSamplerResult(userObject);

// Add Show Current Node button to the tab pane
if (rightSide.getTabCount() > 0) {
Component firstTab = rightSide.getComponentAt(0);
if (firstTab instanceof JPanel) {
JPanel samplerResultTab = (JPanel) firstTab;
// Add button at the top of the sampler result panel
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
JButton nodeInfoButton = new JButton(JMeterUtils.getResString("show_current_node")); // $NON-NLS-1$
nodeInfoButton.addActionListener(e2 -> {
// Get the currently selected node when button is clicked
DefaultMutableTreeNode currentNode = (DefaultMutableTreeNode) jTree.getLastSelectedPathComponent();
if (currentNode != null) {
TreePath path = new TreePath(currentNode.getPath());
// First expand the path to make sure all parent nodes are visible
jTree.expandPath(path);
// Get the bounds before scrolling
Rectangle bounds = jTree.getPathBounds(path);
if (bounds != null) {
// Get the visible rectangle
Rectangle visible = jTree.getVisibleRect();
// Calculate the new viewport position to center the node
int centerY = Math.max(0, bounds.y - (visible.height - bounds.height) / 2);
// Create rectangle that will center the node
Rectangle scrollTo = new Rectangle(0, centerY, 1, visible.height);
jTree.scrollRectToVisible(scrollTo);
// Ensure the node is selected and visible
jTree.setSelectionPath(path);
jTree.repaint();
}
}
});
buttonPanel.add(nodeInfoButton);
samplerResultTab.add(buttonPanel, BorderLayout.NORTH);
samplerResultTab.revalidate();
}
}

resultsRender.setupTabPane(); // Processes Assertions
// display a SampleResult
if (userObject instanceof SampleResult) {
Expand Down Expand Up @@ -431,15 +493,42 @@ private synchronized Component createLeftPanel() {
jTree.setRootVisible(false);
jTree.setShowsRootHandles(true);
JScrollPane treePane = new JScrollPane(jTree);
treePane.setPreferredSize(new Dimension(200, 300));
treePane.setPreferredSize(new Dimension(SCROLL_WIDTH, 300));

VerticalPanel leftPane = new VerticalPanel();
leftPane.add(treePane, BorderLayout.CENTER);
leftPane.add(createComboRender(), BorderLayout.NORTH);

// Add controls panel at the top
JPanel controlsPane = new JPanel(new FlowLayout(FlowLayout.LEFT));
controlsPane.add(createComboRender());
leftPane.add(controlsPane, BorderLayout.NORTH);

// Add tree panel in the center with proper sizing
JPanel treePanel = new JPanel(new BorderLayout());
treePanel.add(treePane, BorderLayout.CENTER);
leftPane.add(treePanel, BorderLayout.CENTER);

// Create bottom panel with checkbox
JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 0));

// Add autoscroll checkbox
autoScrollCB = new JCheckBox(JMeterUtils.getResString("view_results_autoscroll")); // $NON-NLS-1$
autoScrollCB.setSelected(false);
autoScrollCB.setSelected(SCROLL_CHECKBOX);
autoScrollCB.addItemListener(this);
leftPane.add(autoScrollCB, BorderLayout.SOUTH);
bottomPanel.add(autoScrollCB);

// Add autoscroll checkbox
scrollStopCB = new JCheckBox(JMeterUtils.getResString("view_results_autostop")); // $NON-NLS-1$
scrollStopCB.setSelected(SCROLL_STOP_CHECKBOX);
scrollStopCB.addItemListener(this);
bottomPanel.add(scrollStopCB);
autoScrollCB.doClick(); // to set the initial state of scrollStopCB

leftPane.add(bottomPanel, BorderLayout.SOUTH);

// Ensure the left pane maintains its size during window resizing
leftPane.setMinimumSize(new Dimension(SCROLL_WIDTH, 0));
leftPane.setPreferredSize(new Dimension(SCROLL_WIDTH, 0));

return leftPane;
}

Expand Down Expand Up @@ -616,6 +705,14 @@ public Component getTreeCellRendererComponent(JTree tree, Object value,
*/
@Override
public void itemStateChanged(ItemEvent e) {
// NOOP state is held by component
Object source = e.getItemSelectable();
if (source == autoScrollCB) {
scrollStopCB.setEnabled(autoScrollCB.isSelected());
}
}

@Override
public String[] getFileExts() {
return new String[] { ".jtl" };
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public abstract class AbstractVisualizer
/** Logging. */
private static final Logger log = LoggerFactory.getLogger(AbstractVisualizer.class);

/** File Extensions */
/** Default File Extensions */
private static final String[] EXTS = { ".xml", ".jtl", ".csv" }; // $NON-NLS-1$ $NON-NLS-2$ $NON-NLS-3$

/** A panel allowing results to be saved. */
Expand Down Expand Up @@ -150,7 +150,7 @@ public void actionPerformed(ActionEvent e) {
d.setVisible(true);
});

filePanel = new FilePanel(JMeterUtils.getResString("file_visualizer_output_file"), EXTS); // $NON-NLS-1$
filePanel = new FilePanel(JMeterUtils.getResString("file_visualizer_output_file"), getFileExts()); // $NON-NLS-1$
filePanel.addChangeListener(this);
filePanel.add(new JLabel(JMeterUtils.getResString("log_only"))); // $NON-NLS-1$
filePanel.add(errorLogging);
Expand Down Expand Up @@ -346,4 +346,8 @@ public void clearGui(){
super.clearGui();
filePanel.clearGui();
}

public String[] getFileExts() {
return EXTS;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1401,7 +1401,8 @@ view_graph_tree_title=View Graph Tree
view_results_assertion_error=Assertion error:
view_results_assertion_failure=Assertion failure:
view_results_assertion_failure_message=Assertion failure message:
view_results_autoscroll=Scroll automatically?
view_results_autoscroll=Auto scroll
view_results_autostop=Stop scroll on error
view_results_childsamples=Child samples?
view_results_datatype=Data type ("text"|"bin"|""):
view_results_desc=Shows the text results of sampling in tree form
Expand Down Expand Up @@ -1461,6 +1462,7 @@ view_results_table_request_tab_raw=Raw
view_results_table_result_tab_parsed=Parsed
view_results_table_result_tab_raw=Raw
view_results_thread_name=Thread Name:
view_results_sample_name=Sample label:
view_results_title=View Results
view_results_tree_title=View Results Tree
warning=Warning!
Expand Down Expand Up @@ -1545,3 +1547,4 @@ xpath2_extractor_match_number_failure=MatchNumber out of bonds \:
you_must_enter_a_valid_number=You must enter a valid number
zh_cn=Chinese (Simplified)
zh_tw=Chinese (Traditional)
show_current_node=Focus on selected node
12 changes: 12 additions & 0 deletions xdocs/usermanual/properties_reference.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1556,6 +1556,18 @@ JMETER-SERVER</source>
Can be switched off by setting the value to <code>-1</code>.<br/>
Defaults to: <code>10000</code>
</property>
<property name="view.results.tree.autoscroll">
Set to <code>true</code> to enable by default the checkbox for auto scrolling.<br/>
Defaults to: <code>false</code>
</property>
<property name="view.results.tree.autostop">
Set to <code>true</code> to enable by default the checkbox for stop auto scrolling on 1st error.<br/>
Defaults to: <code>true</code>
</property>
<property name="view.results.tree.width">
Default width of the scroll view.<br/>
Defaults to: <code>250</code>
</property>
<property name="document.max_size">
Maximum size (in bytes) of Document that can be parsed by Tika engine<br/>
Set to zero to disable the size check.<br/>
Expand Down
Loading