goodput-http 0.1.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 (76) hide show
  1. goodput_http-0.1.0/.claude/settings.local.json +48 -0
  2. goodput_http-0.1.0/.github/workflows/ci.yml +49 -0
  3. goodput_http-0.1.0/.github/workflows/publish.yml +57 -0
  4. goodput_http-0.1.0/.gitignore +29 -0
  5. goodput_http-0.1.0/ARCHITECTURE.md +192 -0
  6. goodput_http-0.1.0/CHANGELOG.md +47 -0
  7. goodput_http-0.1.0/CLAUDE.md +122 -0
  8. goodput_http-0.1.0/CODE_OF_CONDUCT.md +31 -0
  9. goodput_http-0.1.0/CONTRIBUTING.md +44 -0
  10. goodput_http-0.1.0/LICENSE +19 -0
  11. goodput_http-0.1.0/PKG-INFO +222 -0
  12. goodput_http-0.1.0/README.md +139 -0
  13. goodput_http-0.1.0/SECURITY.md +48 -0
  14. goodput_http-0.1.0/docs/adr/0001-foundational-choices.md +57 -0
  15. goodput_http-0.1.0/docs/concepts.md +69 -0
  16. goodput_http-0.1.0/docs/legal.md +43 -0
  17. goodput_http-0.1.0/examples/01_repeat_endpoint.py +23 -0
  18. goodput_http-0.1.0/examples/02_fixed_concurrency_per_ip.py +55 -0
  19. goodput_http-0.1.0/examples/03_isolated_throttle_scope.py +56 -0
  20. goodput_http-0.1.0/pyproject.toml +138 -0
  21. goodput_http-0.1.0/src/goodput/CLAUDE.md +115 -0
  22. goodput_http-0.1.0/src/goodput/__init__.py +139 -0
  23. goodput_http-0.1.0/src/goodput/api.py +33 -0
  24. goodput_http-0.1.0/src/goodput/checkpoint.py +88 -0
  25. goodput_http-0.1.0/src/goodput/circuit.py +139 -0
  26. goodput_http-0.1.0/src/goodput/classify.py +191 -0
  27. goodput_http-0.1.0/src/goodput/cli.py +178 -0
  28. goodput_http-0.1.0/src/goodput/clock.py +61 -0
  29. goodput_http-0.1.0/src/goodput/config.py +323 -0
  30. goodput_http-0.1.0/src/goodput/control/__init__.py +9 -0
  31. goodput_http-0.1.0/src/goodput/control/aimd.py +112 -0
  32. goodput_http-0.1.0/src/goodput/control/base.py +109 -0
  33. goodput_http-0.1.0/src/goodput/control/gradient.py +83 -0
  34. goodput_http-0.1.0/src/goodput/control_mode.py +268 -0
  35. goodput_http-0.1.0/src/goodput/engine.py +588 -0
  36. goodput_http-0.1.0/src/goodput/events.py +79 -0
  37. goodput_http-0.1.0/src/goodput/exceptions.py +35 -0
  38. goodput_http-0.1.0/src/goodput/models.py +217 -0
  39. goodput_http-0.1.0/src/goodput/plugins.py +63 -0
  40. goodput_http-0.1.0/src/goodput/py.typed +0 -0
  41. goodput_http-0.1.0/src/goodput/ratelimit.py +140 -0
  42. goodput_http-0.1.0/src/goodput/redaction.py +103 -0
  43. goodput_http-0.1.0/src/goodput/report.py +261 -0
  44. goodput_http-0.1.0/src/goodput/retry.py +146 -0
  45. goodput_http-0.1.0/src/goodput/retry_after.py +105 -0
  46. goodput_http-0.1.0/src/goodput/routing/__init__.py +26 -0
  47. goodput_http-0.1.0/src/goodput/routing/health.py +138 -0
  48. goodput_http-0.1.0/src/goodput/routing/route.py +110 -0
  49. goodput_http-0.1.0/src/goodput/routing/selector.py +143 -0
  50. goodput_http-0.1.0/src/goodput/scope.py +64 -0
  51. goodput_http-0.1.0/src/goodput/sim.py +111 -0
  52. goodput_http-0.1.0/src/goodput/sinks/__init__.py +9 -0
  53. goodput_http-0.1.0/src/goodput/sinks/base.py +34 -0
  54. goodput_http-0.1.0/src/goodput/sinks/jsonl.py +76 -0
  55. goodput_http-0.1.0/src/goodput/sinks/memory.py +46 -0
  56. goodput_http-0.1.0/src/goodput/sources.py +126 -0
  57. goodput_http-0.1.0/src/goodput/stats.py +182 -0
  58. goodput_http-0.1.0/src/goodput/transport.py +158 -0
  59. goodput_http-0.1.0/start.md +2159 -0
  60. goodput_http-0.1.0/tests/CLAUDE.md +81 -0
  61. goodput_http-0.1.0/tests/conftest.py +14 -0
  62. goodput_http-0.1.0/tests/test_checkpoint_cli.py +160 -0
  63. goodput_http-0.1.0/tests/test_circuit.py +52 -0
  64. goodput_http-0.1.0/tests/test_classify.py +78 -0
  65. goodput_http-0.1.0/tests/test_config.py +86 -0
  66. goodput_http-0.1.0/tests/test_control_mode.py +123 -0
  67. goodput_http-0.1.0/tests/test_controllers.py +110 -0
  68. goodput_http-0.1.0/tests/test_engine.py +192 -0
  69. goodput_http-0.1.0/tests/test_models.py +49 -0
  70. goodput_http-0.1.0/tests/test_ratelimit.py +60 -0
  71. goodput_http-0.1.0/tests/test_retry.py +142 -0
  72. goodput_http-0.1.0/tests/test_retry_after.py +67 -0
  73. goodput_http-0.1.0/tests/test_routing.py +115 -0
  74. goodput_http-0.1.0/tests/test_simulation.py +80 -0
  75. goodput_http-0.1.0/tests/test_stats_redaction.py +81 -0
  76. goodput_http-0.1.0/tests/test_transport_respx.py +69 -0
@@ -0,0 +1,48 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "PowerShell(pip *)",
5
+ "PowerShell(py *)",
6
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m pip install -q \"httpx[http2]\" anyio pydantic pydantic-settings 2>&1 | Select-Object -Last 5)",
7
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m pip install -q pytest pytest-anyio hypothesis trio respx 2>&1 | Select-Object -Last 5; echo \"DONE\")",
8
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -c \"import httpx, anyio, pydantic, pydantic_settings, trio, respx, hypothesis; print\\('httpx', httpx.__version__\\); print\\('anyio', anyio.__version__\\); print\\('pydantic', pydantic.VERSION\\)\")",
9
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -c \"import importlib.metadata as m; [print\\(p, m.version\\(p\\)\\) for p in ['httpx','anyio','pydantic','pydantic-settings','trio','respx','hypothesis','pytest']]\")",
10
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m pip install -q -e . 2>&1 | Select-Object -Last 10; echo \"EXIT=$LASTEXITCODE\")",
11
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m pip install -q -e . 2>&1 | Select-Object -Last 6; echo \"EXIT=$LASTEXITCODE\")",
12
+ "Bash(.venv/Scripts/python.exe /tmp/smoke.py)",
13
+ "Bash(echo \"EXIT=$?\")",
14
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m pytest tests/test_control_mode.py tests/test_models.py tests/test_retry_after.py tests/test_classify.py tests/test_retry.py -q 2>&1 | Select-Object -Last 30)",
15
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m pytest tests/ -q -p anyio 2>&1 | Select-Object -Last 35)",
16
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m pytest tests/test_controllers.py tests/test_engine.py 2>&1 | Select-Object -Last 40)",
17
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m pytest tests/test_controllers.py tests/test_engine.py 2>&1 | Select-Object -Last 25)",
18
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m pytest tests/test_config.py tests/test_simulation.py 2>&1 | Select-Object -Last 30)",
19
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m pytest tests/ 2>&1 | Select-Object -Last 8)",
20
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m pytest tests/test_transport_respx.py tests/test_checkpoint_cli.py 2>&1 | Select-Object -Last 40)",
21
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m pytest tests/ 2>&1 | Select-Object -Last 6)",
22
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m goodput.cli version; echo \"---\"; & C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m goodput.cli validate --concurrency 20 --adaptive --max-concurrency 200)",
23
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\goodput.exe version 2>&1)",
24
+ "Bash(cat)",
25
+ "Bash(.venv/Scripts/python.exe /tmp/server.py)",
26
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\goodput.exe run --url http://127.0.0.1:8731/ --count 500 --concurrency 20 --adaptive --max-concurrency 100 2>&1 | Select-Object -First 40)",
27
+ "Bash(curl -s \"http://127.0.0.1:8731/__shutdown\")",
28
+ "Bash(powershell.exe -Command 'Get-Process python -ErrorAction SilentlyContinue | Where-Object {$_.Path -like '\\\\''*goodput*'\\\\''} | Stop-Process -Force -ErrorAction SilentlyContinue')",
29
+ "Bash(powershell.exe *)",
30
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -c \"import ast; [ast.parse\\(open\\(f\\).read\\(\\)\\) for f in ['examples/01_repeat_endpoint.py','examples/02_fixed_concurrency_per_ip.py','examples/03_isolated_throttle_scope.py']]; print\\('examples parse OK'\\)\")",
31
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m pip install -q build 2>&1 | Select-Object -Last 2; & C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m build 2>&1 | Select-Object -Last 8)",
32
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m pip install -q ruff 2>&1 | Select-Object -Last 1; & C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m ruff check src tests 2>&1 | Select-Object -Last 40)",
33
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m ruff check --fix src tests 2>&1 | Select-Object -Last 5; echo \"=== REMAINING ===\"; & C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m ruff check src tests 2>&1 | Select-Object -Last 60)",
34
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m ruff check src tests --output-format concise 2>&1)",
35
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m ruff check src tests 2>&1; echo \"=== TESTS ===\"; & C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m pytest tests/ 2>&1 | Select-Object -Last 5)",
36
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m ruff check src tests 2>&1)",
37
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m pip install -q mypy 2>&1 | Select-Object -Last 1; & C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m mypy src 2>&1 | Select-Object -Last 40)",
38
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m mypy src 2>&1 | Select-Object -Last 30)",
39
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m mypy src 2>&1 | Select-Object -Last 20)",
40
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m ruff check src tests 2>&1; echo \"=== PYTEST ===\"; & C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m pytest tests/ 2>&1 | Select-Object -Last 6)",
41
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m ruff check --fix src tests 2>&1 | Select-Object -Last 3; & C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m ruff check src tests 2>&1; echo \"=== mypy ===\"; & C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m mypy src 2>&1 | Select-Object -Last 3)",
42
+ "PowerShell(& C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m pytest tests/ -q 2>&1 | Select-Object -Last 4; echo \"=== build ===\"; & C:\\\\Users\\\\amoradi\\\\projects\\\\misc\\\\goodput\\\\.venv\\\\Scripts\\\\python.exe -m build 2>&1 | Select-Object -Last 2)",
43
+ "Bash(.venv/Scripts/python.exe -m pytest tests/ --co -q)",
44
+ "Bash(.venv/Scripts/python.exe -m pytest tests/ -q)",
45
+ "Bash(.venv/Scripts/python.exe -m pytest tests/)"
46
+ ]
47
+ }
48
+ }
@@ -0,0 +1,49 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ name: test (py${{ matrix.python-version }} / ${{ matrix.os }})
11
+ runs-on: ${{ matrix.os }}
12
+ strategy:
13
+ fail-fast: false
14
+ matrix:
15
+ os: [ubuntu-latest, windows-latest, macos-latest]
16
+ python-version: ["3.10", "3.11", "3.12"]
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: actions/setup-python@v5
20
+ with:
21
+ python-version: ${{ matrix.python-version }}
22
+ - name: Install
23
+ run: |
24
+ python -m pip install --upgrade pip
25
+ pip install -e ".[dev,http2]"
26
+ - name: Lint
27
+ run: ruff check src tests
28
+ - name: Type check
29
+ run: mypy src
30
+ - name: Test
31
+ run: pytest -q
32
+
33
+ build:
34
+ name: build & install from artifacts
35
+ runs-on: ubuntu-latest
36
+ steps:
37
+ - uses: actions/checkout@v4
38
+ - uses: actions/setup-python@v5
39
+ with:
40
+ python-version: "3.12"
41
+ - name: Build wheel + sdist
42
+ run: |
43
+ python -m pip install --upgrade pip build
44
+ python -m build
45
+ - name: Install from wheel
46
+ run: |
47
+ pip install dist/*.whl
48
+ python -c "import goodput; print(goodput.__version__)"
49
+ goodput version
@@ -0,0 +1,57 @@
1
+ name: Publish Python package
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ permissions:
8
+ contents: read
9
+
10
+ jobs:
11
+ build:
12
+ name: Build distribution
13
+ runs-on: ubuntu-latest
14
+
15
+ steps:
16
+ - name: Check out repository
17
+ uses: actions/checkout@v4
18
+
19
+ - name: Set up Python
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.12"
23
+
24
+ - name: Install build tools
25
+ run: python -m pip install --upgrade build
26
+
27
+ - name: Build package
28
+ run: python -m build
29
+
30
+ - name: Store distributions
31
+ uses: actions/upload-artifact@v4
32
+ with:
33
+ name: python-package-distributions
34
+ path: dist/
35
+
36
+ publish:
37
+ name: Publish to PyPI
38
+ needs: build
39
+ runs-on: ubuntu-latest
40
+
41
+ environment:
42
+ name: pypi
43
+ url: https://pypi.org/project/goodput-http/
44
+
45
+ permissions:
46
+ id-token: write
47
+
48
+ steps:
49
+ - name: Download distributions
50
+ uses: actions/download-artifact@v4
51
+ with:
52
+ name: python-package-distributions
53
+ path: dist/
54
+
55
+ - name: Publish distributions to PyPI
56
+ uses: pypa/gh-action-pypi-publish@release/v1
57
+
@@ -0,0 +1,29 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ .venv/
9
+ venv/
10
+
11
+ # Test / type / lint caches
12
+ .pytest_cache/
13
+ .mypy_cache/
14
+ .ruff_cache/
15
+ .hypothesis/
16
+ htmlcov/
17
+ .coverage
18
+ .coverage.*
19
+
20
+ # Editors / OS
21
+ .vscode/
22
+ .idea/
23
+ .DS_Store
24
+ Thumbs.db
25
+
26
+ # goodput run artifacts
27
+ *.jsonl
28
+ checkpoints/
29
+ reports/
@@ -0,0 +1,192 @@
1
+ # goodput — Architecture
2
+
3
+ > An adaptive HTTP execution engine that maximizes **sustainable successful
4
+ > logical-request goodput** while treating every user-declared fixed value as an
5
+ > invariant.
6
+
7
+ This document is the architecture deliverable required by the project brief
8
+ (section 41). It is intentionally concise; deeper rationale lives in the
9
+ `docs/adr/` Architecture Decision Records.
10
+
11
+ ---
12
+
13
+ ## 1. Problem analysis
14
+
15
+ The task is to execute very large volumes of HTTP requests while *automatically
16
+ maximizing the rate of terminally-successful logical requests* — **goodput** —
17
+ subject to a user-controlled authority model. The hard parts are not "send many
18
+ requests" (a `asyncio.gather` loop does that). The hard parts are:
19
+
20
+ 1. **Goodput vs. offered load.** A logical request may take several attempts. We
21
+ must count one logical success/failure regardless of attempts, and optimize
22
+ the *successful* rate, not the attempt rate. Naive load generators happily
23
+ melt a server and report "1M requests sent" while goodput collapses.
24
+ 2. **Authority.** Users must be able to pin any dimension (concurrency,
25
+ per-IP concurrency, route count, rate, retries…) as an invariant the
26
+ optimizer may never touch, while still letting the optimizer tune everything
27
+ else *around* those invariants.
28
+ 3. **Bounded everything.** Memory, queues, task creation, and response retention
29
+ must stay bounded under infinite/lazy workloads.
30
+ 4. **Attribution.** A proxy-auth failure must not penalize the origin; a
31
+ target-wide outage must not quarantine one healthy route.
32
+ 5. **Explainability.** Every adaptive decision must be recordable and auditable.
33
+
34
+ ## 2. Competitor evaluation (summary)
35
+
36
+ | Tool | Role for us |
37
+ |------|-------------|
38
+ | **HTTPX / HTTPCore** | Chosen transport. Async, HTTP/2, proxies, custom transports, source-address binding via `httpcore`. MIT/BSD. Active. |
39
+ | **aiohttp** | Capable but client model is heavier to abstract; HTTPX's `AsyncBaseTransport` seam is cleaner for pluggable routes. Kept as a possible alt-transport plugin. |
40
+ | **AnyIO** | Chosen concurrency foundation: structured concurrency, task groups, capacity limiters, cancellation that works on both asyncio and Trio. |
41
+ | **Tenacity** | Good retry primitive but we need retry tied to *logical-request* accounting, idempotency, and route selection — so retries live behind our own `RetryPolicy` abstraction (Tenacity may back it as a plugin). |
42
+ | **aiometer / aiolimiter / PyrateLimiter** | Algorithmic references. We implement our own token-bucket/GCRA so limiter state can be scoped per route/credential and shared/isolated for throttle-scope testing. |
43
+ | **Locust / k6** | Comparison & interop targets, not the core engine. Their VU model is closed-loop only; we support both open- and closed-loop. |
44
+ | **Netflix concurrency-limits / Envoy adaptive concurrency** | Controller references: AIMD, Gradient/Gradient2, Vegas-style minimum-RTT tracking. We implement AIMD + a gradient controller. |
45
+ | **OpenTelemetry / Prometheus / structlog** | Optional observability integrations behind extras; never required, never configured at import time. |
46
+ | **Redis** | Optional distributed limiter/coordination backend. |
47
+ | **PyArrow / HDR / t-digest** | Optional Parquet sink; streaming stats use an in-house log-bucketed histogram (no required dep). |
48
+
49
+ ## 3. Dependency evaluation
50
+
51
+ **Required (minimal):** `httpx`, `anyio`, `pydantic`, `pydantic-settings`.
52
+ Everything substantial (HTTP/2, SOCKS, Redis, OTel, Prometheus, Parquet, CLI,
53
+ structlog) is an **optional extra**. No required dependency does networking or
54
+ logging configuration at import time.
55
+
56
+ ## 4. Recommended architecture
57
+
58
+ A **closed control loop** wrapped around a **bounded execution pipeline**:
59
+
60
+ ```
61
+ RequestSource (lazy, async) Observability (events → sinks/metrics/logs)
62
+ │ ▲
63
+ ▼ │
64
+ Ingress queue ──► Admission ──► Worker pool ──► Classifier ──► Result accounting
65
+ (bounded) (limiters, (bounded, (success/ (logical vs attempt)
66
+ circuits, AnyIO throttle/ │
67
+ route pick) CapacityLimiter) error) ▼
68
+ ▲ Result sinks
69
+ │ │
70
+ Controllers ◄──────────── metrics window ◄───────────────────────┘
71
+ (authority-gated: only touch ADAPTIVE / BOUNDED_ADAPTIVE knobs)
72
+ ```
73
+
74
+ ## 5. Major subsystem boundaries
75
+
76
+ - **models** — `LogicalRequest`, `Attempt`, `LogicalResult`, classifications.
77
+ - **control_mode** — `Controlled[T]` value with `FIXED/ADAPTIVE/BOUNDED_ADAPTIVE/DISABLED/INHERIT`.
78
+ - **config** — pydantic-validated `EngineConfig`; constraint validation & dry-run plan.
79
+ - **classify** — pluggable success/throttle/error classifiers.
80
+ - **ratelimit / retry_after / retry / circuit** — pacing & resilience primitives.
81
+ - **routing** — `Route`, selectors, health, quarantine, throttle-scope.
82
+ - **control** — controller protocol + AIMD/Gradient implementations + decision records.
83
+ - **scheduler/engine** — bounded pipeline, permit accounting, cancellation, lifecycle.
84
+ - **stats / report / events / sinks** — observability & output.
85
+ - **sim** — deterministic in-memory server transports for controller tests.
86
+
87
+ ## 6. Public API proposal
88
+
89
+ ```python
90
+ import goodput as gp
91
+
92
+ result = await gp.run(
93
+ gp.repeat(gp.Request("GET", "https://api.example/health"), times=10_000),
94
+ config=gp.EngineConfig(
95
+ objective=gp.Objective.MAX_GOODPUT,
96
+ global_concurrency=gp.adaptive(initial=20, minimum=1, maximum=500),
97
+ request_rate=gp.fixed(None), # unpaced
98
+ ),
99
+ )
100
+ print(result.report.summary())
101
+ ```
102
+
103
+ The engine is also usable as an explicit context-managed object
104
+ (`async with gp.Engine(config) as e: report = await e.run(source)`), and a
105
+ synchronous convenience wrapper `gp.run_sync(...)`.
106
+
107
+ ## 7. Configuration-authority model
108
+
109
+ Every tunable is a `Controlled[T]`. Modes: **FIXED** (invariant — optimizer must
110
+ never change), **ADAPTIVE** (optimizer owns it), **BOUNDED_ADAPTIVE** (optimizer
111
+ owns it within `[minimum, maximum]`, respecting `max_step` and `min_interval`),
112
+ **DISABLED** (capability off), **INHERIT** (take broader scope's value). The
113
+ optimizer is *structurally* unable to write a FIXED value — controllers receive
114
+ only the set of `BOUNDED_ADAPTIVE`/`ADAPTIVE` handles.
115
+
116
+ ## 8. Constraint-resolution model
117
+
118
+ Constraints form a hierarchy (global → target → route-group → route → IP →
119
+ credential → request). Resolution rule: **the most specific value wins, but may
120
+ never exceed a hard higher-level ceiling.** The validator detects contradictions
121
+ (e.g. per-route fixed concurrency × route count > global ceiling) *before*
122
+ execution. Runtime conflicts emit explicit `ConstraintEvent`s and are reported,
123
+ never silently resolved by mutating a FIXED value.
124
+
125
+ ## 9. Adaptive-controller strategy
126
+
127
+ Multiple timescales: concurrency changes fast (AIMD/Gradient on latency + error
128
+ signal), rate changes at moderate cadence, route weights periodically.
129
+ Controllers consume a `Sample` (goodput, error ratio, throttle ratio, latency
130
+ percentiles, queue/limiter wait) and emit a `Decision` (variable, old, new,
131
+ mode, bounds, reason, confidence, expected effect) which is recorded and later
132
+ correlated with observed effect. Guards against oscillation, retry storms, and
133
+ sample bias are built into the controllers.
134
+
135
+ ## 10. Route-optimization strategy
136
+
137
+ Routes (direct / local-IP-bound / proxy) are first-class. Selectors: weighted,
138
+ round-robin, least-in-flight, latency-aware, goodput-aware. Health tracking with
139
+ quarantine + recovery probes. The optimizer may redistribute traffic among
140
+ fixed routes (route_weights ADAPTIVE) without changing a FIXED route set or
141
+ per-route concurrency.
142
+
143
+ ## 11. Throttle-scope model
144
+
145
+ `DECLARED / SHARED / ISOLATED_FOR_TEST / INFERRED / CUSTOM`. Limiter and
146
+ controller state is keyed by a scope resolver. The library never merges isolated
147
+ scopes when the user has asked for isolation (authorized scope testing), and
148
+ reports inference with confidence, evidence, and limitations — never as proof.
149
+
150
+ ## 12–13. Observability & reporting strategy
151
+
152
+ Events are emitted to an in-process bus; subscribers include sinks, the live CLI
153
+ display, and optional Prometheus/OTel exporters. Reports strictly separate
154
+ planned/generated/admitted/completed/successful/failed/throttled logical
155
+ requests from attempts/retries, and provide per-route/IP/credential breakdowns,
156
+ timelines, and (for authorized tests) throttle-scope findings.
157
+
158
+ ## 14. Testing strategy
159
+
160
+ pytest + AnyIO + RESPX/mock transports + Hypothesis + a deterministic fake clock
161
+ and an in-memory simulated server. Property invariants enforce the safety rules
162
+ in brief §36 (fixed never changes, bounded stays in bounds, one terminal state
163
+ per logical request, no permit leaks, secrets never serialized…).
164
+
165
+ ## 15. Security & misuse-risk analysis
166
+
167
+ Secure defaults (TLS verify on, bounded bodies, redirect limits, secret
168
+ redaction everywhere), authorization scaffolding (host/CIDR allowlists, run
169
+ ceilings, emergency stop, immutable run metadata with authorization reference).
170
+ **Excluded by design:** credential theft, exploit delivery, CAPTCHA bypass,
171
+ stealth/evasion, target discovery. The library does not grant permission to test
172
+ any system; the operator is responsible for authorization.
173
+
174
+ ## 16. Phased implementation plan
175
+
176
+ Mirrors brief §42. The repository is kept runnable + tested after every phase.
177
+ Implemented here: Phase 1 (core), Phase 2 (resilience), Phase 3 (controllers +
178
+ sim), Phase 4 (routing/scope), Phase 5 (report/CLI/sinks). Phase 6 (distributed)
179
+ is scaffolded with documented interfaces.
180
+
181
+ ## 17. ADRs
182
+
183
+ See `docs/adr/`. Key decisions: HTTPX transport, AnyIO concurrency, in-house
184
+ limiter & histogram (zero required deps), `Controlled[T]` authority primitive,
185
+ logical-vs-attempt accounting as the central invariant.
186
+
187
+ ### Environment note
188
+
189
+ The brief requests Python ≥ 3.11. This implementation targets **≥ 3.10** so it
190
+ runs and is tested on the development machine (3.10). All code is 3.10-compatible
191
+ and forward-compatible with 3.11/3.12. Bump `requires-python` to `>=3.11` if you
192
+ do not need 3.10 support.
@@ -0,0 +1,47 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format is based on
4
+ [Keep a Changelog](https://keepachangelog.com/), and this project adheres to
5
+ [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ### Added
10
+
11
+ - GitHub Actions trusted-publishing workflow for releasing `goodput-http` to
12
+ PyPI from GitHub Releases.
13
+
14
+ ### Changed
15
+
16
+ - Renamed the published distribution from `goodput` to `goodput-http` while
17
+ keeping the import package as `goodput`.
18
+ - Corrected project metadata URLs to point at
19
+ `https://github.com/alimoradics/goodput`.
20
+
21
+ ## [0.1.0] - 2026-06-17
22
+
23
+ ### Added
24
+
25
+ - Core domain model with strict logical-request vs. attempt accounting.
26
+ - `Controlled[T]` configuration-authority primitive
27
+ (FIXED / ADAPTIVE / BOUNDED_ADAPTIVE / DISABLED / INHERIT).
28
+ - Validated `EngineConfig` with pre-execution contradiction detection and a
29
+ dry-run plan renderer.
30
+ - Bounded, cancellation-safe execution engine with a closed adaptive control loop.
31
+ - AIMD and latency-gradient concurrency controllers with fully auditable decisions.
32
+ - Pluggable success / throttle / error classifiers; Retry-After and RateLimit
33
+ header parsing.
34
+ - Idempotency-aware retries with retry budgets; circuit breakers.
35
+ - First-class routes (direct / source-IP-bound / proxy), route selectors, health
36
+ tracking with quarantine + recovery, and failure attribution.
37
+ - Throttle-scope modes (DECLARED / SHARED / ISOLATED_FOR_TEST / INFERRED / CUSTOM).
38
+ - Bounded-memory streaming statistics (log-bucketed histogram, EWMA, rolling window).
39
+ - Secret redaction across logs, events, reports, and sinks.
40
+ - Result sinks (bounded memory, JSONL) with explicit failure policies.
41
+ - Checkpoint + resume; entry-point plugin registry.
42
+ - `argparse`-based CLI (`validate` / `run` / `plugins` / `version`).
43
+ - Deterministic simulation transport and controller simulations.
44
+ - 120+ unit, property, integration, and simulation tests.
45
+
46
+ [Unreleased]: https://github.com/alimoradics/goodput/compare/v0.1.0...HEAD
47
+ [0.1.0]: https://github.com/alimoradics/goodput/releases/tag/v0.1.0
@@ -0,0 +1,122 @@
1
+ # CLAUDE.md — goodput
2
+
3
+ Guidance for Claude Code (and humans) working in this repository. Read this
4
+ first. Subdirectory `CLAUDE.md` files add detail for `src/goodput/` and `tests/`.
5
+
6
+ ## What this project is
7
+
8
+ `goodput` is a production-grade, async-first Python library that executes very
9
+ high volumes of HTTP requests while **automatically maximizing sustainable
10
+ successful logical-request goodput** — terminally-successful logical requests per
11
+ unit time, *not* attempt volume. It is a library first, with an optional CLI,
12
+ pluggable policies, route/egress optimization, an adaptive control system, and
13
+ support for **authorized** throttling-validation testing.
14
+
15
+ The full design rationale is in `ARCHITECTURE.md`; conceptual docs in `docs/`.
16
+ This file is operational guidance, not a duplicate of those.
17
+
18
+ ## The one-paragraph mental model
19
+
20
+ A lazy `RequestSource` feeds a **bounded** pipeline: ingress queue → bounded
21
+ worker pool (concurrency capped by an `anyio.CapacityLimiter`) → transport →
22
+ classifier → result accounting → sinks. A periodic **controller loop** samples
23
+ metrics and adjusts *only* the knobs the user marked adaptive. Every tunable is a
24
+ `Controlled[T]` that declares whether the optimizer may touch it; FIXED values
25
+ are structurally immutable. A logical request may make several attempts but
26
+ terminates in exactly one outcome.
27
+
28
+ ## Environment & toolchain
29
+
30
+ - **Python target is `>=3.10`** (not 3.11). The dev machine only has 3.10, so the
31
+ code is written and tested against 3.10 and is forward-compatible with
32
+ 3.11/3.12. `tomllib` is imported with a `tomli` fallback for 3.10. Do not use
33
+ 3.11+-only syntax/stdlib without a fallback.
34
+ - A virtualenv lives at `.venv/`. Always use it:
35
+ - Tests: `.venv/Scripts/python.exe -m pytest tests/`
36
+ - Lint: `.venv/Scripts/python.exe -m ruff check src tests`
37
+ - Types: `.venv/Scripts/python.exe -m mypy src`
38
+ - Build: `.venv/Scripts/python.exe -m build`
39
+ - Shell is PowerShell (or the Bash tool). On Windows the venv binaries are under
40
+ `.venv/Scripts/` (`python.exe`, `goodput.exe`, `ruff`, `mypy`).
41
+
42
+ ## Definition of "done" for any change
43
+
44
+ A change is not complete until **all three** pass:
45
+
46
+ ```
47
+ .venv/Scripts/python.exe -m pytest tests/ # 123 tests, must stay green
48
+ .venv/Scripts/python.exe -m ruff check src tests
49
+ .venv/Scripts/python.exe -m mypy src # strict; must report no issues
50
+ ```
51
+
52
+ `mypy` runs in **strict** mode and `src` must stay clean. Keep the repo runnable
53
+ after every change.
54
+
55
+ ## Invariants that must never regress
56
+
57
+ These are enforced by tests (`tests/test_control_mode.py`, `test_engine.py`,
58
+ `test_retry.py`, `test_routing.py`, …) and encode the project's reason for
59
+ existing (brief §36/§43). Do not weaken them:
60
+
61
+ 1. **FIXED values never change.** The optimizer cannot mutate a FIXED knob.
62
+ 2. **BOUNDED_ADAPTIVE values never exceed their `[minimum, maximum]`.**
63
+ 3. **DISABLED capabilities stay off.**
64
+ 4. **One logical request → exactly one terminal outcome.** Attempts and retries
65
+ are counted separately (1 success after 4 failures = 1 success, 5 attempts,
66
+ 4 retries).
67
+ 5. **Non-idempotent requests are not retried** unless explicitly permitted
68
+ (`RetryPolicy.retry_non_idempotent` or `Request.idempotent=True`).
69
+ 6. **Queues, task creation, and memory stay bounded.** Never create a task per
70
+ request; never retain whole runs or full response bodies by default.
71
+ 7. **Secrets never appear in any serialized output** (logs, events, reports,
72
+ sinks). All such paths go through `redaction.Redactor`.
73
+ 8. **Isolated throttle scopes never silently merge**; shared scopes stay shared.
74
+ 9. **No import-time networking or logging configuration.**
75
+ 10. **Route failures are attributed correctly** — transport/proxy/TLS errors are
76
+ route-attributable (can quarantine); HTTP-status/application errors are not.
77
+
78
+ If a task seems to require violating one of these, stop and surface the conflict
79
+ rather than working around it.
80
+
81
+ ## Authorized-use posture
82
+
83
+ The library supports **authorized** security testing (throttle-scope validation,
84
+ multi-route/IP/credential testing). It must **not** gain offensive capabilities
85
+ unrelated to throughput/throttle testing: no credential theft, exploit delivery,
86
+ CAPTCHA bypass, stealth/evasion, target discovery, or payload generation. Reject
87
+ contributions that add these. See `SECURITY.md` and `docs/legal.md`.
88
+
89
+ ## Known gotchas
90
+
91
+ - **`ManualClock` inflates reported durations.** In tests using `ManualClock`,
92
+ concurrent virtual `sleep`s accumulate on the shared clock, so `report.elapsed`
93
+ and goodput-per-second are artifacts. Assert on *counts* (completed, attempts,
94
+ outcomes), not wall-clock-derived rates, in clock-driven tests. Engine
95
+ integration tests use a real `MonotonicClock` with tiny sim latencies instead.
96
+ - **The `CapacityLimiter` is created inside `run()`**, not `__init__`, because it
97
+ must bind to the running event loop and needs integer `total_tokens`.
98
+ - **anyio's pytest plugin** is enabled via `-p anyio` in `addopts`; async tests
99
+ need `pytestmark = pytest.mark.anyio` or the `@pytest.mark.anyio` marker plus
100
+ the `anyio_backend` fixture (defaults to asyncio in `conftest.py`).
101
+ - **Controllers receive a float view** of integer knobs via `Controlled.as_float()`;
102
+ the engine rounds back when applying. Don't hand a controller a raw int knob.
103
+
104
+ ## Layout
105
+
106
+ ```
107
+ src/goodput/ # the library (see src/goodput/CLAUDE.md)
108
+ control/ # adaptive controllers (AIMD, gradient) + protocol
109
+ routing/ # routes, selectors, health, quarantine
110
+ sinks/ # result sinks (memory, jsonl)
111
+ tests/ # pytest suite (see tests/CLAUDE.md)
112
+ examples/ # runnable usage examples
113
+ docs/ # concepts, legal, ADRs
114
+ ARCHITECTURE.md # full design + §41 deliverables
115
+ ```
116
+
117
+ ## Commit / PR conventions
118
+
119
+ - Conventional Commits (`feat:`, `fix:`, `docs:`, `test:`, `refactor:`).
120
+ - Update `CHANGELOG.md` under *Unreleased* for user-visible changes.
121
+ - Add/extend tests for behavior changes; keep the three checks green.
122
+ - Only commit or push when explicitly asked. Branch first if on the default branch.
@@ -0,0 +1,31 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, religion, or sexual identity and
10
+ orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to a positive environment include showing
15
+ empathy and kindness, respecting differing opinions, giving and gracefully
16
+ accepting constructive feedback, and focusing on what is best for the community.
17
+
18
+ Unacceptable behavior includes harassment, trolling, insulting or derogatory
19
+ comments, public or private harassment, and publishing others' private
20
+ information without explicit permission.
21
+
22
+ ## Enforcement
23
+
24
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
25
+ reported to the project maintainers. All complaints will be reviewed and
26
+ investigated promptly and fairly.
27
+
28
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
29
+ version 2.1.
30
+
31
+ [homepage]: https://www.contributor-covenant.org
@@ -0,0 +1,44 @@
1
+ # Contributing to goodput
2
+
3
+ Thanks for your interest! This project values correctness, bounded operation,
4
+ explainability, and strong typing.
5
+
6
+ ## Development setup
7
+
8
+ ```bash
9
+ python -m venv .venv
10
+ . .venv/Scripts/activate # Windows; source .venv/bin/activate on Unix
11
+ pip install -e ".[dev,http2]"
12
+ pytest
13
+ ```
14
+
15
+ ## Standards
16
+
17
+ - **Python ≥ 3.10**, `src/` layout, full type hints, `py.typed`.
18
+ - **Ruff** for lint/format: `ruff check src tests`.
19
+ - **mypy --strict** (or strict Pyright) for typing.
20
+ - **pytest** + **Hypothesis** for tests; use the deterministic `ManualClock` and
21
+ the `SimTransport` for engine/controller tests — no real network in unit tests.
22
+ - Conventional Commits for messages (`feat:`, `fix:`, `docs:`, `test:`, …).
23
+
24
+ ## Invariants we will not regress
25
+
26
+ These are enforced by tests and must stay true (see brief §36):
27
+
28
+ - fixed values never change; bounded values never exceed bounds;
29
+ - disabled capabilities stay disabled;
30
+ - one logical request → one terminal state; attempts counted separately;
31
+ - non-idempotent operations are not retried without explicit permission;
32
+ - queues stay bounded; no permit leaks; secrets never appear in serialized output.
33
+
34
+ ## Out of scope
35
+
36
+ We do not accept contributions adding offensive capabilities unrelated to
37
+ throughput/throttle validation (see [`SECURITY.md`](SECURITY.md)).
38
+
39
+ ## Pull requests
40
+
41
+ 1. Add/extend tests for your change.
42
+ 2. Run `ruff check`, `mypy`, and `pytest` locally.
43
+ 3. Update `CHANGELOG.md` under *Unreleased*.
44
+ 4. Keep the repository runnable.
@@ -0,0 +1,19 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ Licensed under the Apache License, Version 2.0 (the "License");
6
+ you may not use this file except in compliance with the License.
7
+ You may obtain a copy of the License at
8
+
9
+ http://www.apache.org/licenses/LICENSE-2.0
10
+
11
+ Unless required by applicable law or agreed to in writing, software
12
+ distributed under the License is distributed on an "AS IS" BASIS,
13
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ See the License for the specific language governing permissions and
15
+ limitations under the License.
16
+
17
+ Copyright 2026 goodput contributors
18
+
19
+ Full text: https://www.apache.org/licenses/LICENSE-2.0.txt