Node reference
Every kind of node an experiment can hold, in the groups of the add menu: actions, waits, emulation, faults, data, checks and flow. How to add and wire them is in The editor; signallab nodes prints the same catalogue as JSON, for scripts and assistants (The command line).
Reading this page
Each node has a table of its fields:
- Field is the name in the properties pane; In the file is the key in the experiment's JSON.
- Default is what a node gets when you add it in the editor. Where a file may leave a key out, the value it then takes is given as if absent; other keys are required in a file.
- Templates: yes — the field takes
{{templates}}: parameters, variables set earlier, secrets and generators, resolved as the step runs (Data and templates). Parameters only — it is opened before the first step, when only parameters are known. No — the value is taken as written.
Times are in milliseconds. Limits are checked before a run starts; a field out of range keeps the experiment from running and is shown on the node.
A node in a file
In an experiment file, a node is an object with an id (unique in the experiment), its type, its place on the canvas (x, y, zero or more), its fields, and the settings it uses (retry, repeat, load, left out when off). A wire is an edge from one node's output (port, next if absent) to another node:
{
"nodes": [
{ "id": "start", "type": "start", "x": 40, "y": 80 },
{ "id": "ping", "type": "udp", "x": 270, "y": 80, "target": "127.0.0.1:9000", "text": "PING",
"retry": { "attempts": 3, "delay_ms": 500, "backoff": "fixed" } },
{ "id": "end", "type": "end", "x": 500, "y": 80 }
],
"edges": [
{ "from": "start", "to": "ping", "port": "next" },
{ "from": "ping", "to": "end", "port": "next" }
]
}The examples below show one node each, as a file holds it.
Settings shared by many nodes
These are switched on in the lower part of a node's properties. Which node takes which is listed under each node.
| Setting | Takes it | What it does |
|---|---|---|
| Retry | Nodes that send or listen: HTTP request, TCP message, OSC message, UDP datagram, MQTT publish, WebSocket connect, WebSocket send, and every wait | Tries again when the step fails |
| Repeat | Nodes that send: HTTP request, TCP message, OSC message, UDP datagram, MQTT publish, WebSocket send | Sends again and again, a number of times or for a time |
| Load | HTTP request | Sends the request on a load profile, measured and judged by thresholds |
| Waiting for a reply | OSC message, UDP datagram | Sends and waits for the answer in the same step |
Retry
retry on failure: when the step fails — no connection, a timeout, a wait with nothing matching — it pauses and runs again. Each failed attempt is a row in the timeline; the step fails when the last attempt does. A template that cannot be resolved is not retried. Stop also ends a pause.
| Field | In the file | What | Default and limits |
|---|---|---|---|
| Attempts | retry.attempts | Attempts in all, the first included | 3; 2–10 in the editor (a file may also say 1) |
| Pause, ms | retry.delay_ms | The pause before the second attempt | 500; 0–60 000 |
| Pauses | retry.backoff | the same (fixed): the same pause each time; doubling (exponential): twice as long after each failure | fixed (also if absent) |
No pause is longer than 60 seconds, however it doubles. A wait whose Timeout output has a wire does not fail on a timeout — it leaves through that output — so it is not retried then.
Repeat
repeat sending: the node sends again and again — a heartbeat, a poll, a steady stream — without a loop in the graph. Each send reads its templates afresh ({{counter}} is its number, {{now}} its time), and Retry, when on, applies to each send. The step passes when every send did; a send that fails for good fails the step. The timeline reports progress at most once a second.
| Field | In the file | What | Default and limits |
|---|---|---|---|
| Repeat | repeat.until | a number of times (count) or for a time (duration) | count (also if absent) |
| Times | repeat.count | Sends in all, the first included | 10 (also if absent); 2–10 000 |
| For, ms | repeat.duration_ms | How long to keep sending, from the first send | 10 000 (also if absent); 1–300 000 |
| Every, ms | repeat.interval_ms | The pause between two sends | 1 000; 10–60 000; required in a file |
| Jitter, ms | repeat.jitter_ms | Each pause up to this much longer, drawn from the run's seed | 0 (also if absent); 0–60 000 |
The repeats must fit in a run's 300 seconds, and for a time must need fewer than 10 000 sends (its time divided by the interval).
Load
send under load, on an HTTP request only: the request is sent on a profile — a constant rate, a ramp, steps, a spike or random arrivals — with up to 512 in flight at once (32 by default), and measured: latencies, errors, the rate achieved. Thresholds decide whether the step passes. Load replaces Repeat and Retry (a failed request is counted, not tried again), and leaves no response for the checks after it. Its fields and results are in Load testing.
Waiting for a reply
wait for a reply, on an OSC message or a UDP datagram: the message is sent from the port the reply is awaited on, so a device that answers the sender is heard, and the step passes only when a matching reply arrives in time. No reply fails the step — Retry sends again. The reply is stored in a variable, as a wait's is.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Reply on (IP:port) | reply.bind | IP:port to send from and listen on; port 0 takes any free port | 0.0.0.0:0 | No |
| Reply address pattern (OSC) | reply.address | The reply's address pattern, as in Wait for OSC | /* | Yes |
| Argument rules (OSC) | reply.args | Argument rules, as in Wait for OSC | none; at most 16 | Values: yes |
| Reply payload (UDP) | reply.mode | any, contains, regex or hex — see Payload matching | any (also if absent) | No |
| Pattern (UDP) | reply.pattern | What the reply must contain or match | empty; required unless any | Yes |
| Timeout, ms | reply.timeout_ms | How long to wait | 2 000 (also if absent); 1–120 000 | No |
| Reply variable | reply.variable | The variable the reply is stored in | reply (also if absent) | No |
The reply's port is opened before the first step, like a wait's.
Actions
Nodes that send. A wait after an action counts messages from the moment the action started.
HTTP request
Sends one HTTP request and keeps the response for the checks, branches and Extract value nodes after it.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Method | request.method | GET, HEAD, POST, PUT, PATCH, DELETE or OPTIONS (a file may name any method) | GET | No |
| URL | request.url | An http:// or https:// URL | http://127.0.0.1:8080/ | Yes |
| Timeout (ms) | request.timeout_ms | For the whole exchange | 4 000 (10 000 if absent); 1–120 000 | No |
| Request headers | request.headers | [[name, value], …]; a row with an empty name is skipped | none | Yes, names and values |
| Body | request.body | Text, or null for none | null | Yes |
| Authentication | request.auth | None, Basic, Bearer token or Digest, with User name and Password, or Token | none | Yes |
- Any answer passes the step, 404 and 500 included: check the status with HTTP status or branch on it with Status branch. A request that gets no answer — refused, a timeout, a name that does not resolve, a certificate that is not trusted — fails the step.
- Redirects are followed, ten at most.
https://certificates are verified. - The response body is kept up to 256 KiB for the checks; a larger body is cut there (the checks say so when what they look for may be past the cut).
- Digest answers the server's 401 challenge and sends the request again. The credentials go only into the request: steps, reports and the Inspector never show the
Authorizationheader. Write a password as{{secret.NAME}}. - While the experiment keeps cookies (on by default, under Parameters), what servers set is sent back with the run's later requests to them.
Outputs: Output. Settings: Retry, Repeat, Load.
{ "id": "cue", "type": "http", "x": 270, "y": 80,
"request": { "method": "POST", "url": "{{api}}/cue", "headers": [["Content-Type", "application/json"]],
"body": "{\"cue\": 1}", "timeout_ms": 5000,
"auth": { "scheme": "bearer", "token": "{{secret.API_TOKEN}}" } } }See also HTTP.
TCP message
Connects to a host over TCP, writes the payload, waits up to 250 ms for the first bytes of an answer (it reads at most 1 024 bytes, once) and closes the connection. The answer's size is reported, not checked.
In the Inspector the step is two tcp frames with the source experiment: the payload written and, when one came, the answer read. Secrets in use are masked in both, as in any frame.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Host | host | A host name or an IP address | 127.0.0.1 | Yes |
| Port | port | 9000; 1–65 535 | No | |
| Timeout (ms) | timeout_ms | For connecting, writing and the answer together | 4 000 (also if absent); 1–120 000 | No |
| Payload | payload | The text written once connected, as UTF-8 | hello | Yes |
The step fails when the connection is refused, the name does not resolve or the time runs out. Outputs: Output. Settings: Retry, Repeat. Send now connects and writes the payload once, and the node's result says how many bytes were sent and came back.
{ "id": "go", "type": "tcp", "x": 270, "y": 80, "host": "127.0.0.1", "port": 5000, "payload": "GO\r\n", "timeout_ms": 2000 }OSC message
Sends one OSC 1.0 message over UDP.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Target host:port | target | IP:port or host:port; a host name is looked up when the step sends, its IPv4 address taken when it has one | 127.0.0.1:9000 | Yes |
| OSC address | address | Starts with / | /test | Yes |
| Arguments | args | [{ "type", "value" }, …] — int, float, str, long, double, bool, blob (bytes), nil (no value) | none | Text (str) values: yes |
| wait for a reply | reply | Optional: send and wait for the answer — see Waiting for a reply | off |
Outputs: Output; with a reply expected, it is followed only when the reply came. Settings: Retry, Repeat, a reply. ⚡ Route through impairment in its properties puts an Impairment in front of it.
{ "id": "fader", "type": "osc", "x": 270, "y": 80, "target": "{{device}}", "address": "/fader/1",
"args": [{ "type": "float", "value": 0.75 }] }See also OSC.
UDP datagram
Sends a text payload as one UDP datagram to one or more targets.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Target host:port | target | IP:port or host:port; several separated by commas, semicolons or new lines each get the datagram. A host name is looked up when the step sends, its IPv4 address taken when it has one | 127.0.0.1:9000 | Yes |
| Payload | text | The payload, as UTF-8 | hello; at most 65 507 bytes | Yes |
| wait for a reply | reply | Optional: send and wait for the answer — see Waiting for a reply | off |
The step fails if any target cannot be reached. Outputs: Output. Settings: Retry, Repeat, a reply.
{ "id": "ping", "type": "udp", "x": 270, "y": 80, "target": "{{device}}", "text": "PING {{run.id}}",
"reply": { "bind": "0.0.0.0:0", "mode": "contains", "pattern": "PONG", "timeout_ms": 1000, "variable": "pong" } }MQTT publish
Connects to an MQTT broker, publishes one message and disconnects. The connection is MQTT 3.1.1 over plain TCP, with a clean session and no user name or password. Connecting, publishing and the broker's acknowledgement must all happen within 15 seconds.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Broker host | host | The broker's host name or address | 127.0.0.1 | Yes |
| Port | port | 1883; 1–65 535 | No | |
| Topic | topic | No wildcards (+, #) | lab/test | Yes |
| Payload | payload | The message, as text | hello | Yes |
| QoS | qos | 0, 1 or 2 | 0 | No |
| Retain message | retain | true: the broker keeps it as the topic's value | false | No |
All six keys are required in a file. The step fails when the broker cannot be reached or refuses the connection or the message. Outputs: Output. Settings: Retry, Repeat.
{ "id": "light", "type": "mqtt", "x": 270, "y": 80, "host": "{{broker}}", "port": 1883,
"topic": "lab/light/1/set", "payload": "on", "qos": 1, "retain": false }See also MQTT.
WebSocket connect
Opens a WebSocket for the rest of the run, or until a WebSocket close. What arrives from then on is kept for the Wait for WebSocket steps on it. The URL and headers are resolved when the step runs, so a token extracted earlier can be in them. Run again — in a Loop — it closes its previous connection first and opens a new one. When the run ends, in any way, its connections are closed with a close frame.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| URL | url | A ws:// or wss:// URL | ws://127.0.0.1:9001/ | Yes |
| Request headers | headers | [[name, value], …] sent with the upgrade request | none | Yes, names and values |
| Subprotocols | protocols | Subprotocols to offer, in order of preference; the server picks one | none | No |
| Timeout (ms) | timeout_ms | For connecting and the upgrade | 5 000 (10 000 if absent); 1–120 000 | No |
wss:// trusts the same certificates as https://. The step fails when the connection or the upgrade fails; the server's status is in the reason. Outputs: Output. Settings: Retry (not Repeat).
{ "id": "socket", "type": "ws_connect", "x": 270, "y": 80, "url": "ws://127.0.0.1:9001/chat",
"headers": [["Authorization", "Bearer {{token}}"]], "protocols": ["chat.v1"], "timeout_ms": 5000 }See also WebSocket.
WebSocket send
Sends one message on the connection a WebSocket connect opened.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Connection | connection | The id of a WebSocket connect node of this experiment | the first one | No |
| Format | binary | Text (false), or Binary (hex) (true): the payload is bytes written as hex, de ad be ef | false (also if absent) | No |
| Payload | text | The message | hello; at most 16 MiB | Yes |
The connect must come before the send on its path; a send whose connection is not open fails. Answers count from the moment the message is written. Outputs: Output. Settings: Retry, Repeat.
{ "id": "hello", "type": "ws_send", "x": 500, "y": 80, "connection": "socket",
"text": "{\"type\":\"ping\",\"id\":\"{{uuid}}\"}", "binary": false }WebSocket close
Closes a connection with a close handshake. The timeline says who closed it: this step, the server earlier (with its code), or a connection that had broken.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Connection | connection | The id of a WebSocket connect node | the first one | No |
| Close code | code | 1000 (normal), or 3000–4999 for an application's own | 1000 (also if absent) | No |
| Reason | reason | Sent with the code | empty; at most 123 bytes, after templates | Yes |
Outputs: Output. No settings.
{ "id": "bye", "type": "ws_close", "x": 960, "y": 80, "connection": "socket", "code": 1000, "reason": "done" }Log marker
Writes a line into the timeline and the report — a checkpoint, or the values a run reached.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Message | message | The text | Check point; at most 10 000 characters | Yes |
Outputs: Output. No settings.
{ "id": "ready", "type": "log", "x": 500, "y": 80, "message": "device {{device}} ready" }Waits
The Observe group: nodes that wait for something to arrive. They share these rules:
- They listen from the start of the run. A wait's port or broker subscription is opened before the first step, so a device that answers faster than the next step begins is not missed. Two waits on the same address share one socket.
- They count from the latest action on their branch. A message that arrived before the branch's last request is not an answer to it; before any action, everything since the run started counts.
- The first matching message is taken. A message one wait took is not seen by another.
- Matched or Timeout. On a match the message is stored in the wait's variable and the flow follows Matched. When the time runs out, it follows Timeout if that output has a wire; otherwise the step fails, saying how many other messages arrived.
- Each socket keeps the latest 1 024 messages (and 64 MiB); older ones are dropped, and a timeout says how many were.
- Listen now listens with that one step, from now on.
Outputs: Matched (required), Timeout (optional). Settings: Retry.
Payload matching
Wait for UDP, Wait for MQTT, Wait for WebSocket and a UDP reply choose how the payload must look:
| Option | In the file | Matches when the payload |
|---|---|---|
| Any datagram | any | is anything |
| Contains text | contains | read as UTF-8 text, contains the pattern (case-sensitive) |
| Matches regex | regex | read as UTF-8 text, matches the regular expression |
| Contains bytes (hex) | hex | contains the bytes, written as hex pairs: de ad be ef, deadbeef, 0xde,0xad, DE:AD |
The matched message is stored as an object. Later steps read its fields as {{reply.text}} (with the variable's name in place of reply):
| Field | What |
|---|---|
text | The payload as text |
hex, bytes | The payload in hex (its first 1 024 bytes), and its size in bytes |
match | What matched: the text, the regular expression's first group (or the whole match), or the bytes |
from | The sender's IP:port |
ms | Milliseconds from the branch's latest action (or the start of the run) to the message |
topic | Wait for MQTT: the topic it was published to |
json, kind | Wait for WebSocket: the message parsed as JSON (null when it is not), and text or binary |
Wait for OSC
Waits for an OSC message whose address matches a pattern and whose arguments meet every rule. In a bundle, the first message that matches is the one taken.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Listen on (IP:port) | bind | IP:port to listen on; 0.0.0.0 for every network card | 127.0.0.1:9001 | No |
| Address pattern | address | * any characters, ? one, [0-9] a set ([!0-9] outside it), {ping,pong} either; wildcards stay within one / segment | /pong; at most 512 characters | Yes |
| Argument rules | args | [{ "index", "op", "value" }, …]: argument index compared with value by op (Comparisons); all must hold | none; at most 16, index 0–63 | Values: yes |
| Timeout, ms | timeout_ms | 2 000 (also if absent); 1–120 000 | No | |
| Reply variable | variable | Where the message is stored | reply (also if absent) | No |
An argument compares as text: numbers as written, strings without quotes, true/false, a blob in hex. A rule on an argument the message does not have does not hold. The stored message has address, args ({{reply.args[0]}}), from and ms.
{ "id": "status", "type": "wait_osc", "x": 500, "y": 80, "bind": "0.0.0.0:9001", "address": "/status",
"args": [{ "index": 0, "op": "eq", "value": "ready" }], "timeout_ms": 5000, "variable": "reply" }Wait for UDP
Waits for a UDP datagram whose payload matches.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Listen on (IP:port) | bind | IP:port to listen on | 127.0.0.1:9001 | No |
| Payload | mode | See Payload matching | contains (any if absent) | No |
| Pattern | pattern | What the payload must contain or match | pong; required unless any | Yes |
| Timeout, ms | timeout_ms | 2 000 (also if absent); 1–120 000 | No | |
| Reply variable | variable | reply (also if absent) | No |
{ "id": "ready", "type": "wait_udp", "x": 500, "y": 80, "bind": "0.0.0.0:9002", "mode": "contains",
"pattern": "READY", "timeout_ms": 5000, "variable": "reply" }Wait for MQTT
Waits for a message published to a topic at a broker, whose payload matches. The run connects and subscribes before its first step. Retained messages the broker replays on subscribing are ignored: only what is published after the run started counts.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Broker host | host | The broker | 127.0.0.1 | Parameters only |
| Port | port | 1883; 1–65 535 | No | |
| Topic filter | topic | A filter: + is any one level, # everything below (last only) | lab/# | Parameters only |
| Payload | mode | See Payload matching | any (also if absent) | No |
| Pattern | pattern | empty; required unless any | Yes | |
| Timeout, ms | timeout_ms | 2 000 (also if absent); 1–120 000 | No | |
| Reply variable | variable | reply (also if absent) | No |
{ "id": "state", "type": "wait_mqtt", "x": 500, "y": 80, "host": "{{broker}}", "port": 1883,
"topic": "lab/+/state", "mode": "contains", "pattern": "on", "timeout_ms": 5000, "variable": "reply" }Wait for HTTP request
Waits for an HTTP request — a webhook, a callback — to the run's Emulator on that address, or, when the run has no HTTP emulator there, to a listener of the run's own that answers every request with 204. The request must match the method, the path and every condition.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Listen on (IP:port) | bind | IP:port | 127.0.0.1:18080 — where a new Emulator listens | No |
| Method | method | A method, or Any (ANY); GET also takes HEAD | ANY (also if absent) | No |
| Path | path | /hooks/:name names a segment ({{request.params.name}}); a final /* takes the rest | /* (also if absent); at most 512 characters | Yes |
| Conditions | when | [{ "on", "name", "op", "value" }, …] on a header, a query parameter, the body or a json path; every one must hold | none; at most 16 | Yes, names and values |
| Timeout, ms | timeout_ms | 5 000 (2 000 if absent); 1–120 000 | No | |
| Reply variable | variable | request (also if absent) | No |
The stored request has method, path, query, headers, body, json, params, from and ms: {{request.json.event}}, {{request.headers.x-key}}.
{ "id": "hook", "type": "wait_http", "x": 500, "y": 80, "bind": "127.0.0.1:18081", "method": "POST",
"path": "/hooks/:name", "when": [{ "on": "json", "name": "$.event", "op": "eq", "value": "deploy" }],
"timeout_ms": 5000, "variable": "request" }Wait for WebSocket
Waits for a message on the connection a WebSocket connect opened, whose payload matches. Messages since the latest action on the branch count — the connect itself, a send, or any other request.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Connection | connection | The id of a WebSocket connect node | the first one | No |
| Payload | mode | See Payload matching | any (also if absent) | No |
| Pattern | pattern | empty; required unless any | Yes | |
| Timeout, ms | timeout_ms | 2 000 (also if absent); 1–120 000 | No | |
| Reply variable | variable | reply (also if absent) | No |
A JSON message is readable field by field: {{reply.json.type}}. The connect must come before the wait on its path.
{ "id": "pong", "type": "wait_ws", "x": 730, "y": 80, "connection": "socket", "mode": "contains",
"pattern": "pong", "timeout_ms": 3000, "variable": "reply" }Emulation
Emulator
Plays a dependency — an HTTP API, an OSC, UDP or TCP device, an MQTT broker — for the whole run. It opens before the first step and answers until the run ends; in the flow the step passes at once. What it received is counted, rule by rule, in the run's report.
| Field | In the file | What | Default |
|---|---|---|---|
| Edit… | emulator | The emulator: name, bind (IP:port), protocol (http, osc, udp, tcp, mqtt), its routes or rules, and an optional outage | An HTTP API named API on 127.0.0.1:18080 answering /health |
The properties show what it plays in one line. Edit… opens its rules, the same editor as the Emulators screen; To the library keeps a copy in the emulator library, and From the library replaces this one with a copy from it. The rules — routes, responses, faults, outages — are described there.
- An HTTP emulator is also what a Wait for HTTP request on its address reads; an OSC or UDP emulator shares its port with the run's waits there.
- Two emulators of one transport cannot share a port in a run.
- Emulator down/up takes it down and brings it back.
Outputs: Output. No settings.
{ "id": "api", "type": "emulator", "x": 270, "y": 80,
"emulator": { "name": "Orders API", "bind": "127.0.0.1:18080", "protocol": "http",
"routes": [{ "method": "GET", "path": "/orders/:id", "order": "sequence",
"responses": [{ "status": 503 }, { "status": 200, "body": "{\"id\":\"{{request.params.id}}\"}" }] }] } }Faults
Nodes that break things on cue. A branch of Delay nodes and these beside the traffic reads as a schedule; Faults on a schedule shows how.
Impairment
An impairment relay for the whole run: the system under test sends to (or connects to) Listen on instead of the real target; the relay forwards to Forward to, and the replies come back the same way, impaired by the profile. It opens before the first step and closes when the run ends, in any way, so nothing stays impaired; in the flow the step passes at once. Every decision draws from the run's seed: the same seed and the same traffic meet the same fate.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Listen on | listen | IP:port the system under test sends to | 127.0.0.1:9010 | Parameters only |
| Forward to | target | IP:port of the real destination, or host:port — a host name is looked up when the run starts, and a name that cannot be found stops the run at this node | 127.0.0.1:9000 | Parameters only |
| Protocol | protocol | UDP (udp): each datagram meets its own fate; TCP (tcp): each connection is joined to one of its own to the target, and both streams are impaired | UDP (udp if absent) | No |
| Preset and the values under it | profile | What the relay does to the traffic — see The profile | LAN (no impairment if absent) | No |
A relay's listening address cannot be another socket of the run, and relays may not forward to each other in a circle. A target given by name is followed once it is looked up, so a circle through a name stops the run as it starts. The report counts each phase of a relay apart.
Outputs: Output. No settings.
{ "id": "relay", "type": "impairment", "x": 270, "y": 80, "listen": "127.0.0.1:9010", "target": "{{device}}",
"profile": { "name": "lan", "latency_ms": 1, "jitter_ms": 1 } }The profile
A preset chip — LAN, Busy Wi-Fi, 4G, Satellite, Intermittent, Offline — fills in every value; change any of them after. A relay reads only the values of its protocol; in a file every key may be left out (zero, off).
| Field | In the file | What | Limits | Protocol |
|---|---|---|---|---|
| — | name | A label for the timeline and the report: a preset's key (lan, wifi, 4g, satellite, intermittent, offline) or your own | at most 60 characters | both |
| Offline — nothing gets through | offline | Nothing gets through | true / false | both |
| Latency | latency_ms | Delay added to every packet, or chunk of a stream | 0–60 000 (the slider goes to 1 000) | both |
| Jitter | jitter_ms | A random extra delay up to this much; a TCP stream stays in order | 0–60 000 (the slider goes to 500) | both |
| Bandwidth, kbit/s | rate_kbps | A bandwidth limit, 0 for none. UDP: past a second of queue, datagrams are dropped as throttled; TCP: the sender is slowed down, nothing is dropped | 0, or 8–10 000 000 | both |
| Packet loss | loss | The chance a datagram is dropped | 0–1 (the slider shows %) | UDP |
| Burst loss, Burst length, datagrams | burst_start, burst_length | The chance a burst of loss starts, and how many datagrams it lasts on average | 0–1; 1–1 000 when bursts are on | UDP |
| Duplication | duplicate | The chance a datagram is sent twice | 0–1 | UDP |
| Corruption | corrupt | The chance one bit of a datagram is flipped | 0–1 | UDP |
| Reordering | reorder | The chance a datagram is held back, so later ones overtake it | 0–1 | UDP |
| Connection reset | reset | The chance a chunk of a stream resets its connection instead — both sides get a reset | 0–1 | TCP |
| Half-open | stall | The chance a chunk leaves its connection half-open: nothing more goes through either way, and neither side is told | 0–1 | TCP |
More on relays, presets and what they model on the Impairment page.
Change impairment
Switches one of the run's Impairment nodes to another profile from this step on, without dropping its port. The phase so far is closed and counted in the report.
| Field | In the file | What | Default | Templates |
|---|---|---|---|---|
| Impairment | relay | The id of an Impairment node of this experiment | the first one | No |
| Preset and the values under it | profile | What it impairs with from now on — see The profile; the relay reads the values of its own protocol | Offline (no impairment if absent) | No |
The step fails if the relay is not running — it failed to relay, say. Outputs: Output. No settings.
{ "id": "cut", "type": "impairment_change", "x": 730, "y": 200, "relay": "relay",
"profile": { "name": "offline", "offline": true } }Emulator down/up
Takes one of the run's emulators down, or brings it back up. While it is down an HTTP emulator answers as While down says; a TCP device and an MQTT broker drop their connections and refuse new ones; OSC and UDP devices answer nothing. Up again, the emulator follows its own outage schedule, if it has one.
| Field | In the file | What | Default |
|---|---|---|---|
| Emulator | emulator | The id of an Emulator node of this experiment | the first one |
| State | down | Down (true) or Up (false) | down (false if absent) |
| While down | fault | HTTP only: 503 Unavailable (unavailable), Close the connection (reset: the connection is closed without an answer) or No answer (timeout: the request is held until the client gives up, 120 s at most) | unavailable (also if absent) |
Outputs: Output. No settings. Nothing is templated.
{ "id": "down", "type": "emulator_state", "x": 500, "y": 200, "emulator": "api", "down": true, "fault": "unavailable" }Data
Extract value
Saves a part of the latest HTTP response on its path as a variable, for later fields ({{token}}), checks and branches. An HTTP request must come before it on every path. Clicking a value in a Send now response adds one for you.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Variable | variable | The name: letters, digits and _, not starting with a digit, not a reserved word, not a parameter's name | token | No |
| Take from | from | JSON field (json), Header (header), Status code (status), Whole body (body) or Regular expression (regex) | json | No |
| JSON path, Header name or Pattern (group 1 if present) | expr | A JSON path ($.data.token, $.items[0], $["first name"]), a header name (any case), or a regular expression — its first group, or the whole match | $.token; not used for status and body | No |
The step fails when there is nothing to take: the body is not JSON, the path or header is missing, the expression does not match, or — for a JSON field or the whole body — the body was longer than the 256 KiB kept. A status is stored as a number; the rest as text, or as the JSON value found. Outputs: Output. No settings.
{ "id": "token", "type": "extract", "x": 500, "y": 80, "variable": "token", "from": "json", "expr": "$.data.token" }More on variables in Data and templates.
Checks
A check passes, or fails the run. The four response checks read the latest HTTP response on their path, so an HTTP request — not one under load — must come before them on every path.
HTTP status
Passes when the latest response's status is exactly the one given.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Expected status | status | 200; 100–599 | No |
Outputs: Output. No settings.
{ "id": "ok", "type": "assert_status", "x": 500, "y": 80, "status": 200 }Response text
Passes when the latest response's body contains the text, exactly (case included). Only the first 256 KiB of a body are kept: text not found in a body that was cut fails with that reason.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Contains text | contains | ok; required | Yes |
Outputs: Output. No settings.
{ "id": "ready", "type": "assert_body", "x": 500, "y": 80, "contains": "ready" }Response header
Passes when the latest response has the header and its value contains the text. The header's name is matched in any case; the value exactly.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Header name | name | content-type; required | Yes | |
| Contains text | contains | What its value must contain; empty: the header only has to be there | application/json | Yes |
Outputs: Output. No settings.
{ "id": "json", "type": "assert_header", "x": 500, "y": 80, "name": "Content-Type", "contains": "json" }Response time
Passes when the latest response took at most this long, from sending the request to the end of its body.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Maximum time, ms | max_ms | 1 000; 1–120 000 | No |
Outputs: Output. No settings.
{ "id": "fast", "type": "assert_latency", "x": 500, "y": 80, "max_ms": 250 }Check value
Compares a value — usually a variable, written as a template — with an expected one, and passes when the comparison holds.
| Field | In the file | What | Default | Templates |
|---|---|---|---|---|
| Value | value | What is compared: {{token}}, {{reply.args[0]}} | {{token}} | Yes |
| Condition | op | See Comparisons | is not empty | No |
| Expected | expected | Not used by is empty and is not empty | empty (also if absent) | Yes |
Outputs: Output. No settings.
{ "id": "state", "type": "assert_value", "x": 730, "y": 80, "value": "{{state}}", "op": "eq", "expected": "ready" }Comparisons
Check value, Branch on value, the exit condition of a Loop, OSC argument rules and HTTP conditions compare the same way:
| Option | In the file | Holds when the value |
|---|---|---|
| equals | eq | equals the expected one — as numbers when both are numbers (200 = 200.0), else as exact text |
| does not equal | ne | does not equal it, by the same rule |
| less than, at most, greater than, at least | lt, le, gt, ge | is less, at most, greater, at least — both must be numbers: otherwise a check, a branch or a Loop fails the step, and an argument rule or an HTTP condition does not hold |
| contains | contains | contains the expected text |
| matches regex | matches | matches the expected regular expression |
| is empty, is not empty | empty, not_empty | is empty (spaces count as empty) / is not |
Flow
Nodes that decide where the run goes. More on branches, joins and loops in Flow.
Start
Where the run begins; every experiment has exactly one. It has no input and no fields. The timeline's first row gives the run's seed.
Outputs: Output, required. Several wires from it start parallel branches at once.
{ "id": "start", "type": "start", "x": 40, "y": 80 }End
Where the run completes; every experiment has exactly one, and it has no outputs. Several branches may lead to it: the run passes once, after the last branch finished, and only if none failed. A run that never reaches End fails.
{ "id": "end", "type": "end", "x": 960, "y": 80 }Delay
Waits a fixed time before the next step.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Delay (ms) | ms | 300; 0–60 000 | No |
Outputs: Output. No settings. For longer waits, put several in a row or in a Loop.
{ "id": "pause", "type": "delay", "x": 500, "y": 80, "ms": 500 }Status branch
Chooses Yes when the latest HTTP response has this status, else No. An HTTP request must come before it on every path.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Expected status | status | 200; 100–599 | No |
Outputs: Yes and No, both required. No settings.
{ "id": "branch", "type": "branch_status", "x": 500, "y": 80, "status": 200 }Branch on value
Chooses Yes when a comparison holds, else No. Its fields and comparisons are those of Check value; a comparison that cannot be made (lt on text) fails the step.
| Field | In the file | What | Default | Templates |
|---|---|---|---|---|
| Value | value | What is compared | {{token}} | Yes |
| Condition | op | equals | No | |
| Expected | expected | empty (also if absent) | Yes |
Outputs: Yes and No, both required. No settings.
{ "id": "ok", "type": "branch_value", "x": 730, "y": 80, "value": "{{reply.args[0]}}", "op": "eq", "expected": "ok" }Parallel branch
Runs what follows Branch 1 and Branch 2 at the same time, each branch with its own copy of the variables. Each output may have more wires for more branches. No fields.
Outputs: Branch 1 and Branch 2, both required.
{ "id": "split", "type": "fork", "x": 270, "y": 80 }Join branches
Waits until every wire into it has been reached, then continues once, with the branches' variables merged — where two branches set the same variable, the one whose wire comes later in the file wins — and the latest HTTP response of the last of them that had one. No fields.
Only branches that all run meet here: a Join branches behind a Status branch, whose Yes and No never both happen, never continues; when no other path reaches End, the run fails at this node, saying how many branches it still waited for.
Outputs: Output, required.
Any other node with several wires into it runs once for each arrival.
{ "id": "joined", "type": "join", "x": 730, "y": 80 }Loop
Runs the steps on Body — which lead back to it — again and again: at most a number of times, and, when it has an exit condition, until that holds.
| Field | In the file | What | Default and limits | Templates |
|---|---|---|---|---|
| Iterations at most | max | Iterations at most | 5; 1–1 000 | No |
| stop early when | until | Optional exit condition { "value", "op", "expected" }, as Check value | off | Value and expected: yes |
- The body always runs at least once. The exit condition is read after each iteration, so the body can set what it tests.
- Done follows when the condition holds — or, without a condition, after the last iteration.
- Limit follows when the iterations ran out before the condition held. Without a wire on it, that fails the step.
- Inside the body,
{{counter}}is the iteration's number. - A body runs as one branch: each output inside it has one wire; it holds no Start, End, Parallel branch, Join branches or other Loop; it is entered only through Body; and every wire in it leads on in the body or back to the loop.
Outputs: Body and Done (required), Limit (optional). No settings.
{ "id": "poll", "type": "loop", "x": 270, "y": 80, "max": 10,
"until": { "value": "{{status.args[0]}}", "op": "eq", "expected": "ready" } }