skip to content

A JMeter plan using a jp@gc element runs locally but dies on a clean CI checkout before any sample. Why?

level: seniorimportance: must knowfreq 57%

answer

  1. The plan stores class names, not jars
  2. Failure lands before the first thread starts
  3. Exit code and results file both testify
  4. A different element type fails a different way

basics

~20 s

The .jmx names the plugin's Java class, and the CI agent has no plugin jar in lib/ext. JMeter cannot resolve that class while parsing the plan, so the run aborts at load and writes no results file.

solid answer

~40 s

A `.jmx` is XML holding the fully qualified class name of every element - a jp@gc element resolves to something like `kg.apc.jmeter.samplers.DummySampler`. The workstation has that class because the plugin jar sits in `lib/ext`; a clean CI checkout has only the plan. JMeter's `SaveService` cannot map the element to a class, and the load throws rather than degrading: in non-GUI mode you get `An error occurred: Error in NonGUIDriver ... Problem loading XML from:'/path/plan.jmx'`, exit status 1, and no JTL at all. The fix is to provision the plugin as part of the injector, not the plan: bake the jar into the image, vendor it into a checked-in `lib/ext`, or install it from the third-party Plugins Manager's command line before the run - pinning an exact version either way.

code

bash · 24 lines
bash
#!/usr/bin/env bash
set -euo pipefail

JMETER_HOME=/opt/apache-jmeter-6.0.0

# 1. Plugins Manager and cmdrunner are baked into the image, pinned.
#    (Plugins Manager is third-party: jmeter-plugins.org, not Apache.)
test -f "$JMETER_HOME/lib/ext/jmeter-plugins-manager-2.0.jar"
test -f "$JMETER_HOME/lib/cmdrunner-2.3.jar"

# 2. Create bin/PluginsManagerCMD.sh once, if it is not already there.
if [ ! -x "$JMETER_HOME/bin/PluginsManagerCMD.sh" ]; then
  java -cp "$JMETER_HOME/lib/ext/jmeter-plugins-manager-2.0.jar" \
       org.jmeterplugins.repository.PluginManagerCMDInstaller
fi

# 3. Install the exact plugin versions this plan needs. Pin them.
"$JMETER_HOME/bin/PluginsManagerCMD.sh" install jpgc-dummy=0.4

# 4. Prove the jar is really there before JMeter ever reads the plan.
ls "$JMETER_HOME"/lib/ext/jmeter-plugins-dummy-0.4.jar

# 5. Now run. A missing plugin here would exit 1 with no JTL at all.
"$JMETER_HOME/bin/jmeter" -n -t plan.jmx -l results.jtl

go deeper

for a junior

Remember that a .jmx records plugin class names, so the plan alone is not enough - the machine running it needs the same plugin jars installed.

for a middle

Explain that the class cannot be resolved while the XML is parsed, so JMeter aborts at load with a non-zero exit and writes no results file at all.

for a senior

Diagnose from the evidence: no JTL plus exit 1 plus 'Problem loading XML from:' means a missing element class, while a full JTL of ERROR-labelled samples means a missing JavaSamplerClient.

for a principal

Decide where plugin provenance lives - image layer, vendored directory or an install step against an internal mirror - and hold the line on pinned versions and licence review.

