strobengine 0.2.1__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. strobengine-0.3.0/.dockerignore +34 -0
  2. {strobengine-0.2.1 → strobengine-0.3.0}/.github/workflows/ci.yml +4 -0
  3. strobengine-0.3.0/.github/workflows/docker-release.yml +53 -0
  4. strobengine-0.3.0/.pre-commit-config.yaml +47 -0
  5. {strobengine-0.2.1 → strobengine-0.3.0}/CHANGELOG.md +57 -0
  6. {strobengine-0.2.1 → strobengine-0.3.0}/Cargo.lock +9 -2
  7. {strobengine-0.2.1 → strobengine-0.3.0}/Cargo.toml +2 -1
  8. strobengine-0.3.0/Dockerfile +28 -0
  9. strobengine-0.3.0/Makefile +11 -0
  10. strobengine-0.2.1/README.md → strobengine-0.3.0/PKG-INFO +58 -20
  11. strobengine-0.2.1/PKG-INFO → strobengine-0.3.0/README.md +41 -37
  12. strobengine-0.3.0/docs/docker.md +94 -0
  13. {strobengine-0.2.1 → strobengine-0.3.0}/docs/roadmap.md +13 -11
  14. strobengine-0.3.0/docs/testing.md +129 -0
  15. {strobengine-0.2.1 → strobengine-0.3.0}/examples/http_methods.py +7 -7
  16. strobengine-0.3.0/examples/load_test.py +27 -0
  17. {strobengine-0.2.1 → strobengine-0.3.0}/examples/spike_test.py +1 -1
  18. {strobengine-0.2.1 → strobengine-0.3.0}/examples/stress_test.py +1 -1
  19. {strobengine-0.2.1 → strobengine-0.3.0}/pyproject.toml +9 -5
  20. {strobengine-0.2.1 → strobengine-0.3.0}/src/config.rs +8 -0
  21. {strobengine-0.2.1 → strobengine-0.3.0}/src/lib.rs +313 -288
  22. strobengine-0.3.0/src/logging.rs +44 -0
  23. strobengine-0.3.0/src/metrics.rs +343 -0
  24. {strobengine-0.2.1 → strobengine-0.3.0}/src/progress.rs +1 -1
  25. {strobengine-0.2.1 → strobengine-0.3.0}/src/strobengine/_strobengine.pyi +29 -0
  26. {strobengine-0.2.1 → strobengine-0.3.0}/src/strobengine/cli.py +51 -22
  27. {strobengine-0.2.1 → strobengine-0.3.0}/src/strobengine/engine.py +17 -2
  28. {strobengine-0.2.1 → strobengine-0.3.0}/src/strobengine/reporter.py +70 -27
  29. {strobengine-0.2.1 → strobengine-0.3.0}/src/worker.rs +15 -12
  30. {strobengine-0.2.1 → strobengine-0.3.0}/tests/conftest.py +9 -0
  31. strobengine-0.3.0/tests/e2e/__init__.py +0 -0
  32. strobengine-0.3.0/tests/e2e/conftest.py +72 -0
  33. strobengine-0.3.0/tests/e2e/mock_server.py +69 -0
  34. strobengine-0.3.0/tests/e2e/test_cli_interface.py +149 -0
  35. strobengine-0.3.0/tests/e2e/test_http_scenarios.py +84 -0
  36. strobengine-0.3.0/tests/e2e/test_performance_sanity.py +61 -0
  37. {strobengine-0.2.1 → strobengine-0.3.0}/tests/test_engine.py +18 -5
  38. {strobengine-0.2.1 → strobengine-0.3.0}/tests/test_reporter.py +35 -5
  39. strobengine-0.3.0/uv.lock +799 -0
  40. strobengine-0.2.1/examples/load_test.py +0 -14
  41. strobengine-0.2.1/src/logging.rs +0 -34
  42. strobengine-0.2.1/src/metrics.rs +0 -150
  43. strobengine-0.2.1/uv.lock +0 -207
  44. {strobengine-0.2.1 → strobengine-0.3.0}/.github/workflows/publish-test.yml +0 -0
  45. {strobengine-0.2.1 → strobengine-0.3.0}/.github/workflows/publish.yml +0 -0
  46. {strobengine-0.2.1 → strobengine-0.3.0}/.gitignore +0 -0
  47. {strobengine-0.2.1 → strobengine-0.3.0}/.python-version +0 -0
  48. {strobengine-0.2.1 → strobengine-0.3.0}/LICENSE +0 -0
  49. {strobengine-0.2.1 → strobengine-0.3.0}/cliff.toml +0 -0
  50. {strobengine-0.2.1 → strobengine-0.3.0}/docs/benchmarks.md +0 -0
  51. {strobengine-0.2.1 → strobengine-0.3.0}/docs/http_methods.md +0 -0
  52. {strobengine-0.2.1 → strobengine-0.3.0}/docs/infrastructure.md +0 -0
  53. {strobengine-0.2.1 → strobengine-0.3.0}/src/chaos.rs +0 -0
  54. {strobengine-0.2.1 → strobengine-0.3.0}/src/strobengine/__init__.py +0 -0
  55. {strobengine-0.2.1 → strobengine-0.3.0}/src/strobengine/constants.py +0 -0
  56. {strobengine-0.2.1 → strobengine-0.3.0}/src/strobengine/py.typed +0 -0
  57. {strobengine-0.2.1 → strobengine-0.3.0}/tests/__init__.py +0 -0
  58. {strobengine-0.2.1 → strobengine-0.3.0}/tests/test_cli.py +0 -0
  59. {strobengine-0.2.1 → strobengine-0.3.0}/tests/test_logging.py +0 -0
