Editing JMX Files from the Command Line
Techniques for modifying Apache JMeter test plans from the terminal using sed, awk, and nano - and when these approaches fall short.
Mark
Performance Testing Expert
JMeter test plans are stored as XML files with the .jmx extension. While the JMeter GUI is the standard way to edit these files, there are situations where command-line editing becomes necessary: headless servers, CI/CD pipelines, batch modifications across multiple files, or simply not wanting to wait for the GUI to load for a quick change.
This article explores traditional CLI approaches to JMX editing, their limitations, and when you might need something more robust.
Understanding JMX File Structure
Before diving into editing, it helps to understand what we’re working with. A JMX file is XML with a specific structure:
<?xml version="1.0" encoding="UTF-8"?>
<jmeterTestPlan version="1.2" properties="5.0">
<hashTree>
<TestPlan guiclass="TestPlanGui" testclass="TestPlan" testname="Test Plan">
<stringProp name="TestPlan.comments"></stringProp>
<boolProp name="TestPlan.functional_mode">false</boolProp>
<!-- More properties... -->
</TestPlan>
<hashTree>
<ThreadGroup guiclass="ThreadGroupGui" testclass="ThreadGroup" testname="Thread Group">
<stringProp name="ThreadGroup.num_threads">10</stringProp>
<stringProp name="ThreadGroup.ramp_time">1</stringProp>
<!-- More properties... -->
</ThreadGroup>
<!-- More nested elements... -->
</hashTree>
</hashTree>
</jmeterTestPlan>
The nested hashTree elements represent parent-child relationships. This structure makes targeted edits with text-processing tools challenging.
Using nano for Quick Edits
For simple, one-off changes, nano works when you know exactly what you’re looking for:
nano test.jmx
Use Ctrl+W to search for specific values. For example, searching for num_threads will take you to the thread count property.
Pros:
- Visual feedback
- Undo support (
Alt+U) - No risk of regex mistakes
Cons:
- Tedious for multiple files
- Not scriptable
- Still requires knowing the XML structure
Using sed for Text Substitution
sed is the go-to tool for scripted text replacement. Here’s how to change the thread count:
# Change thread count from 10 to 50
sed -i 's/<stringProp name="ThreadGroup.num_threads">10</<stringProp name="ThreadGroup.num_threads">50</g' test.jmx
This works, but notice the fragility. We’re matching the entire opening tag and value to ensure we hit the right property. If the file has different formatting or whitespace, the match fails silently.
Batch Updates with sed
To update multiple JMX files:
for file in *.jmx; do
sed -i 's/<stringProp name="ThreadGroup.num_threads">10</<stringProp name="ThreadGroup.num_threads">50</g' "$file"
done
The Whitespace Problem
JMX files can have varying indentation. This simple substitution:
sed -i 's/num_threads">10</num_threads">50</g' test.jmx
Might match unintended properties if another element happens to contain similar text. XML doesn’t care about whitespace, but your regex does.
Using awk for More Complex Patterns
awk provides better control for multi-line patterns:
# Extract current thread count
awk -F'[<>]' '/ThreadGroup.num_threads/{print $3}' test.jmx
To replace values with awk:
awk '{
if ($0 ~ /name="ThreadGroup.num_threads"/) {
gsub(/>10</, ">50<")
}
print
}' test.jmx > test_modified.jmx
This is safer than blind substitution, but still doesn’t understand XML structure. If you have multiple Thread Groups, this changes all of them.
Targeting Specific Elements
What if you need to change the thread count for only one Thread Group? With sed or awk, this requires tracking context:
# Change thread count only in "API Thread Group", not "Web Thread Group"
awk '
/testname="API Thread Group"/ { in_target=1 }
/testname="Web Thread Group"/ { in_target=0 }
in_target && /ThreadGroup.num_threads/ {
gsub(/>10</, ">50<")
}
{ print }
' test.jmx > test_modified.jmx
This works for simple cases but becomes unwieldy as complexity grows. What if “API Thread Group” appears in a comment? What if the Thread Groups are nested differently?
Using xmllint and xmlstarlet
For proper XML manipulation, dedicated tools exist:
# Extract value with xmlstarlet
xmlstarlet sel -t -v "//ThreadGroup[@testname='Thread Group']/stringProp[@name='ThreadGroup.num_threads']" test.jmx
# Update value
xmlstarlet ed -u "//ThreadGroup[@testname='Thread Group']/stringProp[@name='ThreadGroup.num_threads']" -v "50" test.jmx > test_modified.jmx
This is XML-aware and handles structure correctly. However, JMX files use hashTree for parent-child relationships rather than direct nesting, which makes XPath expressions complex:
# Finding elements by path requires understanding hashTree relationships
xmlstarlet sel -t -v "//TestPlan/following-sibling::hashTree/ThreadGroup/stringProp[@name='ThreadGroup.num_threads']" test.jmx
Not intuitive, and getting the XPath wrong produces silent failures or incorrect matches.
Common CI/CD Patterns
Despite the challenges, these approaches are widely used in pipelines:
#!/bin/bash
# ci-update-jmx.sh - Parameterize JMX for CI/CD
THREADS=${THREADS:-10}
DURATION=${DURATION:-300}
TARGET_HOST=${TARGET_HOST:-localhost}
for file in tests/*.jmx; do
# Update thread count
sed -i "s/<stringProp name=\"ThreadGroup.num_threads\">[0-9]*</<stringProp name=\"ThreadGroup.num_threads\">${THREADS}</g" "$file"
# Update duration
sed -i "s/<stringProp name=\"ThreadGroup.duration\">[0-9]*</<stringProp name=\"ThreadGroup.duration\">${DURATION}</g" "$file"
# Update target host
sed -i "s/<stringProp name=\"HTTPSampler.domain\">[^<]*</<stringProp name=\"HTTPSampler.domain\">${TARGET_HOST}</g" "$file"
done
This script is fragile. If a JMX file uses intProp instead of stringProp for a value, the substitution silently fails. If the host contains special characters, the regex breaks.
When Text Tools Aren’t Enough
The limitations become apparent when you need to:
- Add new elements - Inserting a new HTTP Request or Timer requires understanding the hashTree structure
- Delete elements - Removing a sampler without breaking the tree is error-prone
- Enable/disable elements - The enabled flag is an attribute, not a child element
- Rename elements - The testname attribute affects how JMeter displays the element
- Work with duplicate names - Multiple “HTTP Request” elements require positional awareness
# This doesn't work - can't add elements with sed/awk safely
# You'd need to know exactly where in the hashTree to insert
Alternative: Property Files
For parameterization, JMeter’s property file approach avoids editing JMX entirely:
# Run with external properties
jmeter -n -t test.jmx -Jthreads=50 -Jduration=300 -Jhost=api.example.com
This requires the JMX to reference properties:
<stringProp name="ThreadGroup.num_threads">${__P(threads,10)}</stringProp>
Clean for parameterization, but doesn’t help when you need to modify the test plan structure itself.
The Trade-offs
| Approach | Best For | Limitations |
|---|---|---|
| nano | Quick one-off edits | Not scriptable |
| sed | Simple value substitution | No XML awareness, fragile |
| awk | Pattern-based changes | Complex for nested structures |
| xmlstarlet | XML-aware queries | JMX hashTree structure is awkward |
| Property files | Runtime parameterization | Can’t modify structure |
Conclusion
Traditional CLI tools can handle basic JMX modifications, but they treat the file as text rather than a structured test plan. For simple substitutions in CI/CD pipelines, sed scripts work if you’re careful. For anything structural, you either need deep familiarity with XPath and JMX internals, or a tool that understands JMeter’s element hierarchy.
If you frequently edit JMX files from the command line, investing in proper tooling that understands JMeter’s structure can eliminate the guesswork and reduce the risk of corrupted test plans.
Further Reading
Tags: