Skip to main content

Try Distributed Testing on Your Own Machine

Section 12 covers the theory of distributed testing - controller, workers, RMI, and the many things that can go wrong between them. This section is where you actually do it, end to end, on hardware you already own.

The medium is Multipass, Canonical's lightweight VM manager. You spin up disposable Ubuntu VMs on your own machine, use them as JMeter workers, and run a real distributed test against them. No cloud account, no credit card, nothing to clean up but a VM.

The point is not Multipass itself. It is that every concept from Section 12 - starting jmeter-server, RMI working in both directions, thread counts multiplying per worker, distributing test data, collecting results back - becomes something you can watch happen, and break on purpose, in a few minutes. Once the workflow makes sense here, moving to cloud workers is a change of address, not a change of method.

Note: Multipass is the medium, not the lesson. Everything in this section transfers to any Linux worker - a spare laptop, a colleague's desktop, or a cloud VM. For a cloud-specific walkthrough, the archived OCI Linux Slave Setup covers Oracle Cloud step by step.


Why Multipass for This​

  • Free — no cloud account, no credit card, no egress charges
  • Fast — a worker VM boots in ~30 seconds; tear it down just as fast
  • Same subnet as the host — on Windows, Multipass VMs sit on the Hyper-V Default Switch network alongside the host, so RMI works in both directions out of the box. This sidesteps the NAT callback problem that breaks cloud slaves (see Section 12 — NAT breaks distributed testing)
  • Disposable — snapshot a configured VM, clone it, or delete --purge when done

The trade-off: workers share your host's CPU and RAM, so this is for learning the workflow and validating a setup, not generating serious load. Once the method is proven here, real scale needs dedicated machines.


Prerequisites​

  • Windows 10/11 (this guide assumes the Hyper-V backend, which Multipass uses by default on Windows)
  • Spare host resources — budget ~2 vCPUs and ~4 GB RAM per worker on top of what your host needs
  • The same JMeter version you run on your controller (this guide uses 5.6.3 throughout)

Note: Multipass enables Hyper-V automatically on Windows Pro/Enterprise. On Windows Home (no Hyper-V), Multipass falls back to VirtualBox — the VM steps are identical, but the networking details in Step 3 differ.


Step 1: Install Multipass​

Download the installer from multipass.run, or use winget:

winget install Canonical.Multipass

The installer enables Hyper-V and may require a reboot. Verify:

multipass version

Step 2: Launch Worker VMs​

Launch one VM per worker. Give each a clear, numbered name so you can tell them apart in logs:

multipass launch 24.04 --name jmeter-slave-1 --cpus 2 --memory 4G --disk 10G
multipass launch 24.04 --name jmeter-slave-2 --cpus 2 --memory 4G --disk 10G

Check they're running and note their IPs:

multipass list
Name State IPv4 Image
jmeter-slave-1 Running 172.28.x.x Ubuntu 24.04 LTS
jmeter-slave-2 Running 172.28.y.y Ubuntu 24.04 LTS

The 172.28.x.x addresses come from the Hyper-V Default Switch. Write them down — you'll use them as the -R targets on the controller. (They can change on host reboot; see Step 3.)


Step 3: How Multipass Networking Works (Default Switch)​

This is the part that makes local distributed testing just work, so it's worth understanding.

On Windows, Multipass attaches each VM to the Hyper-V Default Switch, an internal NAT network:

  • Your host gets a virtual adapter on this network — vEthernet (Default Switch), typically 172.28.x.1
  • Each VM gets an address on the same subnet — 172.28.x.y
  • The NAT only applies to VM → internet traffic. Host ↔ VM traffic is direct, because both sides are on the same internal subnet

That direct, same-subnet path is exactly what bidirectional RMI needs:

  1. Controller → slave (send the test plan) — host reaches the VM's 172.28.x.y directly
  2. Slave → controller (send results back) — the VM reaches the host's 172.28.x.1 directly

Contrast this with a typical cloud setup, where the controller sits behind a home or office router with no address the cloud workers can call back to. Here, host and VMs are effectively on one subnet — the ideal case from Section 12.