This is the failure that turns a plugin into an operational problem rather than an authoring convenience, and it is worth being able to describe from both ends: what the log says, and what you change so it stops happening. ## What the failure actually is A JMeter test plan is XML in which every element is recorded by its Java class. Core elements resolve to `org.apache.jmeter.*` classes that are always present; a third-party element resolves to the plugin's own class, and jp@gc plugins publish theirs - the Dummy Sampler's marker class is `kg.apc.jmeter.samplers.DummySampler`, for instance. Nothing in the file says where the jar came from or which version it was. When JMeter reads the plan it hands the XML to its object mapper. If a class in the file is not on the classpath the mapper cannot resolve it, and `SaveService` deliberately turns that into a hard failure: it catches the resolution error and rethrows an `IllegalArgumentException` reading `Problem loading XML from:'<absolute path>'` with the root cause attached. In non-GUI mode that surfaces as `Error in NonGUIDriver`, the top-level handler prints `An error occurred: ...` and calls `System.exit(1)`. So the shape of the failure is: - it happens **at load**, before the test tree is compiled and before any thread starts; - the process exits **non-zero**, and the message names the file it could not load; - **no JTL is produced** at all, so there is not even a partial results file to inspect. ## The other shape: a missing class that does not stop the run Not every missing class behaves this way, and knowing the difference saves an hour of debugging. A Java Request sampler stores its target class as a *string field*, not as the element's own class. If that class is absent, the plan still loads; the sampler substitutes an internal error client, and every sample comes back failed, labelled `ERROR: <classname>`, with response data `Class not found: <classname>`. That run produces a full JTL of failures at whatever rate the thread group asked for. So: **a missing plugin element makes the plan unloadable; a missing JavaSamplerClient makes the samples fail.** One is a red build in five seconds, the other is a red build in fifteen minutes. ## Making the plugin survive a clean checkout The plan cannot carry the plugin, so the environment must. Three approaches, roughly in order of how much a team tends to like them once they have lived with them: 1. **Bake it into the runner image.** The Dockerfile or the AMI copies the pinned plugin jars into `lib/ext` and their dependencies into `lib`. Reproducible, offline, and the image tag records the plugin set. 2. **Vendor the jars into the repository** beside the plan and point `search_paths` (for plugin jars) and `plugin_dependency_paths` (for their dependencies) at the checked-out directory. The plan and its plugins move together, at the cost of binaries in git. 3. **Install at the start of the job with the Plugins Manager's command line.** The Plugins Manager is a third-party tool from JMeter-Plugins.org, not part of the Apache download. Put its jar in `lib/ext`, make sure a `cmdrunner` jar is in `lib`, and generate the wrapper scripts by running its `PluginManagerCMDInstaller` class; `PluginsManagerCMD` then takes `status`, `available`, `upgrades`, `install`, `install-all-except`, `install-for-jmx` and `uninstall`. `install-for-jmx` reads a `.jmx` and installs what that plan needs, which is convenient but resolves versions at job time; `install jpgc-dummy=0.4` pins one instead. ## The rules that keep it fixed - **Pin the version.** A plugin id without a version installs whatever the repository offers today, which makes yesterday's green build unreproducible. - **Do not rely on network access at run time.** An install step that reaches a public repository turns an outage into a red build; the Plugins Manager can be pointed at an internal mirror through its `jpgc.repo.address` property if you want the convenience without the dependency. - **Fail loudly at provisioning time.** Verifying the jars exist before invoking JMeter turns an obscure XML error into a clear message. - **Vet what you install.** Licence and supply-chain review of a third-party jar is a real obligation and belongs with your application-security practice, not with the test plan. - **Watch the coupling.** A plugin binds to JMeter's internals, so a JMeter upgrade can outrun a plugin that has not been rebuilt for it. Upgrade both together and keep the previous image tag until the new one has been through a run.

  • How would you tell this failure apart from a plan that ran and produced only failed samples?
    Look for a results file. A missing plugin element aborts the load, so JMeter exits non-zero and writes no JTL. A run that produced failures wrote a JTL with rows in it. Check the log for `Problem loading XML from:` to confirm the load-time case.
  • Why does a Java Request whose class is missing behave differently from a missing plugin element?
    The Java Request element itself is core JMeter, so the plan loads; the missing class is only the value of its Classname field. The sampler falls back to an internal error client that returns a failed result labelled `ERROR: <classname>` with response data `Class not found: <classname>`, so you get a full run of failures rather than an aborted load.
  • What does install-for-jmx give you over listing plugin ids by hand, and what does it cost?
    It reads the `.jmx` and installs the plugins that plan needs, so the list cannot drift from the plan. The cost is that it resolves versions at job time from the repository, which is neither pinned nor reproducible; listing explicit ids with versions, such as `jpgc-dummy=0.4`, trades the convenience for a build you can repeat.

saying these in an interview costs you the question

  • Says a missing plugin simply skips that element and carries on
  • Expects a partial JTL from a plan that failed to load
  • Assumes the .jmx file itself carries the plugin it needs
  • Installs plugins at run time with no version pinned
  • Blames the plan format instead of the missing class on the classpath