Vayu vs Apache JMeter - a native HTTP load tester without the JVM¶
Apache JMeter has been the default open source load tester for two decades, and that longevity cuts both ways. It tests almost every protocol an enterprise runs, and it does so through a Java Swing interface and a thread-per-user model that has to be tuned, heap-sized, and usually run headless to get honest numbers. Vayu covers the HTTP case with a native C++ engine, from the same app you build the request in.
| Vayu | Apache JMeter | |
|---|---|---|
| Execution engine | C++ (native) | Java (JVM) |
| Concurrency model | Multi-worker event loop | Thread per virtual user |
| API client + load test | Both, one app | Load test only |
| Load test throughput | Tens of thousands of req/s | Moderate (thread-heavy) |
| UI | Native desktop app | Java Swing (dated) |
| UI responsiveness | High (sidecar architecture) | Laggy under load |
| Memory usage | Low (direct memory) | High (RAM-intensive) |
| Protocol breadth | HTTP, GraphQL, SSE | HTTP, JDBC, JMS, FTP, LDAP, SMTP and more |
| Scripting | QuickJS (pm.* syntax) |
Groovy / BeanShell |
| Distributed execution | No - single machine | Yes (controller + workers) |
| MCP / agent control | Built in, local, drives the load engine | No |
| Proxy / custom CA / client certs | In Settings - four proxy modes, additive CAs, per-host certs* | JVM properties and the Java keystore |
| Postman collection import | Yes (v2.0 + v2.1) | No |
| OpenAPI import | Yes (3.1 / 3.0 / 2.0) | No |
JMeter .jmx import |
Yes - samplers, extractors, assertions, timers and logic controllers, an unmapped class counted rather than dropped** | N/A |
| Open source | Yes (dual-license) | Yes (Apache 2.0) |
* One asterisk that row has earned. system proxy mode resolves a PAC
script once, against a probe URL, applying that one answer engine-wide
rather than per URL; a headless engine with nothing resolved falls back to
environment-variable pickup rather than to no proxy. Client certificates carry
no qualification any more - they are proven on a wire on all three platforms,
in both PEM-pair and PKCS#12 form. Detail:
proxy settings,
TLS trust and
client certificates.
** Common, not complete. JMeter's own class list is open-ended - plugins add more of them - so this is a mapping table, not a JMeter-compatibility claim: HTTP samplers, JSON/regex/boundary extractors, the five assertion kinds, all four timer kinds and the six logic controllers map onto Vayu's element model, and a class this importer has no mapping for (a listener, a cookie manager, a protocol Vayu's HTTP engine does not speak) is counted by its own class name rather than silently dropped. Detail: the JMeter import doc.
The engine, and what it does not cost you¶
JMeter's classic constraint is its concurrency model: one thread per virtual
user, on a JVM, which means the load generator's own resource profile becomes
something you tune before you can trust the numbers - heap size, thread ramp,
and the standing advice to run without the GUI because the GUI itself distorts
the result. Vayu's engine is a multi-worker libcurl event loop in C++23, so
concurrency is not threads, and it runs as a sidecar process next to the UI
rather than inside it. That is the architectural reason you can watch a live
dashboard while the run saturates a target: the renderer never shares a thread
with the request load. Measured on a loopback target it reached 56,880 req/s,
matching wrk and edging past vegeta on the same machine -
method and reproduction.
Build the request, then load it¶
In JMeter a test is a tree of samplers, config elements, controllers and
listeners that you assemble before anything is sent, and the request you were
debugging in an API client is rebuilt there by hand. Vayu starts from the other
end: the saved request, with its environment variables, its auth resolved
engine-side, and its pm.* scripts, is what the load run drives. Collections run
as ordered scenarios with per-step results and threshold verdicts, driven from a
CSV, TSV, JSON or JSONL file when you need each virtual user to send different
data. As in JMeter, a failed assertion can fail the run, not just the sample it
ran on: assert.* element outcomes and pm.test results share one failure-rate
budget (maxAssertionFailureRatePct), and thresholds.failRun turns a missed
budget into a Failed run. Existing work comes across too - Postman v2.0/v2.1, Insomnia v4, and
OpenAPI 3.1/3.0 or Swagger 2.0 specs generate a ready-to-use collection. JMeter's
setUp / tearDown thread groups, which run once before or once after the whole
test, are script.setup / script.teardown here - elements on the collection
itself, run once at the run's own start or end in either run mode, rather than
a thread group of their own.
When to choose JMeter¶
JMeter is still the right answer for a large class of work, and none of these are things Vayu plans to catch up on:
- You test more than HTTP. JDBC against a database, JMS queues, FTP, LDAP, SMTP, TCP - JMeter's sampler catalogue and plugin ecosystem covers protocols Vayu's HTTP engine does not speak at all.
- You need distributed load. JMeter's controller-and-workers model drives a test from many machines. Vayu runs from one.
- Your
.jmxplans lean on what the importer does not map - a protocol other than HTTP, distributed execution, a plugin sampler,SwitchController, or a JSR223/BeanShell script (imported disabled, since Groovy and BeanShell are not JavaScript). A plan built from the common element set (HTTP samplers, the extractors, assertions, timers and logic controllers in the table above) imports and runs; a team fluent in JMeter's full plugin ecosystem is still better served staying there. - Your load model needs a load-run logic jump. Think time and pacing
timers work under load now (
timer.think, including a gaussian option;timer.pacing, including its shared-cadence case,perUser: false- one cadence held across every virtual user, not one per user; andtimer.throughput, the analogue of JMeter's Constant Throughput Timer, which targets a rate - N per minute - shared across every virtual user by default rather than held per user), and logic controllers - conditionals, once-only, throughput, loops and named transactions - are elements (control.if/.once/.switch/.throughput/.loop/.transaction) that run under load too:control.switch's dispatch andcontrol.loop's repeat jump a scenario load run's virtual users the same way they jump the sequential run,control.throughputcan share one budget across every virtual user (perUser: false) instead of one per user, andcontrol.transactioncan fold a between-membertimer.*wait into its own reported latency (includeTimers). Correlation across a scenario's steps - a login's token reaching the next step's header, per virtual user - does work under load now too: mark the extracting element or scriptinline(or set the run'selements.scriptsoverride), or let it stay in the post-run replay by default. - It has to be JVM-native for your infrastructure, monitoring, or compliance reasons.
Try it¶
Windows:
macOS and Linux: