org.apache.jmeter.samplers.SampleResult Maven / Gradle / Ivy
/*
* 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.jmeter.samplers;
import org.apache.jmeter.assertions.AssertionResult;
import org.apache.jmeter.gui.Searchable;
import org.apache.jmeter.testelement.TestPlan;
import org.apache.jmeter.threads.JMeterContext.TestLogicalAction;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jorphan.util.JOrphanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
// For unit tests, @see TestSampleResult
/**
* This is a nice packaging for the various information returned from taking a
* sample of an entry.
*
*/
public class SampleResult implements Serializable, Cloneable, Searchable {
private static final long serialVersionUID = 241L;
// Needs to be accessible from Test code
static Logger log = LoggerFactory.getLogger(SampleResult.class);
/**
* The default encoding to be used if not overridden.
* The value is ISO-8859-1.
*/
public static final String DEFAULT_HTTP_ENCODING = StandardCharsets.ISO_8859_1.name();
private static final String OK_CODE = Integer.toString(HttpURLConnection.HTTP_OK);
private static final String OK_MSG = "OK"; // $NON-NLS-1$
private static final String INVALID_CALL_SEQUENCE_MSG = "Invalid call sequence"; // $NON-NLS-1$
// Bug 33196 - encoding ISO-8859-1 is only suitable for Western countries
// However the suggested System.getProperty("file.encoding") is Cp1252 on
// Windows
// So use a new property with the original value as default
// needs to be accessible from test code
/**
* The default encoding to be used to decode the responseData byte array.
* The value is defined by the property "sampleresult.default.encoding"
* with a default of DEFAULT_HTTP_ENCODING if that is not defined.
*/
protected static final String DEFAULT_ENCODING
= JMeterUtils.getPropDefault("sampleresult.default.encoding", // $NON-NLS-1$
DEFAULT_HTTP_ENCODING);
/**
* The default used by {@link #setResponseData(String, String)}
*/
private static final String DEFAULT_CHARSET = Charset.defaultCharset().name();
/**
* Data type value ({@value}) indicating that the response data is text.
*
* @see #getDataType
* @see #setDataType(java.lang.String)
*/
public static final String TEXT = "text"; // $NON-NLS-1$
/**
* Data type value ({@value}) indicating that the response data is binary.
*
* @see #getDataType
* @see #setDataType(java.lang.String)
*/
public static final String BINARY = "bin"; // $NON-NLS-1$
private static final boolean DISABLE_SUBRESULTS_RENAMING = JMeterUtils.getPropDefault("subresults.disable_renaming", false);
// List of types that are known to be binary
private static final String[] BINARY_TYPES = {
"image/", //$NON-NLS-1$
"audio/", //$NON-NLS-1$
"video/", //$NON-NLS-1$
};
// List of types that are known to be ascii, although they may appear to be binary
private static final String[] NON_BINARY_TYPES = {
"audio/x-mpegurl", //$NON-NLS-1$ (HLS Media Manifest)
"audio/mpegurl", //$NON-NLS-1$ (HLS Media Manifest)
"video/f4m", //$NON-NLS-1$ (Flash Media Manifest)
"image/svg+xml" //$NON-NLS-1$ (SVG is xml)
};
/**
* empty array which can be returned instead of null
*/
private static final byte[] EMPTY_BA = new byte[0];
private static final SampleResult[] EMPTY_SR = new SampleResult[0];
private static final AssertionResult[] EMPTY_AR = new AssertionResult[0];
private static final boolean START_TIMESTAMP =
JMeterUtils.getPropDefault("sampleresult.timestamp.start", false); // $NON-NLS-1$
/**
* Allow read-only access from test code
*/
private static final boolean USE_NANO_TIME =
JMeterUtils.getPropDefault("sampleresult.useNanoTime", true); // $NON-NLS-1$
/**
* How long between checks of nanotime; default 5000ms; set to <=0 to disable the thread
*/
private static final long NANOTHREAD_SLEEP =
JMeterUtils.getPropDefault("sampleresult.nanoThreadSleep", 5000); // $NON-NLS-1$
private static final String NULL_FILENAME = "NULL";
static {
if (START_TIMESTAMP) {
log.info("Note: Sample TimeStamps are START times");
} else {
log.info("Note: Sample TimeStamps are END times");
}
log.info("sampleresult.default.encoding is set to {}", DEFAULT_ENCODING);
log.info("sampleresult.useNanoTime={}", USE_NANO_TIME);
log.info("sampleresult.nanoThreadSleep={}", NANOTHREAD_SLEEP);
if (USE_NANO_TIME && NANOTHREAD_SLEEP > 0) {
// Make sure we start with a reasonable value
NanoOffset.nanoOffset = System.currentTimeMillis() - SampleResult.sampleNsClockInMs();
NanoOffset nanoOffset = new NanoOffset();
nanoOffset.setDaemon(true);
nanoOffset.setName("NanoOffset");
nanoOffset.start();
}
}
/**
* 定制自定义添加 =================
*/
private String samplerId;
private String resourceId;
private String extVars;
public String getSamplerId() {
return this.samplerId;
}
public String getResourceId() {
return this.resourceId;
}
// 数据格式 List 多层父级按照同级统计
private String scenario;
public String getScenario() {
return this.scenario;
}
public void setExtVars(String vars) {
this.extVars = vars;
}
public String getExtVars() {
return this.extVars;
}
/**
* 定制自定义添加 =================
*/
private SampleSaveConfiguration saveConfig;
private SampleResult parent;
private byte[] responseData = EMPTY_BA;
private String responseCode = "";// Never return null
private String label = "";// Never return null
/** Filename used by ResultSaver */
private String resultFileName = "";
/** The data used by the sampler */
private String samplerData;
private String threadName = ""; // Never return null
private String responseMessage = "";
private String responseHeaders = ""; // Never return null
private String requestHeaders = "";
/**
* timeStamp == 0 means either not yet initialised or no stamp available (e.g. when loading a results file)
* the time stamp - can be start or end
*/
private long timeStamp = 0;
private long startTime = 0;
private long endTime = 0;
private long idleTime = 0;// Allow for non-sample time
/** Start of pause (if any) */
private long pauseTime = 0;
private List assertionResults;
private List subResults;
/**
* The data type of the sample
*
* @see #getDataType()
* @see #setDataType(String)
* @see #TEXT
* @see #BINARY
*/
private String dataType = ""; // Don't return null if not set
private boolean success;
/**
* Files that this sample has been saved in.
* In Non GUI mode and when best config is used, size never exceeds 1,
* but as a compromise set it to 2
*/
private final Set files = ConcurrentHashMap.newKeySet(2);
// TODO do contentType and/or dataEncoding belong in HTTPSampleResult instead?
private String dataEncoding;// (is this really the character set?) e.g.
// ISO-8895-1, UTF-8
private String contentType = ""; // e.g. text/html; charset=utf-8
/** elapsed time */
private long elapsedTime = 0;
/** time to first response */
private long latency = 0;
/** time to end connecting */
private long connectTime = 0;
/** Way to signal what to do on Test */
private TestLogicalAction testLogicalAction = TestLogicalAction.CONTINUE;
/** Should thread terminate? */
private boolean stopThread = false;
/** Should test terminate? */
private boolean stopTest = false;
/** Should test terminate abruptly? */
private boolean stopTestNow = false;
private int sampleCount = 1;
private long bytes = 0; // Allows override of sample size in case sampler does not want to store all the data
private int headersSize = 0;
private long bodySize = 0;
/** Currently active threads in this thread group */
private volatile int groupThreads = 0;
/** Currently active threads in all thread groups */
private volatile int allThreads = 0;
private final long nanoTimeOffset;
// Allow testcode access to the settings
final boolean useNanoTime;
final long nanoThreadSleep;
private long sentBytes;
private URL location;
private transient boolean ignore;
private transient int subResultIndex;
/**
* Cache for responseData as string to avoid multiple computations
*/
private transient volatile String responseDataAsString;
public SampleResult() {
this(USE_NANO_TIME, NANOTHREAD_SLEEP);
}
// Allow test code to change the default useNanoTime setting
SampleResult(boolean nanoTime) {
this(nanoTime, NANOTHREAD_SLEEP);
}
// Allow test code to change the default useNanoTime and nanoThreadSleep settings
SampleResult(boolean nanoTime, long nanoThreadSleep) {
this.elapsedTime = 0;
this.useNanoTime = nanoTime;
this.nanoThreadSleep = nanoThreadSleep;
this.nanoTimeOffset = initOffset();
// 增加请求ID的获取
Sampler sampler = JMeterContextService.getContext().getCurrentSampler();
if (sampler != null) {
this.samplerId = sampler.getPropertyAsString("MS-ID");
this.resourceId = sampler.getPropertyAsString("MS-RESOURCE-ID");
this.scenario = sampler.getPropertyAsString("MS-SCENARIO");
}
}
/**
* Copy constructor.
*
* @param res existing sample result
*/
public SampleResult(SampleResult res) {
this();
allThreads = res.allThreads;//OK
assertionResults = res.assertionResults;
bytes = res.bytes;
headersSize = res.headersSize;
bodySize = res.bodySize;
contentType = res.contentType;//OK
dataEncoding = res.dataEncoding;//OK
dataType = res.dataType;//OK
endTime = res.endTime;//OK
// files is created automatically, and applies per instance
groupThreads = res.groupThreads;//OK
idleTime = res.idleTime;
label = res.label;//OK
latency = res.latency;
connectTime = res.connectTime;
location = res.location;//OK
parent = res.parent;
pauseTime = res.pauseTime;
requestHeaders = res.requestHeaders;//OK
responseCode = res.responseCode;//OK
responseData = res.responseData;//OK
responseDataAsString = null;
responseHeaders = res.responseHeaders;//OK
responseMessage = res.responseMessage;//OK
// Don't copy this; it is per instance resultFileName = res.resultFileName;
sampleCount = res.sampleCount;
samplerData = res.samplerData;
saveConfig = res.saveConfig;
sentBytes = res.sentBytes;
startTime = res.startTime;//OK
stopTest = res.stopTest;
stopTestNow = res.stopTestNow;
stopThread = res.stopThread;
testLogicalAction = res.testLogicalAction;
subResults = res.subResults;
success = res.success;//OK
threadName = res.threadName;//OK
elapsedTime = res.elapsedTime;
timeStamp = res.timeStamp;
}
/**
* Create a sample with a specific elapsed time but don't allow the times to
* be changed later
*
* (only used by HTTPSampleResult)
*
* @param elapsed
* time
* @param atend
* create the sample finishing now, else starting now
*/
protected SampleResult(long elapsed, boolean atend) {
this();
long now = currentTimeInMillis();
if (atend) {
setTimes(now - elapsed, now);
} else {
setTimes(now, now + elapsed);
}
}
/**
* Allow users to create a sample with specific timestamp and elapsed times
* for cloning purposes, but don't allow the times to be changed later
*
* Currently used by CSVSaveService and
* StatisticalSampleResult
*
* @param stamp this may be a start time or an end time (both in
* milliseconds)
* @param elapsed time in milliseconds
*/
public SampleResult(long stamp, long elapsed) {
this();
stampAndTime(stamp, elapsed);
}
private long initOffset() {
if (useNanoTime) {
return nanoThreadSleep > 0 ? NanoOffset.getNanoOffset() : System.currentTimeMillis() - sampleNsClockInMs();
} else {
return Long.MIN_VALUE;
}
}
/**
* @param propertiesToSave
* The propertiesToSave to set.
*/
public void setSaveConfig(SampleSaveConfiguration propertiesToSave) {
this.saveConfig = propertiesToSave;
}
public SampleSaveConfiguration getSaveConfig() {
return saveConfig;
}
public boolean isStampedAtStart() {
return START_TIMESTAMP;
}
/**
* Create a sample with specific start and end times for test purposes, but
* don't allow the times to be changed later
*
* (used by StatVisualizerModel.Test)
*
* @param start
* start time in milliseconds since unix epoch
* @param end
* end time in milliseconds since unix epoch
* @return sample with given start and end time
*/
public static SampleResult createTestSample(long start, long end) {
SampleResult res = new SampleResult();
res.setStartTime(start);
res.setEndTime(end);
return res;
}
/**
* Create a sample with a specific elapsed time for test purposes, but don't
* allow the times to be changed later
*
* @param elapsed
* - desired elapsed time in milliseconds
* @return sample that starts 'now' and ends elapsed
milliseconds later
*/
public static SampleResult createTestSample(long elapsed) {
long now = System.currentTimeMillis();
return createTestSample(now, now + elapsed);
}
private static long sampleNsClockInMs() {
return System.nanoTime() / 1000000;
}
/**
* Helper method to get 1 ms resolution timing.
*
* @return the current time in milliseconds
* @throws RuntimeException
* when useNanoTime
is true
but
* nanoTimeOffset
is not set
*/
public long currentTimeInMillis() {
if (useNanoTime) {
if (nanoTimeOffset == Long.MIN_VALUE) {
throw new IllegalStateException("Invalid call; nanoTimeOffset has not been set");
}
return sampleNsClockInMs() + nanoTimeOffset;
}
return System.currentTimeMillis();
}
// Helper method to maintain timestamp relationships
private void stampAndTime(long stamp, long elapsed) {
if (START_TIMESTAMP) {
startTime = stamp;
endTime = stamp + elapsed;
} else {
startTime = stamp - elapsed;
endTime = stamp;
}
timeStamp = stamp;
elapsedTime = elapsed;
}
/**
* For use by SaveService only.
*
* @param stamp
* this may be a start time or an end time (both in milliseconds)
* @param elapsed
* time in milliseconds
* @throws RuntimeException
* when startTime
or endTime
has been
* set already
*/
public void setStampAndTime(long stamp, long elapsed) {
if (startTime != 0 || endTime != 0) {
throw new IllegalStateException("Calling setStampAndTime() after start/end times have been set");
}
stampAndTime(stamp, elapsed);
}
/**
* Set the "marked" flag to show that the result has been written to the file.
*
* @param filename the name of the file
* @return true
if the result was previously marked
*/
public boolean markFile(String filename) {
return !files.add(filename != null ? filename : NULL_FILENAME);
}
public String getResponseCode() {
return responseCode;
}
/**
* Set response code to OK, i.e. "200"
*
*/
public void setResponseCodeOK() {
responseCode = OK_CODE;
}
public void setResponseCode(String code) {
responseCode = code;
}
public boolean isResponseCodeOK() {
return responseCode.equals(OK_CODE);
}
public String getResponseMessage() {
return responseMessage;
}
public void setResponseMessage(String msg) {
responseMessage = msg;
}
public void setResponseMessageOK() {
responseMessage = OK_MSG;
}
/**
* Set result statuses OK - shorthand method to set:
*
* - ResponseCode
* - ResponseMessage
* - Successful status
*
*/
public void setResponseOK() {
setResponseCodeOK();
setResponseMessageOK();
setSuccessful(true);
}
public String getThreadName() {
return threadName;
}
public void setThreadName(String threadName) {
this.threadName = threadName;
}
/**
* Get the sample timestamp, which may be either the start time or the end time.
*
* @see #getStartTime()
* @see #getEndTime()
*
* @return timeStamp in milliseconds
*/
public long getTimeStamp() {
return timeStamp;
}
public String getSampleLabel() {
return label;
}
/**
* Get the sample label for use in summary reports etc.
*
* @param includeGroup whether to include the thread group name
* @return the label
*/
public String getSampleLabel(boolean includeGroup) {
if (includeGroup) {
// while JMeters own samplers always set the threadName, that might not be the case for plugins
int lastSpacePos = Math.max(0, threadName.lastIndexOf(' '));
return threadName.substring(0, lastSpacePos) + ":" + label;
}
return label;
}
public void setSampleLabel(String label) {
this.label = label;
}
public void addAssertionResult(AssertionResult assertResult) {
if (assertionResults == null) {
assertionResults = new ArrayList<>();
}
assertionResults.add(assertResult);
}
/**
* Gets the assertion results associated with this sample.
*
* @return an array containing the assertion results for this sample.
* Returns empty array if there are no assertion results.
*/
public AssertionResult[] getAssertionResults() {
if (assertionResults == null) {
return EMPTY_AR;
}
return assertionResults.toArray(new AssertionResult[assertionResults.size()]);
}
/**
* Add a subresult and adjust the parent byte count and end-time.
*
* @param subResult
* the {@link SampleResult} to be added
*/
public void addSubResult(SampleResult subResult) {
addSubResult(subResult, isRenameSampleLabel());
}
/**
* see https://bz.apache.org/bugzilla/show_bug.cgi?id=63055
* @return true if TestPlan is in functional mode or property subresults.disable_renaming is true
*/
public static boolean isRenameSampleLabel() {
return !(TestPlan.getFunctionalMode() || DISABLE_SUBRESULTS_RENAMING);
}
/**
* Add a subresult and adjust the parent byte count and end-time.
*
* @param subResult
* the {@link SampleResult} to be added
* @param renameSubResults boolean do we rename subResults based on position
*/
public void addSubResult(SampleResult subResult, boolean renameSubResults) {
if (subResult == null) {
// see https://bz.apache.org/bugzilla/show_bug.cgi?id=54778
return;
}
String tn = getThreadName();
if (tn.length() == 0) {
tn = Thread.currentThread().getName();
this.setThreadName(tn);
}
subResult.setThreadName(tn);
// Extend the time to the end of the added sample
setEndTime(Math.max(getEndTime(), subResult.getEndTime() + nanoTimeOffset - subResult.nanoTimeOffset)); // Bug 51855
// Include the byte count for the added sample
setBytes(getBytesAsLong() + subResult.getBytesAsLong());
setSentBytes(getSentBytes() + subResult.getSentBytes());
setHeadersSize(getHeadersSize() + subResult.getHeadersSize());
setBodySize(getBodySizeAsLong() + subResult.getBodySizeAsLong());
addRawSubResult(subResult, renameSubResults);
}
/**
* Add a subresult to the collection without updating any parent fields.
*
* @param subResult
* the {@link SampleResult} to be added
*/
public void addRawSubResult(SampleResult subResult) {
storeSubResult(subResult, isRenameSampleLabel());
}
/**
* Add a subresult to the collection without updating any parent fields.
*
* @param subResult
* the {@link SampleResult} to be added
*/
private void addRawSubResult(SampleResult subResult, boolean renameSubResults) {
storeSubResult(subResult, renameSubResults);
}
/**
* Add a subresult read from a results file.
*
* As for {@link SampleResult#addSubResult(SampleResult)
* addSubResult(SampleResult)}, except that the fields don't need to be
* accumulated
*
* @param subResult
* the {@link SampleResult} to be added
*/
public void storeSubResult(SampleResult subResult) {
storeSubResult(subResult, isRenameSampleLabel());
}
/**
* Add a subresult read from a results file.
*
* As for {@link SampleResult#addSubResult(SampleResult)
* addSubResult(SampleResult)}, except that the fields don't need to be
* accumulated
*
* @param subResult
* the {@link SampleResult} to be added
* @param renameSubResults boolean do we rename subResults based on position
*/
public void storeSubResult(SampleResult subResult, boolean renameSubResults) {
if (subResults == null) {
subResults = new ArrayList<>();
}
if (renameSubResults) {
subResult.setSampleLabel(getSampleLabel() + "-" + subResultIndex++);
}
subResults.add(subResult);
subResult.setParent(this);
}
/**
* Gets the subresults associated with this sample.
*
* @return an array containing the subresults for this sample. Returns an
* empty array if there are no subresults.
*/
public SampleResult[] getSubResults() {
if (subResults == null) {
return EMPTY_SR;
}
return subResults.toArray(new SampleResult[subResults.size()]);
}
/**
* Sets the responseData attribute of the SampleResult object.
*
* If the parameter is null, then the responseData is set to an empty byte array.
* This ensures that getResponseData() can never be null.
*
* @param response
* the new responseData value
*/
public void setResponseData(byte[] response) {
responseDataAsString = null;
responseData = response == null ? EMPTY_BA : response;
}
/**
* Sets the responseData attribute of the SampleResult object.
* Should only be called after setting the dataEncoding (if necessary)
*
* @param response
* the new responseData value (String)
*
* @deprecated - only intended for use from BeanShell code
*/
@Deprecated
public void setResponseData(String response) {
responseDataAsString = null;
try {
responseData = response.getBytes(getDataEncodingWithDefault());
} catch (UnsupportedEncodingException e) {
log.warn("Could not convert string, using default encoding. {}", e.getLocalizedMessage());
responseData = response.getBytes(Charset.defaultCharset()); // N.B. default charset is used deliberately here
}
}
/**
* Sets the encoding and responseData attributes of the SampleResult object.
*
* @param response the new responseData value (String)
* @param encoding the encoding to set and then use (if null, use platform default)
*
*/
public void setResponseData(final String response, final String encoding) {
responseDataAsString = null;
String encodeUsing = encoding != null ? encoding : DEFAULT_CHARSET;
try {
responseData = response.getBytes(encodeUsing);
setDataEncoding(encodeUsing);
} catch (UnsupportedEncodingException e) {
log.warn("Could not convert string using '{}', using default encoding: {}", encodeUsing, DEFAULT_CHARSET,
e);
responseData = response.getBytes(Charset.defaultCharset()); // N.B. default charset is used deliberately here
setDataEncoding(DEFAULT_CHARSET);
}
}
/**
* Gets the responseData attribute of the SampleResult object.
*
* Note that some samplers may not store all the data, in which case
* getResponseData().length will be incorrect.
*
* Instead, always use {@link #getBytes()} to obtain the sample result byte count.
*
* @return the responseData value (cannot be null)
*/
public byte[] getResponseData() {
return responseData;
}
/**
* Gets the responseData of the SampleResult object as a String
*
* @return the responseData value as a String, converted according to the encoding
*/
public String getResponseDataAsString() {
try {
if (responseDataAsString == null) {
responseDataAsString = new String(responseData, getDataEncodingWithDefault());
}
return responseDataAsString;
} catch (UnsupportedEncodingException e) {
log.warn("Using platform default as {} caused {}", getDataEncodingWithDefault(), e.getLocalizedMessage());
return new String(responseData, Charset.defaultCharset()); // N.B. default charset is used deliberately here
}
}
public void setSamplerData(String s) {
samplerData = s;
}
public String getSamplerData() {
return samplerData;
}
/**
* Get the time it took this sample to occur.
*
* @return elapsed time in milliseconds
*
*/
public long getTime() {
return elapsedTime;
}
public boolean isSuccessful() {
return success;
}
/**
* Sets the data type of the sample.
* @param dataType String containing {@link #BINARY} or {@link #TEXT}
* @see #BINARY
* @see #TEXT
*/
public void setDataType(String dataType) {
this.dataType = dataType;
}
/**
* Returns the data type of the sample.
*
* @return String containing {@link #BINARY} or {@link #TEXT} or the empty string
* @see #BINARY
* @see #TEXT
*/
public String getDataType() {
return dataType;
}
/**
* Extract and save the DataEncoding and DataType from the parameter provided.
* Does not save the full content Type.
* @see #setContentType(String) which should be used to save the full content-type string
*
* @param ct - content type (may be null)
*/
public void setEncodingAndType(String ct) {
if (ct != null) {
// Extract charset and store as DataEncoding
// N.B. The meta tag:
//
// is now processed by HTTPSampleResult#getDataEncodingWithDefault
final String charsetPrefix = "charset="; // $NON-NLS-1$
int cset = ct.toLowerCase(java.util.Locale.ENGLISH).indexOf(charsetPrefix);
if (cset >= 0) {
String charSet = ct.substring(cset + charsetPrefix.length());
// handle: ContentType: text/plain; charset=ISO-8859-1; format=flowed
int semiColon = charSet.indexOf(';');
if (semiColon >= 0) {
charSet = charSet.substring(0, semiColon);
}
// Check for quoted string
if (charSet.startsWith("\"") || charSet.startsWith("\'")) { // $NON-NLS-1$
setDataEncoding(charSet.substring(1, charSet.length() - 1)); // remove quotes
} else {
setDataEncoding(charSet);
}
}
if (isBinaryType(ct)) {
setDataType(BINARY);
} else {
setDataType(TEXT);
}
}
}
/*
* Determine if content-type is known to be binary, i.e. not displayable as text.
*
* @param ct content type
* @return true if content-type is of type binary.
*/
public static boolean isBinaryType(String ct) {
for (String entry : NON_BINARY_TYPES) {
if (ct.startsWith(entry)) {
return false;
}
}
for (String binaryType : BINARY_TYPES) {
if (ct.startsWith(binaryType)) {
return true;
}
}
return false;
}
/**
* Sets the successful attribute of the SampleResult object.
*
* @param success
* the new successful value
*/
public void setSuccessful(boolean success) {
this.success = success;
}
/**
* Returns the display name.
*
* @return display name of this sample result
*/
@Override
public String toString() {
return getSampleLabel();
}
/**
* Returns the dataEncoding or the default if no dataEncoding was provided.
*
* @return the value of the dataEncoding or DEFAULT_ENCODING
*/
public String getDataEncodingWithDefault() {
return getDataEncodingWithDefault(DEFAULT_ENCODING);
}
/**
* Returns the dataEncoding or the default if no dataEncoding was provided.
*
* @param defaultEncoding the default to be applied
* @return the value of the dataEncoding or the provided default
*/
protected String getDataEncodingWithDefault(String defaultEncoding) {
if (dataEncoding != null && dataEncoding.length() > 0) {
return dataEncoding;
}
return defaultEncoding;
}
/**
* Returns the dataEncoding. May be null or the empty String.
* @return the value of the dataEncoding
*/
public String getDataEncodingNoDefault() {
return dataEncoding;
}
/**
* Sets the dataEncoding.
*
* @param dataEncoding
* the dataEncoding to set, e.g. ISO-8895-1, UTF-8
*/
public void setDataEncoding(String dataEncoding) {
this.dataEncoding = dataEncoding;
}
/**
* @return whether to stop the test waiting for current running Sampler to end
*/
public boolean isStopTest() {
return stopTest;
}
/**
* @return whether to stop the test now interrupting current running samplers
*/
public boolean isStopTestNow() {
return stopTestNow;
}
/**
* @return whether to stop this thread
*/
public boolean isStopThread() {
return stopThread;
}
public void setStopTest(boolean b) {
stopTest = b;
}
public void setStopTestNow(boolean b) {
stopTestNow = b;
}
public void setStopThread(boolean b) {
stopThread = b;
}
/**
* @return the request headers
*/
public String getRequestHeaders() {
return requestHeaders;
}
/**
* @return the response headers
*/
public String getResponseHeaders() {
return responseHeaders;
}
/**
* @param string -
* request headers
*/
public void setRequestHeaders(String string) {
requestHeaders = string;
}
/**
* @param string -
* response headers
*/
public void setResponseHeaders(String string) {
responseHeaders = string;
}
/**
* @return the full content type - e.g. text/html [;charset=utf-8 ]
*/
public String getContentType() {
return contentType;
}
/**
* Get the media type from the Content Type
* @return the media type - e.g. text/html (without charset, if any)
*/
public String getMediaType() {
return JOrphanUtils.trim(contentType, " ;").toLowerCase(java.util.Locale.ENGLISH);
}
/**
* Stores the content-type string, e.g. text/xml; charset=utf-8
* @see #setEncodingAndType(String) which can be used to extract the charset.
*
* @param string the content-type to be set
*/
public void setContentType(String string) {
contentType = string;
}
/**
* @return idleTime
*/
public long getIdleTime() {
return idleTime;
}
/**
* @return the end time
*/
public long getEndTime() {
return endTime;
}
/**
* @return the start time
*/
public long getStartTime() {
return startTime;
}
/*
* Helper methods N.B. setStartTime must be called before setEndTime
*
* setStartTime is used by HTTPSampleResult to clone the parent sampler and
* allow the original start time to be kept
*/
public void setStartTime(long start) {
startTime = start;
if (START_TIMESTAMP) {
timeStamp = startTime;
}
}
public void setEndTime(long end) {
endTime = end;
if (!START_TIMESTAMP) {
timeStamp = endTime;
}
if (startTime == 0) {
log.error("setEndTime must be called after setStartTime", new Throwable(INVALID_CALL_SEQUENCE_MSG));
} else {
elapsedTime = endTime - startTime - idleTime;
}
}
/**
* Set idle time pause.
* For use by SampleResultConverter/CSVSaveService.
* @param idle long
*/
public void setIdleTime(long idle) {
idleTime = idle;
}
private void setTimes(long start, long end) {
setStartTime(start);
setEndTime(end);
}
/**
* Record the start time of a sample
*
*/
public void sampleStart() {
if (startTime == 0) {
setStartTime(currentTimeInMillis());
} else {
log.error("sampleStart called twice", new Throwable(INVALID_CALL_SEQUENCE_MSG));
}
}
/**
* Record the end time of a sample and calculate the elapsed time
*
*/
public void sampleEnd() {
if (endTime == 0) {
setEndTime(currentTimeInMillis());
} else {
log.error("sampleEnd called twice", new Throwable(INVALID_CALL_SEQUENCE_MSG));
}
}
/**
* Pause a sample
*
*/
public void samplePause() {
if (pauseTime != 0) {
log.error("samplePause called twice", new Throwable(INVALID_CALL_SEQUENCE_MSG));
}
pauseTime = currentTimeInMillis();
}
/**
* Resume a sample
*
*/
public void sampleResume() {
if (pauseTime == 0) {
log.error("sampleResume without samplePause", new Throwable(INVALID_CALL_SEQUENCE_MSG));
}
idleTime += currentTimeInMillis() - pauseTime;
pauseTime = 0;
}
/**
* When a Sampler is working as a monitor
*
* @param monitor
* flag whether this sampler is working as a monitor
*
* @deprecated since 3.2 NOOP
*/
@Deprecated
public void setMonitor(boolean monitor) {
// NOOP
}
/**
* If the sampler is a monitor, method will return true.
*
* @return true if the sampler is a monitor
* @deprecated since 3.2 always return false
*/
@Deprecated
public boolean isMonitor() {
return false;
}
/**
* The statistical sample sender aggregates several samples to save on
* transmission costs.
*
* @param count number of samples represented by this instance
*/
public void setSampleCount(int count) {
sampleCount = count;
}
/**
* return the sample count. by default, the value is 1.
*
* @return the sample count
*/
public int getSampleCount() {
return sampleCount;
}
/**
* Returns the count of errors.
*
* @return 0 - or 1 if the sample failed
*
* TODO do we need allow for nested samples?
*/
public int getErrorCount() {
return success ? 0 : 1;
}
public void setErrorCount(int i) {// for reading from CSV files
// ignored currently
}
/*
* TODO: error counting needs to be sorted out.
*
* At present the Statistical Sampler tracks errors separately
* It would make sense to move the error count here, but this would
* mean lots of changes.
* It's also tricky maintaining the count - it can't just be incremented/decremented
* when the success flag is set as this may be done multiple times.
* The work-round for now is to do the work in the StatisticalSampleResult,
* which overrides this method.
* Note that some JMS samplers also create samples with > 1 sample count
* Also the Transaction Controller probably needs to be changed to do
* proper sample and error accounting.
* The purpose of this work-round is to allow at least minimal support for
* errors in remote statistical batch mode.
*
*/
/**
* In the event the sampler does want to pass back the actual contents, we
* still want to calculate the throughput. The bytes are the bytes of the
* response data.
*
* @param length
* the number of bytes of the response data for this sample
*/
public void setBytes(long length) {
bytes = length;
}
/**
* In the event the sampler does want to pass back the actual contents, we
* still want to calculate the throughput. The bytes are the bytes of the
* response data.
*
* @param length
* the number of bytes of the response data for this sample
* @deprecated use setBytes(long)
*/
@Deprecated
public void setBytes(int length) {
setBytes((long) length);
}
/**
*
* @param sentBytesCount long sent bytes
*/
public void setSentBytes(long sentBytesCount) {
sentBytes = sentBytesCount;
}
/**
* @return the sentBytes
*/
public long getSentBytes() {
return sentBytes;
}
/**
* return the bytes returned by the response.
*
* @return byte count
* @deprecated use getBytesAsLong
*/
@Deprecated
public int getBytes() {
return (int) getBytesAsLong();
}
/**
* return the bytes returned by the response.
*
* @return byte count
*/
public long getBytesAsLong() {
long tmpSum = this.getHeadersSize() + this.getBodySizeAsLong();
return tmpSum == 0 ? bytes : tmpSum;
}
/**
* @return Returns the latency.
*/
public long getLatency() {
return latency;
}
/**
* Set the time to the first response
*
*/
public void latencyEnd() {
latency = currentTimeInMillis() - startTime - idleTime;
}
/**
* This is only intended for use by SampleResultConverter!
*
* @param latency
* The latency to set.
*/
public void setLatency(long latency) {
this.latency = latency;
}
/**
* @return Returns the connect time.
*/
public long getConnectTime() {
return connectTime;
}
/**
* Set the time to the end of connecting
*/
public void connectEnd() {
connectTime = currentTimeInMillis() - startTime - idleTime;
}
/**
* This is only intended for use by SampleResultConverter!
*
* @param time The connect time to set.
*/
public void setConnectTime(long time) {
this.connectTime = time;
}
/**
* This is only intended for use by SampleResultConverter!
*
* @param timeStamp
* The timeStamp to set.
*/
public void setTimeStamp(long timeStamp) {
this.timeStamp = timeStamp;
}
public void setURL(URL location) {
this.location = location;
}
public URL getURL() {
return location;
}
/**
* Get a String representation of the URL (if defined).
*
* @return ExternalForm of URL, or empty string if url is null
*/
public String getUrlAsString() {
return location == null ? "" : location.toExternalForm();
}
/**
* @return Returns the parent.
*/
public SampleResult getParent() {
return parent;
}
/**
* @param parent
* The parent to set.
*/
public void setParent(SampleResult parent) {
this.parent = parent;
}
public String getResultFileName() {
return resultFileName;
}
public void setResultFileName(String resultFileName) {
this.resultFileName = resultFileName;
}
public int getGroupThreads() {
return groupThreads;
}
public void setGroupThreads(int n) {
this.groupThreads = n;
}
public int getAllThreads() {
return allThreads;
}
public void setAllThreads(int n) {
this.allThreads = n;
}
// Bug 47394
/**
* Allow custom SampleSenders to drop unwanted assertionResults
*/
public void removeAssertionResults() {
this.assertionResults = null;
}
/**
* Allow custom SampleSenders to drop unwanted subResults
*/
public void removeSubResults() {
this.subResults = null;
}
/**
* Set the headers size in bytes
*
* @param size
* the number of bytes of the header
*/
public void setHeadersSize(int size) {
this.headersSize = size;
}
/**
* Get the headers size in bytes
*
* @return the headers size
*/
public int getHeadersSize() {
return headersSize;
}
/**
* @return the body size in bytes
* @deprecated replaced by getBodySizeAsLong()
*/
@Deprecated
public int getBodySize() {
return (int) getBodySizeAsLong();
}
/**
* @return the body size in bytes
*/
public long getBodySizeAsLong() {
return bodySize == 0 ? responseData.length : bodySize;
}
/**
* @param bodySize the body size to set
*/
public void setBodySize(long bodySize) {
this.bodySize = bodySize;
}
/**
* @param bodySize the body size to set
* @deprecated use setBodySize(long)
*/
@Deprecated
public void setBodySize(int bodySize) {
this.bodySize = bodySize;
}
private static class NanoOffset extends Thread {
private static volatile long nanoOffset;
static long getNanoOffset() {
return nanoOffset;
}
@Override
public void run() {
// Wait longer than a clock pulse (generally 10-15ms)
getOffset(30L); // Catch an early clock pulse to reduce slop.
while(true) {
getOffset(NANOTHREAD_SLEEP); // Can now afford to wait a bit longer between checks
}
}
private static void getOffset(long wait) {
try {
TimeUnit.MILLISECONDS.sleep(wait);
long clock = System.currentTimeMillis();
long nano = SampleResult.sampleNsClockInMs();
nanoOffset = clock - nano;
} catch (InterruptedException ignore) {
// ignored
Thread.currentThread().interrupt();
}
}
}
/**
* @return the startNextThreadLoop
* @deprecated use {@link SampleResult#getTestLogicalAction()}
*/
@Deprecated
public boolean isStartNextThreadLoop() {
return testLogicalAction == TestLogicalAction.START_NEXT_ITERATION_OF_THREAD;
}
/**
* @deprecated use SampleResult#setTestLogicalAction(TestLogicalAction)
* @param startNextThreadLoop the startNextLoop to set
*/
@Deprecated
public void setStartNextThreadLoop(boolean startNextThreadLoop) {
if(startNextThreadLoop) {
testLogicalAction = TestLogicalAction.START_NEXT_ITERATION_OF_THREAD;
} else {
testLogicalAction = TestLogicalAction.CONTINUE;
}
}
/**
* Clean up cached data
*/
public void cleanAfterSample() {
this.responseDataAsString = null;
}
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
throw new IllegalStateException("This should not happen");
}
}
@Override
public List getSearchableTokens() throws Exception {
List datasToSearch = new ArrayList<>(4);
datasToSearch.add(getSampleLabel());
datasToSearch.add(getResponseDataAsString());
datasToSearch.add(getRequestHeaders());
datasToSearch.add(getResponseHeaders());
return datasToSearch;
}
/**
* @return boolean true if this SampleResult should not be sent to Listeners
*/
public boolean isIgnore() {
return ignore;
}
/**
* Call this method to tell JMeter to ignore this SampleResult by Listeners
*/
public void setIgnore() {
this.ignore = true;
}
/**
* @return String first non null assertion failure message if assertionResults is not null, null otherwise
*/
public String getFirstAssertionFailureMessage() {
String message = null;
AssertionResult[] results = getAssertionResults();
if (results != null) {
// Find the first non-null message
for (AssertionResult result : results) {
message = result.getFailureMessage();
if (message != null) {
break;
}
}
}
return message;
}
/**
* @return the testLogicalAction
*/
public TestLogicalAction getTestLogicalAction() {
return testLogicalAction;
}
/**
* @param testLogicalAction the testLogicalAction to set
*/
public void setTestLogicalAction(TestLogicalAction testLogicalAction) {
this.testLogicalAction = testLogicalAction;
}
}