Merge Rules
Motivation
Merge rules are one of XLT’s most powerful and versatile features. They act as “bucketing” rules for your requests, defining how requests are categorized, merged, or split in reports. This leads to more meaningful request tables and better insights. While designing effective rules requires careful consideration, well-crafted merge rules significantly enhance the value of your results.
Essentials
The main concept when setting up merge rules is defining a new name (which can also be called a named bucket) for requests that match certain criteria.
When you check the timers.csv files on disk, you can see the original names used. Requests are automatically given a name. These mostly depend on the action name chosen when designing your test suite. If a page (or whatever occurs in the action block) consists of several requests, such as XHR requests after the page load, each request name is also assigned an index number.
Here is a small example. The CSV lines have been condensed for better readability:
R,AddToCart.1,...,200,https://host.info/Cart-GetPID?pid=1921,application/json,...
R,AddToCart.2,...,200,https://host.info/Cart-AddProduct,text/html,...
R,AddToCart.3,...,200,https://host.info/Product-Popup,text/html,...
A,AddToCart,1566427027660,373,false
This quick example shows that we will likely see three different URLs. Activities are later displayed under a single name in the report: AddToCart. The index number might also appear, depending on the initial merge rules your test suite contains. If the index is also used, you will see three lines; however, because the index may or may not be stable, the data might not be consistent.
Rules are defined to match and capture data to form or assign a new name. Merge rules are numbered (using positive numbers; gaps are permitted) and are executed in the order defined by these numbers with the ability to skip rules using the continueOnMatchAtId and continueOnNoMatchAtId parameters, if needed for performance reasons or just to make the rules more readable.
Rules apply only to requests and can evaluate several data points, such as the URL, name, agent details, content type, status code, and more. The rules use regular expressions and some offer an additional text only match for maximum versatility.
Before processing data, it is essential to understand what each request represents. Define the requirements for your reports and then craft appropriate regular expressions. Avoid separating “successful” from “failed” requests if they perform the same basic function; instead, use merge rules to isolate redirects or consolidate identical requests across different contexts. Be mindful that overly specific rules (smaller buckets) may dilute the statistical significance of the measured data.
More about the collected data and the CSV format can be found in the chapter Result Data.
Parameters
These are the parameters that you can define for a merge rule:
newName .................. new request name (required)
[n] namePattern .......... regex defining a matching request name
[t] transactionPattern ... regex defining a matching transaction name
[a] agentPattern ......... regex defining a matching agent name
[c] contentTypePattern ... regex defining a matching response content type
[s] statusCodePattern .... regex defining a matching status code
[u] urlPattern ........... regex defining a matching request URL
[u] urlText .............. substring defining a matching request URL
[m] methodPattern ........ regex defining a matching HTTP request method
[r] runTimeRanges ........ list of runtime segment boundaries
Because URLs are often containing very random parameters, the regexp matching result cannot be efficiently cached and the regex must be applied again and again. To tune this, you can also specify just a text to be contained in the URL. This is much faster. If you specify text (contains) and a regex pattern, both must match and the text match is done first.
stopOnMatch .............. whether to stop processing further rules if the current rule matches
(defaults to true)
dropOnMatch .............. whether to discard a matching request instead of renaming it
(defaults to false; implies stopOnMatch)
continueOnMatchAtId ...... the ID of the next rule to evaluate if the current rule matches
continueOnNoMatchAtId .... the ID of the next rule to evaluate if the current rule does not match
At least one of namePattern, transactionPattern, agentPattern, contentTypePattern, statusCodePattern, urlPattern, methodPattern or runTimeRanges must be specified. If more than one pattern is given, all given patterns must match.
Note that newName may contain placeholders replaced by specific capturing groups from the patterns. The format is {<category>:<capturingGroupIndex>}, where <category> is the type code (see brackets in the parameter list above) and <capturingGroupIndex> is the numeric index of the regex group.
runTimeRanges is a list of runtime segment boundaries. The format is start,end. The start and end values are in milliseconds. Capturing is not support via regular expressions. The text is automatically setup and offered as {r} when ranges are defined.
You can also use {<category>} (e.g., {s}) as a shorthand placeholder. These do not require a corresponding pattern and resolve to the full text of the request attribute. This is useful, for example, to append a status code directly to a request name without setting up a dedicated pattern rule.
Excluding Patterns
To exclude a pattern instead of including it, use:
com.xceptance.xlt.reportgenerator.requestMergeRules.<num>.<param>.exclude = <value>
Requests that match the exclude pattern will not be selected. For example, to create a bucket for all non-JavaScript resources, you could set up a rule like:
com.xceptance.xlt.reportgenerator.requestMergeRules.1.newName = {n} NonJS
com.xceptance.xlt.reportgenerator.requestMergeRules.1.contentTypePattern.exclude = javascript
com.xceptance.xlt.reportgenerator.requestMergeRules.1.stopOnMatch = false
Note that an include pattern and an exclude pattern can be specified for a pattern type simultaneously. In this case, a request is selected if and only if it matches the include pattern and does not match the exclude pattern. You cannot capture data using exclude patterns.
URL Substring Match
To improve performance, you can use urlText instead of urlPattern. This option specifies a plain text substring that must be included in the request URL. Substring search is significantly faster than regular expression matching.
If you need to capture data or add more complex conditions, you can combine `urlText` and `urlPattern`. Both must match (AND condition), except when using `.exclude`, where only one must match. Note that you cannot capture data using `{u}` with `urlText`. `urlText` is always evaluated first, when specified. Only when it matches, the `urlPattern` is evaluated.
### Dropping Requests
By default, the load test report includes all requests. To remove certain request patterns, you must define a request merge rule to identify the relevant requests and delete them from the report:
```txt
## Delete all requests without a valid status code.
com.xceptance.xlt.reportgenerator.requestMergeRules.10.statusCodePattern = 0
com.xceptance.xlt.reportgenerator.requestMergeRules.10.dropOnMatch = true
You can also first merge and split data into named buckets and then drop a bucket based on a namePattern rule.
Example
To give you an understanding of how merge rules work, let’s look at a more sophisticated example. You created a report, and what you get in your requests table is this:
Everything is named similarly; we have no idea what happened here or whether it’s good or bad. So, let’s take a closer look at these measured requests. It is always a URL and a name. The name is determined as explained above (based on action naming and index).
# COLogin.1
https://host.net/s/Foo/cart?dwcont=C1250297253
# COLogin.2
https://host.net/Sites-Foo-Site/en_US/COCustomer-Start
# COLogin.3
https://host.net/Sites-Foo-Site/en_US/
COAddress-UpdateShippingMethodList?address1=&address2=&countryCode
# COLogin.4
https://host.net/Sites-Foo-Site/en_US/
COAddress-UpdateShippingMethodList?shippingId=direct
# COLogin.5
https://host.net/Sites-Foo-Site/en_US/COBilling-UpdateSummary
# COLogin.6
https://host.net/Sites-Foo-Site/en_US/__Analytics-Start?res=1600x1200
Step 1: Split Off __Analytics-Start
First, let’s split off the __Analytics-Start requests seen in COLogin.6. These probably appear in other parts of your load test as well. Since they have no real connection to your login process, let’s summarize them in one large Analytics bucket. For this, we need a rule that matches URLs with __Analytics-Start before ’?’.
## Summarize Analytics Start
...requestMergeRules.10.newName = __Analytics-Start
...requestMergeRules.10.urlPattern = /__Analytics-Start\\?
...requestMergeRules.10.stopOnMatch = true
Pay attention to the double backslash before the question mark (\\?). The first \ escapes the ? from the regular expression perspective, treating it as a literal character. The second \ escapes the \ character because this is a Java property file format. Here, \ is a special character (see Java Properties).
Step 2: Remove the Index
We don’t need the sub-request naming pattern at the moment, so let’s remove the dot and summarize our requests as “COLogin”. We want to apply other merge rules later, so we do not stop the processing here.
## First, we eliminate the sub-request naming pattern, because we do not need
## that at the moment. This turns all "name.1" or "name.1.1" and so on into
## just "name".
...requestMergeRules.20.newName = {n:1}
...requestMergeRules.20.namePattern = ^([^\\.]*)(\\.[0-9]+)+$
...requestMergeRules.20.stopOnMatch = false
You can access the captured data of the regular expression by specifying its index (e.g., {n:1}) to build the new name. If you use {n:0} or {n}, it will use all available data. In that case, no specific pattern needs to be specified in the rule, as this data is made available automatically. See the next examples as well.
An option is available to handle the index automatically. Automatic handling is faster than explicit handling, and it is recommended.
## Whether to automatically remove any indexes from request names
## (i.e. "HomePage.1.27" -> "HomePage"), so no special request processing rule
## is required for that.
com.xceptance.xlt.reportgenerator.requests.removeIndexes = true
ā ļø Warning
Automatic index handling might fail if the request name contains an index-like structure that is part of the name by design (e.g., “4.10 Initial Step”). In such cases, explicit index handling is recommended.
Step 3: Redirect Code as Part of the Name
Usually, we expect 200 response codes for requests, so other codes might be of special interest. With another merge rule, we match every response code from 300 to 309 and add the code to the new name:
## Get us the redirect codes into the name
...requestMergeRules.60.newName = {n:0} [{s:0}]
...requestMergeRules.60.namePattern = .*
...requestMergeRules.60.statusCodePattern = (30[0-9])
...requestMergeRules.60.stopOnMatch = false
šSpeed up Processing
To speed up data processing, XLT automatically provides the full data for each parameter. You don’t have to use a regular expression with a capturing group like .+ to fill the context for {n:0}.
A faster, optimized rule looks like this:
## Get us the redirect codes into the name when they are 300 to 309.
...requestMergeRules.60.newName = {n} [{s}]
...requestMergeRules.60.statusCodePattern = 30[0-9]
...requestMergeRules.60.stopOnMatch = false
Step 4: Capture a Part of the URL
Remaining requests follow the pattern ’-Site/locale/Action’. We now sort requests by the action part, appending it to the bucket name (ensuring we do not capture any URL parameters starting at ’?’):
# Do a split by action name
...requestMergeRules.80.newName = {n:0} ({u:1})
...requestMergeRules.80.urlPattern = -Site/[^/]+/([^/\\?]+).*
...requestMergeRules.80.stopOnMatch = false
Step 5: Result
We sorted our COLogin requests to appear in the requests table. Additionally, there will be another table row for all __Analytics-Start requests:
This sorted table provides more precise data and helps pinpoint higher runtimes.
While setting up merge rules requires careful planning and a solid understanding of regular expressions, the resulting reports are far more precise and provide much clearer insights into performance bottlenecks.
Flow Control
Terminating Early
If you have several rules, and the initial rules already finalize the name, with subsequent rules making no further changes, consider stopping processing earlier using the stopOnMatch feature.
Rule Jumps
By default, rules are evaluated sequentially. However, if you know that subsequent rules are not applicable, you can jump forward to a specific rule ID, skipping intermediate ones.
com.xceptance.xlt.reportgenerator.requestMergeRules.5.newName = {n} CSS
com.xceptance.xlt.reportgenerator.requestMergeRules.5.namePattern =
com.xceptance.xlt.reportgenerator.requestMergeRules.5.urlPattern = \\.css$
com.xceptance.xlt.reportgenerator.requestMergeRules.5.stopOnMatch = false
com.xceptance.xlt.reportgenerator.requestMergeRules.5.continueOnMatchAtId = 10
Use continueOnMatchAtId to jump if the current rule matches, or continueOnNoMatchAtId if it does not. The target rule ID must be higher than the current one (forward jumps only). If the target ID does not exist, the next available rule with a higher ID is used.
Performance
Every rule you add influences the report generation speed. You can improve the speed by avoiding redundant rules, skipping remaining rules when a match occurs, capturing only necessary data, and more.
Superfluous Data
We discussed this earlier, but here is the example again to show where you can save time when generating the report.
...requestMergeRules.60.newName = {n:0} [{s:0}]
...requestMergeRules.60.namePattern = .+
...requestMergeRules.60.statusCodePattern = (30[0-9])
XLT automatically provides the full data for each request topic. You don’t have to use a regular expression and capturing groups like .+ or (30[0-9]) to fill the context for {n:0} and {s:0}.
The improved rule:
...requestMergeRules.60.newName = {n} [{s}]
...requestMergeRules.60.statusCodePattern = 30[0-9]
Don’t Capture Data You Don’t Need
Only when you need parts of the data do you have to use a capturing group and access its content. In this example, the first capturing group is not used in the new name definition, so there’s no need to capture it.
# Too much capturing
...requestMergeRules.60.newName = <{n:2}>
...requestMergeRules.60.namePattern = ^(Homepage) (.*)$
# Capture only what is needed
...requestMergeRules.60.newName = <{n:1}>
...requestMergeRules.60.namePattern = ^Homepage (.*)$
In this example, the string Homepage is static. Therefore, you don’t have to copy this data from the regex to the new name; instead, define it directly.
# Capturing static content
...requestMergeRules.60.newName = <{n:1}>
...requestMergeRules.60.namePattern = ^(Homepage).*$
# Define static content rather than capturing it
...requestMergeRules.60.newName = Homepage
...requestMergeRules.60.namePattern = ^Homepage.*$
Superfluous Rules
If you define rules that will never match (because your data doesn’t contain such entries or you inherited rules from another project), remove them.
Expensive Regular Expressions
Regular expressions can be computationally expensive, especially if they are inefficient in determining matches. For more information, see this example: Regexes: The Bad, the Better, and the Best.

