【问题标题】:JMeter JDBC sampler fails on large ResultSetJMeter JDBC 采样器在大型 ResultSet 上失败
【发布时间】:2017-05-10 19:40:56
【问题描述】:

在我的 JMeter 3.2 运行测试中,我有一个 JDBC 采样器,其设置如下:

<JDBCSampler enabled="true" guiclass="TestBeanGUI" testclass="JDBCSampler" testname="query1">
  <stringProp name="dataSource">jdbcConfig_tpcds</stringProp>
  <stringProp name="query">${__FileToString(sql/query1.sql)}</stringProp>
  <stringProp name="queryArguments"/>
  <stringProp name="queryArgumentsTypes"/>
  <stringProp name="queryType">Select Statement</stringProp>
  <stringProp name="resultVariable"/>
  <stringProp name="variableNames"/>
  <stringProp name="queryTimeout"/>
  <stringProp name="resultSetHandler">Count Records</stringProp>
</JDBCSampler>

查询测试数据库性能并返回非常大的 ResultSet(~10M 记录)。 我不关心结果本身,所以 resultSetHandler 选项设置为 Count Records 所以我的期望是我会得到一个行数并且 ResultSet 将被关闭。然而,在这个长查询结束时,当 db 完成时,JMeter 失败并出现 OutOfMemoryError: Java heap space 错误,这很可能是由于 Java 试图处理可怕的 ResultSet。

我可以使用股票 JDBC 采样器做些什么还是需要自己编写?

我不能通过向查询中添加 LIMIT 等来限制 ResultSet,因为它是一个性能查询

附:这看起来不太好,因为在查看堆栈跟踪之后很明显,无论resultSetHandler 的值如何,JMeter 代码仍然循环遍历整个 ResultSet 并尝试将整个集合吸入内存

2017/05/05 00:07:42 ERROR - jmeter.threads.JMeterThread: Test failed! java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3332)
    at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
    at java.lang.StringBuilder.append(StringBuilder.java:136)
    at java.lang.StringBuilder.append(StringBuilder.java:131)
    at org.apache.jmeter.protocol.jdbc.AbstractJDBCTestElement.getStringFromResultSet(AbstractJDBCTestElement.java:548)
    at org.apache.jmeter.protocol.jdbc.AbstractJDBCTestElement.execute(AbstractJDBCTestElement.java:175)
    at org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler.sample(JDBCSampler.java:89)

【问题讨论】:

  • 你是如何运行这些测试的?非 GUI 模式在侦听器数量较少的情况下更可取。
  • @NaveenKumarNamachivayam 是的,非 gui。但是我没有看到相关性。实际上,我花了一个下午的时间看代码。除非您正在执行存储过程(可调用语句),否则甚至不会考虑该设置
  • 你的JMeter堆内存是多少?

标签: jdbc jmeter performance-testing resultset


【解决方案1】:

解决方案在:

【讨论】:

  • 1 和 2 不适用于我的情况。通过简单地扩展 JDBCSampler,我最终得到了 #3
【解决方案2】:

这里有 2 个文件需要放入 JAR 并添加到 JMeter/lib/ext。这将为您提供具有“丢弃记录”选项的自定义 JDBC 采样器。启用该选项后,只会提取第一条记录,然后关闭结果集。您可以通过注释掉代码来禁用第一行拉动

package test.jmeter.protocol.jdbc.sampler;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.jmeter.protocol.jdbc.sampler.JDBCSampler;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.threads.JMeterVariables;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DiscardResultsJDBCSampler extends JDBCSampler {
    private static final long serialVersionUID = 3528634569296356066L;
    private static final Logger logger = LoggerFactory.getLogger(DiscardResultsJDBCSampler.class);
    static final String SELECT = "Select Statement";
    static final String RS_DISCARD_RECORDS = "Discard Records";

    @Override
    protected byte[] execute(final Connection conn, final SampleResult sample)
            throws SQLException, IOException, UnsupportedOperationException {
        if (SELECT.equals(getQueryType()) && RS_DISCARD_RECORDS.equals(getResultSetHandler()))
            return processCountOnly(conn, sample);
        return super.execute(conn, sample);
    }

    private byte[] processCountOnly(final Connection conn, final SampleResult sample)
            throws SQLException, UnsupportedEncodingException {
        Statement stmt = null;
        try {
            stmt = conn.createStatement();
            stmt.setFetchSize(1);
            stmt.setQueryTimeout(getIntegerQueryTimeout());
            ResultSet rs = null;
            try {
                final String query = getQuery();
                logger.info("Calling: " + query);
                rs = stmt.executeQuery(query);
                logger.info("Got result set, processing");
                final ResultSetMetaData meta = rs.getMetaData();
                final StringBuilder sb = new StringBuilder();
                final int numColumns = meta.getColumnCount();
                for (int i = 1; i <= numColumns; i++) {
                    sb.append(meta.getColumnLabel(i));
                    if (i == numColumns) {
                        sb.append('\n');
                    } else {
                        sb.append('\t');
                    }
                }

                final JMeterVariables jmvars = getThreadContext().getVariables();
                final String[] varNames = getVariableNames().split(",");
                final String resultVariable = getResultVariable().trim();
                List<Map<String, Object>> results = null;
                if (resultVariable.length() > 0) {
                    results = new ArrayList<>();
                    jmvars.putObject(resultVariable, results);
                }
                logger.info("Goint to call rs.next()");
                if (rs.next()) {
                    logger.info("Processing first record");
                    Map<String, Object> row = null;
                    for (int i = 1; i <= numColumns; i++) {
                        Object o = rs.getObject(i);
                        if (results != null) {
                            if (row == null) {
                                row = new HashMap<>(numColumns);
                                results.add(row);
                            }
                            row.put(meta.getColumnLabel(i), o);
                        }
                        if (o instanceof byte[]) {
                            o = new String((byte[]) o, ENCODING);
                        }
                        sb.append(o);
                        if (i == numColumns) {
                            sb.append('\n');
                        } else {
                            sb.append('\t');
                        }
                        if (i <= varNames.length) { // i starts at 1
                            final String name = varNames[i - 1].trim();
                            if (name.length() > 0) { // Save the value in the variable if present
                                jmvars.put(name + "_" + 0, o == null ? null : o.toString());
                            }
                        }
                    }
                }
                final String firstRow = sb.toString();
                logger.info("First row results: " + firstRow);
                sample.latencyEnd();
                return firstRow == null ? new byte[0] : firstRow.getBytes(ENCODING);
            } finally {
                logger.info("Done with result set, cleaning up and closing the result set");
                close(rs);
            }
        } finally {
            logger.info("Done with the statement, cleaning up and closing");
            close(stmt);
            logger.info("All finished, exiting");
        }
    }

}

和bean信息文件

package test.jmeter.protocol.jdbc.sampler;

import java.beans.PropertyDescriptor;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.jmeter.protocol.jdbc.JDBCTestElementBeanInfoSupport;

public class DiscardResultsJDBCSamplerBeanInfo extends JDBCTestElementBeanInfoSupport {

    /**
     * @param beanClass
     */
    public DiscardResultsJDBCSamplerBeanInfo() {
        super(DiscardResultsJDBCSampler.class);

        // Add "Discard Records" option
        final PropertyDescriptor p = property("resultSetHandler");
        String[] tags = (String[]) p.getValue(TAGS);
        tags = ArrayUtils.add(tags, DiscardResultsJDBCSampler.RS_DISCARD_RECORDS);
        p.setValue(TAGS, tags);
    }

}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-05-01
    • 2019-06-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多