Find your host's Default Switch IP — you'll need it in Step 8:

ipconfig

Look for Ethernet adapter vEthernet (Default Switch) and note its IPv4 address (e.g. 172.28.x.1).

Dynamic IP caveat: Windows recreates the Default Switch subnet on every host reboot, so both the VM IPs and the host's Default Switch IP can change. After a reboot, re-run multipass list and ipconfig and update your -R target and RMI hostname accordingly. This is the one rough edge of the default networking.


Step 4: Install Java (Inside Each VM)​

Open a shell into the VM (you land as the ubuntu user with passwordless sudo):

multipass shell jmeter-slave-1

Then, inside the VM, install Java 17 (match the major version you run on the controller):

sudo apt-get update
sudo apt-get install -y openjdk-17-jdk
java -version
# Expected: openjdk version "17.0.x"

Step 5: Install JMeter (Inside Each VM)​

Still inside the VM, install JMeter to /opt/jmeter:

cd /opt
sudo wget https://dlcdn.apache.org//jmeter/binaries/apache-jmeter-5.6.3.tgz
sudo tar -xzf apache-jmeter-5.6.3.tgz
sudo ln -sfn /opt/apache-jmeter-5.6.3 /opt/jmeter
sudo rm -f apache-jmeter-5.6.3.tgz

/opt/jmeter/bin/jmeter --version
# Should show: Apache JMeter 5.6.3

Important: Use the same JMeter version on every VM and the controller. Version mismatches cause silent serialization failures in distributed mode.


Step 6: Create the Start/Stop Scripts (Inside Each VM)​

The user path is ubuntu, Multipass's default account. HOST_IP auto-detects the VM's Default Switch address, so you don't have to hardcode it.

start-slave.sh​

mkdir -p /home/ubuntu/jmeter-PT/linux
cat > /home/ubuntu/jmeter-PT/linux/start-slave.sh << 'EOF'
#!/bin/bash
JMETER_HOME="/opt/jmeter"
HOST_IP=$(hostname -I | awk '{print $1}')

export JVM_ARGS="-Xms512m -Xmx1g -XX:+UseG1GC -XX:MaxGCPauseMillis=100"

pkill -f "jmeter-server" 2>/dev/null || true
sleep 1

echo "Starting JMeter slave on ${HOST_IP}..."
nohup ${JMETER_HOME}/bin/jmeter-server \
-Djava.rmi.server.hostname=${HOST_IP} \
-Dserver.rmi.localport=50000 \
-Dserver_port=1099 \
-Dserver.rmi.ssl.disable=true \
> /home/ubuntu/jmeter-PT/linux/jmeter-slave.log 2>&1 &

echo "JMeter slave PID: $!"
EOF

chmod +x /home/ubuntu/jmeter-PT/linux/start-slave.sh

Key flags (see Section 12 - RMI Configuration for the reasoning behind each):

  • -Djava.rmi.server.hostname — announces the VM's Default Switch IP so the controller's callbacks land
  • -Dserver.rmi.localport=50000 / -Dserver_port=1099 — pin the RMI ports
  • -Dserver.rmi.ssl.disable=true — disable RMI SSL (no rmi_keystore.jks on internal networks)

stop-slave.sh​

cat > /home/ubuntu/jmeter-PT/linux/stop-slave.sh << 'EOF'
#!/bin/bash
pkill -f "jmeter-server" 2>/dev/null && echo "Stopped." || echo "No slave process found."
EOF
chmod +x /home/ubuntu/jmeter-PT/linux/stop-slave.sh

Firewall note: Multipass Ubuntu images ship with ufw inactive, so there's nothing to open inside the VM. If you enabled ufw yourself, run sudo ufw allow 1099/tcp && sudo ufw allow 50000/tcp.


Step 7: Start the Slave​

Inside each VM:

bash /home/ubuntu/jmeter-PT/linux/start-slave.sh
tail -10 /home/ubuntu/jmeter-PT/linux/jmeter-slave.log

You should see:

Created remote object: UnicastServerRef2 [liveRef: [endpoint:[172.28.x.y:50000]...]]

Type exit to leave the VM shell — the slave keeps running thanks to nohup.


Step 8: Configure the Controller (Windows Host)​

The controller is your Windows host running JMeter. For the slaves to call results back, the controller must announce its Default Switch IP (from Step 3), not 127.0.0.1.

Allow Java through the Windows Firewall so the slaves' RMI callbacks reach the controller. Either approve the prompt Windows shows on first run, or pin the callback port and add a rule for it (run PowerShell as Administrator):

New-NetFirewallRule -DisplayName "JMeter RMI callback" -Direction Inbound `
-Protocol TCP -LocalPort 60000 -Action Allow

Step 9: Run the Distributed Test​

From the controller (Windows host), targeting the VM IPs from multipass list:

jmeter -n -t Dummy-HTTP-Test.jmx -l results.jtl ^
-R 172.28.x.y,172.28.y.y ^
-Djava.rmi.server.hostname=172.28.x.1 ^
-Jclient.rmi.localport=60000 ^
-Jserver.rmi.ssl.disable=true
  • -R — comma-separated VM IPs (the slaves)
  • -Djava.rmi.server.hostname — the host's Default Switch IP, so slaves can send results back
  • -Jclient.rmi.localport=60000 — pin the callback port to match the firewall rule
  • -Jserver.rmi.ssl.disable=true — disable RMI SSL on the controller side too

When it finishes, confirm the JTL actually has rows (the console may still show the summary = 0 display quirk — see Section 12).


Step 10: Share Test Data with the VMs​

JMeter sends the .jmx to slaves but not CSV data or JARs (see Section 12 — Files Are Not Automatically Distributed). Multipass makes this easy with a host-folder mount:

multipass mount "C:\Users\user\Documents\VTC\2026\testing-knowledge-base\jmeter-working-dir\test_data" jmeter-slave-1:/home/ubuntu/jmeter-PT/linux/test_data

Now the VM sees your test data at the same path the test plan expects. Unmount with multipass umount jmeter-slave-1.


Troubleshooting​

VM IP changed after a host reboot​

The Default Switch subnet is recreated on reboot. Re-run multipass list for the new VM IPs and ipconfig for the new host Default Switch IP, then restart the slave (start-slave.sh auto-detects the new VM IP) and update the -R / -Djava.rmi.server.hostname values in your run command.

Controller can't reach the slave​

multipass list REM is the VM Running?
ping 172.28.x.y REM can the host reach the VM IP?

If ping works but JMeter doesn't connect, confirm the slave is listening (tail the slave log for the Created remote object line).

Test starts but JTL is empty​

Almost always the result callback being blocked:

  • Confirm -Djava.rmi.server.hostname is the host's Default Switch IP (172.28.x.1), not 127.0.0.1
  • Confirm the Windows Firewall allows inbound java.exe / port 60000 (Step 8)

Version mismatch errors​

Serialization errors (ClassNotFoundException, InvalidClassException) mean the VM and controller run different JMeter versions. Reinstall the matching version (Step 5).


Tips​

  • Start absurdly small — get a 2-thread / 30-second run returning a populated JTL before you scale anything up. Proving the plumbing costs nothing here, so there is no excuse for skipping it
  • Pause to reclaim resources — multipass stop jmeter-slave-1 frees the RAM/CPU without deleting the VM; multipass start brings it back
  • Snapshot a configured worker — once Java + JMeter + scripts are in place, multipass snapshot jmeter-slave-1 lets you roll back or clone instead of rebuilding
  • Automate the build — pass a --cloud-init file to multipass launch to install Java, download JMeter, and drop the scripts in on first boot, turning Steps 4–6 into one command
  • Keep JMeter versions identical across all VMs and the controller — the single most common cause of silent distributed failures
  • This is for learning and validation, not scale — workers compete with your host for CPU/RAM. Once the workflow is proven here, move real load onto dedicated machines or cloud workers; the method is identical, only the addresses change