Write a scenario file
vegaload new my-scenario
scaffolds my-scenario.vl.js (JavaScript by default; -python for a
.py scenario shelling out to a local python3). A scenario's http and
ws globals make real HTTP and WebSocket calls, carrying a value from one
into the next — a script can create something over HTTP, then open a
WebSocket and read frames until it's done, the way
examples/scenarios/http-ws-chain.vl.js
does against the sample app (its Python twin,
http_ws_chain.py, does the same
thing):
vegaload run -vus 5 -duration 10s examples/scenarios/http-ws-chain.vl.js
(flags before the scenario file — vegaload's flag parser stops at the
first non-flag argument; run vegaload run -h for the full flag list.) A
host outside localhost needs -allow-target or -yes — the same FR-CLI-06
allowlist protocol-direct mode's -target uses, just enforced per call
since a script's own targets aren't known until it runs. A scenario with no
network calls at all is still useful for pure per-iteration logic against
VegaLoad's VU pool and executor shapes (fixed-VU, ramp, step,
constant-arrival-rate) —
examples/scenarios/smoke.vl.js is a
minimal one:
vegaload run -vus 5 -duration 5s examples/scenarios/smoke.vl.js
To load test an actual HTTP target without writing a script at all, use protocol-direct mode instead — or generate a runbook of protocol-direct commands from an OpenAPI spec:
vegaload new -from-openapi examples/sample-app/openapi.json sample-app
writes two files. sample-app.vl.js is a runnable scenario that calls every
operation of the spec once per iteration, each as a named step (add -python
for a Python scenario; flags go before the name). It sends the example bodies
and required query parameters from the spec, reads a credential from env
(give it with -secret-env), and uses the id that a create returns in the calls
under it. A spec describes each endpoint on its own, so the order (creates,
reads, updates, deletes) is a guess: the file says so, and marks the ids and
other values it could not fill. Check it with vegaload validate and edit it to
fit your real flow. sample-app.vegaload-plan.md is a runbook with one
ready-to-run vegaload run command per endpoint, for loading one endpoint at a
time.
Call other protocols from a scenario
The full list, with options and examples, is in Supported protocols.
A JavaScript scenario can also use the tcp, udp, mqtt, kafka, grpc, postgres, mysql, redis, rabbitmq, ftp and sftp globals. Each
function is one call that does one job and returns what it read:
export default function () {
const r = tcp.send("tcp://localhost:6379", { body: "PING\r\n", until: "\r\n" });
check(r, { "got PONG": (x) => x.ok && x.body === "+PONG\r\n" });
mqtt.publish("mqtt://localhost", { topic: "sensors/1", body: "21.5", qos: 1 });
const m = mqtt.subscribe("mqtt://localhost", { topic: "sensors/#", count: 2, timeout: "5s" });
console.log(m.messages.map((x) => x.topic + "=" + x.body).join(", "));
}
tcp.send(url, options)andudp.send(url, options): the options are the same as the-optkeys in the TCP and UDP guide (until,read,expect,tls,reply, and so on), plusbody. The reply isr.body. A script's text is sent as written: backslash escapes are off unless you passescape: true.mqtt.publish,mqtt.subscribeandmqtt.roundtriptaketopic,bodyand the other MQTT-optkeys (notmode: the function sets it). The messages that were read are inr.messages, each withtopicandbody. Give a password aspassword: env.MQTT_PASSWORDand run with-secret-env MQTT_PASSWORD, so it is removed from every output.kafka.produce,kafka.consume,kafka.roundtripandkafka.admintake the Kafka-optkeys (topic,key,count,expect,from,action,sasl,username,acks, and so on; notmode) plusvalue(the record value,bodymeans the same). The records are inr.records, each withtopic,partition,offset,keyandvalue. Forproduce, the partition and offset say where the broker stored the record. An admin answer is inr.text. A Kafka client is made on the first call and kept for the rest of that virtual user's run, soproducedoes not connect again for every call.consumeandroundtripread with their own connection, as in the load-test mode. A SASL password ispassword: env.NAME. The safety allowlist checks the first broker you name, not the other brokers the cluster announces. The kept client stops at the longest timeout it was built with (the first call'stimeout, and at least one minute), so a single Kafka call cannot run longer than that.grpc.call(url, options)makes one unary gRPC call. The options aremethod(required, such as"/package.Service/Method"),body(an object, or JSON text; leave it out for an empty message) andheaders(an object, sent as gRPC metadata). The reply hasstatus(the gRPC code, 0 when it worked),statusName(such as"NotFound"),body(the reply as JSON text) andjson(the same, already parsed). The script needs no.protofile: the first call to a method asks the server for its message types (server reflection), and the answer is kept. A JSON body needs the server to offer reflection. Sent to a server without it, the call throws. Call such a server withencoding: "base64":bodyand the reply are then the encoded message in base64. A server's error status is a reply withokfalse, not an exception. Only unary methods work for now.urlishost:port,grpc://host:portorgrpcs://host:portfor TLS.grpcs://takes the four options under "TLS with your own CA". One connection is made on the first call and kept for the rest of that virtual user's run.postgres.query(url, options)runs one SQL text, given asbody, and takes the other PostgreSQL-optkeys (username,database,sslmode,args,min_rows,expect,max_rows,query_mode,allow_writes,pool).argsis a list:args: [42, "paid"]. The reply hasrows(a list of objects, one per row, keyed by column name, up tomax_rows),columns(the names, in order),rowCount(all the rows, also those pastmax_rows),rowsAffectedandcommandTag(such as"SELECT 2"). Numbers, text, booleans and NULL (null) keep their type, and JSON columns are parsed. Times are text in UTC (RFC 3339), andnumeric,uuidand byte columns are text, so no precision is lost. If two columns have the same name, the last one wins: give them different names withas. Give the password aspassword: env.DB_PASSWORDand run with-secret-env DB_PASSWORD. A pool with one connection is made on the first call and kept for the rest of that virtual user's run, so each call does not connect again. A server error (a bad SQL, a failed constraint) is a reply withokfalse and the SQLSTATE code inerror, not an exception. Scripts are read-only like the command line: a script that writes passesallow_writes: true.mysql.query(url, options)runs one SQL text, given asbody, and takes the other MySQL-optkeys (username,database,tls,args,min_rows,expect,max_rows,query_mode,allow_writes,pool). Placeholders are?, andargsis a list:args: [42, "paid"]. The reply hasrows,columns,rowCountandrowsAffected, the same as PostgreSQL, pluslastInsertIdfor a single insert. It has nocommandTag. A text of several statements reportsrowsAffectedas 0. Give the password aspassword: env.DB_PASSWORDand run with-secret-env DB_PASSWORD. A pool with one connection is made on the first call and kept for the rest of that virtual user's run. A server error is a reply withokfalse, and the error number and SQLSTATE are inerror. Scripts are read-only like the command line: a script that writes passesallow_writes: true.urlmay bemysql://ormariadb://.redis.command(url, options)runs one Redis command, given asbody, or several, one per line. It takes the other Redis-optkeys (username,database,tls,pool,protocol,allow_writes,allow_admin,transaction,args,min_rows,expect,max_rows).argsis a list:args: ["user:42", "ann"]. A?is one whole argument. The reply hasvalue(the last command),values(one entry per command) androwCount. In Python,r.valuesis that list (andr["values"]is the same). It has norows,columns,rowsAffected,commandTagorlastInsertId. Give the password aspassword: env.REDIS_PASSWORDand run with-secret-env REDIS_PASSWORD. A client with one connection is made on the first call and kept for the rest of that virtual user's run. A server error, and a command the read-only check refuses, is a reply withokfalse, not an exception. A script that writes passesallow_writes: truein the connection options. Admin commands also passallow_admin: true.urlmay beredis://orrediss://.rabbitmq.publish,rabbitmq.consume,rabbitmq.roundtripandrabbitmq.admintake the RabbitMQ-optkeys (notmode: the function sets it) plusbody. The messages that were read are inr.messages, each withexchange,routingKey,body,messageIdandredelivered. An admin answer is inr.text. Give the password aspassword: env.RABBITMQ_PASSWORDand run with-secret-env RABBITMQ_PASSWORD. A connection is made on the first call and kept for the rest of that virtual user's run. A server error, and a call the safety check refuses, is a reply withokfalse, not an exception. A script that removes messages passesallow_writes: true. Admin actions that delete or purge also passallow_admin: true.urlmay beamqp://oramqps://.ftp.connect,ftp.download,ftp.upload,ftp.list,ftp.stat,ftp.deleteandftp.roundtriptake the FTP-optkeys (notmode: the function sets it) plusbody. The names that were read are inr.entries, each withname,type,sizeandtime.r.sizeis the file size,r.totalis how many names the listing had,r.timingis the parts of the call in milliseconds, andr.textis the short answer. Give the password aspassword: env.FTP_PASSWORDand run with-secret-env FTP_PASSWORD. A session pool is made on the first call and kept for the rest of that virtual user's run. A server error, and a call the safety check refuses, is a reply withokfalse, not an exception. A script that writes passesallow_writes: truein the connection options. A delete, or an upload to a fixed path, also passesallow_admin: true.urlmay beftp://orftps://.sftp.connect,sftp.download,sftp.upload,sftp.list,sftp.stat,sftp.deleteandsftp.roundtriptake the SFTP-optkeys (notmode: the function sets it) plusbody. The reply fields are the same asftp. Give the password aspassword: env.SFTP_PASSWORDand a key passphrase askey_passphrase: env.SFTP_PP.password_envandkey_passphrase_envare refused.key_fileis a path. One set of SSH connections is made on the first call and kept for the rest of that virtual user's run. A server error, and a call the safety check refuses, is a reply withokfalse, not an exception. A script that writes passesallow_writes: truein the connection options. A delete, or an upload to a fixed path, also passesallow_admin: true.urlissftp://.insecure: trueskips the host key check. TLS options are refused.- Every function also accepts
insecure(skip TLS checks; for SFTP this skips the host key check) andtimeout(milliseconds, or text such as"2s"). - Every call returns
{ok, error, bytesSent, bytesReceived, body, messages}. Kafka calls also returnrecordsandtext, gRPC calls returnstatus,statusNameandjson, PostgreSQL calls returnrows,columns,rowCount,rowsAffectedandcommandTag, MySQL calls returnrows,columns,rowCount,rowsAffectedandlastInsertId, Redis calls returnvalue,valuesandrowCount, and RabbitMQ calls returnmessages(each{exchange, routingKey, body, messageId, redelivered}) andtext, and FTP and SFTP calls returnentries(each{name, type, size, time}),size,total,timingandtext. A network failure does not throw:okis false anderrorsays why. A call that is set up wrongly (an unknown option, a missing topic) does throw. - The safety allowlist is checked for the host of every call, like
http. - Each call opens its own connection and closes it, like the load-test mode.
For
mqtt.subscribe, the message must arrive during the call, so run it when something else publishes.
Python scenarios have the same globals, with the same options and the
same reply. The options are keyword arguments, the reply works as
r.ok and as r["ok"], and a word that Python reserves gets a trailing
underscore (from_="end"):
def iteration():
r = tcp.send("tcp://localhost:6379", body="PING\r\n", until="\r\n")
check(r, {"got PONG": lambda x: x.ok and x.body == "+PONG\r\n"})
k = kafka.produce("kafka://localhost", topic="orders", key="k1", value="v")
print(k.records[0].partition, k.records[0].offset)
g = grpc.call("localhost:50051", method="/pkg.Greeter/SayHello", body={"name": "x"})
check(g, {"greeted": lambda x: x.ok and x.json.message == "Hello x"})
grpc.call sends and reads JSON, which needs server reflection on the
server. Without it, the call throws. For such a server use
encoding="base64", and send the encoded message in base64.
A call set up wrongly raises VegaloadError.
examples/scenarios/mqtt-tcp.vl.js
(and its Python twin, mqtt_tcp.py)
is a small example, and
examples/scenarios/kafka-orders.vl.js
shows Kafka, and
examples/scenarios/mixed-protocols.vl.js
(Python: mixed_protocols.py) mixes
HTTP, gRPC and WebSocket in one flow, and
examples/scenarios/postgres-orders.vl.js
(Python: postgres_orders.py)
writes and reads rows in PostgreSQL, and
examples/scenarios/mysql-orders.vl.js
(Python: mysql_orders.py)
reads and writes a MySQL row.
examples/scenarios/redis-cache.vl.js
(Python: redis_cache.py)
reads a cache key, fills it on a miss, and updates a counter.