@@ -0,0 +1,34 @@
1
+ # Git metadata
2
+ .git/
3
+ .gitignore
4
+
5
+ # Rust build artifacts & cargo cache
6
+ target/
7
+ **/*.rs.bk
8
+
9
+ # Python cache & virtual environments
10
+ .venv/
11
+ venv/
12
+ env/
13
+ __pycache__/
14
+ *.pyc
15
+ *.pyo
16
+ *.pyd
17
+ .pytest_cache/
18
+ .mypy_cache/
19
+ .ruff_cache/
20
+
21
+ # IDE and OS metadata
22
+ .idea/
23
+ .vscode/
24
+ *.swp
25
+ .DS_Store
26
+
27
+ # Test & benchmark output artifacts
28
+ results/
29
+ dist/
30
+ *.egg-info/
31
+
32
+ # Docker files
33
+ Dockerfile
34
+ .dockerignore
@@ -56,3 +56,7 @@ jobs:
56
56
  run: |
57
57
  uv run ruff check
58
58
  uv run ruff format --check
59
+
60
+ - name: Run Tests
61
+ run: |
62
+ uv run pytest -v
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: Build & Push Docker Image
3
+
4
+ on:
5
+ release:
6
+ types: [published]
7
+ push:
8
+ tags:
9
+ - 'v*.*.*'
10
+ workflow_dispatch:
11
+
12
+ jobs:
13
+ docker-publish:
14
+ name: Build and Push Docker Image
15
+ runs-on: ubuntu-latest
16
+
17
+ steps:
18
+ - name: Checkout Code
19
+ uses: actions/checkout@v7
20
+
21
+ - name: Set up QEMU (for multi-arch support)
22
+ uses: docker/setup-qemu-action@v4
23
+
24
+ - name: Set up Docker Buildx
25
+ uses: docker/setup-buildx-action@v4
26
+
27
+ - name: Log in to Docker Hub
28
+ uses: docker/login-action@v4
29
+ with:
30
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
31
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
32
+
33
+ - name: Extract Docker Metadata (Tags & Labels)
34
+ id: meta
35
+ uses: docker/metadata-action@v6
36
+ with:
37
+ images: ${{ secrets.DOCKERHUB_USERNAME }}/strobengine
38
+ tags: |
39
+ type=semver,pattern={{version}}
40
+ type=semver,pattern={{major}}.{{minor}}
41
+ type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') }}
42
+
43
+ - name: Build and Push Docker Image
44
+ uses: docker/build-push-action@v7
45
+ with:
46
+ context: .
47
+ file: ./Dockerfile
48
+ push: true
49
+ platforms: linux/amd64,linux/arm64
50
+ tags: ${{ steps.meta.outputs.tags }}
51
+ labels: ${{ steps.meta.outputs.labels }}
52
+ cache-from: type=gha
53
+ cache-to: type=gha,mode=max
@@ -0,0 +1,47 @@
1
+ ---
2
+ default_stages: [pre-push]
3
+
4
+ repos:
5
+ - repo: local
6
+ hooks:
7
+ - id: cargo-fmt
8
+ name: cargo fmt
9
+ entry: cargo fmt --check
10
+ language: system
11
+ types: [rust]
12
+ pass_filenames: false
13
+
14
+ - id: cargo-clippy
15
+ name: cargo clippy
16
+ entry: cargo clippy --all-targets --all-features -- -D warnings
17
+ language: system
18
+ types: [rust]
19
+ pass_filenames: false
20
+
21
+ - id: cargo-test
22
+ name: cargo test
23
+ entry: cargo test
24
+ language: system
25
+ types: [rust]
26
+ pass_filenames: false
27
+
28
+ - id: ruff-check
29
+ name: ruff check
30
+ entry: uv run ruff check .
31
+ language: system
32
+ types: [python]
33
+ pass_filenames: false
34
+
35
+ - id: ruff-format
36
+ name: ruff format
37
+ entry: uv run ruff format --check .
38
+ language: system
39
+ types: [python]
40
+ pass_filenames: false
41
+
42
+ - id: pytest
43
+ name: pytest
44
+ entry: uv run pytest -v
45
+ language: system
46
+ types: [python]
47
+ pass_filenames: false
@@ -4,6 +4,62 @@ All notable changes to `strobengine` will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.3.0] - 2026-08-13
8
+
9
+ ### Bug Fixes
10
+
11
+ - *(logging)* Log warning on invalid EnvFilter string (#59)
12
+ - *(logging)* Warn when log file creation fails (#60)
13
+ - *(cli)* Auto-detect value-taking flags in positional arg detection (#61)
14
+ - *(cli)* Map trace log level to custom level 5 instead of DEBUG (#63)
15
+
16
+ ### Documentation
17
+
18
+ - *(readme)* Add custom HTTP headers usage examples (#55)
19
+ - Add --form flag to README CLI reference
20
+ - Update roadmap to reflect recent project changes (#62)
21
+ - *(worker)* Document u64::MAX fallback for extreme latencies (#66)
22
+ - Consolidate testing guide into docs/testing.md
23
+ - *(docker)* Add Docker usage documentation
24
+
25
+ ### Features
26
+
27
+ - *(rust)* Add form payload support to TestConfig and Content-Type logic
28
+ - *(python)* Add --form flag and type stubs
29
+ - *(cli)* Add --form payload support for URL-encoded request bodies (#56)
30
+ - *(metrics)* Add latency distribution, status code aggregation, and structured JSON repor (#72)
31
+
32
+ ### Miscellaneous Tasks
33
+
34
+ - *(dev)* Setup pre-commit hooks and Makefile for local pre-push checks (#58)
35
+ - *(github)* Add Docker build and push workflow for releases
36
+
37
+ ### Other
38
+
39
+ - *(deps)* Add urlencoding crate for form payload encoding
40
+ - *(deps)* Add aiohttp dev dependency for e2e test server
41
+ - *(docker)* Add multi-stage Dockerfile for Maturin package
42
+ - *(docker)* Add .dockerignore to optimize build context
43
+ - *(docker)* Switch runtime container execution to non-root user
44
+
45
+ ### Refactoring
46
+
47
+ - *(engine)* Unify load test execution via ConcurrencyStrategy (#54)
48
+ - *(worker)* Remove unused status_code and is_error from RequestMetric (#57)
49
+ - *(progress)* Remove unused `_total_duration` param from `create_progress_bar` (#64)
50
+
51
+ ### Testing
52
+
53
+ - Remove trivial allocator unit test (#65)
54
+ - *(e2e)* Add async mock server with status, delay, echo, and flaky endpoints
55
+ - Add e2e mock server (#67)
56
+ - *(e2e)* Add `cli_bin` fixture to detect strobengine CLI path (#68)
57
+ - *(e2e)* Harden mock server with AppKey types and header normalization
58
+ - *(e2e)* Switch mock_server fixture to threaded AppRunner
59
+ - *(e2e)* Add HTTP scenario tests against mock server
60
+ - *(e2e)* Harden mock server and consolidate testing docs
61
+ - *(e2e)* Add subprocess-based CLI interface tests (#70)
62
+ - *(e2e)* Configure --e2e pytest flag and CI pipeline step (#71)
7
63
  ## [0.2.1] - 2026-08-03
8
64
 
9
65
  ### Bug Fixes
@@ -24,6 +80,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
24
80
 
25
81
  - Remove unnecessary clippy allow on run_load_test (#47)
26
82
  - Add multi-architecture matrix for macOS wheel builds (#52)
83
+ - Prepare for release 0.2.1
27
84
 
28
85
  ### Refactoring
29
86
 
@@ -210,7 +210,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
210
210
  checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
211
211
  dependencies = [
212
212
  "libc",
213
- "windows-sys 0.52.0",
213
+ "windows-sys 0.61.2",
214
214
  ]
215
215
 
216
216
  [[package]]
@@ -1273,7 +1273,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
1273
1273
 
1274
1274
  [[package]]
1275
1275
  name = "strobengine"
1276
- version = "0.2.1"
1276
+ version = "0.3.0"
1277
1277
  dependencies = [
1278
1278
  "bytes",
1279
1279
  "fastrand",
@@ -1286,6 +1286,7 @@ dependencies = [
1286
1286
  "tokio-util",
1287
1287
  "tracing",
1288
1288
  "tracing-subscriber",
1289
+ "urlencoding",
1289
1290
  ]
1290
1291
 
1291
1292
  [[package]]
@@ -1616,6 +1617,12 @@ dependencies = [
1616
1617
  "serde",
1617
1618
  ]
1618
1619
 
1620
+ [[package]]
1621
+ name = "urlencoding"
1622
+ version = "2.1.3"
1623
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1624
+ checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
1625
+
1619
1626
  [[package]]
1620
1627
  name = "utf8_iter"
1621
1628
  version = "1.0.4"
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "strobengine"
3
- version = "0.2.1"
3
+ version = "0.3.0"
4
4
  edition = "2024"
5
5
  readme = "README.md"
6
6
 
@@ -23,5 +23,6 @@ tokio-util = { version = "0.7", features = ["rt"] }
23
23
  tracing = "0.1"
24
24
  tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt", "registry"] }
25
25
  mimalloc = "0.1.52"
26
+ urlencoding = "2"
26
27
  fastrand = "2.5"
27
28
  indicatif = "0.18.6"
@@ -0,0 +1,28 @@
1
+ # Stage 1: Build wheel
2
+ FROM python:3.11-slim AS builder
3
+ RUN apt-get update && apt-get install -y curl build-essential
4
+ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
5
+ ENV PATH="/root/.cargo/bin:${PATH}"
6
+ RUN pip install maturin
7
+
8
+ WORKDIR /app
9
+ COPY . .
10
+ RUN maturin build --release --out dist
11
+
12
+ # Stage 2: Minimal Runtime Image
13
+ FROM python:3.11-slim
14
+
15
+ # Create a non-privileged system user and group
16
+ RUN groupadd -r appuser && useradd -r -g appuser appuser
17
+
18
+ WORKDIR /app
19
+
20
+ # Copy built wheels with proper ownership
21
+ COPY --from=builder /app/dist/*.whl .
22
+ RUN pip install --no-cache-dir *.whl && rm *.whl
23
+
24
+ # Switch execution context away from root
25
+ USER appuser
26
+
27
+ ENTRYPOINT ["strobengine"]
28
+ CMD ["--help"]
@@ -0,0 +1,11 @@
1
+ .PHONY: check fix
2
+
3
+ # Shortcut for manual pre-commit check
4
+ check:
5
+ pre-commit run --hook-stage pre-push --all-files
6
+
7
+ # Quick command to auto-fix formatting before pushing
8
+ fix:
9
+ cargo fmt
10
+ uv run ruff check --fix .
11
+ uv run ruff format .
@@ -1,3 +1,19 @@
1
+ Metadata-Version: 2.4
2
+ Name: strobengine
3
+ Version: 0.3.0
4
+ Requires-Dist: rich>=13.0.0
5
+ Requires-Dist: typer>=0.27.0
6
+ License-File: LICENSE
7
+ Summary: A load testing framework with a Python UI and a bare-metal Rust engine core.
8
+ Author-email: riccione <28346232+riccione@users.noreply.github.com>
9
+ License-Expression: Apache-2.0
10
+ Requires-Python: >=3.11
11
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
12
+ Project-URL: Changelog, https://github.com/strobeops/strobengine/blob/main/CHANGELOG.md
13
+ Project-URL: Documentation, https://github.com/strobeops/strobengine
14
+ Project-URL: Homepage, https://github.com/strobeops/strobengine
15
+ Project-URL: Source, https://github.com/strobeops/strobengine
16
+
1
17
  # strobengine
2
18
 
3
19
  A high-performance HTTP load testing engine with a Python API and a bare-metal Rust core.
@@ -36,6 +52,18 @@ uv sync
36
52
 
37
53
  `uv sync` invokes [maturin](https://github.com/PyO3/maturin) under the hood, which compiles the Rust code into a native Python extension module and installs it into your virtual environment.
38
54
 
55
+ ### Docker
56
+
57
+ Alternatively, pull and run strobengine directly from Docker Hub:
58
+
59
+ ```bash
60
+ docker pull strobeops/strobengine:latest
61
+ docker run --rm -it strobeops/strobengine load http://host.docker.internal:8080/api/health -c 50 -d 30
62
+ ```
63
+
64
+ > See [docs/docker.md](docs/docker.md) for full Docker documentation including
65
+ > version tags, host networking, and building locally.
66
+
39
67
  ## Quick Start Usage
40
68
 
41
69
  ```python
@@ -46,27 +74,29 @@ from strobengine.reporter import print_summary
46
74
  engine = StrobEngine(url="http://localhost:8080/api/health", concurrency=50, duration=30)
47
75
  summary = engine.run()
48
76
 
49
- print_summary(summary, url=engine._url, duration_secs=30)
77
+ print_summary(summary)
50
78
 
51
79
  # Ramp/stress test (10 -> 200 workers over 60s, hold 30s)
52
80
  engine = StrobEngine.stress_test(
53
81
  "http://localhost:8080/api/health",
54
82
  start_concurrency=10, max_concurrency=200,
55
83
  ramp_duration=60, hold_duration=30,
84
+ headers=[("Authorization", "Bearer token123")],
56
85
  )
57
86
  summary = engine.run()
58
87
 
59
- print_summary(summary, url=engine._url, duration_secs=30)
88
+ print_summary(summary)
60
89
 
61
90
  # Spike test (baseline 5 -> peak 500 -> back to 5)
62
91
  engine = StrobEngine.spike_test(
63
92
  "http://localhost:8080/api/health",
64
93
  baseline=5, peak_concurrency=500,
65
94
  pre_spike_duration=5, spike_duration=10, post_spike_duration=5,
95
+ headers=[("X-Custom", "value")],
66
96
  )
67
97
  summary = engine.run()
68
98
 
69
- print_summary(summary, url=engine._url, duration_secs=30)
99
+ print_summary(summary)
70
100
 
71
101
  # POST request with JSON body and custom headers
72
102
  engine = StrobEngine(
@@ -109,6 +139,16 @@ strobengine load http://localhost:8080/api/data --method POST --body '{"key": "v
109
139
  strobengine load http://localhost:8080/api/resource/1 \
110
140
  --method PUT --body '{"name": "updated"}' --header "Authorization: Bearer token"
111
141
 
142
+ # Multiple headers (repeatable -H flag)
143
+ strobengine load http://localhost:8080/api/data \
144
+ --method POST --body '{"key": "val"}' \
145
+ --header "Authorization: Bearer token" \
146
+ --header "X-Request-ID: abc-123"
147
+
148
+ # POST with URL-encoded form data
149
+ strobengine load http://localhost:8080/api/data \
150
+ --method POST --form "key1=value1&key2=value2"
151
+
112
152
  # DELETE
113
153
  strobengine load http://localhost:8080/api/resource/1 --method DELETE
114
154
 
@@ -119,7 +159,7 @@ strobengine load http://localhost:8080/api/health -vv
119
159
  strobengine load http://localhost:8080/api/health -q
120
160
  ```
121
161
 
122
- By default, this spawns **10 concurrent workers** for **10 seconds** with a **10-second request timeout**. Results are displayed as a formatted table with total requests, errors, requests/sec, and latency percentiles (avg, p95, p99).
162
+ By default, this spawns **10 concurrent workers** for **10 seconds** with a **10-second request timeout**. Results are displayed as a formatted table with total requests, errors, requests/sec, and latency percentiles (min, avg, p50, p90, p95, p99, max).
123
163
 
124
164
  ### Subcommands
125
165
 
@@ -138,6 +178,7 @@ By default, this spawns **10 concurrent workers** for **10 seconds** with a **10
138
178
  | `-t`, `--timeout` | `10` | Per-request timeout in seconds |
139
179
  | `--method` | `GET` | HTTP method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) |
140
180
  | `--body` | none | Request body (raw string) |
181
+ | `--form` | none | Form data body (e.g. key1=val1&key2=val2) |
141
182
  | `--header` | none | Custom header key:value (repeatable) |
142
183
  | `--chaos` | off | Enable fault injection (~10% of requests) |
143
184
  | `--no-progress` | off | Suppress live progress bar |
@@ -157,6 +198,7 @@ By default, this spawns **10 concurrent workers** for **10 seconds** with a **10
157
198
  | `-t`, `--timeout` | `10` | Per-request timeout in seconds |
158
199
  | `--method` | `GET` | HTTP method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) |
159
200
  | `--body` | none | Request body (raw string) |
201
+ | `--form` | none | Form data body (e.g. key1=val1&key2=val2) |
160
202
  | `--header` | none | Custom header key:value (repeatable) |
161
203
  | `--chaos` | off | Enable fault injection (~10% of requests) |
162
204
  | `--no-progress` | off | Suppress live progress bar |
@@ -177,6 +219,7 @@ By default, this spawns **10 concurrent workers** for **10 seconds** with a **10
177
219
  | `-t`, `--timeout` | `10` | Per-request timeout in seconds |
178
220
  | `--method` | `GET` | HTTP method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) |
179
221
  | `--body` | none | Request body (raw string) |
222
+ | `--form` | none | Form data body (e.g. key1=val1&key2=val2) |
180
223
  | `--header` | none | Custom header key:value (repeatable) |
181
224
  | `--chaos` | off | Enable fault injection (~10% of requests) |
182
225
  | `--no-progress` | off | Suppress live progress bar |
@@ -230,7 +273,7 @@ strobengine separates configuration, execution, and metrics into clean Rust modu
230
273
 
231
274
  - **`config`** -- `TestConfig` for static load, `LoadProfile` enum for dynamic profiles (Constant, Ramp, Spike) with target concurrency interpolation.
232
275
  - **`worker`** -- Async worker loops with method-aware request building, static payload reuse (Bytes), and zero-allocation header management via `ClientBuilder::default_headers()`.
233
- - **`metrics`** -- Lock-free atomic counters (`AtomicUsize`) track total requests and errors without contention. An aggregator task collects raw latencies, then `calculate_summary` computes average, p95, and p99 percentiles in Rust at bare-metal speed.
276
+ - **`metrics`** -- Lock-free atomic counters (`AtomicUsize`) track total requests and errors without contention. An aggregator task collects raw latencies, then `calculate_summary` computes min, average, p50, p90, p95, p99, and max percentiles in Rust at bare-metal speed.
234
277
  - **`chaos`** -- Protocol-agnostic fault injection engine with `ChaosEngine` evaluator and `ChaosFault` enum (LatencySpike, CorruptedPayload, MetadataCorruption, ConnectionDrop).
235
278
  - **`progress`** -- Background Tokio render task sampling atomic metrics every 200ms, displaying live RPS, active VUs, and latency via indicatif.
236
279
  - **Orchestrator** -- Supervisor task ticks every 200ms, calculates target concurrency from the active profile curve, spawns/aborts workers dynamically.
@@ -239,27 +282,21 @@ The Python GIL is released entirely via `py.detach()` during test execution, all
239
282
 
240
283
  ## Testing
241
284
 
242
- Run the full test suite with:
243
-
244
- ```bash
245
- # Rust unit tests
246
- cargo test
247
-
248
- # Python unit tests
249
- uv run pytest -v
250
- ```
285
+ > See [docs/testing.md](docs/testing.md) for the full testing guide, including
286
+ > e2e tests, CI/CD checks, and running individual test suites.
251
287
 
252
288
  ## How to Contribute
253
289
 
254
290
  1. Fork the repository
255
291
  2. Create a feature branch (`git checkout -b feat/my-feature`)
256
- 3. Make your changes
257
- 4. Run formatting, linting, and tests:
292
+ 3. Install dependencies and set up pre-push Git hooks:
293
+ ```bash
294
+ uv sync
295
+ uv run pre-commit install --hook-type pre-push
296
+ ```
297
+ 4. Run formatting, linting, and tests manually (or let git push run them automatically):
258
298
  ```bash
259
- cargo fmt
260
- cargo clippy --all-targets -- -D warnings
261
- cargo test
262
- uv run pytest -v
299
+ make check
263
300
  ```
264
301
  5. Commit your changes following [Conventional Commits](https://www.conventionalcommits.org/)
265
302
  6. Push to your branch and open a Pull Request
@@ -267,3 +304,4 @@ uv run pytest -v
267
304
  ## License
268
305
 
269
306
  This project is licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for the full text.
307
+
@@ -1,19 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: strobengine
3
- Version: 0.2.1
4
- Requires-Dist: rich>=13.0.0
5
- Requires-Dist: typer>=0.27.0
6
- License-File: LICENSE
7
- Summary: A load testing framework with a Python UI and a bare-metal Rust engine core.
8
- Author-email: riccione <28346232+riccione@users.noreply.github.com>
9
- License-Expression: Apache-2.0
10
- Requires-Python: >=3.11
11
- Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
12
- Project-URL: Changelog, https://github.com/strobe-ops/strobengine/blob/main/CHANGELOG.md
13
- Project-URL: Documentation, https://github.com/strobe-ops/strobengine
14
- Project-URL: Homepage, https://github.com/strobe-ops/strobengine
15
- Project-URL: Source, https://github.com/strobe-ops/strobengine
16
-
17
1
  # strobengine
18
2
 
19
3
  A high-performance HTTP load testing engine with a Python API and a bare-metal Rust core.
@@ -52,6 +36,18 @@ uv sync
52
36
 
53
37
  `uv sync` invokes [maturin](https://github.com/PyO3/maturin) under the hood, which compiles the Rust code into a native Python extension module and installs it into your virtual environment.
54
38
 
39
+ ### Docker
40
+
41
+ Alternatively, pull and run strobengine directly from Docker Hub:
42
+
43
+ ```bash
44
+ docker pull strobeops/strobengine:latest
45
+ docker run --rm -it strobeops/strobengine load http://host.docker.internal:8080/api/health -c 50 -d 30
46
+ ```
47
+
48
+ > See [docs/docker.md](docs/docker.md) for full Docker documentation including
49
+ > version tags, host networking, and building locally.
50
+
55
51
  ## Quick Start Usage
56
52
 
57
53
  ```python
@@ -62,27 +58,29 @@ from strobengine.reporter import print_summary
62
58
  engine = StrobEngine(url="http://localhost:8080/api/health", concurrency=50, duration=30)
63
59
  summary = engine.run()
64
60
 
65
- print_summary(summary, url=engine._url, duration_secs=30)
61
+ print_summary(summary)
66
62
 
67
63
  # Ramp/stress test (10 -> 200 workers over 60s, hold 30s)
68
64
  engine = StrobEngine.stress_test(
69
65
  "http://localhost:8080/api/health",
70
66
  start_concurrency=10, max_concurrency=200,
71
67
  ramp_duration=60, hold_duration=30,
68
+ headers=[("Authorization", "Bearer token123")],
72
69
  )
73
70
  summary = engine.run()
74
71
 
75
- print_summary(summary, url=engine._url, duration_secs=30)
72
+ print_summary(summary)
76
73
 
77
74
  # Spike test (baseline 5 -> peak 500 -> back to 5)
78
75
  engine = StrobEngine.spike_test(
79
76
  "http://localhost:8080/api/health",
80
77
  baseline=5, peak_concurrency=500,
81
78
  pre_spike_duration=5, spike_duration=10, post_spike_duration=5,
79
+ headers=[("X-Custom", "value")],
82
80
  )
83
81
  summary = engine.run()
84
82
 
85
- print_summary(summary, url=engine._url, duration_secs=30)
83
+ print_summary(summary)
86
84
 
87
85
  # POST request with JSON body and custom headers
88
86
  engine = StrobEngine(
@@ -125,6 +123,16 @@ strobengine load http://localhost:8080/api/data --method POST --body '{"key": "v
125
123
  strobengine load http://localhost:8080/api/resource/1 \
126
124
  --method PUT --body '{"name": "updated"}' --header "Authorization: Bearer token"
127
125
 
126
+ # Multiple headers (repeatable -H flag)
127
+ strobengine load http://localhost:8080/api/data \
128
+ --method POST --body '{"key": "val"}' \
129
+ --header "Authorization: Bearer token" \
130
+ --header "X-Request-ID: abc-123"
131
+
132
+ # POST with URL-encoded form data
133
+ strobengine load http://localhost:8080/api/data \
134
+ --method POST --form "key1=value1&key2=value2"
135
+
128
136
  # DELETE
129
137
  strobengine load http://localhost:8080/api/resource/1 --method DELETE
130
138
 
@@ -135,7 +143,7 @@ strobengine load http://localhost:8080/api/health -vv
135
143
  strobengine load http://localhost:8080/api/health -q
136
144
  ```
137
145
 
138
- By default, this spawns **10 concurrent workers** for **10 seconds** with a **10-second request timeout**. Results are displayed as a formatted table with total requests, errors, requests/sec, and latency percentiles (avg, p95, p99).
146
+ By default, this spawns **10 concurrent workers** for **10 seconds** with a **10-second request timeout**. Results are displayed as a formatted table with total requests, errors, requests/sec, and latency percentiles (min, avg, p50, p90, p95, p99, max).
139
147
 
140
148
  ### Subcommands
141
149
 
@@ -154,6 +162,7 @@ By default, this spawns **10 concurrent workers** for **10 seconds** with a **10
154
162
  | `-t`, `--timeout` | `10` | Per-request timeout in seconds |
155
163
  | `--method` | `GET` | HTTP method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) |
156
164
  | `--body` | none | Request body (raw string) |
165
+ | `--form` | none | Form data body (e.g. key1=val1&key2=val2) |
157
166
  | `--header` | none | Custom header key:value (repeatable) |
158
167
  | `--chaos` | off | Enable fault injection (~10% of requests) |
159
168
  | `--no-progress` | off | Suppress live progress bar |
@@ -173,6 +182,7 @@ By default, this spawns **10 concurrent workers** for **10 seconds** with a **10
173
182
  | `-t`, `--timeout` | `10` | Per-request timeout in seconds |
174
183
  | `--method` | `GET` | HTTP method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) |
175
184
  | `--body` | none | Request body (raw string) |
185
+ | `--form` | none | Form data body (e.g. key1=val1&key2=val2) |
176
186
  | `--header` | none | Custom header key:value (repeatable) |
177
187
  | `--chaos` | off | Enable fault injection (~10% of requests) |
178
188
  | `--no-progress` | off | Suppress live progress bar |
@@ -193,6 +203,7 @@ By default, this spawns **10 concurrent workers** for **10 seconds** with a **10
193
203
  | `-t`, `--timeout` | `10` | Per-request timeout in seconds |
194
204
  | `--method` | `GET` | HTTP method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) |
195
205
  | `--body` | none | Request body (raw string) |
206
+ | `--form` | none | Form data body (e.g. key1=val1&key2=val2) |
196
207
  | `--header` | none | Custom header key:value (repeatable) |
197
208
  | `--chaos` | off | Enable fault injection (~10% of requests) |
198
209
  | `--no-progress` | off | Suppress live progress bar |
@@ -246,7 +257,7 @@ strobengine separates configuration, execution, and metrics into clean Rust modu
246
257
 
247
258
  - **`config`** -- `TestConfig` for static load, `LoadProfile` enum for dynamic profiles (Constant, Ramp, Spike) with target concurrency interpolation.
248
259
  - **`worker`** -- Async worker loops with method-aware request building, static payload reuse (Bytes), and zero-allocation header management via `ClientBuilder::default_headers()`.
249
- - **`metrics`** -- Lock-free atomic counters (`AtomicUsize`) track total requests and errors without contention. An aggregator task collects raw latencies, then `calculate_summary` computes average, p95, and p99 percentiles in Rust at bare-metal speed.
260
+ - **`metrics`** -- Lock-free atomic counters (`AtomicUsize`) track total requests and errors without contention. An aggregator task collects raw latencies, then `calculate_summary` computes min, average, p50, p90, p95, p99, and max percentiles in Rust at bare-metal speed.
250
261
  - **`chaos`** -- Protocol-agnostic fault injection engine with `ChaosEngine` evaluator and `ChaosFault` enum (LatencySpike, CorruptedPayload, MetadataCorruption, ConnectionDrop).
251
262
  - **`progress`** -- Background Tokio render task sampling atomic metrics every 200ms, displaying live RPS, active VUs, and latency via indicatif.
252
263
  - **Orchestrator** -- Supervisor task ticks every 200ms, calculates target concurrency from the active profile curve, spawns/aborts workers dynamically.
@@ -255,27 +266,21 @@ The Python GIL is released entirely via `py.detach()` during test execution, all
255
266
 
256
267
  ## Testing
257
268
 
258
- Run the full test suite with:
259
-
260
- ```bash
261
- # Rust unit tests
262
- cargo test
263
-
264
- # Python unit tests
265
- uv run pytest -v
266
- ```
269
+ > See [docs/testing.md](docs/testing.md) for the full testing guide, including
270
+ > e2e tests, CI/CD checks, and running individual test suites.
267
271
 
268
272
  ## How to Contribute
269
273
 
270
274
  1. Fork the repository
271
275
  2. Create a feature branch (`git checkout -b feat/my-feature`)
272
- 3. Make your changes
273
- 4. Run formatting, linting, and tests:
276
+ 3. Install dependencies and set up pre-push Git hooks:
277
+ ```bash
278
+ uv sync
279
+ uv run pre-commit install --hook-type pre-push
280
+ ```
281
+ 4. Run formatting, linting, and tests manually (or let git push run them automatically):
274
282
  ```bash
275
- cargo fmt
276
- cargo clippy --all-targets -- -D warnings
277
- cargo test
278
- uv run pytest -v
283
+ make check
279
284
  ```
280
285
  5. Commit your changes following [Conventional Commits](https://www.conventionalcommits.org/)
281
286
  6. Push to your branch and open a Pull Request
@@ -283,4 +288,3 @@ uv run pytest -v
283
288
  ## License
284
289
 
285
290
  This project is licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for the full text.
286
-