{"id":19349412,"url":"https://github.com/sazid/lorust","last_synced_at":"2026-06-18T15:31:20.818Z","repository":{"id":176094208,"uuid":"649200944","full_name":"sazid/lorust","owner":"sazid","description":"Load generator Rust","archived":false,"fork":false,"pushed_at":"2024-12-10T07:55:42.000Z","size":68,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-05T21:34:23.524Z","etag":null,"topics":["http","http-client","load","load-generator","load-test","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sazid.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-06-04T05:30:21.000Z","updated_at":"2024-12-10T07:55:46.000Z","dependencies_parsed_at":null,"dependency_job_id":"1204fe03-2b3b-4606-b0a8-e5a474684a64","html_url":"https://github.com/sazid/lorust","commit_stats":null,"previous_names":["sazid/lorust"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sazid%2Florust","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sazid%2Florust/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sazid%2Florust/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sazid%2Florust/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sazid","download_url":"https://codeload.github.com/sazid/lorust/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240457988,"owners_count":19804489,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["http","http-client","load","load-generator","load-test","rust"],"created_at":"2024-11-10T04:26:10.281Z","updated_at":"2026-06-18T15:31:20.811Z","avatar_url":"https://github.com/sazid.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# lorust\n\n\u003e **lo**ad generator **rust**\n\n`lorust` is a Rust load generator for HTTP APIs. It supports simple CLI-driven\nHTTP load tests and TOML flow files with RustPython scripting between actions.\n\n## Build\n\n```sh\ncargo build --release\ntarget/release/lorust --help\n```\n\nFor development, replace `target/release/lorust` with:\n\n```sh\ncargo run --\n```\n\n## Quick Start\n\nRun 100 total requests at 10 requests per second:\n\n```sh\ntarget/release/lorust http https://example.com \\\n    -n 100 \\\n    -r 10 \\\n    --output-path metrics.json\n```\n\nRun for 60 seconds at 10 requests per second:\n\n```sh\ntarget/release/lorust http https://example.com \\\n    --duration 60s \\\n    -r 10 \\\n    --output-path metrics.json\n```\n\nSend a POST request with headers and a raw body:\n\n```sh\ntarget/release/lorust http https://api.example.com/users \\\n    -n 50 \\\n    -r 5 \\\n    -m POST \\\n    -H 'Content-Type: application/json' \\\n    -d '{\"name\":\"Ada\"}' \\\n    --timeout 10 \\\n    --output-path metrics.json\n```\n\nFail the process when thresholds are missed:\n\n```sh\ntarget/release/lorust http https://example.com \\\n    --duration 30s \\\n    -r 20 \\\n    --max-error-rate 1 \\\n    --max-p95-ms 500 \\\n    --min-rps 18 \\\n    --output-path metrics.json\n```\n\n## CLI Modes\n\n`lorust` has two primary modes:\n\n- `http`: run a simple HTTP load test without writing a flow file.\n- `run`: run a TOML or JSON flow definition.\n\nLegacy top-level `--flow` and `--flow-path` flags are still supported.\n\n## Global Options\n\nThese flags work with both `http` and `run`.\n\n| Option | Description | Default |\n| --- | --- | --- |\n| `--output-path \u003cpath\u003e` | Write raw HTTP metrics JSON to this path. | `metrics_output` |\n| `--run-id \u003cid\u003e` | Logical run ID attached to every emitted metric. | generated |\n| `--worker-id \u003cid\u003e` | Worker ID attached to every emitted metric. | `local` |\n\nExample:\n\n```sh\ntarget/release/lorust \\\n    --run-id local-check \\\n    --worker-id laptop \\\n    http https://example.com -n 10 -r 2\n```\n\n## HTTP Command\n\nUsage:\n\n```sh\ntarget/release/lorust http [OPTIONS] \u003cURL\u003e\n```\n\n| Option | Description | Default |\n| --- | --- | --- |\n| `\u003cURL\u003e` | URL to request. | required |\n| `-n, --requests \u003cn\u003e` | Total number of requests to execute. | `1` when `--duration` is absent |\n| `-r, --rate \u003cn\u003e` | Requests to start per second. | `1` |\n| `--duration \u003cduration\u003e` | Start requests for a duration. Supports plain seconds, `s`, `m`, and `h`. | unset |\n| `-m, --method \u003cmethod\u003e` | HTTP method. Defaults to `POST` when `--body` is provided, otherwise `GET`. | `GET` or `POST` |\n| `-H, --header \u003cheader\u003e` | Header in `Name: value` format. Can be passed multiple times. | none |\n| `-d, --body \u003cbody\u003e` | Raw request body. | none |\n| `--timeout \u003cseconds\u003e` | Per-request timeout in seconds. | `30` |\n| `--redirect-limit \u003cn\u003e` | Maximum redirects to follow. | `5` |\n| `--max-response-body-bytes \u003cn\u003e` | Maximum failed-response body bytes to store in metrics. | `4096` |\n| `--max-error-rate \u003cpercent\u003e` | Fail if error rate is above this percentage. | unset |\n| `--max-p95-ms \u003cms\u003e` | Fail if p95 latency is above this many milliseconds. | unset |\n| `--max-p99-ms \u003cms\u003e` | Fail if p99 latency is above this many milliseconds. | unset |\n| `--min-rps \u003crps\u003e` | Fail if achieved requests/sec is below this value. | unset |\n\n`--requests` and `--duration` are mutually exclusive. Use request-count mode for\nfixed-size tests and duration mode for time-window tests.\n\n## Output\n\n`lorust` prints a human-readable summary to stdout:\n\n```text\n=== Load test complete ===\nTOTAL TASKS: 100\nPASSED: 100\nFAILED: 0\nCollected metrics array size: 100\n=== HTTP metric summary ===\nREQUESTS: 100\nHTTP 2XX: 100\nHTTP NON-2XX/ERROR: 0\nERROR RATE: 0.00%\nREQUESTS/SEC: 9.98\nAVG LATENCY MS: 42.10\nP50 LATENCY MS: 40\nP95 LATENCY MS: 75\nP99 LATENCY MS: 91\nSaving collected metrics to: \"metrics.json\"\n```\n\nIt also writes raw per-request metrics to `--output-path` as a JSON array.\n\nEach HTTP metric includes:\n\n- `run_id`\n- `worker_id`\n- `task_id`\n- `sequence`\n- `url`\n- `http_verb`\n- `status_code`\n- `response_body_size`\n- `time_stamp`\n- `started_at_nanos`\n- `response_body`\n- `response_body_truncated`\n- upload/download totals and speeds\n- DNS/connect/TLS/start-transfer/elapsed/redirect timings in milliseconds\n\nMetrics are sorted by:\n\n1. `started_at_nanos`\n2. `worker_id`\n3. `task_id`\n4. `sequence`\n\nThis makes local output easier to inspect and prepares the format for future\nmulti-worker aggregation.\n\n## Exit Behavior\n\nThe process exits successfully when the load test passes and all configured\nthresholds pass.\n\nThe process exits non-zero when:\n\n- any task fails\n- HTTP failures make tasks fail\n- a configured threshold fails\n- a flow or CLI argument is invalid\n\nEven when a threshold fails, `lorust` still writes the metrics file before\nreturning the error.\n\n## Flow Files\n\nUse flow files for multi-step scenarios, scripting, or variable interpolation.\nTOML is the preferred authoring format because multiline Python code can be\nwritten as literal strings. JSON flow files are still supported for compatibility.\n\nRun a flow file:\n\n```sh\ntarget/release/lorust run --flow-path flow.toml --output-path metrics.json\n```\n\nRun an inline JSON flow:\n\n```sh\ntarget/release/lorust run \\\n    --flow '{\"functions\":[{\"LoadGen\":{\"max_tasks\":1,\"spawn_rate\":\"1\",\"timeout\":5,\"functions_to_execute\":[{\"Sleep\":{\"duration\":\"1\"}}]}}]}'\n```\n\nLegacy form:\n\n```sh\ntarget/release/lorust --flow-path flow.toml --output-path metrics.json\n```\n\nFlow files are parsed by extension. Use `.toml` for the readable flow syntax and\n`.json` for the legacy enum-shaped JSON syntax.\n\n### Load Generator\n\nEach `[[loadgen]]` table schedules virtual-user tasks.\n\nCommon fields:\n\n| Field | Description |\n| --- | --- |\n| `spawn_rate` | Python expression returning tasks to start per second. `TICK` is available. |\n| `timeout` | Per virtual-user timeout in seconds. |\n| `max_tasks` | Total tasks to start. |\n| `duration` | Seconds to start tasks for. |\n| `run_id` | Optional run ID. CLI `--run-id` overrides this. |\n| `worker_id` | Optional worker ID. CLI `--worker-id` overrides this. |\n| `thresholds` | Optional summary threshold object. |\n| `step` | Ordered `[[loadgen.step]]` tables each virtual user executes. |\n\n`max_tasks` and `duration` can both be omitted only if the CLI path supplies a\ndefault. In flow files, provide at least one.\n\nRequest-count example:\n\n```toml\n[[loadgen]]\nmax_tasks = 100\nspawn_rate = \"10\"\ntimeout = 30\n\n[[loadgen.step]]\ntype = \"http\"\nurl = \"https://example.com\"\ntimeout = 10\n```\n\nDuration example:\n\n```toml\n[[loadgen]]\nduration = 60\nspawn_rate = \"10\"\ntimeout = 30\n\n[[loadgen.step]]\ntype = \"http\"\nurl = \"https://example.com\"\ntimeout = 10\n```\n\nThreshold example:\n\n```toml\n[[loadgen]]\nduration = 30\nspawn_rate = \"20\"\ntimeout = 30\n\n[loadgen.thresholds]\nmax_error_rate = 1.0\nmax_p95_latency_ms = 500\nmax_p99_latency_ms = 1000\nmin_requests_per_sec = 18.0\n\n[[loadgen.step]]\ntype = \"http\"\nurl = \"https://example.com\"\n```\n\n### HTTP Step\n\n`type = \"http\"` performs one HTTP request.\n\nFields:\n\n| Field | Description | Default |\n| --- | --- | --- |\n| `url` | Request URL. | required |\n| `method` | HTTP method. | `GET` |\n| `headers` | Inline table of HTTP header names to values. | `{}` |\n| `body` | Raw request body string. | empty |\n| `form_data` | Inline table of multipart form field names to string values or file tables. | unset |\n| `form_urlencoded` | Inline table of form field names to values. | unset |\n| `timeout` | Request timeout in seconds. | `60` |\n| `redirect_limit` | Maximum redirects to follow. | `5` |\n| `max_response_body_bytes` | Maximum failed-response body bytes to store in metrics. | `4096` |\n\nGET example:\n\n```toml\n[[loadgen.step]]\ntype = \"http\"\nurl = \"https://example.com\"\ntimeout = 10\n```\n\nPOST with raw JSON body:\n\n```toml\n[[loadgen.step]]\ntype = \"http\"\nmethod = \"POST\"\nurl = \"https://api.example.com/users\"\nheaders = { \"Content-Type\" = \"application/json\" }\nbody = '''\n{\"name\":\"Ada\"}\n'''\ntimeout = 10\nmax_response_body_bytes = 4096\n```\n\nOnly one body style can be used on an HTTP step:\n\n- omit `body`, `form_data`, and `form_urlencoded` for an empty body\n- `body = '''...'''` for a raw request body\n- `form_data = { name = \"Ada\" }` for multipart form fields\n- `form_data = { avatar = { file_path = \"avatar.png\", content_type = \"image/png\" } }` for multipart files\n- `form_urlencoded = { name = \"Ada\" }` for URL-encoded form bodies\n\n`BinaryOctetFilePath` exists in the type but is not implemented yet.\n\n### Python Step\n\n`type = \"python\"` runs RustPython code against the current virtual-user local\nscope.\n\nValues assigned in Python are written back to the virtual-user scope when they\ncan be serialized to JSON.\n\nExample:\n\n```toml\n[[loadgen.step]]\ntype = \"python\"\ncode = '''\nuser_id = http_response[\"data\"][0][\"id\"]\n'''\n```\n\n### Variable Interpolation\n\nStrings can interpolate Python expressions with `%|...|%`.\n\nExample:\n\n```toml\n[[loadgen.step]]\ntype = \"http\"\nurl = \"https://api.example.com/users/%|user_id|%\"\n```\n\nThe expression is evaluated against the virtual-user local scope.\n\n### Sleep\n\n`type = \"sleep\"` pauses the virtual user for a number of seconds.\n\n```toml\n[[loadgen.step]]\ntype = \"sleep\"\nduration = \"1\"\n```\n\n## Multi-Step Flow Example\n\n```toml\n[[loadgen]]\nmax_tasks = 2\nspawn_rate = \"1\"\ntimeout = 300\n\n[[loadgen.step]]\ntype = \"http\"\nurl = \"https://reqres.in/api/users?page=1\"\ntimeout = 300\n\n[[loadgen.step]]\ntype = \"python\"\ncode = '''\nuser_id = http_response[\"data\"][0][\"id\"]\nprint(f\"Picked user_id: {user_id}\")\n'''\n\n[[loadgen.step]]\ntype = \"http\"\nurl = \"https://reqres.in/api/users/%|user_id|%\"\ntimeout = 300\n\n[[loadgen.step]]\ntype = \"python\"\ncode = '''\ndata = http_response[\"data\"]\nprint(data[\"first_name\"] + \" \" + data[\"last_name\"])\n'''\n```\n\n## Distributed Work\n\n`lorust` is still a single-node load generator today. The metric format already\nincludes `run_id`, `worker_id`, `task_id`, and `sequence` so future\nsupervisor/worker aggregation can merge outputs safely.\n\nThe distributed implementation plan lives in:\n\n```text\ndocs/distributed-load-testing-plan.md\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsazid%2Florust","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsazid%2Florust","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsazid%2Florust/lists"}