Skip to content
Merged
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
10 changes: 10 additions & 0 deletions bin/jmeter.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1180,6 +1180,13 @@ cookies=cookies
# Set to 0 to disable the size check and display the whole response
#view.results.tree.max_size=10485760

# UI gets unresponsive when response contains very long lines,
# So we break lines by adding artificial line breaks
# The break is introduced somewhere in between soft_wrap_line_size..max_line_size
# We try to break on word boundaries first
#view.results.tree.max_line_size=110000
#view.results.tree.soft_wrap_line_size=100000

# Order of Renderers in View Results Tree
# Note full class names should be used for non JMeter core renderers
# For JMeter core renderers, class names start with '.' and are automatically
Expand All @@ -1194,6 +1201,9 @@ view.results.tree.renderers_order=.RenderAsText,.RenderAsRegexp,.RenderAsBoundar
# Set to 0 to disable the size check
#document.max_size=0

# Configures the maximum document length for rendering with kerning enabled
#text.kerning.max_document_size=10000

#JMS options
# Enable the following property to stop JMS Point-to-Point Sampler from using
# the properties java.naming.security.[principal|credentials] when creating the queue connection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ public void setSamplerResult(Object userObject) {
public void renderResult(SampleResult sampleResult) {
clearData();
String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult);
response = ViewResultsFullVisualizer.wrapLongLines(response);
boundaryExtractorDataField.setText(response);
boundaryExtractorDataField.setCaretPosition(0);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ private String process(String textToParse) {
public void renderResult(SampleResult sampleResult) {
clearData();
String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult);
response = ViewResultsFullVisualizer.wrapLongLines(response);
cssJqueryDataField.setText(response);
cssJqueryDataField.setCaretPosition(0);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ private String process(String textToParse) {
public void renderResult(SampleResult sampleResult) {
clearData();
String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult);
response = ViewResultsFullVisualizer.wrapLongLines(response);
regexpDataField.setText(response);
regexpDataField.setCaretPosition(0);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ private Document parseResponse(String unicodeData, XPathExtractor extractor)
@Override
public void renderResult(SampleResult sampleResult) {
String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult);
response = ViewResultsFullVisualizer.wrapLongLines(response);
try {
xmlDataField.setText(response == null ? "" : response);
xmlDataField.setCaretPosition(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ private String getDocumentNamespaces(String textToParse) {
@Override
public void renderResult(SampleResult sampleResult) {
String response = ViewResultsFullVisualizer.getResponseAsString(sampleResult);
response = ViewResultsFullVisualizer.wrapLongLines(response);
try {
xmlDataField.setText(response == null ? "" : response);
xmlDataField.setCaretPosition(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import org.apache.jorphan.gui.GuiUtils;
import org.apache.jorphan.gui.ObjectTableModel;
import org.apache.jorphan.gui.RendererUtils;
import org.apache.jorphan.gui.ui.KerningOptimizer;
import org.apache.jorphan.reflect.Functor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -688,10 +689,12 @@ protected void setTextOptimized(String data) {
Document blank = new DefaultStyledDocument();
results.setDocument(blank);
try {
data = ViewResultsFullVisualizer.wrapLongLines(data);
document.insertString(0, data == null ? "" : data, null);
} catch (BadLocationException ex) {
LOGGER.error("Error inserting text", ex);
}
KerningOptimizer.INSTANCE.configureKerning(results, document.getLength());
results.setDocument(document);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.visualizers.gui.AbstractVisualizer;
import org.apache.jorphan.gui.JMeterUIDefaults;
import org.apache.jorphan.util.StringWrap;
import org.apiguardian.api.API;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -103,6 +105,14 @@ public class ViewResultsFullVisualizer extends AbstractVisualizer
private static final int MAX_DISPLAY_SIZE =
JMeterUtils.getPropDefault("view.results.tree.max_size", 10485760); // $NON-NLS-1$

// Default limited to 110K
private static final int MAX_LINE_SIZE =
JMeterUtils.getPropDefault("view.results.tree.max_line_size", 110000); // $NON-NLS-1$

// Limit the soft wrap to 100K (hard limit divided by 1.1)
private static final int SOFT_WRAP_LINE_SIZE =
JMeterUtils.getPropDefault("view.results.tree.soft_wrap_line_size", (int) (MAX_LINE_SIZE / 1.1f)); // $NON-NLS-1$

// default display order
private static final String VIEWERS_ORDER =
JMeterUtils.getPropDefault("view.results.tree.renderers_order", ""); // $NON-NLS-1$ //$NON-NLS-2$
Expand Down Expand Up @@ -563,6 +573,19 @@ public static String getResponseAsString(SampleResult res) {
return response;
}

@API(status = API.Status.INTERNAL, since = "5.5")
public static String wrapLongLines(String input) {
if (input == null || input.isEmpty()) {
return input;
}
if (SOFT_WRAP_LINE_SIZE > 0 && MAX_LINE_SIZE > 0) {
StringWrap stringWrap = new StringWrap(SOFT_WRAP_LINE_SIZE, MAX_LINE_SIZE);
return stringWrap.wrap(input, "\n");
}
return input;
}


private static class ResultsNodeRenderer extends DefaultTreeCellRenderer {
private static final long serialVersionUID = 4159626601097711565L;

Expand Down
4 changes: 4 additions & 0 deletions src/core/src/main/java/org/apache/jmeter/JMeter.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
import org.apache.jorphan.collections.SearchByClass;
import org.apache.jorphan.gui.ComponentUtil;
import org.apache.jorphan.gui.JMeterUIDefaults;
import org.apache.jorphan.gui.ui.KerningOptimizer;
import org.apache.jorphan.reflect.ClassTools;
import org.apache.jorphan.util.HeapDumper;
import org.apache.jorphan.util.JMeterException;
Expand Down Expand Up @@ -374,6 +375,9 @@ private void startGui(String testFile) {
System.out.println("Check : https://jmeter.apache.org/usermanual/best-practices.html");//NOSONAR
System.out.println("================================================================================");//NOSONAR

KerningOptimizer.INSTANCE.setMaxTextLengthWithKerning(
JMeterUtils.getPropDefault("text.kerning.max_document_size", 10000)
);
JMeterUIDefaults.INSTANCE.install();

String jMeterLaf = LookAndFeelCommand.getPreferredLafCommand();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.jorphan.gui.ui;

import java.awt.Font;
import java.awt.font.TextAttribute;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.ref.WeakReference;
import java.util.Collections;

import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;

import org.apiguardian.api.API;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Text rendering might be slow for long lines when kerning is enabled, so it is worth disabling kerning for long
* texts.
*/
@API(since = "5.5", status = API.Status.INTERNAL)
public class KerningOptimizer {
private final static Logger log = LoggerFactory.getLogger(KerningOptimizer.class);

public static final KerningOptimizer INSTANCE = new KerningOptimizer();

private volatile int maxLengthWithKerning = 10000;

/**
* Cache to avoid repeated calls to {@link Font#getAttributes()} since it copies the map every time.
*/
static class FontKerningCache {
private static final String CLIENT_PROPERTY_KEY = "[jmeter]FontKerningCache";
WeakReference<Font> font;
boolean kerning;

static Boolean kerningOf(JComponent component) {
Font font = component.getFont();
if (font == null) {
return null;
}
if (!font.hasLayoutAttributes()) {
return false;
}
FontKerningCache cache = (FontKerningCache) component.getClientProperty(CLIENT_PROPERTY_KEY);
if (cache == null) {
cache = new FontKerningCache();
component.putClientProperty(CLIENT_PROPERTY_KEY, cache);
}
if (cache.font == null || !font.equals(cache.font.get())) {
cache.font = new WeakReference<>(font);
cache.kerning = TextAttribute.KERNING_ON.equals(font.getAttributes().get(TextAttribute.KERNING));
}
return cache.kerning;
}
}

/**
* Configures the maximum document length for rendering with kerning enabled.
*
* @param length maximum document length for rendering with kerning enabled
*/
public void setMaxTextLengthWithKerning(int length) {
maxLengthWithKerning = length;
}

public int getMaxTextLengthWithKerning() {
return maxLengthWithKerning;
}

/**
* Configures text kerning according to the expected document length. This might be useful before setting the
* document so the kerning is disabled before updating the document.
*
* @param component text component for kerning configuration
* @param documentLength expected document length
*/
public void configureKerning(JComponent component, int documentLength) {
Boolean kerning = FontKerningCache.kerningOf(component);
if (kerning == null) {
return;
}
boolean desiredKerning = documentLength <= maxLengthWithKerning;
if (kerning != desiredKerning) {
if (log.isDebugEnabled()) {
log.info("Updating kerning (old: {}, new: {}), documentLength={}, component {}, ", kerning, desiredKerning, documentLength, component);
}
Font font = component.getFont();
Font newFont = font.deriveFont(Collections.singletonMap(TextAttribute.KERNING, desiredKerning ? TextAttribute.KERNING_ON : 0));
SwingUtilities.invokeLater(() -> component.setFont(newFont));
}
}

/**
* Adds a listener that disables kerning if text length reaches a certain threshold.
*
* @param textComponent text component for kerning configuration
*/
public void installKerningListener(JTextComponent textComponent) {
log.debug("Installing KerningOptimizer {} to {}", this, textComponent);
textComponent.addPropertyChangeListener("document", new DisableKerningForLargeTexts(textComponent));
}

/**
* Removes the listener that disables kerning if text length reaches a certain threshold.
*
* @param textComponent text component for kerning configuration
*/
public void uninstallKerningListener(JTextComponent textComponent) {
DisableKerningForLargeTexts kerningListener = null;
for (PropertyChangeListener listener : textComponent.getPropertyChangeListeners("document")) {
if (listener instanceof DisableKerningForLargeTexts) {
kerningListener = (DisableKerningForLargeTexts) listener;
}
}
if (kerningListener == null) {
return;
}
log.debug("Uninstalling KerningOptimizer {} from {}", this, textComponent);
Document document = textComponent.getDocument();
if (document != null) {
document.removeDocumentListener(kerningListener);
}
textComponent.removePropertyChangeListener("document", kerningListener);
}

static class DisableKerningForLargeTexts implements PropertyChangeListener, DocumentListener {
final JTextComponent component;

DisableKerningForLargeTexts(JTextComponent component) {
this.component = component;
}

private void configureKerning(Document e) {
// RSyntaxTextArea and other implementations do not expect setFont called from document change listeners
// It looks like invokeLater fixes that
Boolean kerning = FontKerningCache.kerningOf(component);
if (kerning == null) {
return;
}
boolean desiredKerning = e.getLength() <= INSTANCE.getMaxTextLengthWithKerning();
if (kerning != desiredKerning) {
SwingUtilities.invokeLater(() -> INSTANCE.configureKerning(component, e.getLength()));
}
}

@Override
public void propertyChange(PropertyChangeEvent evt) {
if (!"document".equals(evt.getPropertyName())) {
return;
}
Document oldDocument = (Document) evt.getOldValue();
if (oldDocument != null) {
oldDocument.removeDocumentListener(this);
}
Document newDocument = (Document) evt.getNewValue();
if (newDocument != null) {
newDocument.addDocumentListener(this);
configureKerning(newDocument);
}
}


@Override
public void insertUpdate(DocumentEvent e) {
configureKerning(e.getDocument());
}

@Override
public void removeUpdate(DocumentEvent e) {
configureKerning(e.getDocument());
}

@Override
public void changedUpdate(DocumentEvent e) {
configureKerning(e.getDocument());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ public static void install(UIDefaults defaults) {
*/
@SuppressWarnings("unused")
public static ComponentUI createUI(JComponent component) {
KerningOptimizer.INSTANCE.installKerningListener((JTextComponent) component);
TextComponentUI.INSTANCE.installUndo((JTextComponent) component);
if (component.getClass() == JTextArea.class) {
component.addPropertyChangeListener("UI",
Expand Down
Loading