diff --git a/README.md b/README.md
index 51325e7..871abab 100644
--- a/README.md
+++ b/README.md
@@ -1,358 +1,622 @@
# LoadDensity
-[](https://pypi.org/project/je_load_density/)
-[](https://pypi.org/project/je_load_density/)
-[](https://opensource.org/licenses/MIT)
-[](https://loaddensity.readthedocs.io/en/latest/)
-
-**LoadDensity** is a high-performance load & stress testing automation framework built on top of [Locust](https://locust.io/). It provides a simplified wrapper around Locust's core functionality, enabling fast user spawning, flexible test configuration via templates and JSON-driven scripts, report generation in multiple formats (HTML / JSON / XML), a built-in GUI, remote execution via TCP socket server, and a callback mechanism for post-test workflows.
-
-**[繁體中文](README/README_zh-TW.md)** | **[简体中文](README/README_zh-CN.md)**
+
+ Multi-protocol load and stress automation: Locust + WebSocket + gRPC + MQTT + raw sockets, plus a JSON-driven action executor with batteries included.
+
+
+
+
+
+
+
+
+
+
+ 繁體中文 |
+ 简体中文
+
---
-## Features
-
-- **Simplified Locust Wrapper** — Abstracts Locust's `Environment`, `Runner`, and `User` classes behind a clean, high-level API.
-- **Two User Types** — Supports both `HttpUser` and `FastHttpUser` (geventhttpclient-based, higher throughput).
-- **Fast User Spawning** — Scale to thousands of concurrent users with configurable spawn rate.
-- **JSON-Driven Test Scripts** — Define test scenarios as JSON files and execute them without writing Python code.
-- **Action Executor** — A built-in event-driven executor that maps action names to functions. Supports batch execution and file-driven execution.
-- **Report Generation** — Export test results in three formats:
- - **HTML** — Styled tables with success/failure records
- - **JSON** — Structured data for programmatic consumption
- - **XML** — Standard XML output for CI/CD integration
-- **Request Hook** — Automatically records every request (success and failure) with method, URL, status code, response body, headers, and errors.
-- **Callback Executor** — Chain a trigger function with a callback function for post-test workflows (e.g., run test then generate report).
-- **TCP Socket Server** — Remote execution server based on gevent. Accepts JSON commands over TCP to execute tests remotely.
-- **Project Scaffolding** — Auto-generate project directory structure with keyword templates and executor scripts.
-- **Package Manager** — Dynamically load external Python packages and register their functions into the executor at runtime.
-- **GUI (Optional)** — PySide6-based graphical interface with real-time log display, supporting English and Traditional Chinese.
-- **CLI Support** — Run tests, execute scripts, or scaffold projects directly from the command line.
-- **Cross-Platform** — Works on Windows, macOS, and Linux.
+LoadDensity (`je_load_density`) started as a Locust wrapper and grew into a full multi-protocol load framework: HTTP, FastHttp, WebSocket, gRPC, MQTT, and raw TCP/UDP user templates behind one JSON-driven action executor, plus modules for parameterised data, scenario flow, reports, observability, distributed runners, recording, persistent storage, and an MCP control surface so Claude can drive load tests end-to-end. Every executor command has a deterministic name (`LD_*`) and a single dispatch point, so an action JSON can mix protocols, exporters, and reports in the same script.
+
+> **Optional dependencies, opt-in install** — every protocol driver and exporter ships behind a `pip install je_load_density[]` extra. The base install footprint is unchanged for users who only need HTTP load testing.
+
+## Table of Contents
+
+- [Highlights](#highlights)
+- [Installation](#installation)
+- [Architecture](#architecture)
+- [Quick Start](#quick-start)
+- [Core API](#core-api)
+- [Action Executor](#action-executor)
+- [User Templates](#user-templates)
+ - [HTTP / FastHttp](#http--fasthttp)
+ - [WebSocket](#websocket)
+ - [gRPC](#grpc)
+ - [MQTT](#mqtt)
+ - [Raw TCP / UDP](#raw-tcp--udp)
+- [Parameter Resolver](#parameter-resolver)
+- [Scenario Modes](#scenario-modes)
+- [Assertions & Extractors](#assertions--extractors)
+- [Reports](#reports)
+- [Observability](#observability)
+- [Distributed Master / Worker](#distributed-master--worker)
+- [HAR Record / Replay](#har-record--replay)
+- [Persistent Records (SQLite)](#persistent-records-sqlite)
+- [MCP Server (for Claude)](#mcp-server-for-claude)
+- [Hardened Control Socket](#hardened-control-socket)
+- [GUI](#gui)
+- [CLI Usage](#cli-usage)
+- [Test Record](#test-record)
+- [Exception Handling](#exception-handling)
+- [Logging](#logging)
+- [Supported Platforms](#supported-platforms)
+- [License](#license)
+
+## Highlights
+
+- **One executor, six protocols** — HTTP, FastHttp, WebSocket, gRPC, MQTT, raw TCP/UDP — all dispatched from the same `LD_start_test` command via a `user` key.
+- **JSON-driven** — Every test is an action JSON list; the same script can be hand-authored, generated by HAR import, scheduled by an MCP tool, or sent over the control socket.
+- **Parameter resolver** — `${var.x}`, `${env.X}`, `${csv.source.col}`, `${faker.method}`, plus built-in `${uuid()}`, `${now()}`, `${randint(min,max)}` helpers; values can also be extracted from responses and reused downstream.
+- **Scenario flow** — Declare tasks as `sequence` (default), `weighted`, or `conditional` (`run_if` / `skip_if` predicates) without touching Python.
+- **Six report formats** — HTML, JSON, XML, CSV, JUnit XML, and a percentile-summary JSON. The summary covers totals, failure rate, and per-name p50 / p90 / p95 / p99 latencies for trend tracking.
+- **Three exporters** — Prometheus HTTP endpoint, InfluxDB line-protocol UDP/HTTP sink, and OpenTelemetry OTLP gRPC exporter.
+- **Distributed runners** — `runner_mode="master"` / `"worker"` for cross-machine load with the same start_test API.
+- **HAR record / replay** — Convert real browser traffic into a runnable action JSON with regex include/exclude filters.
+- **Persistent records** — Optional SQLite sink with run / record / metadata schema for cross-run regression checks.
+- **MCP server** — `python -m je_load_density.mcp_server` exposes 11 tools so Claude (Desktop, Code, any MCP client) can drive LoadDensity end-to-end.
+- **Hardened control socket** — Length-prefixed framing, optional TLS, shared-secret token (env or arg), with backwards-compatible legacy mode for existing IDE integrations such as PyBreeze.
+- **Live GUI** — Optional PySide6 GUI with a live stats panel (RPS / avg / p95 / failures), translated to English, Traditional Chinese, Japanese, and Korean.
+- **CLI subcommands** — `run` / `run-dir` / `run-str` / `init` / `serve`. Legacy `-e/-d/-c/--execute_str` flags remain for downstream tools.
## Installation
-### Basic (CLI & Library)
-
```bash
pip install je_load_density
```
-### With GUI Support
+Pulls in [Locust](https://locust.io/) and `defusedxml` — nothing else.
+
+### Optional extras
+
+| Extra | Adds |
+|-------|------|
+| `gui` | PySide6 + qt-material (graphical front-end) |
+| `websocket` | `websocket-client` (WebSocket user template) |
+| `grpc` | `grpcio` + `protobuf` (gRPC user template) |
+| `mqtt` | `paho-mqtt` (MQTT user template) |
+| `prometheus` | `prometheus-client` (Prometheus exporter) |
+| `opentelemetry` | OpenTelemetry SDK + OTLP gRPC exporter |
+| `metrics` | `prometheus` + `opentelemetry` bundle |
+| `faker` | `Faker` (powers `${faker.method}` placeholders) |
+| `mcp` | `mcp` SDK (drives the MCP server) |
+| `all` | Everything above |
+
+```bash
+pip install "je_load_density[gui]"
+pip install "je_load_density[mqtt,grpc,websocket]"
+pip install "je_load_density[metrics]"
+pip install "je_load_density[mcp]"
+pip install "je_load_density[all]"
+```
+
+### Development install
```bash
-pip install je_load_density[gui]
+git clone https://github.com/Integration-Automation/LoadDensity.git
+cd LoadDensity
+pip install -e ".[all]"
+pip install -r requirements.txt
```
-This installs [PySide6](https://doc.qt.io/qtforpython/) and [qt-material](https://github.com/UN-GCPDS/qt-material) for the graphical interface.
+## Architecture
-## Requirements
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ CLI / MCP / GUI / Control Socket │
+└──────────────────┬──────────────────────────────────────────────┘
+ │ action JSON
+┌──────────────────▼──────────────────────────────────────────────┐
+│ Action Executor (LD_* dispatch + safe builtins) │
+└──────────────────┬──────────────────────────────────────────────┘
+ │ start_test
+┌──────────────────▼──────────────────────────────────────────────┐
+│ locust_wrapper_proxy (per-protocol task store) │
+└──────────────────┬──────────────────────────────────────────────┘
+ │
+ ┌───────────────┴───────────────┬──────────────┬──────────────┐
+ ▼ ▼ ▼ ▼
+HTTP / FastHttp WebSocket gRPC MQTT Raw TCP / UDP
+ │ │ │ │
+ └───────────────┬───────────────┴──────────────┴──────────────┘
+ │ Locust events
+ ┌───────────────┴───────────────┐
+ ▼ ▼
+test_record_instance Prometheus / InfluxDB / OTel
+ │
+ ├── HTML / JSON / XML / CSV / JUnit / Summary reports
+ └── SQLite persistence (cross-run comparison)
+```
-- Python **3.10** or later
-- [Locust](https://locust.io/) (installed automatically as a dependency)
+The dependency direction always points from the action layer down to Locust, never the other way around.
## Quick Start
-### 1. Using the Python API
+### HTTP load test in Python
```python
from je_load_density import start_test
-# Define user configuration and tasks
-result = start_test(
+start_test(
user_detail_dict={"user": "fast_http_user"},
user_count=50,
spawn_rate=10,
- test_time=10,
- tasks={
- "get": {"request_url": "http://httpbin.org/get"},
- "post": {"request_url": "http://httpbin.org/post"},
- }
+ test_time=30,
+ variables={"base": "https://httpbin.org"},
+ tasks=[
+ {"method": "get", "request_url": "${var.base}/get"},
+ {"method": "post", "request_url": "${var.base}/post",
+ "json": {"hello": "world"},
+ "assertions": [{"type": "status_code", "value": 200}]},
+ ],
)
```
-**Parameters:**
-| Parameter | Type | Default | Description |
-|---|---|---|---|
-| `user_detail_dict` | `dict` | — | User type configuration. `{"user": "fast_http_user"}` or `{"user": "http_user"}` |
-| `user_count` | `int` | `50` | Total number of simulated users |
-| `spawn_rate` | `int` | `10` | Number of users spawned per second |
-| `test_time` | `int` | `60` | Test duration in seconds. `None` for unlimited |
-| `web_ui_dict` | `dict` | `None` | Enable Locust Web UI, e.g. `{"host": "127.0.0.1", "port": 8089}` |
-| `tasks` | `dict` | — | HTTP method to request URL mapping |
+### Action JSON
-### 2. Using JSON Script Files
+```json
+{"load_density": [
+ ["LD_register_variables", {"variables": {"base": "https://httpbin.org"}}],
+ ["LD_start_test", {
+ "user_detail_dict": {"user": "fast_http_user"},
+ "user_count": 20, "spawn_rate": 10, "test_time": 30,
+ "tasks": [
+ {"method": "get", "request_url": "${var.base}/get"},
+ {"method": "post", "request_url": "${var.base}/post",
+ "json": {"hello": "world"}}
+ ]
+ }],
+ ["LD_generate_summary_report", {"report_name": "smoke"}]
+]}
+```
-Create a JSON file (`test_scenario.json`):
+Run via the CLI:
-```json
-[
- ["LD_start_test", {
- "user_detail_dict": {"user": "fast_http_user"},
- "user_count": 50,
- "spawn_rate": 10,
- "test_time": 5,
- "tasks": {
- "get": {"request_url": "http://httpbin.org/get"},
- "post": {"request_url": "http://httpbin.org/post"}
- }
- }]
-]
-```
-
-Execute from Python:
+```bash
+python -m je_load_density run smoke.json
+```
+
+## Core API
```python
-from je_load_density import execute_action, read_action_json
+from je_load_density import (
+ start_test, prepare_env, create_env,
+ execute_action, execute_files, executor, add_command_to_executor,
+ test_record_instance, locust_wrapper_proxy,
+ register_variable, register_variables,
+ register_csv_source, register_csv_sources,
+ parameter_resolver, resolve,
+ har_to_action_json, har_to_tasks, load_har,
+ persist_records, list_runs, fetch_run_records,
+ start_prometheus_exporter, stop_prometheus_exporter,
+ start_influxdb_sink, stop_influxdb_sink,
+ start_opentelemetry_exporter, stop_opentelemetry_exporter,
+ start_load_density_socket_server,
+ generate_html_report, generate_json_report, generate_xml_report,
+ generate_csv_report, generate_junit_report, generate_summary_report,
+ build_summary,
+ create_project_dir, callback_executor, read_action_json,
+)
+```
+
+`__all__` documents the full public surface in `je_load_density/__init__.py`.
+
+## Action Executor
-execute_action(read_action_json("test_scenario.json"))
+The action executor maps command strings to callable functions. Every action is a list:
+
+```python
+["command_name"] # No parameters
+["command_name", {"key": "value"}] # Keyword arguments
+["command_name", [arg1, arg2]] # Positional arguments
```
-### 3. Using the CLI
+The top-level document is either a bare list or `{"load_density": [...]}`.
-```bash
-# Execute a single JSON script file
-python -m je_load_density -e test_scenario.json
+### Built-in `LD_*` commands
+
+| Group | Commands |
+|-------|----------|
+| Core | `LD_start_test`, `LD_execute_action`, `LD_execute_files`, `LD_add_package_to_executor`, `LD_start_socket_server` |
+| Reports | `LD_generate_html(_report)`, `LD_generate_json(_report)`, `LD_generate_xml(_report)`, `LD_generate_csv_report`, `LD_generate_junit_report`, `LD_generate_summary_report`, `LD_summary` |
+| Persistence | `LD_persist_records`, `LD_list_runs`, `LD_fetch_run_records`, `LD_clear_records` |
+| Parameters | `LD_register_variable(s)`, `LD_register_csv_source(s)`, `LD_clear_resolver` |
+| Recording | `LD_load_har`, `LD_har_to_tasks`, `LD_har_to_action_json` |
+| Metrics | `LD_start/stop_prometheus_exporter`, `LD_start/stop_influxdb_sink`, `LD_start/stop_opentelemetry_exporter` |
-# Execute all JSON files in a directory
-python -m je_load_density -d ./test_scripts/
+Safe Python built-ins (`print`, `len`, `range`, …) are also accepted; `eval`, `exec`, `compile`, `__import__`, `breakpoint`, `open`, and `input` are explicitly blocked.
+
+### Custom commands
+
+```python
+from je_load_density import add_command_to_executor
-# Execute an inline JSON string
-python -m je_load_density --execute_str '[["LD_start_test", {"user_detail_dict": {"user": "fast_http_user"}, "user_count": 10, "spawn_rate": 5, "test_time": 5, "tasks": {"get": {"request_url": "http://httpbin.org/get"}}}]]'
+def slack_notify(message: str) -> None:
+ ...
-# Scaffold a new project with templates
-python -m je_load_density -c MyProject
+add_command_to_executor({"LD_slack_notify": slack_notify})
```
-### 4. Using the GUI
+## User Templates
-```python
-from je_load_density.gui.main_window import LoadDensityUI
-from PySide6.QtWidgets import QApplication
-import sys
+Every template registers under `start_test` via `user_detail_dict={"user": ""}`. Tasks share the same shape across HTTP, WebSocket, gRPC, MQTT, and raw socket users; only the protocol-specific fields differ.
-app = QApplication(sys.argv)
-window = LoadDensityUI()
-window.show()
-sys.exit(app.exec())
+### HTTP / FastHttp
+
+```python
+start_test(
+ user_detail_dict={"user": "fast_http_user"},
+ user_count=50, spawn_rate=10, test_time=60,
+ variables={"base": "https://api.example.com"},
+ tasks=[
+ {"method": "post", "request_url": "${var.base}/login",
+ "json": {"email": "u@example.com", "password": "secret"},
+ "extract": [{"var": "auth", "from": "json_path", "path": "data.token"}]},
+ {"method": "get", "request_url": "${var.base}/profile",
+ "headers": {"Authorization": "Bearer ${var.auth}"},
+ "assertions": [{"type": "status_code", "value": 200}]},
+ ],
+)
```
-## Report Generation
+### WebSocket
-After running a test, generate reports from the recorded data:
+`pip install je_load_density[websocket]`
```python
-from je_load_density import (
- generate_html_report,
- generate_json_report,
- generate_xml_report,
+start_test(
+ user_detail_dict={"user": "websocket_user"},
+ user_count=10, spawn_rate=5, test_time=60,
+ tasks=[
+ {"method": "connect", "request_url": "wss://echo.example.com/socket"},
+ {"method": "sendrecv", "payload": '{"ping": 1}', "expect": "pong"},
+ {"method": "close"},
+ ],
)
+```
-# HTML report — creates "my_report.html"
-generate_html_report("my_report")
+### gRPC
-# JSON report — creates "my_report_success.json" and "my_report_failure.json"
-generate_json_report("my_report")
+`pip install je_load_density[grpc]`
-# XML report — creates "my_report_success.xml" and "my_report_failure.xml"
-generate_xml_report("my_report")
+```python
+start_test(
+ user_detail_dict={"user": "grpc_user"},
+ user_count=20, spawn_rate=5, test_time=60,
+ tasks=[{
+ "name": "say_hello",
+ "target": "localhost:50051",
+ "stub_path": "pkg.greeter_pb2_grpc.GreeterStub",
+ "request_path": "pkg.greeter_pb2.HelloRequest",
+ "method": "SayHello",
+ "payload": {"name": "world"},
+ "metadata": [["x-token", "abc"]],
+ "timeout": 5,
+ }],
+)
```
-## Advanced Usage
+`stub_path` and `request_path` are validated against a strict identifier regex before `importlib.import_module`, so traversal-style attacks are rejected.
-### Action Executor
+### MQTT
-The executor maps string action names to callable functions. All built-in Python functions are also available.
+`pip install je_load_density[mqtt]`
```python
-from je_load_density import executor, add_command_to_executor
+start_test(
+ user_detail_dict={"user": "mqtt_user"},
+ user_count=10, spawn_rate=5, test_time=60,
+ tasks=[
+ {"method": "connect", "broker": "127.0.0.1:1883"},
+ {"method": "subscribe", "topic": "telemetry/in", "qos": 1},
+ {"method": "publish", "topic": "telemetry/out", "payload": "ping", "qos": 1},
+ {"method": "disconnect"},
+ ],
+)
+```
-# Register a custom function
-def my_custom_action(message):
- print(f"Custom: {message}")
+### Raw TCP / UDP
-add_command_to_executor({"my_action": my_custom_action})
+Stdlib only; nothing to install.
-# Execute actions programmatically
-executor.execute_action([
- ["my_action", ["Hello World"]],
- ["print", ["Test complete"]],
-])
+```python
+start_test(
+ user_detail_dict={"user": "socket_user"},
+ user_count=20, spawn_rate=5, test_time=60,
+ tasks=[
+ {"protocol": "tcp", "target": "127.0.0.1:9000",
+ "payload": "PING\n", "expect_bytes": 64,
+ "expect_substring": "PONG"},
+ {"protocol": "udp", "target": "127.0.0.1:9000",
+ "payload": "hex:DEADBEEF", "expect_bytes": 4},
+ ],
+)
```
-**Built-in executor actions:**
-| Action Name | Description |
-|---|---|
-| `LD_start_test` | Start a load test |
-| `LD_generate_html` | Generate HTML fragments |
-| `LD_generate_html_report` | Generate full HTML report file |
-| `LD_generate_json` | Generate JSON data structure |
-| `LD_generate_json_report` | Generate JSON report files |
-| `LD_generate_xml` | Generate XML strings |
-| `LD_generate_xml_report` | Generate XML report files |
-| `LD_execute_action` | Execute a list of actions |
-| `LD_execute_files` | Execute actions from multiple files |
-| `LD_add_package_to_executor` | Dynamically load a package into the executor |
+## Parameter Resolver
-### Callback Executor
+Placeholders are expanded automatically on every task:
-Chain a trigger function with a callback:
+| Placeholder | Resolves to |
+|-------------|-------------|
+| `${var.NAME}` | Value passed to `register_variable(s)` |
+| `${env.NAME}` | Environment variable `NAME` |
+| `${csv.SOURCE.COL}` | Next row from CSV source `SOURCE` (cycles by default) |
+| `${faker.METHOD}` | `Faker().METHOD()` (lazy import) |
+| `${uuid()}` | New UUID 4 string |
+| `${now()}` | Local ISO-8601 timestamp (seconds) |
+| `${randint(min, max)}` | Cryptographically-strong random int |
```python
-from je_load_density import callback_executor
+from je_load_density import register_variable, register_csv_source
-def after_test():
- print("Test finished, generating report...")
+register_variable("base", "https://api.example.com")
+register_csv_source("users", "users.csv")
+```
-callback_executor.callback_function(
- trigger_function_name="user_test",
- callback_function=after_test,
- user_detail_dict={"user": "fast_http_user"},
- user_count=10,
- spawn_rate=5,
- test_time=5,
- tasks={"get": {"request_url": "http://httpbin.org/get"}},
-)
+Or from action JSON:
+
+```json
+["LD_register_variables", {"variables": {"base": "https://api.example.com"}}]
+["LD_register_csv_sources", {"sources": [{"name": "users", "file_path": "users.csv"}]}]
```
-### TCP Socket Server (Remote Execution)
+Unknown placeholders are left in place so missing data is visible during a dry run.
-Start a TCP server that accepts JSON commands:
+## Scenario Modes
-```python
-from je_load_density import start_load_density_socket_server
+```json
+{
+ "mode": "weighted",
+ "tasks": [
+ {"method": "get", "request_url": "/products", "weight": 3},
+ {"method": "get", "request_url": "/expensive", "weight": 1}
+ ]
+}
+```
+
+| Mode | Behaviour |
+|------|-----------|
+| `sequence` | Run every task in order each tick (default) |
+| `weighted` | Pick one task per tick by `weight` |
+| `conditional` | Use `run_if` / `skip_if` predicates evaluated against the parameter resolver |
+
+Predicates: `bool`, `"${var.x}"`, `{"equals": [a,b]}`, `{"not_equals": [a,b]}`, `{"in": [needle, haystack]}`, `{"truthy": value}`.
-# Start server (blocking)
-start_load_density_socket_server(host="localhost", port=9940)
+## Assertions & Extractors
+
+Both run under Locust's `catch_response`; failed assertions surface in every report.
+
+```json
+{
+ "method": "post",
+ "request_url": "${var.base}/login",
+ "json": {"email": "u@example.com", "password": "secret"},
+ "assertions": [
+ {"type": "status_code", "value": 200},
+ {"type": "json_path", "path": "data.role", "value": "admin"}
+ ],
+ "extract": [
+ {"var": "auth_token", "from": "json_path", "path": "data.token"},
+ {"var": "request_id", "from": "header", "name": "X-Request-Id"}
+ ]
+}
```
-Send commands from a client:
+Assertion types: `status_code`, `contains`, `not_contains`, `json_path`, `header`. Extractor sources: `json_path`, `header`, `status_code`.
+
+## Reports
+
+Six formats consumed from `test_record_instance`:
```python
-import socket, json
+from je_load_density import (
+ generate_html_report, generate_json_report, generate_xml_report,
+ generate_csv_report, generate_junit_report, generate_summary_report,
+)
-sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-sock.connect(("localhost", 9940))
+generate_html_report("report") # report.html
+generate_json_report("report") # report_success.json + report_failure.json
+generate_xml_report("report") # report_success.xml + report_failure.xml
+generate_csv_report("report") # report.csv
+generate_junit_report("report-junit") # report-junit.xml (CI)
+generate_summary_report("report-sum") # totals + per-name p50/p90/p95/p99
+```
-command = json.dumps([
- ["LD_start_test", {
- "user_detail_dict": {"user": "fast_http_user"},
- "user_count": 10, "spawn_rate": 5, "test_time": 5,
- "tasks": {"get": {"request_url": "http://httpbin.org/get"}}
- }]
-])
-sock.send(command.encode("utf-8"))
-response = sock.recv(8192)
-print(response.decode("utf-8"))
-sock.close()
+## Observability
+
+```python
+from je_load_density import (
+ start_prometheus_exporter, start_influxdb_sink, start_opentelemetry_exporter,
+)
+
+start_prometheus_exporter(port=9646, addr="127.0.0.1")
+start_influxdb_sink(transport="udp", host="influxdb", port=8089)
+start_opentelemetry_exporter(endpoint="http://otel-collector:4317",
+ service_name="loaddensity")
```
-Send `"quit_server"` to gracefully shut down the server.
+| Sink | Metrics |
+|------|---------|
+| Prometheus | `loaddensity_requests_total`, `loaddensity_request_latency_ms`, `loaddensity_response_bytes` |
+| InfluxDB | `loaddensity_request` line-protocol points (UDP or HTTP) |
+| OTel | `loaddensity.requests`, `loaddensity.request.latency`, `loaddensity.response.size` |
-### Project Scaffolding
+All three are loaded lazily and gated by the matching install extra.
-Generate a project with keyword templates and executor scripts:
+## Distributed Master / Worker
```python
-from je_load_density import create_project_dir
+# master
+start_test(
+ user_detail_dict={"user": "fast_http_user"},
+ runner_mode="master",
+ master_bind_host="0.0.0.0", master_bind_port=5557,
+ expected_workers=4,
+ web_ui_dict={"host": "0.0.0.0", "port": 8089},
+ user_count=400, spawn_rate=40, test_time=600,
+ tasks=[...],
+)
-create_project_dir(project_path="./my_tests", parent_name="LoadDensity")
+# worker
+start_test(
+ user_detail_dict={"user": "fast_http_user"},
+ runner_mode="worker",
+ master_host="10.0.0.10", master_port=5557,
+ tasks=[...],
+)
```
-This creates:
-```
-my_tests/
-└── LoadDensity/
- ├── keyword/
- │ ├── keyword1.json # FastHttpUser test template
- │ └── keyword2.json # HttpUser test template
- └── executor/
- ├── executor_one_file.py # Execute single keyword file
- └── executor_folder.py # Execute all files in keyword/
+The master waits up to 60 s for `expected_workers` workers to register before starting the load ramp.
+
+## HAR Record / Replay
+
+```python
+from je_load_density import load_har, har_to_action_json
+
+har = load_har("recording.har")
+action_json = har_to_action_json(
+ har,
+ user="fast_http_user",
+ user_count=20, spawn_rate=10, test_time=120,
+ include=[r"api\.example\.com"],
+ exclude=[r"\.svg$"],
+)
```
-### Dynamic Package Loading
+Captures from Chrome / Firefox DevTools, mitmproxy, Charles, etc. all work. Status codes flow through as `status_code` assertions on every generated task.
-Load external packages and register their functions into the executor:
+## Persistent Records (SQLite)
```python
-from je_load_density import executor
+from je_load_density import persist_records, list_runs, fetch_run_records
+
+run_id = persist_records(
+ "loadtests.db",
+ label="checkout-2026-04-28",
+ metadata={"branch": "dev", "commit": "abc1234"},
+)
+for row in list_runs("loadtests.db", limit=10):
+ print(row)
+```
+
+Schema is created lazily; an empty file is fine. Indexes on `run_id` and `name` keep cross-run queries fast.
+
+## MCP Server (for Claude)
+
+```bash
+pip install "je_load_density[mcp]"
+python -m je_load_density.mcp_server
+```
-# Load a package and make its functions available as executor actions
-executor.execute_action([
- ["LD_add_package_to_executor", ["my_custom_package"]]
-])
+Wire it into Claude Desktop / Code:
+
+```json
+{
+ "mcpServers": {
+ "loaddensity": {
+ "command": "python",
+ "args": ["-m", "je_load_density.mcp_server"]
+ }
+ }
+}
+```
+
+Eleven tools are exposed: `run_test`, `run_action_json`, `create_project`, `list_executor_commands`, `import_har`, `generate_reports`, `summary`, `persist_records`, `list_runs`, `fetch_run`, `clear_records`.
+
+## Hardened Control Socket
+
+```bash
+python -m je_load_density serve \
+ --host 0.0.0.0 --port 9940 --framed \
+ --token "$LOAD_DENSITY_SOCKET_TOKEN" \
+ --tls-cert /etc/loaddensity/server.crt \
+ --tls-key /etc/loaddensity/server.key
```
-### Test Records
+- 4-byte big-endian length-prefixed frames (1 MiB cap)
+- Optional TLS (cert/key on disk; `ssl.create_default_context`, TLS 1.2+ minimum)
+- Shared-secret token compared with `hmac.compare_digest`; once configured, all payloads must use `{"token": "...", "command": [...]}` and may set `"op": "quit"` to stop the server
+- Token also reads from `LOAD_DENSITY_SOCKET_TOKEN` env var
+- Legacy unauthenticated mode preserved for backwards compatibility
-Access raw test records programmatically:
+## GUI
+
+```bash
+pip install "je_load_density[gui]"
+```
```python
-from je_load_density import test_record_instance
+import sys
+from PySide6.QtWidgets import QApplication
+from je_load_density.gui.main_window import LoadDensityUI
+
+app = QApplication(sys.argv)
+window = LoadDensityUI()
+window.show()
+sys.exit(app.exec())
+```
-# After running a test
-for record in test_record_instance.test_record_list:
- print(record["Method"], record["test_url"], record["status_code"])
+The GUI ships English, Traditional Chinese, Japanese, and Korean translations and a live stats panel that polls `test_record_instance` once a second (RPS, average / p95 latency, failure count).
-for error in test_record_instance.error_record_list:
- print(error["Method"], error["test_url"], error["error"])
+## CLI Usage
-# Clear records
-test_record_instance.clear_records()
+```
+python -m je_load_density run FILE # execute one action JSON file
+python -m je_load_density run-dir DIR # execute every .json in DIR
+python -m je_load_density run-str JSON # execute an inline JSON string
+python -m je_load_density init PATH # scaffold a project skeleton
+python -m je_load_density serve [--host ...] # start the control socket
```
-## Architecture
+Legacy single-flag form (`-e/-d/-c/--execute_str`) is still accepted for backwards compatibility with downstream tools.
+
+## Test Record
+
+`test_record_instance.test_record_list` and `error_record_list` collect every request with `Method`, `test_url`, `name`, `status_code`, `response_time_ms`, `response_length`, and (for failures) `error`. Reports and the SQLite sink read directly from these lists.
+
+## Exception Handling
```
-je_load_density/
-├── __init__.py # Public API exports
-├── __main__.py # CLI entry point
-├── gui/ # PySide6 GUI (optional dependency)
-│ ├── main_window.py # Main window (QMainWindow)
-│ ├── main_widget.py # Test parameter form & log panel
-│ ├── load_density_gui_thread.py # Background thread for tests
-│ ├── log_to_ui_filter.py # Log interceptor for GUI display
-│ └── language_wrapper/ # i18n (English, Traditional Chinese)
-├── wrapper/
-│ ├── create_locust_env/ # Locust Environment & Runner setup
-│ ├── start_wrapper/ # High-level start_test() entry point
-│ ├── user_template/ # HttpUser & FastHttpUser wrappers
-│ ├── proxy/ # User proxy container & configuration
-│ └── event/ # Request hook (records all requests)
-└── utils/
- ├── executor/ # Action executor (event-driven)
- ├── generate_report/ # HTML, JSON, XML report generators
- ├── test_record/ # Test record storage
- ├── socket_server/ # TCP server for remote execution
- ├── callback/ # Callback function executor
- ├── project/ # Project scaffolding & templates
- ├── package_manager/ # Dynamic package loading
- ├── json/ # JSON file read/write utilities
- ├── xml/ # XML structure utilities
- ├── file_process/ # Directory file listing
- ├── logging/ # Logger instance
- └── exception/ # Custom exceptions & error tags
-```
-
-## Tested Platforms
-
-- Windows 10 / 11
-- macOS 10.15 ~ 11 (Big Sur)
-- Ubuntu 20.04
-- Raspberry Pi 3B+
+LoadDensityTestException
+├── LoadDensityTestJsonException
+├── LoadDensityGenerateJsonReportException
+├── LoadDensityTestExecuteException
+├── LoadDensityAssertException
+├── LoadDensityHTMLException
+├── LoadDensityAddCommandException
+├── XMLException → XMLTypeException
+└── CallbackExecutorException
+```
-## License
+All custom exceptions inherit from `LoadDensityTestException`; catching that one class covers the public surface.
-This project is licensed under the [MIT License](LICENSE).
+## Logging
-## Contributing
+LoadDensity exposes a single configured logger (`load_density_logger`) under `je_load_density.utils.logging.loggin_instance`. Hook it into your existing log infrastructure with the standard `logging` module APIs.
-See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
+## Supported Platforms
-## Links
+| Platform | Status |
+|----------|--------|
+| Windows 10 / 11 | Fully supported |
+| macOS | Fully supported |
+| Ubuntu / Linux | Fully supported |
+| Raspberry Pi | Tested on 3B+ and later |
+
+Python 3.10+ required.
+
+## License
-- **PyPI**: https://pypi.org/project/je_load_density/
-- **Documentation**: https://loaddensity.readthedocs.io/en/latest/
-- **Source Code**: https://github.com/Intergration-Automation-Testing/LoadDensity
+MIT — see [LICENSE](LICENSE).
diff --git a/README/README_zh-CN.md b/README/README_zh-CN.md
index fcd92f3..0301d51 100644
--- a/README/README_zh-CN.md
+++ b/README/README_zh-CN.md
@@ -1,358 +1,590 @@
# LoadDensity
-[](https://pypi.org/project/je_load_density/)
-[](https://pypi.org/project/je_load_density/)
-[](https://opensource.org/licenses/MIT)
-[](https://loaddensity.readthedocs.io/en/latest/)
-
-**LoadDensity** 是一个基于 [Locust](https://locust.io/) 构建的高性能负载与压力测试自动化框架。它对 Locust 的核心功能进行了简化封装,提供快速用户生成、通过模板与 JSON 脚本进行灵活测试配置、多格式报告生成(HTML / JSON / XML)、内置 GUI 图形界面、通过 TCP Socket 服务器进行远程执行,以及测试后工作流程的回调机制。
-
-**[English](../README.md)** | **[繁體中文](README_zh-TW.md)**
+
+ 多协议压力与负载自动化框架:Locust + WebSocket + gRPC + MQTT + 原生 socket,搭配内建电池的 JSON 动作执行器。
+
+
+
+
+
+
+
+
+
+
+ English |
+ 繁體中文
+
---
-## 功能特性
-
-- **简化的 Locust 封装** — 将 Locust 的 `Environment`、`Runner` 和 `User` 类抽象化为简洁的高层 API。
-- **两种用户类型** — 同时支持 `HttpUser` 和 `FastHttpUser`(基于 geventhttpclient,吞吐量更高)。
-- **快速用户生成** — 可配置生成速率,轻松扩展至数千名并发用户。
-- **JSON 驱动的测试脚本** — 将测试场景定义为 JSON 文件,无需编写 Python 代码即可执行。
-- **动作执行器** — 内置的事件驱动执行器,将动作名称映射到函数。支持批量执行与文件驱动执行。
-- **报告生成** — 导出三种格式的测试结果:
- - **HTML** — 包含成功/失败记录的样式化表格
- - **JSON** — 适合程序化处理的结构化数据
- - **XML** — 标准 XML 输出,适合 CI/CD 集成
-- **请求钩子** — 自动记录每个请求(成功与失败),包含方法、URL、状态码、响应内容、头部与错误信息。
-- **回调执行器** — 将触发函数与回调函数串联,用于测试后工作流程(例如:执行测试后自动生成报告)。
-- **TCP Socket 服务器** — 基于 gevent 的远程执行服务器。通过 TCP 接收 JSON 命令以远程执行测试。
-- **项目脚手架** — 自动生成项目目录结构,包含关键字模板与执行器脚本。
-- **包管理器** — 在运行时动态加载外部 Python 包,并将其函数注册到执行器中。
-- **GUI 图形界面(可选)** — 基于 PySide6 的图形界面,支持实时日志显示,提供英文与繁体中文界面。
-- **CLI 命令行支持** — 直接从命令行执行测试、运行脚本或创建项目结构。
-- **跨平台** — 支持 Windows、macOS 和 Linux。
+LoadDensity(`je_load_density`)从 Locust 封装起家,逐步扩展为完整的多协议负载框架:HTTP、FastHttp、WebSocket、gRPC、MQTT 与原生 TCP/UDP 等使用者模板,皆通过同一个 JSON 驱动的动作执行器;另含数据参数化、情境流程、报告、可观测性、分布式 runner、录制、持久化存储,以及让 Claude 端到端驱动测试的 MCP 控制接口。每个 executor 指令以 `LD_*` 命名、使用单一派发点,因此一份动作 JSON 可同时混用协议、exporter 与报告。
+
+> **可选依赖、可选安装** — 每个协议驱动与 exporter 都以 `pip install je_load_density[]` 提供。仅做 HTTP 压测者运行期不受影响。
+
+## 目录
+
+- [亮点](#亮点)
+- [安装](#安装)
+- [架构](#架构)
+- [Quick Start](#quick-start)
+- [核心 API](#核心-api)
+- [动作 Executor](#动作-executor)
+- [使用者模板](#使用者模板)
+- [参数解析器](#参数解析器)
+- [情境模式](#情境模式)
+- [断言与提取](#断言与提取)
+- [报告](#报告)
+- [可观测性](#可观测性)
+- [分布式 Master / Worker](#分布式-master--worker)
+- [HAR 录制/重放](#har-录制重放)
+- [持久化记录(SQLite)](#持久化记录sqlite)
+- [MCP Server(给 Claude)](#mcp-server给-claude)
+- [硬化控制 Socket](#硬化控制-socket)
+- [GUI](#gui)
+- [CLI 用法](#cli-用法)
+- [测试记录](#测试记录)
+- [异常处理](#异常处理)
+- [日志](#日志)
+- [支持平台](#支持平台)
+- [许可证](#许可证)
+
+## 亮点
+
+- **一个 executor,六种协议** — HTTP、FastHttp、WebSocket、gRPC、MQTT、原生 TCP/UDP,全部通过 `LD_start_test` 以 `user` 切换派发。
+- **JSON 驱动** — 每支测试皆为动作 JSON 列表;可手写、由 HAR 导入产生、由 MCP 工具调度,或经控制 socket 传送。
+- **参数解析器** — `${var.x}`、`${env.X}`、`${csv.source.col}`、`${faker.method}`,以及内置 `${uuid()}`、`${now()}`、`${randint(min,max)}` 等 helper;可从响应提取值,后续 task 再用。
+- **情境流程** — 以 `sequence`(默认)/`weighted`/`conditional`(`run_if`、`skip_if`)声明 task 流程,无需动到 Python。
+- **六种报告格式** — HTML、JSON、XML、CSV、JUnit XML,以及百分位摘要 JSON(总计、失败率、per-name p50/p90/p95/p99)。
+- **三种 exporter** — Prometheus HTTP 端点、InfluxDB line-protocol UDP/HTTP sink、OpenTelemetry OTLP gRPC。
+- **分布式 runner** — `runner_mode="master"` / `"worker"`,跨机压测使用同一份 start_test API。
+- **HAR 录制/重放** — 将真实浏览流量转成可执行动作 JSON,含 regex include/exclude 过滤。
+- **持久化记录** — 可选 SQLite sink,含 run/record/metadata schema,便于跨次回归检查。
+- **MCP server** — `python -m je_load_density.mcp_server` 对外开 11 个工具,让 Claude 端到端驱动 LoadDensity。
+- **硬化控制 socket** — Length-prefix framing、可选 TLS、共享密钥 token(环境变数或参数),同时保留与 PyBreeze 等工具兼容的 legacy 模式。
+- **实时 GUI** — 可选的 PySide6 GUI 含实时统计面板(RPS、平均、p95、失败),翻译为英文、繁体中文、日文、韩文。
+- **CLI 子命令** — `run` / `run-dir` / `run-str` / `init` / `serve`。旧式 `-e/-d/-c/--execute_str` 标志保留以维持下游工具兼容。
## 安装
-### 基本安装(CLI 与库)
-
```bash
pip install je_load_density
```
-### 包含 GUI 支持
+引入 [Locust](https://locust.io/) 与 `defusedxml`,仅此而已。
+
+### 可选 extras
+
+| Extra | 加入 |
+|-------|------|
+| `gui` | PySide6 + qt-material(图形界面) |
+| `websocket` | `websocket-client`(WebSocket user 模板) |
+| `grpc` | `grpcio` + `protobuf`(gRPC user 模板) |
+| `mqtt` | `paho-mqtt`(MQTT user 模板) |
+| `prometheus` | `prometheus-client`(Prometheus exporter) |
+| `opentelemetry` | OpenTelemetry SDK + OTLP gRPC exporter |
+| `metrics` | `prometheus` + `opentelemetry` 一次装 |
+| `faker` | `Faker`(驱动 `${faker.method}` 占位符) |
+| `mcp` | `mcp` SDK(驱动 MCP server) |
+| `all` | 上列全部 |
+
+```bash
+pip install "je_load_density[gui]"
+pip install "je_load_density[mqtt,grpc,websocket]"
+pip install "je_load_density[metrics]"
+pip install "je_load_density[mcp]"
+pip install "je_load_density[all]"
+```
+
+### 开发安装
```bash
-pip install je_load_density[gui]
+git clone https://github.com/Integration-Automation/LoadDensity.git
+cd LoadDensity
+pip install -e ".[all]"
+pip install -r requirements.txt
```
-这会安装 [PySide6](https://doc.qt.io/qtforpython/) 和 [qt-material](https://github.com/UN-GCPDS/qt-material) 以提供图形界面。
+## 架构
-## 系统要求
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ CLI / MCP / GUI / 控制 Socket │
+└──────────────────┬──────────────────────────────────────────────┘
+ │ 动作 JSON
+┌──────────────────▼──────────────────────────────────────────────┐
+│ 动作 Executor(LD_* 派发 + 安全 builtin) │
+└──────────────────┬──────────────────────────────────────────────┘
+ │ start_test
+┌──────────────────▼──────────────────────────────────────────────┐
+│ locust_wrapper_proxy(每协议 task store) │
+└──────────────────┬──────────────────────────────────────────────┘
+ │
+ ┌───────────────┴───────────────┬──────────────┬──────────────┐
+ ▼ ▼ ▼ ▼
+HTTP / FastHttp WebSocket gRPC MQTT 原生 TCP / UDP
+ │ │ │ │
+ └───────────────┬───────────────┴──────────────┴──────────────┘
+ │ Locust 事件
+ ┌───────────────┴───────────────┐
+ ▼ ▼
+test_record_instance Prometheus / InfluxDB / OTel
+ │
+ ├── HTML / JSON / XML / CSV / JUnit / Summary 报告
+ └── SQLite 持久化(跨次比对)
+```
-- Python **3.10** 或更高版本
-- [Locust](https://locust.io/)(会作为依赖项自动安装)
+依赖方向永远是动作层 → Locust。
-## 快速上手
+## Quick Start
-### 1. 使用 Python API
+### Python API
```python
from je_load_density import start_test
-# 定义用户配置与任务
-result = start_test(
+start_test(
user_detail_dict={"user": "fast_http_user"},
- user_count=50,
- spawn_rate=10,
- test_time=10,
- tasks={
- "get": {"request_url": "http://httpbin.org/get"},
- "post": {"request_url": "http://httpbin.org/post"},
- }
+ user_count=50, spawn_rate=10, test_time=30,
+ variables={"base": "https://httpbin.org"},
+ tasks=[
+ {"method": "get", "request_url": "${var.base}/get"},
+ {"method": "post", "request_url": "${var.base}/post",
+ "json": {"hello": "world"},
+ "assertions": [{"type": "status_code", "value": 200}]},
+ ],
)
```
-**参数说明:**
-| 参数 | 类型 | 默认值 | 说明 |
-|---|---|---|---|
-| `user_detail_dict` | `dict` | — | 用户类型配置。`{"user": "fast_http_user"}` 或 `{"user": "http_user"}` |
-| `user_count` | `int` | `50` | 模拟用户总数 |
-| `spawn_rate` | `int` | `10` | 每秒生成的用户数量 |
-| `test_time` | `int` | `60` | 测试持续时间(秒)。设为 `None` 则无限执行 |
-| `web_ui_dict` | `dict` | `None` | 启用 Locust Web UI,例如 `{"host": "127.0.0.1", "port": 8089}` |
-| `tasks` | `dict` | — | HTTP 方法对应请求 URL 的映射 |
-
-### 2. 使用 JSON 脚本文件
-
-创建 JSON 文件(`test_scenario.json`):
+### 动作 JSON
```json
-[
- ["LD_start_test", {
- "user_detail_dict": {"user": "fast_http_user"},
- "user_count": 50,
- "spawn_rate": 10,
- "test_time": 5,
- "tasks": {
- "get": {"request_url": "http://httpbin.org/get"},
- "post": {"request_url": "http://httpbin.org/post"}
- }
- }]
-]
-```
-
-从 Python 执行:
+{"load_density": [
+ ["LD_register_variables", {"variables": {"base": "https://httpbin.org"}}],
+ ["LD_start_test", {
+ "user_detail_dict": {"user": "fast_http_user"},
+ "user_count": 20, "spawn_rate": 10, "test_time": 30,
+ "tasks": [
+ {"method": "get", "request_url": "${var.base}/get"},
+ {"method": "post", "request_url": "${var.base}/post",
+ "json": {"hello": "world"}}
+ ]
+ }],
+ ["LD_generate_summary_report", {"report_name": "smoke"}]
+]}
+```
-```python
-from je_load_density import execute_action, read_action_json
+CLI 执行:
-execute_action(read_action_json("test_scenario.json"))
+```bash
+python -m je_load_density run smoke.json
```
-### 3. 使用 CLI 命令行
+## 核心 API
-```bash
-# 执行单个 JSON 脚本文件
-python -m je_load_density -e test_scenario.json
+完整公开接口见 `je_load_density/__init__.py` 的 `__all__`。
+
+```python
+from je_load_density import (
+ start_test, prepare_env, create_env,
+ execute_action, execute_files, executor, add_command_to_executor,
+ test_record_instance, locust_wrapper_proxy,
+ register_variable, register_variables,
+ register_csv_source, register_csv_sources,
+ parameter_resolver, resolve,
+ har_to_action_json, har_to_tasks, load_har,
+ persist_records, list_runs, fetch_run_records,
+ start_prometheus_exporter, stop_prometheus_exporter,
+ start_influxdb_sink, stop_influxdb_sink,
+ start_opentelemetry_exporter, stop_opentelemetry_exporter,
+ start_load_density_socket_server,
+ generate_html_report, generate_json_report, generate_xml_report,
+ generate_csv_report, generate_junit_report, generate_summary_report,
+ build_summary,
+ create_project_dir, callback_executor, read_action_json,
+)
+```
-# 执行目录中所有 JSON 文件
-python -m je_load_density -d ./test_scripts/
+## 动作 Executor
-# 执行内联 JSON 字符串
-python -m je_load_density --execute_str '[["LD_start_test", {"user_detail_dict": {"user": "fast_http_user"}, "user_count": 10, "spawn_rate": 5, "test_time": 5, "tasks": {"get": {"request_url": "http://httpbin.org/get"}}}]]'
+每个动作为列表:
-# 使用模板创建新项目
-python -m je_load_density -c MyProject
+```python
+["command_name"] # 无参数
+["command_name", {"key": "value"}] # 关键字参数
+["command_name", [arg1, arg2]] # 位置参数
```
-### 4. 使用 GUI 图形界面
+最上层为裸列表,或 `{"load_density": [...]}` 包装。
+
+### 内置 `LD_*` 指令
+
+| 群组 | 指令 |
+|------|------|
+| 核心 | `LD_start_test`、`LD_execute_action`、`LD_execute_files`、`LD_add_package_to_executor`、`LD_start_socket_server` |
+| 报告 | `LD_generate_html(_report)`、`LD_generate_json(_report)`、`LD_generate_xml(_report)`、`LD_generate_csv_report`、`LD_generate_junit_report`、`LD_generate_summary_report`、`LD_summary` |
+| 持久化 | `LD_persist_records`、`LD_list_runs`、`LD_fetch_run_records`、`LD_clear_records` |
+| 参数 | `LD_register_variable(s)`、`LD_register_csv_source(s)`、`LD_clear_resolver` |
+| 录制 | `LD_load_har`、`LD_har_to_tasks`、`LD_har_to_action_json` |
+| 指标 | `LD_start/stop_prometheus_exporter`、`LD_start/stop_influxdb_sink`、`LD_start/stop_opentelemetry_exporter` |
+
+安全的 Python builtin(`print`、`len`、`range` 等)也可使用;`eval`、`exec`、`compile`、`__import__`、`breakpoint`、`open`、`input` 已被明确封锁。
+
+### 自定义指令
```python
-from je_load_density.gui.main_window import LoadDensityUI
-from PySide6.QtWidgets import QApplication
-import sys
+from je_load_density import add_command_to_executor
-app = QApplication(sys.argv)
-window = LoadDensityUI()
-window.show()
-sys.exit(app.exec())
+def slack_notify(message: str) -> None:
+ ...
+
+add_command_to_executor({"LD_slack_notify": slack_notify})
```
-## 报告生成
+## 使用者模板
-执行测试后,从记录的数据生成报告:
+所有模板皆通过 `start_test` 的 `user_detail_dict={"user": ""}` 注册;HTTP / WebSocket / gRPC / MQTT / raw socket 共用相同 task 结构,仅协议相关字段不同。
+
+### HTTP / FastHttp
```python
-from je_load_density import (
- generate_html_report,
- generate_json_report,
- generate_xml_report,
+start_test(
+ user_detail_dict={"user": "fast_http_user"},
+ user_count=50, spawn_rate=10, test_time=60,
+ variables={"base": "https://api.example.com"},
+ tasks=[
+ {"method": "post", "request_url": "${var.base}/login",
+ "json": {"email": "u@example.com", "password": "secret"},
+ "extract": [{"var": "auth", "from": "json_path", "path": "data.token"}]},
+ {"method": "get", "request_url": "${var.base}/profile",
+ "headers": {"Authorization": "Bearer ${var.auth}"},
+ "assertions": [{"type": "status_code", "value": 200}]},
+ ],
)
+```
+
+### WebSocket
-# HTML 报告 — 创建 "my_report.html"
-generate_html_report("my_report")
+```python
+start_test(
+ user_detail_dict={"user": "websocket_user"},
+ user_count=10, spawn_rate=5, test_time=60,
+ tasks=[
+ {"method": "connect", "request_url": "wss://echo.example.com/socket"},
+ {"method": "sendrecv", "payload": '{"ping": 1}', "expect": "pong"},
+ {"method": "close"},
+ ],
+)
+```
-# JSON 报告 — 创建 "my_report_success.json" 和 "my_report_failure.json"
-generate_json_report("my_report")
+### gRPC
-# XML 报告 — 创建 "my_report_success.xml" 和 "my_report_failure.xml"
-generate_xml_report("my_report")
+```python
+start_test(
+ user_detail_dict={"user": "grpc_user"},
+ user_count=20, spawn_rate=5, test_time=60,
+ tasks=[{
+ "name": "say_hello",
+ "target": "localhost:50051",
+ "stub_path": "pkg.greeter_pb2_grpc.GreeterStub",
+ "request_path": "pkg.greeter_pb2.HelloRequest",
+ "method": "SayHello",
+ "payload": {"name": "world"},
+ "metadata": [["x-token", "abc"]],
+ "timeout": 5,
+ }],
+)
```
-## 高级用法
+`stub_path` 与 `request_path` 在 `importlib.import_module` 之前皆通过严格标识符 regex 验证,traversal 攻击将被拒绝。
+
+### MQTT
+
+```python
+start_test(
+ user_detail_dict={"user": "mqtt_user"},
+ user_count=10, spawn_rate=5, test_time=60,
+ tasks=[
+ {"method": "connect", "broker": "127.0.0.1:1883"},
+ {"method": "subscribe", "topic": "telemetry/in", "qos": 1},
+ {"method": "publish", "topic": "telemetry/out", "payload": "ping", "qos": 1},
+ {"method": "disconnect"},
+ ],
+)
+```
-### 动作执行器
+### 原生 TCP / UDP
-执行器将字符串动作名称映射到可调用的函数。所有 Python 内置函数也可使用。
+仅用标准库,无需安装。
```python
-from je_load_density import executor, add_command_to_executor
+start_test(
+ user_detail_dict={"user": "socket_user"},
+ user_count=20, spawn_rate=5, test_time=60,
+ tasks=[
+ {"protocol": "tcp", "target": "127.0.0.1:9000",
+ "payload": "PING\n", "expect_bytes": 64,
+ "expect_substring": "PONG"},
+ {"protocol": "udp", "target": "127.0.0.1:9000",
+ "payload": "hex:DEADBEEF", "expect_bytes": 4},
+ ],
+)
+```
+
+## 参数解析器
+
+| 占位符 | 解析为 |
+|--------|--------|
+| `${var.NAME}` | `register_variable(s)` 设定的值 |
+| `${env.NAME}` | 环境变量 `NAME` |
+| `${csv.SOURCE.COL}` | CSV 来源 `SOURCE` 的下一行(默认循环) |
+| `${faker.METHOD}` | `Faker().METHOD()`(lazy import) |
+| `${uuid()}` | 新 UUID 4 字串 |
+| `${now()}` | 本地 ISO-8601 时间(秒) |
+| `${randint(min, max)}` | 密码学强度随机整数 |
-# 注册自定义函数
-def my_custom_action(message):
- print(f"自定义动作: {message}")
+未知占位符原样保留,便于 dry run 调试。
-add_command_to_executor({"my_action": my_custom_action})
+## 情境模式
-# 程序化执行动作
-executor.execute_action([
- ["my_action", ["Hello World"]],
- ["print", ["测试完成"]],
-])
+```json
+{
+ "mode": "weighted",
+ "tasks": [
+ {"method": "get", "request_url": "/products", "weight": 3},
+ {"method": "get", "request_url": "/expensive", "weight": 1}
+ ]
+}
```
-**内置执行器动作:**
-| 动作名称 | 说明 |
-|---|---|
-| `LD_start_test` | 启动负载测试 |
-| `LD_generate_html` | 生成 HTML 片段 |
-| `LD_generate_html_report` | 生成完整 HTML 报告文件 |
-| `LD_generate_json` | 生成 JSON 数据结构 |
-| `LD_generate_json_report` | 生成 JSON 报告文件 |
-| `LD_generate_xml` | 生成 XML 字符串 |
-| `LD_generate_xml_report` | 生成 XML 报告文件 |
-| `LD_execute_action` | 执行动作列表 |
-| `LD_execute_files` | 从多个文件执行动作 |
-| `LD_add_package_to_executor` | 动态加载包到执行器 |
+| 模式 | 行为 |
+|------|------|
+| `sequence` | 依序执行所有 task(默认) |
+| `weighted` | 每 tick 依 `weight` 加权挑一个 |
+| `conditional` | 以 `run_if` / `skip_if` 谓词控制 |
+
+谓词:`bool`、`"${var.x}"`、`{"equals": [a,b]}`、`{"not_equals": [a,b]}`、`{"in": [needle, haystack]}`、`{"truthy": value}`。
-### 回调执行器
+## 断言与提取
-将触发函数与回调串联:
+```json
+{
+ "method": "post",
+ "request_url": "${var.base}/login",
+ "json": {"email": "u@example.com", "password": "secret"},
+ "assertions": [
+ {"type": "status_code", "value": 200},
+ {"type": "json_path", "path": "data.role", "value": "admin"}
+ ],
+ "extract": [
+ {"var": "auth_token", "from": "json_path", "path": "data.token"},
+ {"var": "request_id", "from": "header", "name": "X-Request-Id"}
+ ]
+}
+```
+
+断言类型:`status_code`、`contains`、`not_contains`、`json_path`、`header`。
+提取来源:`json_path`、`header`、`status_code`。
+
+## 报告
```python
-from je_load_density import callback_executor
+from je_load_density import (
+ generate_html_report, generate_json_report, generate_xml_report,
+ generate_csv_report, generate_junit_report, generate_summary_report,
+)
+
+generate_html_report("report") # report.html
+generate_json_report("report") # report_success.json + report_failure.json
+generate_xml_report("report") # report_success.xml + report_failure.xml
+generate_csv_report("report") # report.csv
+generate_junit_report("report-junit") # report-junit.xml(CI)
+generate_summary_report("report-sum") # 总计 + per-name p50/p90/p95/p99
+```
-def after_test():
- print("测试完成,正在生成报告...")
+## 可观测性
-callback_executor.callback_function(
- trigger_function_name="user_test",
- callback_function=after_test,
- user_detail_dict={"user": "fast_http_user"},
- user_count=10,
- spawn_rate=5,
- test_time=5,
- tasks={"get": {"request_url": "http://httpbin.org/get"}},
+```python
+from je_load_density import (
+ start_prometheus_exporter, start_influxdb_sink, start_opentelemetry_exporter,
)
+
+start_prometheus_exporter(port=9646, addr="127.0.0.1")
+start_influxdb_sink(transport="udp", host="influxdb", port=8089)
+start_opentelemetry_exporter(endpoint="http://otel-collector:4317",
+ service_name="loaddensity")
```
-### TCP Socket 服务器(远程执行)
+| Sink | 指标 |
+|------|------|
+| Prometheus | `loaddensity_requests_total`、`loaddensity_request_latency_ms`、`loaddensity_response_bytes` |
+| InfluxDB | `loaddensity_request` line-protocol(UDP 或 HTTP) |
+| OTel | `loaddensity.requests`、`loaddensity.request.latency`、`loaddensity.response.size` |
-启动接收 JSON 命令的 TCP 服务器:
+三者皆 lazy load,由对应 install extra 控管依赖。
+
+## 分布式 Master / Worker
```python
-from je_load_density import start_load_density_socket_server
+# master
+start_test(
+ user_detail_dict={"user": "fast_http_user"},
+ runner_mode="master",
+ master_bind_host="0.0.0.0", master_bind_port=5557,
+ expected_workers=4,
+ web_ui_dict={"host": "0.0.0.0", "port": 8089},
+ user_count=400, spawn_rate=40, test_time=600,
+ tasks=[...],
+)
-# 启动服务器(阻塞式)
-start_load_density_socket_server(host="localhost", port=9940)
+# worker
+start_test(
+ user_detail_dict={"user": "fast_http_user"},
+ runner_mode="worker",
+ master_host="10.0.0.10", master_port=5557,
+ tasks=[...],
+)
```
-从客户端发送命令:
+Master 在开始 ramp 前最多等 60 秒,等待 `expected_workers` 个 worker 加入。
+
+## HAR 录制/重放
```python
-import socket, json
+from je_load_density import load_har, har_to_action_json
+
+har = load_har("recording.har")
+action_json = har_to_action_json(
+ har,
+ user="fast_http_user",
+ user_count=20, spawn_rate=10, test_time=120,
+ include=[r"api\.example\.com"],
+ exclude=[r"\.svg$"],
+)
+```
-sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-sock.connect(("localhost", 9940))
+可吃 Chrome / Firefox DevTools、mitmproxy、Charles 等录制。状态码会转成 `status_code` 断言。
-command = json.dumps([
- ["LD_start_test", {
- "user_detail_dict": {"user": "fast_http_user"},
- "user_count": 10, "spawn_rate": 5, "test_time": 5,
- "tasks": {"get": {"request_url": "http://httpbin.org/get"}}
- }]
-])
-sock.send(command.encode("utf-8"))
-response = sock.recv(8192)
-print(response.decode("utf-8"))
-sock.close()
-```
+## 持久化记录(SQLite)
-发送 `"quit_server"` 可优雅地关闭服务器。
+```python
+from je_load_density import persist_records, list_runs, fetch_run_records
-### 项目脚手架
+run_id = persist_records(
+ "loadtests.db",
+ label="checkout-2026-04-28",
+ metadata={"branch": "dev", "commit": "abc1234"},
+)
+for row in list_runs("loadtests.db", limit=10):
+ print(row)
+```
-生成包含关键字模板与执行器脚本的项目:
+Schema 采延迟建立。`run_id` 与 `name` 上有索引,跨次查询快速。
-```python
-from je_load_density import create_project_dir
+## MCP Server(给 Claude)
-create_project_dir(project_path="./my_tests", parent_name="LoadDensity")
+```bash
+pip install "je_load_density[mcp]"
+python -m je_load_density.mcp_server
```
-这会创建以下结构:
+接到 Claude Desktop / Code:
+
+```json
+{
+ "mcpServers": {
+ "loaddensity": {
+ "command": "python",
+ "args": ["-m", "je_load_density.mcp_server"]
+ }
+ }
+}
```
-my_tests/
-└── LoadDensity/
- ├── keyword/
- │ ├── keyword1.json # FastHttpUser 测试模板
- │ └── keyword2.json # HttpUser 测试模板
- └── executor/
- ├── executor_one_file.py # 执行单个关键字文件
- └── executor_folder.py # 执行 keyword/ 目录中所有文件
+
+对外开 11 个工具:`run_test`、`run_action_json`、`create_project`、`list_executor_commands`、`import_har`、`generate_reports`、`summary`、`persist_records`、`list_runs`、`fetch_run`、`clear_records`。
+
+## 硬化控制 Socket
+
+```bash
+python -m je_load_density serve \
+ --host 0.0.0.0 --port 9940 --framed \
+ --token "$LOAD_DENSITY_SOCKET_TOKEN" \
+ --tls-cert /etc/loaddensity/server.crt \
+ --tls-key /etc/loaddensity/server.key
```
-### 动态包加载
+- 4-byte big-endian 长度前缀框架(1 MiB 上限)
+- 可选 TLS(`ssl.create_default_context`,TLS 1.2+ minimum)
+- 共享密钥 token,以 `hmac.compare_digest` 比对;一旦设定,所有 payload 须使用 `{"token": "...", "command": [...]}` 信封,可以 `"op": "quit"` 停机
+- Token 也可由 `LOAD_DENSITY_SOCKET_TOKEN` 环境变数读取
+- 保留未验证 legacy 模式以维持兼容
-加载外部包并将其函数注册到执行器中:
+## GUI
+
+```bash
+pip install "je_load_density[gui]"
+```
```python
-from je_load_density import executor
+import sys
+from PySide6.QtWidgets import QApplication
+from je_load_density.gui.main_window import LoadDensityUI
-# 加载包并使其函数可作为执行器动作使用
-executor.execute_action([
- ["LD_add_package_to_executor", ["my_custom_package"]]
-])
+app = QApplication(sys.argv)
+window = LoadDensityUI()
+window.show()
+sys.exit(app.exec())
```
-### 测试记录
+GUI 提供英文、繁体中文、日文、韩文翻译,以及每秒轮询 `test_record_instance` 的实时统计面板(RPS、平均、p95、失败)。
-以程序化方式访问原始测试记录:
+## CLI 用法
-```python
-from je_load_density import test_record_instance
+```
+python -m je_load_density run FILE # 执行单一动作 JSON 档
+python -m je_load_density run-dir DIR # 执行 DIR 下所有 .json
+python -m je_load_density run-str JSON # 执行 inline JSON
+python -m je_load_density init PATH # 建立专案骨架
+python -m je_load_density serve [--host ...] # 启动控制 socket
+```
-# 执行测试后
-for record in test_record_instance.test_record_list:
- print(record["Method"], record["test_url"], record["status_code"])
+旧式 `-e/-d/-c/--execute_str` 仍接受,兼容下游工具。
-for error in test_record_instance.error_record_list:
- print(error["Method"], error["test_url"], error["error"])
+## 测试记录
-# 清除记录
-test_record_instance.clear_records()
-```
+`test_record_instance.test_record_list` 与 `error_record_list` 收集每笔请求:`Method`、`test_url`、`name`、`status_code`、`response_time_ms`、`response_length`,失败则含 `error`。报告与 SQLite sink 直接读取此处。
-## 架构
+## 异常处理
```
-je_load_density/
-├── __init__.py # 公开 API 导出
-├── __main__.py # CLI 入口点
-├── gui/ # PySide6 GUI(可选依赖)
-│ ├── main_window.py # 主窗口(QMainWindow)
-│ ├── main_widget.py # 测试参数表单与日志面板
-│ ├── load_density_gui_thread.py # 测试后台线程
-│ ├── log_to_ui_filter.py # GUI 显示的日志拦截器
-│ └── language_wrapper/ # 国际化(英文、繁体中文)
-├── wrapper/
-│ ├── create_locust_env/ # Locust Environment 与 Runner 设置
-│ ├── start_wrapper/ # 高层 start_test() 入口点
-│ ├── user_template/ # HttpUser 与 FastHttpUser 封装
-│ ├── proxy/ # 用户代理容器与配置
-│ └── event/ # 请求钩子(记录所有请求)
-└── utils/
- ├── executor/ # 动作执行器(事件驱动)
- ├── generate_report/ # HTML、JSON、XML 报告生成器
- ├── test_record/ # 测试记录存储
- ├── socket_server/ # 远程执行 TCP 服务器
- ├── callback/ # 回调函数执行器
- ├── project/ # 项目脚手架与模板
- ├── package_manager/ # 动态包加载
- ├── json/ # JSON 文件读写工具
- ├── xml/ # XML 结构工具
- ├── file_process/ # 目录文件列表
- ├── logging/ # Logger 实例
- └── exception/ # 自定义异常与错误标签
-```
-
-## 已测试平台
-
-- Windows 10 / 11
-- macOS 10.15 ~ 11(Big Sur)
-- Ubuntu 20.04
-- Raspberry Pi 3B+
+LoadDensityTestException
+├── LoadDensityTestJsonException
+├── LoadDensityGenerateJsonReportException
+├── LoadDensityTestExecuteException
+├── LoadDensityAssertException
+├── LoadDensityHTMLException
+├── LoadDensityAddCommandException
+├── XMLException → XMLTypeException
+└── CallbackExecutorException
+```
-## 许可证
+所有自定义异常皆继承 `LoadDensityTestException`;拦该类别即可全面处理。
-本项目采用 [MIT 许可证](../LICENSE)。
+## 日志
-## 贡献指南
+LoadDensity 提供已配置好的 logger(`load_density_logger`,位于 `je_load_density.utils.logging.loggin_instance`)。以标准 `logging` 模组 API 即可整合现有日志系统。
-请参阅 [CONTRIBUTING.md](../CONTRIBUTING.md) 了解贡献规范。
+## 支持平台
-## 相关链接
+| 平台 | 状态 |
+|------|------|
+| Windows 10 / 11 | 完整支持 |
+| macOS | 完整支持 |
+| Ubuntu / Linux | 完整支持 |
+| Raspberry Pi | 已测 3B+ 以上 |
+
+需 Python 3.10+。
+
+## 许可证
-- **PyPI**:https://pypi.org/project/je_load_density/
-- **文档**:https://loaddensity.readthedocs.io/en/latest/
-- **源代码**:https://github.com/Intergration-Automation-Testing/LoadDensity
+MIT — 见 [LICENSE](../LICENSE)。
diff --git a/README/README_zh-TW.md b/README/README_zh-TW.md
index 0245400..0bf0bd6 100644
--- a/README/README_zh-TW.md
+++ b/README/README_zh-TW.md
@@ -1,358 +1,590 @@
# LoadDensity
-[](https://pypi.org/project/je_load_density/)
-[](https://pypi.org/project/je_load_density/)
-[](https://opensource.org/licenses/MIT)
-[](https://loaddensity.readthedocs.io/en/latest/)
-
-**LoadDensity** 是一個基於 [Locust](https://locust.io/) 建構的高效能負載與壓力測試自動化框架。它對 Locust 的核心功能進行了簡化封裝,提供快速使用者生成、透過模板與 JSON 腳本進行彈性測試配置、多格式報告生成(HTML / JSON / XML)、內建 GUI 圖形介面、透過 TCP Socket 伺服器進行遠端執行,以及測試後工作流程的回呼機制。
-
-**[English](../README.md)** | **[简体中文](README_zh-CN.md)**
+
+ 多協定壓力與負載自動化框架:Locust + WebSocket + gRPC + MQTT + 原生 socket,搭配內建電池的 JSON 動作執行器。
+
+
+
+
+
+
+
+
+
+
+ English |
+ 简体中文
+
---
-## 功能特色
-
-- **簡化的 Locust 封裝** — 將 Locust 的 `Environment`、`Runner` 和 `User` 類別抽象化為簡潔的高階 API。
-- **兩種使用者類型** — 同時支援 `HttpUser` 和 `FastHttpUser`(基於 geventhttpclient,吞吐量更高)。
-- **快速使用者生成** — 可配置生成速率,輕鬆擴展至數千名並行使用者。
-- **JSON 驅動的測試腳本** — 將測試場景定義為 JSON 檔案,無需撰寫 Python 程式碼即可執行。
-- **動作執行器** — 內建的事件驅動執行器,將動作名稱映射到函式。支援批次執行與檔案驅動執行。
-- **報告生成** — 匯出三種格式的測試結果:
- - **HTML** — 包含成功/失敗記錄的樣式化表格
- - **JSON** — 適合程式化處理的結構化資料
- - **XML** — 標準 XML 輸出,適合 CI/CD 整合
-- **請求鉤子** — 自動記錄每個請求(成功與失敗),包含方法、URL、狀態碼、回應內容、標頭與錯誤資訊。
-- **回呼執行器** — 將觸發函式與回呼函式串聯,用於測試後工作流程(例如:執行測試後自動生成報告)。
-- **TCP Socket 伺服器** — 基於 gevent 的遠端執行伺服器。透過 TCP 接收 JSON 指令以遠端執行測試。
-- **專案腳手架** — 自動生成專案目錄結構,包含關鍵字模板與執行器腳本。
-- **套件管理器** — 在執行期間動態載入外部 Python 套件,並將其函式註冊到執行器中。
-- **GUI 圖形介面(選用)** — 基於 PySide6 的圖形介面,支援即時日誌顯示,提供英文與繁體中文介面。
-- **CLI 命令列支援** — 直接從命令列執行測試、運行腳本或建立專案結構。
-- **跨平台** — 支援 Windows、macOS 和 Linux。
+LoadDensity(`je_load_density`)從 Locust 封裝起家,逐步擴展為完整的多協定負載框架:HTTP、FastHttp、WebSocket、gRPC、MQTT 與原生 TCP/UDP 等使用者模板,皆透過同一個 JSON 驅動的動作執行器;另含資料參數化、情境流程、報告、可觀測性、分散式 runner、錄製、持久化儲存,以及讓 Claude 端對端驅動測試的 MCP 控制介面。每個 executor 指令以 `LD_*` 命名、使用單一派發點,因此一份動作 JSON 可同時混用協定、exporter 與報告。
+
+> **選用相依、可選安裝** — 每個協定驅動與 exporter 都以 `pip install je_load_density[]` 提供。僅做 HTTP 壓測者執行期不受影響。
+
+## 目次
+
+- [亮點](#亮點)
+- [安裝](#安裝)
+- [架構](#架構)
+- [Quick Start](#quick-start)
+- [核心 API](#核心-api)
+- [動作 Executor](#動作-executor)
+- [使用者模板](#使用者模板)
+- [參數解析器](#參數解析器)
+- [情境模式](#情境模式)
+- [斷言與擷取](#斷言與擷取)
+- [報告](#報告)
+- [可觀測性](#可觀測性)
+- [分散式 Master / Worker](#分散式-master--worker)
+- [HAR 錄製/重放](#har-錄製重放)
+- [持久化紀錄(SQLite)](#持久化紀錄sqlite)
+- [MCP Server(給 Claude)](#mcp-server給-claude)
+- [硬化控制 Socket](#硬化控制-socket)
+- [GUI](#gui)
+- [CLI 用法](#cli-用法)
+- [測試紀錄](#測試紀錄)
+- [例外處理](#例外處理)
+- [日誌](#日誌)
+- [支援平台](#支援平台)
+- [授權](#授權)
+
+## 亮點
+
+- **一個 executor,六種協定** — HTTP、FastHttp、WebSocket、gRPC、MQTT、原生 TCP/UDP,全部透過 `LD_start_test` 以 `user` 切換派發。
+- **JSON 驅動** — 每支測試皆為動作 JSON 列表;可手寫、由 HAR 匯入產生、由 MCP 工具排程,或經控制 socket 傳送。
+- **參數解析器** — `${var.x}`、`${env.X}`、`${csv.source.col}`、`${faker.method}`,以及內建 `${uuid()}`、`${now()}`、`${randint(min,max)}` 等 helper;可從回應擷取值,後續 task 再用。
+- **情境流程** — 以 `sequence`(預設)/`weighted`/`conditional`(`run_if`、`skip_if`)宣告 task 流程,無需動到 Python。
+- **六種報告格式** — HTML、JSON、XML、CSV、JUnit XML,以及百分位摘要 JSON(總計、失敗率、per-name p50/p90/p95/p99)。
+- **三種 exporter** — Prometheus HTTP 端點、InfluxDB line-protocol UDP/HTTP sink、OpenTelemetry OTLP gRPC。
+- **分散式 runner** — `runner_mode="master"` / `"worker"`,跨機壓測使用同一份 start_test API。
+- **HAR 錄製/重放** — 將真實瀏覽流量轉成可執行動作 JSON,含 regex include/exclude 過濾。
+- **持久化紀錄** — 選用 SQLite sink,含 run/record/metadata schema,便於跨次回歸檢查。
+- **MCP server** — `python -m je_load_density.mcp_server` 對外開 11 個工具,讓 Claude 端對端驅動 LoadDensity。
+- **硬化控制 socket** — Length-prefix framing、選用 TLS、共享密鑰 token(環境變數或參數),同時保留與 PyBreeze 等工具相容的 legacy 模式。
+- **即時 GUI** — 選用的 PySide6 GUI 含即時統計面板(RPS、平均、p95、失敗),翻譯為英文、繁體中文、日文、韓文。
+- **CLI 子指令** — `run` / `run-dir` / `run-str` / `init` / `serve`。舊式 `-e/-d/-c/--execute_str` 旗標保留以維持下游工具相容。
## 安裝
-### 基本安裝(CLI 與函式庫)
-
```bash
pip install je_load_density
```
-### 包含 GUI 支援
+引入 [Locust](https://locust.io/) 與 `defusedxml`,僅此而已。
+
+### 選用 extras
+
+| Extra | 加入 |
+|-------|------|
+| `gui` | PySide6 + qt-material(圖形介面) |
+| `websocket` | `websocket-client`(WebSocket user 模板) |
+| `grpc` | `grpcio` + `protobuf`(gRPC user 模板) |
+| `mqtt` | `paho-mqtt`(MQTT user 模板) |
+| `prometheus` | `prometheus-client`(Prometheus exporter) |
+| `opentelemetry` | OpenTelemetry SDK + OTLP gRPC exporter |
+| `metrics` | `prometheus` + `opentelemetry` 一次裝 |
+| `faker` | `Faker`(驅動 `${faker.method}` 占位符) |
+| `mcp` | `mcp` SDK(驅動 MCP server) |
+| `all` | 上列全部 |
```bash
-pip install je_load_density[gui]
+pip install "je_load_density[gui]"
+pip install "je_load_density[mqtt,grpc,websocket]"
+pip install "je_load_density[metrics]"
+pip install "je_load_density[mcp]"
+pip install "je_load_density[all]"
```
-這會安裝 [PySide6](https://doc.qt.io/qtforpython/) 和 [qt-material](https://github.com/UN-GCPDS/qt-material) 以提供圖形介面。
+### 開發安裝
-## 系統需求
+```bash
+git clone https://github.com/Integration-Automation/LoadDensity.git
+cd LoadDensity
+pip install -e ".[all]"
+pip install -r requirements.txt
+```
-- Python **3.10** 或更高版本
-- [Locust](https://locust.io/)(會作為依賴項自動安裝)
+## 架構
-## 快速上手
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ CLI / MCP / GUI / 控制 Socket │
+└──────────────────┬──────────────────────────────────────────────┘
+ │ 動作 JSON
+┌──────────────────▼──────────────────────────────────────────────┐
+│ 動作 Executor(LD_* 派發 + 安全 builtin) │
+└──────────────────┬──────────────────────────────────────────────┘
+ │ start_test
+┌──────────────────▼──────────────────────────────────────────────┐
+│ locust_wrapper_proxy(每協定 task store) │
+└──────────────────┬──────────────────────────────────────────────┘
+ │
+ ┌───────────────┴───────────────┬──────────────┬──────────────┐
+ ▼ ▼ ▼ ▼
+HTTP / FastHttp WebSocket gRPC MQTT 原生 TCP / UDP
+ │ │ │ │
+ └───────────────┬───────────────┴──────────────┴──────────────┘
+ │ Locust 事件
+ ┌───────────────┴───────────────┐
+ ▼ ▼
+test_record_instance Prometheus / InfluxDB / OTel
+ │
+ ├── HTML / JSON / XML / CSV / JUnit / Summary 報告
+ └── SQLite 持久化(跨次比對)
+```
+
+依賴方向永遠是動作層 → Locust。
+
+## Quick Start
-### 1. 使用 Python API
+### Python API
```python
from je_load_density import start_test
-# 定義使用者配置與任務
-result = start_test(
+start_test(
user_detail_dict={"user": "fast_http_user"},
- user_count=50,
- spawn_rate=10,
- test_time=10,
- tasks={
- "get": {"request_url": "http://httpbin.org/get"},
- "post": {"request_url": "http://httpbin.org/post"},
- }
+ user_count=50, spawn_rate=10, test_time=30,
+ variables={"base": "https://httpbin.org"},
+ tasks=[
+ {"method": "get", "request_url": "${var.base}/get"},
+ {"method": "post", "request_url": "${var.base}/post",
+ "json": {"hello": "world"},
+ "assertions": [{"type": "status_code", "value": 200}]},
+ ],
)
```
-**參數說明:**
-| 參數 | 類型 | 預設值 | 說明 |
-|---|---|---|---|
-| `user_detail_dict` | `dict` | — | 使用者類型配置。`{"user": "fast_http_user"}` 或 `{"user": "http_user"}` |
-| `user_count` | `int` | `50` | 模擬使用者總數 |
-| `spawn_rate` | `int` | `10` | 每秒生成的使用者數量 |
-| `test_time` | `int` | `60` | 測試持續時間(秒)。設為 `None` 則無限執行 |
-| `web_ui_dict` | `dict` | `None` | 啟用 Locust Web UI,例如 `{"host": "127.0.0.1", "port": 8089}` |
-| `tasks` | `dict` | — | HTTP 方法對應請求 URL 的映射 |
+### 動作 JSON
-### 2. 使用 JSON 腳本檔案
+```json
+{"load_density": [
+ ["LD_register_variables", {"variables": {"base": "https://httpbin.org"}}],
+ ["LD_start_test", {
+ "user_detail_dict": {"user": "fast_http_user"},
+ "user_count": 20, "spawn_rate": 10, "test_time": 30,
+ "tasks": [
+ {"method": "get", "request_url": "${var.base}/get"},
+ {"method": "post", "request_url": "${var.base}/post",
+ "json": {"hello": "world"}}
+ ]
+ }],
+ ["LD_generate_summary_report", {"report_name": "smoke"}]
+]}
+```
-建立 JSON 檔案(`test_scenario.json`):
+CLI 執行:
-```json
-[
- ["LD_start_test", {
- "user_detail_dict": {"user": "fast_http_user"},
- "user_count": 50,
- "spawn_rate": 10,
- "test_time": 5,
- "tasks": {
- "get": {"request_url": "http://httpbin.org/get"},
- "post": {"request_url": "http://httpbin.org/post"}
- }
- }]
-]
-```
-
-從 Python 執行:
+```bash
+python -m je_load_density run smoke.json
+```
+
+## 核心 API
+
+完整公開介面見 `je_load_density/__init__.py` 的 `__all__`。
```python
-from je_load_density import execute_action, read_action_json
+from je_load_density import (
+ start_test, prepare_env, create_env,
+ execute_action, execute_files, executor, add_command_to_executor,
+ test_record_instance, locust_wrapper_proxy,
+ register_variable, register_variables,
+ register_csv_source, register_csv_sources,
+ parameter_resolver, resolve,
+ har_to_action_json, har_to_tasks, load_har,
+ persist_records, list_runs, fetch_run_records,
+ start_prometheus_exporter, stop_prometheus_exporter,
+ start_influxdb_sink, stop_influxdb_sink,
+ start_opentelemetry_exporter, stop_opentelemetry_exporter,
+ start_load_density_socket_server,
+ generate_html_report, generate_json_report, generate_xml_report,
+ generate_csv_report, generate_junit_report, generate_summary_report,
+ build_summary,
+ create_project_dir, callback_executor, read_action_json,
+)
+```
+
+## 動作 Executor
-execute_action(read_action_json("test_scenario.json"))
+每個動作為列表:
+
+```python
+["command_name"] # 無參數
+["command_name", {"key": "value"}] # 關鍵字參數
+["command_name", [arg1, arg2]] # 位置參數
```
-### 3. 使用 CLI 命令列
+最上層為裸列表,或 `{"load_density": [...]}` 包裝。
-```bash
-# 執行單一 JSON 腳本檔案
-python -m je_load_density -e test_scenario.json
+### 內建 `LD_*` 指令
+
+| 群組 | 指令 |
+|------|------|
+| 核心 | `LD_start_test`、`LD_execute_action`、`LD_execute_files`、`LD_add_package_to_executor`、`LD_start_socket_server` |
+| 報告 | `LD_generate_html(_report)`、`LD_generate_json(_report)`、`LD_generate_xml(_report)`、`LD_generate_csv_report`、`LD_generate_junit_report`、`LD_generate_summary_report`、`LD_summary` |
+| 持久化 | `LD_persist_records`、`LD_list_runs`、`LD_fetch_run_records`、`LD_clear_records` |
+| 參數 | `LD_register_variable(s)`、`LD_register_csv_source(s)`、`LD_clear_resolver` |
+| 錄製 | `LD_load_har`、`LD_har_to_tasks`、`LD_har_to_action_json` |
+| 指標 | `LD_start/stop_prometheus_exporter`、`LD_start/stop_influxdb_sink`、`LD_start/stop_opentelemetry_exporter` |
-# 執行目錄中所有 JSON 檔案
-python -m je_load_density -d ./test_scripts/
+安全的 Python builtin(`print`、`len`、`range` 等)也可使用;`eval`、`exec`、`compile`、`__import__`、`breakpoint`、`open`、`input` 已被明確封鎖。
-# 執行內嵌 JSON 字串
-python -m je_load_density --execute_str '[["LD_start_test", {"user_detail_dict": {"user": "fast_http_user"}, "user_count": 10, "spawn_rate": 5, "test_time": 5, "tasks": {"get": {"request_url": "http://httpbin.org/get"}}}]]'
+### 自訂指令
-# 使用模板建立新專案
-python -m je_load_density -c MyProject
+```python
+from je_load_density import add_command_to_executor
+
+def slack_notify(message: str) -> None:
+ ...
+
+add_command_to_executor({"LD_slack_notify": slack_notify})
```
-### 4. 使用 GUI 圖形介面
+## 使用者模板
+
+所有模板皆透過 `start_test` 的 `user_detail_dict={"user": ""}` 註冊;HTTP / WebSocket / gRPC / MQTT / raw socket 共用相同 task 結構,僅協定相關欄位不同。
+
+### HTTP / FastHttp
```python
-from je_load_density.gui.main_window import LoadDensityUI
-from PySide6.QtWidgets import QApplication
-import sys
+start_test(
+ user_detail_dict={"user": "fast_http_user"},
+ user_count=50, spawn_rate=10, test_time=60,
+ variables={"base": "https://api.example.com"},
+ tasks=[
+ {"method": "post", "request_url": "${var.base}/login",
+ "json": {"email": "u@example.com", "password": "secret"},
+ "extract": [{"var": "auth", "from": "json_path", "path": "data.token"}]},
+ {"method": "get", "request_url": "${var.base}/profile",
+ "headers": {"Authorization": "Bearer ${var.auth}"},
+ "assertions": [{"type": "status_code", "value": 200}]},
+ ],
+)
+```
-app = QApplication(sys.argv)
-window = LoadDensityUI()
-window.show()
-sys.exit(app.exec())
+### WebSocket
+
+```python
+start_test(
+ user_detail_dict={"user": "websocket_user"},
+ user_count=10, spawn_rate=5, test_time=60,
+ tasks=[
+ {"method": "connect", "request_url": "wss://echo.example.com/socket"},
+ {"method": "sendrecv", "payload": '{"ping": 1}', "expect": "pong"},
+ {"method": "close"},
+ ],
+)
+```
+
+### gRPC
+
+```python
+start_test(
+ user_detail_dict={"user": "grpc_user"},
+ user_count=20, spawn_rate=5, test_time=60,
+ tasks=[{
+ "name": "say_hello",
+ "target": "localhost:50051",
+ "stub_path": "pkg.greeter_pb2_grpc.GreeterStub",
+ "request_path": "pkg.greeter_pb2.HelloRequest",
+ "method": "SayHello",
+ "payload": {"name": "world"},
+ "metadata": [["x-token", "abc"]],
+ "timeout": 5,
+ }],
+)
```
-## 報告生成
+`stub_path` 與 `request_path` 在 `importlib.import_module` 之前皆通過嚴格識別符 regex 驗證,traversal 攻擊將被拒絕。
-執行測試後,從記錄的資料生成報告:
+### MQTT
```python
-from je_load_density import (
- generate_html_report,
- generate_json_report,
- generate_xml_report,
+start_test(
+ user_detail_dict={"user": "mqtt_user"},
+ user_count=10, spawn_rate=5, test_time=60,
+ tasks=[
+ {"method": "connect", "broker": "127.0.0.1:1883"},
+ {"method": "subscribe", "topic": "telemetry/in", "qos": 1},
+ {"method": "publish", "topic": "telemetry/out", "payload": "ping", "qos": 1},
+ {"method": "disconnect"},
+ ],
)
+```
-# HTML 報告 — 建立 "my_report.html"
-generate_html_report("my_report")
+### 原生 TCP / UDP
-# JSON 報告 — 建立 "my_report_success.json" 和 "my_report_failure.json"
-generate_json_report("my_report")
+僅用標準函式庫,無需安裝。
-# XML 報告 — 建立 "my_report_success.xml" 和 "my_report_failure.xml"
-generate_xml_report("my_report")
+```python
+start_test(
+ user_detail_dict={"user": "socket_user"},
+ user_count=20, spawn_rate=5, test_time=60,
+ tasks=[
+ {"protocol": "tcp", "target": "127.0.0.1:9000",
+ "payload": "PING\n", "expect_bytes": 64,
+ "expect_substring": "PONG"},
+ {"protocol": "udp", "target": "127.0.0.1:9000",
+ "payload": "hex:DEADBEEF", "expect_bytes": 4},
+ ],
+)
```
-## 進階用法
+## 參數解析器
-### 動作執行器
+| 占位符 | 解析為 |
+|--------|--------|
+| `${var.NAME}` | `register_variable(s)` 設定的值 |
+| `${env.NAME}` | 環境變數 `NAME` |
+| `${csv.SOURCE.COL}` | CSV 來源 `SOURCE` 的下一筆(預設循環) |
+| `${faker.METHOD}` | `Faker().METHOD()`(lazy import) |
+| `${uuid()}` | 新 UUID 4 字串 |
+| `${now()}` | 本地 ISO-8601 時間(秒) |
+| `${randint(min, max)}` | 密碼學強度隨機整數 |
-執行器將字串動作名稱映射到可呼叫的函式。所有 Python 內建函式也可使用。
+未知占位符原樣保留,便於 dry run 偵錯。
-```python
-from je_load_density import executor, add_command_to_executor
+## 情境模式
-# 註冊自訂函式
-def my_custom_action(message):
- print(f"自訂動作: {message}")
+```json
+{
+ "mode": "weighted",
+ "tasks": [
+ {"method": "get", "request_url": "/products", "weight": 3},
+ {"method": "get", "request_url": "/expensive", "weight": 1}
+ ]
+}
+```
+
+| 模式 | 行為 |
+|------|------|
+| `sequence` | 依序執行所有 task(預設) |
+| `weighted` | 每 tick 依 `weight` 加權挑一個 |
+| `conditional` | 以 `run_if` / `skip_if` 預測式控制 |
-add_command_to_executor({"my_action": my_custom_action})
+預測式:`bool`、`"${var.x}"`、`{"equals": [a,b]}`、`{"not_equals": [a,b]}`、`{"in": [needle, haystack]}`、`{"truthy": value}`。
-# 程式化執行動作
-executor.execute_action([
- ["my_action", ["Hello World"]],
- ["print", ["測試完成"]],
-])
+## 斷言與擷取
+
+```json
+{
+ "method": "post",
+ "request_url": "${var.base}/login",
+ "json": {"email": "u@example.com", "password": "secret"},
+ "assertions": [
+ {"type": "status_code", "value": 200},
+ {"type": "json_path", "path": "data.role", "value": "admin"}
+ ],
+ "extract": [
+ {"var": "auth_token", "from": "json_path", "path": "data.token"},
+ {"var": "request_id", "from": "header", "name": "X-Request-Id"}
+ ]
+}
```
-**內建執行器動作:**
-| 動作名稱 | 說明 |
-|---|---|
-| `LD_start_test` | 啟動負載測試 |
-| `LD_generate_html` | 生成 HTML 片段 |
-| `LD_generate_html_report` | 生成完整 HTML 報告檔案 |
-| `LD_generate_json` | 生成 JSON 資料結構 |
-| `LD_generate_json_report` | 生成 JSON 報告檔案 |
-| `LD_generate_xml` | 生成 XML 字串 |
-| `LD_generate_xml_report` | 生成 XML 報告檔案 |
-| `LD_execute_action` | 執行動作列表 |
-| `LD_execute_files` | 從多個檔案執行動作 |
-| `LD_add_package_to_executor` | 動態載入套件到執行器 |
+斷言類型:`status_code`、`contains`、`not_contains`、`json_path`、`header`。
+擷取來源:`json_path`、`header`、`status_code`。
+
+## 報告
+
+```python
+from je_load_density import (
+ generate_html_report, generate_json_report, generate_xml_report,
+ generate_csv_report, generate_junit_report, generate_summary_report,
+)
-### 回呼執行器
+generate_html_report("report") # report.html
+generate_json_report("report") # report_success.json + report_failure.json
+generate_xml_report("report") # report_success.xml + report_failure.xml
+generate_csv_report("report") # report.csv
+generate_junit_report("report-junit") # report-junit.xml(CI)
+generate_summary_report("report-sum") # 總計 + per-name p50/p90/p95/p99
+```
-將觸發函式與回呼串聯:
+## 可觀測性
```python
-from je_load_density import callback_executor
+from je_load_density import (
+ start_prometheus_exporter, start_influxdb_sink, start_opentelemetry_exporter,
+)
+
+start_prometheus_exporter(port=9646, addr="127.0.0.1")
+start_influxdb_sink(transport="udp", host="influxdb", port=8089)
+start_opentelemetry_exporter(endpoint="http://otel-collector:4317",
+ service_name="loaddensity")
+```
-def after_test():
- print("測試完成,正在生成報告...")
+| Sink | 指標 |
+|------|------|
+| Prometheus | `loaddensity_requests_total`、`loaddensity_request_latency_ms`、`loaddensity_response_bytes` |
+| InfluxDB | `loaddensity_request` line-protocol(UDP 或 HTTP) |
+| OTel | `loaddensity.requests`、`loaddensity.request.latency`、`loaddensity.response.size` |
-callback_executor.callback_function(
- trigger_function_name="user_test",
- callback_function=after_test,
+三者皆 lazy load,由對應 install extra 控管相依。
+
+## 分散式 Master / Worker
+
+```python
+# master
+start_test(
user_detail_dict={"user": "fast_http_user"},
- user_count=10,
- spawn_rate=5,
- test_time=5,
- tasks={"get": {"request_url": "http://httpbin.org/get"}},
+ runner_mode="master",
+ master_bind_host="0.0.0.0", master_bind_port=5557,
+ expected_workers=4,
+ web_ui_dict={"host": "0.0.0.0", "port": 8089},
+ user_count=400, spawn_rate=40, test_time=600,
+ tasks=[...],
+)
+
+# worker
+start_test(
+ user_detail_dict={"user": "fast_http_user"},
+ runner_mode="worker",
+ master_host="10.0.0.10", master_port=5557,
+ tasks=[...],
)
```
-### TCP Socket 伺服器(遠端執行)
+Master 在開始 ramp 前最多等 60 秒,等待 `expected_workers` 個 worker 加入。
-啟動接收 JSON 指令的 TCP 伺服器:
+## HAR 錄製/重放
```python
-from je_load_density import start_load_density_socket_server
-
-# 啟動伺服器(阻塞式)
-start_load_density_socket_server(host="localhost", port=9940)
+from je_load_density import load_har, har_to_action_json
+
+har = load_har("recording.har")
+action_json = har_to_action_json(
+ har,
+ user="fast_http_user",
+ user_count=20, spawn_rate=10, test_time=120,
+ include=[r"api\.example\.com"],
+ exclude=[r"\.svg$"],
+)
```
-從客戶端發送指令:
+可吃 Chrome / Firefox DevTools、mitmproxy、Charles 等錄製。狀態碼會轉成 `status_code` 斷言。
+
+## 持久化紀錄(SQLite)
```python
-import socket, json
+from je_load_density import persist_records, list_runs, fetch_run_records
+
+run_id = persist_records(
+ "loadtests.db",
+ label="checkout-2026-04-28",
+ metadata={"branch": "dev", "commit": "abc1234"},
+)
+for row in list_runs("loadtests.db", limit=10):
+ print(row)
+```
+
+Schema 採延遲建立。`run_id` 與 `name` 上有索引,跨次查詢快速。
+
+## MCP Server(給 Claude)
-sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-sock.connect(("localhost", 9940))
+```bash
+pip install "je_load_density[mcp]"
+python -m je_load_density.mcp_server
+```
+
+接到 Claude Desktop / Code:
+
+```json
+{
+ "mcpServers": {
+ "loaddensity": {
+ "command": "python",
+ "args": ["-m", "je_load_density.mcp_server"]
+ }
+ }
+}
+```
-command = json.dumps([
- ["LD_start_test", {
- "user_detail_dict": {"user": "fast_http_user"},
- "user_count": 10, "spawn_rate": 5, "test_time": 5,
- "tasks": {"get": {"request_url": "http://httpbin.org/get"}}
- }]
-])
-sock.send(command.encode("utf-8"))
-response = sock.recv(8192)
-print(response.decode("utf-8"))
-sock.close()
+對外開 11 個工具:`run_test`、`run_action_json`、`create_project`、`list_executor_commands`、`import_har`、`generate_reports`、`summary`、`persist_records`、`list_runs`、`fetch_run`、`clear_records`。
+
+## 硬化控制 Socket
+
+```bash
+python -m je_load_density serve \
+ --host 0.0.0.0 --port 9940 --framed \
+ --token "$LOAD_DENSITY_SOCKET_TOKEN" \
+ --tls-cert /etc/loaddensity/server.crt \
+ --tls-key /etc/loaddensity/server.key
```
-發送 `"quit_server"` 可優雅地關閉伺服器。
+- 4-byte big-endian 長度前綴框架(1 MiB 上限)
+- 選用 TLS(`ssl.create_default_context`,TLS 1.2+ minimum)
+- 共享密鑰 token,以 `hmac.compare_digest` 比對;一旦設定,所有 payload 須使用 `{"token": "...", "command": [...]}` 信封,可以 `"op": "quit"` 停機
+- Token 也可由 `LOAD_DENSITY_SOCKET_TOKEN` 環境變數讀取
+- 保留未驗證 legacy 模式以維持相容
-### 專案腳手架
+## GUI
-生成包含關鍵字模板與執行器腳本的專案:
+```bash
+pip install "je_load_density[gui]"
+```
```python
-from je_load_density import create_project_dir
+import sys
+from PySide6.QtWidgets import QApplication
+from je_load_density.gui.main_window import LoadDensityUI
-create_project_dir(project_path="./my_tests", parent_name="LoadDensity")
+app = QApplication(sys.argv)
+window = LoadDensityUI()
+window.show()
+sys.exit(app.exec())
```
-這會建立以下結構:
+GUI 提供英文、繁體中文、日文、韓文翻譯,以及每秒輪詢 `test_record_instance` 的即時統計面板(RPS、平均、p95、失敗)。
+
+## CLI 用法
+
```
-my_tests/
-└── LoadDensity/
- ├── keyword/
- │ ├── keyword1.json # FastHttpUser 測試模板
- │ └── keyword2.json # HttpUser 測試模板
- └── executor/
- ├── executor_one_file.py # 執行單一關鍵字檔案
- └── executor_folder.py # 執行 keyword/ 目錄中所有檔案
+python -m je_load_density run FILE # 執行單一動作 JSON 檔
+python -m je_load_density run-dir DIR # 執行 DIR 下所有 .json
+python -m je_load_density run-str JSON # 執行 inline JSON
+python -m je_load_density init PATH # 建立專案骨架
+python -m je_load_density serve [--host ...] # 啟動控制 socket
```
-### 動態套件載入
+舊式 `-e/-d/-c/--execute_str` 仍接受,相容下游工具。
-載入外部套件並將其函式註冊到執行器中:
+## 測試紀錄
-```python
-from je_load_density import executor
+`test_record_instance.test_record_list` 與 `error_record_list` 收集每筆請求:`Method`、`test_url`、`name`、`status_code`、`response_time_ms`、`response_length`,失敗則含 `error`。報告與 SQLite sink 直接讀取此處。
+
+## 例外處理
-# 載入套件並使其函式可作為執行器動作使用
-executor.execute_action([
- ["LD_add_package_to_executor", ["my_custom_package"]]
-])
+```
+LoadDensityTestException
+├── LoadDensityTestJsonException
+├── LoadDensityGenerateJsonReportException
+├── LoadDensityTestExecuteException
+├── LoadDensityAssertException
+├── LoadDensityHTMLException
+├── LoadDensityAddCommandException
+├── XMLException → XMLTypeException
+└── CallbackExecutorException
```
-### 測試記錄
+所有自訂例外皆繼承 `LoadDensityTestException`;攔該類別即可全面處理。
-以程式化方式存取原始測試記錄:
+## 日誌
-```python
-from je_load_density import test_record_instance
+LoadDensity 提供已配置好的 logger(`load_density_logger`,位於 `je_load_density.utils.logging.loggin_instance`)。以標準 `logging` 模組 API 即可整合既有日誌系統。
-# 執行測試後
-for record in test_record_instance.test_record_list:
- print(record["Method"], record["test_url"], record["status_code"])
+## 支援平台
-for error in test_record_instance.error_record_list:
- print(error["Method"], error["test_url"], error["error"])
+| 平台 | 狀態 |
+|------|------|
+| Windows 10 / 11 | 完整支援 |
+| macOS | 完整支援 |
+| Ubuntu / Linux | 完整支援 |
+| Raspberry Pi | 已測 3B+ 以上 |
-# 清除記錄
-test_record_instance.clear_records()
-```
+需 Python 3.10+。
-## 架構
+## 授權
-```
-je_load_density/
-├── __init__.py # 公開 API 匯出
-├── __main__.py # CLI 進入點
-├── gui/ # PySide6 GUI(選用依賴)
-│ ├── main_window.py # 主視窗(QMainWindow)
-│ ├── main_widget.py # 測試參數表單與日誌面板
-│ ├── load_density_gui_thread.py # 測試背景執行緒
-│ ├── log_to_ui_filter.py # GUI 顯示的日誌攔截器
-│ └── language_wrapper/ # 國際化(英文、繁體中文)
-├── wrapper/
-│ ├── create_locust_env/ # Locust Environment 與 Runner 設定
-│ ├── start_wrapper/ # 高階 start_test() 進入點
-│ ├── user_template/ # HttpUser 與 FastHttpUser 封裝
-│ ├── proxy/ # 使用者代理容器與配置
-│ └── event/ # 請求鉤子(記錄所有請求)
-└── utils/
- ├── executor/ # 動作執行器(事件驅動)
- ├── generate_report/ # HTML、JSON、XML 報告生成器
- ├── test_record/ # 測試記錄儲存
- ├── socket_server/ # 遠端執行 TCP 伺服器
- ├── callback/ # 回呼函式執行器
- ├── project/ # 專案腳手架與模板
- ├── package_manager/ # 動態套件載入
- ├── json/ # JSON 檔案讀寫工具
- ├── xml/ # XML 結構工具
- ├── file_process/ # 目錄檔案列表
- ├── logging/ # Logger 實例
- └── exception/ # 自訂例外與錯誤標籤
-```
-
-## 已測試平台
-
-- Windows 10 / 11
-- macOS 10.15 ~ 11(Big Sur)
-- Ubuntu 20.04
-- Raspberry Pi 3B+
-
-## 授權條款
-
-本專案採用 [MIT 授權條款](../LICENSE)。
-
-## 貢獻指南
-
-請參閱 [CONTRIBUTING.md](../CONTRIBUTING.md) 了解貢獻規範。
-
-## 相關連結
-
-- **PyPI**:https://pypi.org/project/je_load_density/
-- **文件**:https://loaddensity.readthedocs.io/en/latest/
-- **原始碼**:https://github.com/Intergration-Automation-Testing/LoadDensity
+MIT — 見 [LICENSE](../LICENSE)。
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 4170c03..ece0968 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1 +1,2 @@
-sphinx-rtd-theme
\ No newline at end of file
+sphinx-rtd-theme
+sphinxcontrib-mermaid
diff --git a/docs/source/En/doc/action_executor/action_executor_doc.rst b/docs/source/En/doc/action_executor/action_executor_doc.rst
new file mode 100644
index 0000000..8edab93
--- /dev/null
+++ b/docs/source/En/doc/action_executor/action_executor_doc.rst
@@ -0,0 +1,176 @@
+Action Executor
+===============
+
+Overview
+--------
+
+The action executor maps command strings to callable functions. Action
+scripts are JSON lists, so the same script can be hand-authored,
+generated by HAR import, scheduled by an MCP tool, or sent over the
+control socket.
+
+Every shipped command starts with the ``LD_`` prefix; safe Python
+built-ins (``print``, ``len``, ``range``…) are also available, but
+``eval``, ``exec``, ``compile``, ``__import__``, ``breakpoint``,
+``open``, and ``input`` are explicitly blocked.
+
+Action format
+-------------
+
+.. code-block:: python
+
+ ["command_name"] # No parameters
+ ["command_name", {"key": "value"}] # Keyword arguments
+ ["command_name", [arg1, arg2]] # Positional arguments
+
+The top-level document is either:
+
+.. code-block:: json
+
+ {"load_density": [["LD_start_test", {...}], ...]}
+
+or a bare list of actions.
+
+Quick example
+-------------
+
+.. code-block:: python
+
+ from je_load_density import execute_action
+
+ execute_action({"load_density": [
+ ["LD_register_variables", {"variables": {"base": "https://api.example.com"}}],
+ ["LD_start_test", {
+ "user_detail_dict": {"user": "fast_http_user"},
+ "user_count": 20,
+ "spawn_rate": 10,
+ "test_time": 30,
+ "tasks": [{"method": "get", "request_url": "${var.base}/health"}],
+ }],
+ ["LD_generate_summary_report", {"report_name": "smoke"}],
+ ]})
+
+LD_* commands
+-------------
+
+The executor exposes the following commands. Each is implemented in the
+matching module under ``je_load_density``.
+
+**Core:**
+
+.. list-table::
+ :header-rows: 1
+ :widths: 35 65
+
+ * - Command
+ - Summary
+ * - ``LD_start_test``
+ - Run a Locust load test (HTTP / FastHttp / WebSocket / gRPC /
+ MQTT / Socket).
+ * - ``LD_execute_action``
+ - Execute a nested action list.
+ * - ``LD_execute_files``
+ - Execute every action JSON file in a list.
+ * - ``LD_add_package_to_executor``
+ - Register a Python package's functions into the executor.
+ * - ``LD_start_socket_server``
+ - Start the hardened TCP control plane.
+
+**Reports:**
+
+.. list-table::
+ :header-rows: 1
+ :widths: 35 65
+
+ * - Command
+ - Summary
+ * - ``LD_generate_html`` / ``LD_generate_html_report``
+ - HTML report generators.
+ * - ``LD_generate_json`` / ``LD_generate_json_report``
+ - JSON report generators.
+ * - ``LD_generate_xml`` / ``LD_generate_xml_report``
+ - XML report generators.
+ * - ``LD_generate_csv_report``
+ - One-row-per-request CSV export.
+ * - ``LD_generate_junit_report``
+ - JUnit XML for CI consumers.
+ * - ``LD_generate_summary_report``
+ - JSON summary with per-name p50/p90/p95/p99 latencies.
+ * - ``LD_summary``
+ - In-memory dict of the same summary.
+
+**Test record persistence:**
+
+.. list-table::
+ :header-rows: 1
+ :widths: 35 65
+
+ * - Command
+ - Summary
+ * - ``LD_persist_records``
+ - Save the in-memory records to a SQLite database.
+ * - ``LD_list_runs``
+ - List recent runs in a database.
+ * - ``LD_fetch_run_records``
+ - Load every record for one run.
+ * - ``LD_clear_records``
+ - Drop the in-memory record list.
+
+**Parameter resolver:**
+
+.. list-table::
+ :header-rows: 1
+ :widths: 35 65
+
+ * - Command
+ - Summary
+ * - ``LD_register_variable`` / ``LD_register_variables``
+ - Register one or many ``${var.x}`` values.
+ * - ``LD_register_csv_source`` / ``LD_register_csv_sources``
+ - Bind a CSV file to a ``${csv.name.col}`` source.
+ * - ``LD_clear_resolver``
+ - Reset every registered variable / source.
+
+**Recording / replay:**
+
+.. list-table::
+ :header-rows: 1
+ :widths: 35 65
+
+ * - Command
+ - Summary
+ * - ``LD_load_har``
+ - Read a HAR JSON file from disk.
+ * - ``LD_har_to_tasks``
+ - Convert a HAR document into a list of LoadDensity tasks.
+ * - ``LD_har_to_action_json``
+ - Convert a HAR document into a runnable action JSON.
+
+**Metrics exporters:**
+
+.. list-table::
+ :header-rows: 1
+ :widths: 35 65
+
+ * - Command
+ - Summary
+ * - ``LD_start_prometheus_exporter`` / ``LD_stop_prometheus_exporter``
+ - Toggle the Prometheus HTTP endpoint.
+ * - ``LD_start_influxdb_sink`` / ``LD_stop_influxdb_sink``
+ - Toggle the InfluxDB UDP / HTTP sink.
+ * - ``LD_start_opentelemetry_exporter`` / ``LD_stop_opentelemetry_exporter``
+ - Toggle the OTLP gRPC exporter.
+
+Adding custom commands
+----------------------
+
+.. code-block:: python
+
+ from je_load_density import add_command_to_executor
+
+ def slack_notify(message: str) -> None:
+ ...
+
+ add_command_to_executor({"LD_slack_notify": slack_notify})
+
+Once registered, the new command is callable from any action JSON.
diff --git a/docs/source/En/doc/api_reference/api_reference.rst b/docs/source/En/doc/api_reference/api_reference.rst
new file mode 100644
index 0000000..8e9d0c6
--- /dev/null
+++ b/docs/source/En/doc/api_reference/api_reference.rst
@@ -0,0 +1,37 @@
+API Reference
+=============
+
+The auto-generated Python reference is regenerated by Sphinx
+``autosummary`` on every build.
+
+.. autosummary::
+ :toctree: _autosummary
+ :recursive:
+
+ je_load_density
+ je_load_density.utils.executor.action_executor
+ je_load_density.utils.parameterization.parameter_resolver
+ je_load_density.utils.recording.har_importer
+ je_load_density.utils.metrics.prometheus_exporter
+ je_load_density.utils.metrics.influxdb_sink
+ je_load_density.utils.metrics.opentelemetry_exporter
+ je_load_density.utils.test_record.test_record_class
+ je_load_density.utils.test_record.sqlite_persistence
+ je_load_density.utils.generate_report.generate_html_report
+ je_load_density.utils.generate_report.generate_json_report
+ je_load_density.utils.generate_report.generate_xml_report
+ je_load_density.utils.generate_report.generate_csv_report
+ je_load_density.utils.generate_report.generate_junit_report
+ je_load_density.utils.generate_report.generate_summary_report
+ je_load_density.utils.socket_server.load_density_socket_server
+ je_load_density.wrapper.create_locust_env.create_locust_env
+ je_load_density.wrapper.start_wrapper.start_test
+ je_load_density.wrapper.user_template.request_executor
+ je_load_density.wrapper.user_template.scenario_runner
+ je_load_density.wrapper.user_template.http_user_template
+ je_load_density.wrapper.user_template.fast_http_user_template
+ je_load_density.wrapper.user_template.websocket_user_template
+ je_load_density.wrapper.user_template.grpc_user_template
+ je_load_density.wrapper.user_template.mqtt_user_template
+ je_load_density.wrapper.user_template.socket_user_template
+ je_load_density.mcp_server.server
diff --git a/docs/source/En/doc/architecture/architecture_doc.rst b/docs/source/En/doc/architecture/architecture_doc.rst
new file mode 100644
index 0000000..7f4ee0e
--- /dev/null
+++ b/docs/source/En/doc/architecture/architecture_doc.rst
@@ -0,0 +1,95 @@
+Architecture
+============
+
+Overview
+--------
+
+LoadDensity is a thin facade over Locust that adds a JSON-driven action
+executor, a multi-protocol user template registry, scenario flow, data
+parameterisation, observability sinks, and an MCP control surface.
+
+The dependency direction always points from the action layer down to
+Locust, never the other way around — your action JSON defines what to
+do, the executor maps each command to a Python callable, and Locust
+runs the resulting load.
+
+Layered view
+------------
+
+.. mermaid::
+
+ flowchart TB
+ cli[CLI / MCP / GUI / Socket Server] --> exec[Action Executor]
+ exec --> start[start_test]
+ start --> proxy[locust_wrapper_proxy]
+ proxy --> userhttp[HTTP / FastHttp Wrapper]
+ proxy --> userws[WebSocket Wrapper]
+ proxy --> usergrpc[gRPC Wrapper]
+ proxy --> usermqtt[MQTT Wrapper]
+ proxy --> usersock[Raw TCP/UDP Wrapper]
+ userhttp & userws & usergrpc & usermqtt & usersock --> hooks[Locust events]
+ hooks --> records[test_record_instance]
+ hooks --> exporters[Prometheus / Influx / OTel]
+ records --> reports[HTML / JSON / XML / CSV / JUnit / Summary]
+ records --> sqlite[SQLite persistence]
+
+Module map
+----------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 35 65
+
+ * - Module
+ - Purpose
+ * - ``je_load_density.utils.executor``
+ - ``Executor`` class, dispatch table, ``execute_action`` /
+ ``execute_files`` entrypoints.
+ * - ``je_load_density.utils.parameterization``
+ - ``ParameterResolver`` for ``${var.x}`` / ``${env.X}`` /
+ ``${csv.s.col}`` / ``${faker.method}`` / built-in helpers.
+ * - ``je_load_density.utils.recording``
+ - HAR ingestion → action JSON.
+ * - ``je_load_density.utils.metrics``
+ - Prometheus exporter, InfluxDB sink, OpenTelemetry exporter.
+ * - ``je_load_density.utils.test_record``
+ - In-memory record list plus optional SQLite sink.
+ * - ``je_load_density.utils.generate_report``
+ - HTML / JSON / XML / CSV / JUnit / summary generators.
+ * - ``je_load_density.utils.socket_server``
+ - Length-framed TCP control plane with optional TLS and token.
+ * - ``je_load_density.wrapper.proxy``
+ - Per-protocol proxy holding the configured tasks for each user
+ template.
+ * - ``je_load_density.wrapper.user_template``
+ - Locust user classes for HTTP, FastHttp, WebSocket, gRPC, MQTT,
+ and raw socket.
+ * - ``je_load_density.wrapper.start_wrapper``
+ - ``start_test`` dispatcher that picks a user template and forwards
+ to ``prepare_env``.
+ * - ``je_load_density.wrapper.create_locust_env``
+ - ``prepare_env`` / ``create_env`` building a Locust environment in
+ local, master, or worker mode.
+ * - ``je_load_density.mcp_server``
+ - MCP server exposing 11 tools so Claude can drive LoadDensity.
+ * - ``je_load_density.gui``
+ - Optional PySide6 widgets (form controls + live stats panel).
+
+Action lifecycle
+----------------
+
+#. Caller submits an action JSON via the CLI, MCP tool, socket server,
+ or direct ``execute_action(...)`` call.
+#. ``Executor.execute_action`` dispatches each step against
+ ``event_dict`` (``LD_*`` commands plus safe builtins).
+#. When the step is ``LD_start_test``, the dispatcher selects a user
+ template (``http_user``, ``fast_http_user``, ``websocket_user``,
+ ``grpc_user``, ``mqtt_user``, ``socket_user``), seeds the parameter
+ resolver from any ``variables`` / ``csv_sources``, and calls
+ ``prepare_env``.
+#. ``prepare_env`` builds a Locust ``Environment`` in local, master, or
+ worker mode and starts the run.
+#. Each user runs ``run_scenario`` (or the protocol equivalent) per
+ tick, fires Locust events, and feeds ``test_record_instance``.
+#. Reports, metrics exporters, and SQLite persistence consume the
+ accumulated records.
diff --git a/docs/source/En/doc/assertions/assertions_doc.rst b/docs/source/En/doc/assertions/assertions_doc.rst
new file mode 100644
index 0000000..660281a
--- /dev/null
+++ b/docs/source/En/doc/assertions/assertions_doc.rst
@@ -0,0 +1,76 @@
+Assertions & Extractors
+=======================
+
+Overview
+--------
+
+HTTP and FastHttp tasks accept ``assertions`` and ``extract`` blocks
+that run under Locust's ``catch_response``. Failed assertions mark the
+request as a Locust failure and surface in every report.
+
+Assertions
+----------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 75
+
+ * - ``type``
+ - Behaviour
+ * - ``status_code``
+ - ``int(response.status_code) == int(value)``.
+ * - ``contains``
+ - ``str(value) in response.text``.
+ * - ``not_contains``
+ - ``str(value) not in response.text``.
+ * - ``json_path``
+ - Resolves ``response.json()`` along ``path`` (dot-separated; list
+ indices supported) and compares to ``value``.
+ * - ``header``
+ - ``response.headers[name] == value``.
+
+Example
+~~~~~~~
+
+.. code-block:: json
+
+ {
+ "method": "get",
+ "request_url": "${var.base}/health",
+ "assertions": [
+ {"type": "status_code", "value": 200},
+ {"type": "json_path", "path": "status", "value": "ok"},
+ {"type": "header", "name": "X-Service", "value": "checkout"}
+ ]
+ }
+
+Extractors
+----------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 75
+
+ * - ``from``
+ - Source
+ * - ``json_path``
+ - Same dotted path syntax as the ``json_path`` assertion.
+ * - ``header``
+ - ``response.headers[name]``.
+ * - ``status_code``
+ - ``response.status_code``.
+
+Extracted values are written into the parameter resolver under the
+chosen ``var`` name; subsequent tasks reference them as ``${var.NAME}``.
+
+.. code-block:: json
+
+ {
+ "method": "post",
+ "request_url": "${var.base}/login",
+ "json": {"email": "u@example.com", "password": "secret"},
+ "extract": [
+ {"var": "auth_token", "from": "json_path", "path": "data.token"},
+ {"var": "request_id", "from": "header", "name": "X-Request-Id"}
+ ]
+ }
diff --git a/docs/source/En/doc/cli/cli_doc.rst b/docs/source/En/doc/cli/cli_doc.rst
index ba7af9d..8ff6d3e 100644
--- a/docs/source/En/doc/cli/cli_doc.rst
+++ b/docs/source/En/doc/cli/cli_doc.rst
@@ -1,106 +1,86 @@
CLI (Command Line Interface)
============================
-LoadDensity provides a full command-line interface via ``python -m je_load_density``.
+LoadDensity ships a subcommand-style CLI. Run
+``python -m je_load_density --help`` for the full surface.
-CLI Arguments
--------------
+Subcommands
+-----------
.. list-table::
:header-rows: 1
- :widths: 25 10 65
-
- * - Argument
- - Short
- - Description
- * - ``--execute_file``
- - ``-e``
- - Execute a single JSON script file
- * - ``--execute_dir``
- - ``-d``
- - Execute all JSON files in a directory
- * - ``--execute_str``
- - —
- - Execute an inline JSON string
- * - ``--create_project``
- - ``-c``
- - Scaffold a new project with templates
-
-Execute a Single JSON File
---------------------------
-
-Run a test defined in a single JSON keyword file:
+ :widths: 25 75
+
+ * - Subcommand
+ - Purpose
+ * - ``run FILE``
+ - Execute one action JSON file.
+ * - ``run-dir DIR``
+ - Execute every ``.json`` in a directory.
+ * - ``run-str JSON``
+ - Execute an inline JSON string (Windows double-encoding handled
+ transparently).
+ * - ``init PATH``
+ - Scaffold a new project skeleton.
+ * - ``serve``
+ - Start the hardened TCP control socket server.
+
+``run``
+-------
.. code-block:: bash
- python -m je_load_density -e test_scenario.json
+ python -m je_load_density run smoke.json
-The JSON file should follow the action list format:
+Where ``smoke.json`` is::
-.. code-block:: json
+ {"load_density": [
+ ["LD_start_test", {
+ "user_detail_dict": {"user": "fast_http_user"},
+ "user_count": 20, "spawn_rate": 10, "test_time": 30,
+ "tasks": [{"method": "get", "request_url": "https://httpbin.org/get"}]
+ }],
+ ["LD_generate_summary_report", {"report_name": "smoke"}]
+ ]}
- [
- ["LD_start_test", {
- "user_detail_dict": {"user": "fast_http_user"},
- "user_count": 50,
- "spawn_rate": 10,
- "test_time": 5,
- "tasks": {
- "get": {"request_url": "http://httpbin.org/get"},
- "post": {"request_url": "http://httpbin.org/post"}
- }
- }]
- ]
+``run-dir``
+-----------
-Execute All JSON Files in a Directory
--------------------------------------
+Run every ``.json`` action file in a directory tree::
-Run all JSON keyword files in a specified directory recursively:
+ python -m je_load_density run-dir ./scenarios
-.. code-block:: bash
-
- python -m je_load_density -d ./test_scripts/
-
-This scans the directory for all ``.json`` files and executes each one sequentially.
-
-Execute an Inline JSON String
------------------------------
+``run-str``
+-----------
-Execute a JSON action list directly as a string:
+Inline JSON (handy for CI scripts)::
-.. code-block:: bash
+ python -m je_load_density run-str '{"load_density":[["LD_summary",{}]]}'
- python -m je_load_density --execute_str '[["LD_start_test", {"user_detail_dict": {"user": "fast_http_user"}, "user_count": 10, "spawn_rate": 5, "test_time": 5, "tasks": {"get": {"request_url": "http://httpbin.org/get"}}}]]'
+``init``
+--------
-.. note::
+Scaffold a project at PATH::
- On **Windows**, inline JSON strings are automatically double-parsed due to shell
- escaping differences. The CLI handles this transparently.
+ python -m je_load_density init ./my_load_test
-Create a Project
-----------------
+``serve``
+---------
-Scaffold a new project with keyword templates and executor scripts:
+Start the control socket server. See
+:doc:`../socket_server/socket_server_doc` for protocol details.
.. code-block:: bash
- python -m je_load_density -c MyProject
-
-This generates a project directory structure:
-
-.. code-block:: text
-
- MyProject/
- └── LoadDensity/
- ├── keyword/
- │ ├── keyword1.json
- │ └── keyword2.json
- └── executor/
- ├── executor_one_file.py
- └── executor_folder.py
+ python -m je_load_density serve \
+ --host 0.0.0.0 --port 9940 \
+ --framed --token "$LOAD_DENSITY_SOCKET_TOKEN" \
+ --tls-cert /etc/loaddensity/server.crt \
+ --tls-key /etc/loaddensity/server.key
-Error Handling
---------------
+Legacy flags
+------------
-If no valid argument is provided, the CLI raises a ``LoadDensityTestExecuteException``
-and exits with code 1. All errors are printed to stderr.
+The flat ``-e/-d/-c/--execute_str`` flags from previous releases are
+still accepted (suppressed in ``--help``) for backwards compatibility
+with tools such as PyBreeze. New scripts should use the subcommands.
diff --git a/docs/source/En/doc/create_project/create_project_doc.rst b/docs/source/En/doc/create_project/create_project_doc.rst
new file mode 100644
index 0000000..d36200a
--- /dev/null
+++ b/docs/source/En/doc/create_project/create_project_doc.rst
@@ -0,0 +1,42 @@
+Create Project
+==============
+
+Overview
+--------
+
+``create_project_dir`` (CLI: ``je_load_density init``) scaffolds a
+LoadDensity project skeleton at a chosen path. The skeleton contains
+a sample action JSON, a runner script, and a placeholder for assets.
+
+Python API
+----------
+
+.. code-block:: python
+
+ from je_load_density import create_project_dir
+ create_project_dir("./my_load_test")
+
+CLI
+---
+
+.. code-block:: bash
+
+ python -m je_load_density init ./my_load_test
+
+Layout
+------
+
+::
+
+ my_load_test/
+ ├── run.py # tiny runner that reads the action JSON
+ └── action.json # sample action JSON
+
+After scaffolding, edit ``action.json`` (see
+:doc:`../action_executor/action_executor_doc`) and run::
+
+ python run.py
+
+or::
+
+ python -m je_load_density run action.json
diff --git a/docs/source/En/doc/distributed/distributed_doc.rst b/docs/source/En/doc/distributed/distributed_doc.rst
new file mode 100644
index 0000000..da8edc5
--- /dev/null
+++ b/docs/source/En/doc/distributed/distributed_doc.rst
@@ -0,0 +1,68 @@
+Distributed Master / Worker
+===========================
+
+Overview
+--------
+
+LoadDensity exposes Locust's distributed runner via a ``runner_mode``
+parameter on ``start_test`` / ``prepare_env``. Three modes are
+supported:
+
+* ``local`` — single process (default).
+* ``master`` — coordinates a cluster of workers, optionally serves the
+ Locust Web UI.
+* ``worker`` — joins a master and runs the requested user count.
+
+Master
+------
+
+.. code-block:: python
+
+ from je_load_density import start_test
+
+ start_test(
+ user_detail_dict={"user": "fast_http_user"},
+ runner_mode="master",
+ master_bind_host="0.0.0.0",
+ master_bind_port=5557,
+ expected_workers=4, # wait for 4 workers
+ web_ui_dict={"host": "0.0.0.0", "port": 8089},
+ user_count=400,
+ spawn_rate=40,
+ test_time=600,
+ tasks=[...],
+ )
+
+The master waits up to 60 s for ``expected_workers`` workers to join
+before starting the load ramp. If only N workers (N < expected) join,
+it logs a warning and starts anyway.
+
+Worker
+------
+
+Run on each load-generating node:
+
+.. code-block:: python
+
+ start_test(
+ user_detail_dict={"user": "fast_http_user"},
+ runner_mode="worker",
+ master_host="10.0.0.10",
+ master_port=5557,
+ tasks=[...],
+ )
+
+Workers do not start a Web UI and skip the local stats greenlets — the
+master collects and publishes aggregate stats.
+
+Tips
+----
+
+* Open the master ``master_bind_port`` in your firewall. Default
+ Locust port is ``5557``.
+* Use ``master_bind_host="0.0.0.0"`` only when the master is reachable
+ by the workers; bind to a private interface IP otherwise.
+* Match the user template (``http_user`` / ``fast_http_user`` / ...)
+ on master and workers — the master broadcasts the user class name.
+* If you parameterise tasks with ``${csv.X.col}``, register the same
+ CSV files on every worker (they don't share state).
diff --git a/docs/source/En/doc/exception/exception_doc.rst b/docs/source/En/doc/exception/exception_doc.rst
new file mode 100644
index 0000000..7354416
--- /dev/null
+++ b/docs/source/En/doc/exception/exception_doc.rst
@@ -0,0 +1,42 @@
+Exceptions
+==========
+
+Hierarchy
+---------
+
+::
+
+ Exception
+ └── LocustNotFoundException
+ └── LoadDensityTestException
+ ├── LoadDensityTestJsonException
+ ├── LoadDensityGenerateJsonReportException
+ ├── LoadDensityTestExecuteException
+ ├── LoadDensityAssertException
+ ├── LoadDensityHTMLException
+ ├── LoadDensityAddCommandException
+ ├── XMLException
+ │ └── XMLTypeException
+ └── CallbackExecutorException
+
+When to catch what
+------------------
+
+* ``LoadDensityTestExecuteException`` — Action JSON shape is wrong, or
+ an unknown command was referenced. Catch this to surface user-input
+ errors without crashing on internal exceptions.
+* ``LoadDensityHTMLException`` /
+ ``LoadDensityGenerateJsonReportException`` — Report generation ran
+ with no records (in-memory store empty).
+* ``LoadDensityAssertException`` — Reserved for future use by the
+ assertions layer; HTTP assertions today fail the request via Locust
+ rather than raise.
+* ``XMLException`` / ``XMLTypeException`` — Malformed XML or unexpected
+ payload shape in the XML utilities.
+* ``CallbackExecutorException`` — The callback executor was given an
+ invalid trigger or function reference.
+* ``LoadDensityAddCommandException`` — ``add_command_to_executor`` was
+ passed a non-callable.
+
+All custom exceptions inherit from ``LoadDensityTestException``, so
+catching that one class is enough for blanket error handling.
diff --git a/docs/source/En/doc/generate_report/generate_report_doc.rst b/docs/source/En/doc/generate_report/generate_report_doc.rst
index 9e723b5..27f0d14 100644
--- a/docs/source/En/doc/generate_report/generate_report_doc.rst
+++ b/docs/source/En/doc/generate_report/generate_report_doc.rst
@@ -1,162 +1,91 @@
Report Generation
=================
-LoadDensity can generate test reports in three formats: **HTML**, **JSON**, and **XML**.
-Reports are generated from the test records collected by the request hook during test execution.
+Overview
+--------
-.. note::
+LoadDensity can render six report formats from
+``test_record_instance``: HTML, JSON, XML, CSV, JUnit XML, and a
+percentile-summary JSON.
- Reports can only be generated after a test has been run. If no test records exist,
- a ``LoadDensityHTMLException`` or ``LoadDensityGenerateJsonReportException`` will be raised.
+.. note::
-HTML Report
------------
+ Reports require at least one record; calling a generator on an
+ empty store raises ``LoadDensityHTMLException`` /
+ ``LoadDensityGenerateJsonReportException``.
-Generates a styled HTML file with tables showing success and failure records.
+HTML
+----
.. code-block:: python
from je_load_density import generate_html_report
+ generate_html_report("my_report") # writes my_report.html
- # Generates "my_report.html"
- generate_html_report("my_report")
-
-The HTML report includes:
-
-* **Success records** — displayed in tables with aqua-colored headers, showing Method, URL,
- name, status_code, response text, content, and headers
-* **Failure records** — displayed in tables with red-colored headers, showing Method, URL,
- name, status_code, and error message
-
-To get raw HTML fragments without writing to a file:
-
-.. code-block:: python
-
- from je_load_density import generate_html
-
- success_fragments, failure_fragments = generate_html()
- # success_fragments: List[str] — HTML table strings for each success record
- # failure_fragments: List[str] — HTML table strings for each failure record
-
-JSON Report
------------
-
-Generates structured JSON files for programmatic consumption.
+JSON (split by outcome)
+-----------------------
.. code-block:: python
from je_load_density import generate_json_report
-
- # Generates "my_report_success.json" and "my_report_failure.json"
success_path, failure_path = generate_json_report("my_report")
-**Success JSON format:**
+XML (split by outcome)
+----------------------
-.. code-block:: json
-
- {
- "Success_Test1": {
- "Method": "GET",
- "test_url": "http://httpbin.org/get",
- "name": "/get",
- "status_code": "200",
- "text": "...",
- "content": "...",
- "headers": "..."
- },
- "Success_Test2": {}
- }
-
-**Failure JSON format:**
-
-.. code-block:: json
+.. code-block:: python
- {
- "Failure_Test1": {
- "Method": "POST",
- "test_url": "http://httpbin.org/status/500",
- "name": "/status/500",
- "status_code": "500",
- "error": "..."
- }
- }
+ from je_load_density import generate_xml_report
+ success_path, failure_path = generate_xml_report("my_report")
-To get raw JSON data structures without writing to a file:
+CSV (one row per request)
+-------------------------
.. code-block:: python
- from je_load_density import generate_json
-
- success_dict, failure_dict = generate_json()
+ from je_load_density import generate_csv_report
+ generate_csv_report("my_report") # writes my_report.csv
-XML Report
-----------
+Columns: ``outcome, Method, test_url, name, status_code,
+response_time_ms, response_length, error``.
-Generates XML files for CI/CD integration.
+JUnit XML (CI-friendly)
+-----------------------
.. code-block:: python
- from je_load_density import generate_xml_report
-
- # Generates "my_report_success.xml" and "my_report_failure.xml"
- success_path, failure_path = generate_xml_report("my_report")
+ from je_load_density import generate_junit_report
+ generate_junit_report("loaddensity-junit") # writes loaddensity-junit.xml
-The XML output is pretty-printed using ``xml.dom.minidom``. Each test record is wrapped
-under an ```` root element.
+Each request becomes a ````; failures attach ````
+nodes carrying the error message. Compatible with Jenkins, GitHub
+Actions test annotations, GitLab, etc.
-To get raw XML strings without writing to a file:
+Summary (percentiles)
+---------------------
.. code-block:: python
- from je_load_density import generate_xml
+ from je_load_density import generate_summary_report, build_summary
- success_xml_str, failure_xml_str = generate_xml()
+ summary = build_summary() # in-memory dict
+ generate_summary_report("loaddensity-summary")
-Using in JSON Scripts
----------------------
+The summary contains totals, per-name counts, min / max / mean /
+percentile (p50 / p90 / p95 / p99) latencies, and an overall block.
+Useful for charting and regression checks across runs.
+
+Action JSON
+-----------
-Report generation can be chained with test execution in JSON scripts:
-
-.. code-block:: json
-
- [
- ["LD_start_test", {
- "user_detail_dict": {"user": "fast_http_user"},
- "user_count": 10,
- "spawn_rate": 5,
- "test_time": 5,
- "tasks": {"get": {"request_url": "http://httpbin.org/get"}}
- }],
- ["LD_generate_html_report", {"html_name": "report"}],
- ["LD_generate_json_report", {"json_file_name": "report"}],
- ["LD_generate_xml_report", {"xml_file_name": "report"}]
- ]
-
-Report Functions Summary
-------------------------
-
-.. list-table::
- :header-rows: 1
- :widths: 35 25 40
-
- * - Function
- - Returns
- - Description
- * - ``generate_html()``
- - ``Tuple[List[str], List[str]]``
- - HTML fragments for success and failure records
- * - ``generate_html_report(html_name)``
- - ``str``
- - Write HTML report file, returns file path
- * - ``generate_json()``
- - ``Tuple[Dict, Dict]``
- - JSON dicts for success and failure records
- * - ``generate_json_report(json_file_name)``
- - ``Tuple[str, str]``
- - Write JSON report files, returns paths
- * - ``generate_xml()``
- - ``Tuple[str, str]``
- - XML strings for success and failure records
- * - ``generate_xml_report(xml_file_name)``
- - ``Tuple[str, str]``
- - Write XML report files, returns paths
+Chain reports into a test::
+
+ {"load_density": [
+ ["LD_start_test", {...}],
+ ["LD_generate_html_report", {"html_name": "report"}],
+ ["LD_generate_json_report", {"json_file_name": "report"}],
+ ["LD_generate_xml_report", {"xml_file_name": "report"}],
+ ["LD_generate_csv_report", {"csv_name": "report"}],
+ ["LD_generate_junit_report", {"report_name": "report-junit"}],
+ ["LD_generate_summary_report",{"report_name": "report-summary"}]
+ ]}
diff --git a/docs/source/En/doc/getting_started/getting_started_doc.rst b/docs/source/En/doc/getting_started/getting_started_doc.rst
index f30bcc8..4f8b1d9 100644
--- a/docs/source/En/doc/getting_started/getting_started_doc.rst
+++ b/docs/source/En/doc/getting_started/getting_started_doc.rst
@@ -1,240 +1,100 @@
Getting Started
===============
-This guide walks you through the basics of using LoadDensity to run your first load test.
+This guide walks you through the basics of running your first
+LoadDensity load test.
-User Types
+User types
----------
-LoadDensity supports two types of Locust users:
+LoadDensity ships six user templates:
-.. list-table::
- :header-rows: 1
- :widths: 25 25 50
+* ``fast_http_user`` — high-throughput HTTP (``locust.FastHttpUser`` +
+ geventhttpclient).
+* ``http_user`` — ``locust.HttpUser`` + ``requests``.
+* ``websocket_user``, ``grpc_user``, ``mqtt_user``, ``socket_user`` —
+ see Chapter 4.
- * - User Type Key
- - Locust Class
- - Description
- * - ``fast_http_user``
- - ``FastHttpUser``
- - Uses ``geventhttpclient`` for higher throughput. Recommended for most use cases.
- * - ``http_user``
- - ``HttpUser``
- - Uses Python ``requests`` library. Better compatibility, lower throughput.
-
-Supported HTTP Methods
-----------------------
-
-LoadDensity supports the following HTTP methods:
-
-* ``get``
-* ``post``
-* ``put``
-* ``patch``
-* ``delete``
-* ``head``
-* ``options``
-
-Running a Test with Python API
-------------------------------
-
-The simplest way to run a load test is to call ``start_test()``:
+Run a test (Python API)
+-----------------------
.. code-block:: python
from je_load_density import start_test
- result = start_test(
+ start_test(
user_detail_dict={"user": "fast_http_user"},
user_count=50,
spawn_rate=10,
- test_time=10,
- tasks={
- "get": {"request_url": "http://httpbin.org/get"},
- "post": {"request_url": "http://httpbin.org/post"},
- }
+ test_time=30,
+ variables={"base": "https://httpbin.org"},
+ tasks=[
+ {"method": "get", "request_url": "${var.base}/get"},
+ {"method": "post", "request_url": "${var.base}/post",
+ "json": {"hello": "world"},
+ "assertions": [{"type": "status_code", "value": 200}]},
+ ],
)
-``start_test()`` Parameters
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. list-table::
- :header-rows: 1
- :widths: 20 15 10 55
-
- * - Parameter
- - Type
- - Default
- - Description
- * - ``user_detail_dict``
- - ``dict``
- - (required)
- - User type configuration. ``{"user": "fast_http_user"}`` or ``{"user": "http_user"}``
- * - ``user_count``
- - ``int``
- - ``50``
- - Total number of simulated users to spawn
- * - ``spawn_rate``
- - ``int``
- - ``10``
- - Number of users spawned per second
- * - ``test_time``
- - ``int`` or ``None``
- - ``60``
- - Test duration in seconds. Pass ``None`` for unlimited duration
- * - ``web_ui_dict``
- - ``dict`` or ``None``
- - ``None``
- - Enable Locust Web UI. e.g. ``{"host": "127.0.0.1", "port": 8089}``
-
-Return Value
-~~~~~~~~~~~~
-
-``start_test()`` returns a dictionary summarizing the test configuration:
-
-.. code-block:: python
-
- {
- "user_detail": {"user": "fast_http_user"},
- "user_count": 50,
- "spawn_rate": 10,
- "test_time": 10,
- "web_ui": None,
- }
-
-Enabling the Locust Web UI
---------------------------
-
-To monitor the test in real-time through the Locust Web UI:
+Launch the Locust Web UI
+------------------------
.. code-block:: python
- from je_load_density import start_test
-
- result = start_test(
- user_detail_dict={"user": "http_user"},
- user_count=100,
- spawn_rate=20,
- test_time=30,
+ start_test(
+ user_detail_dict={"user": "fast_http_user"},
+ user_count=50, spawn_rate=10, test_time=30,
web_ui_dict={"host": "127.0.0.1", "port": 8089},
- tasks={
- "get": {"request_url": "http://httpbin.org/get"},
- }
+ tasks=[{"method": "get", "request_url": "https://httpbin.org/get"}],
)
-Then open ``http://127.0.0.1:8089`` in your browser to view real-time statistics.
-
-Running a Test with JSON Script Files
--------------------------------------
+Then open ``http://127.0.0.1:8089`` in your browser.
-You can define test scenarios as JSON files and execute them without writing Python code.
+Run a JSON action script
+------------------------
-Create a ``test_scenario.json`` file:
+Create ``test_scenario.json``:
.. code-block:: json
- [
- ["LD_start_test", {
- "user_detail_dict": {"user": "fast_http_user"},
- "user_count": 50,
- "spawn_rate": 10,
- "test_time": 5,
- "tasks": {
- "get": {"request_url": "http://httpbin.org/get"},
- "post": {"request_url": "http://httpbin.org/post"}
- }
- }]
- ]
-
-Execute from Python:
+ {"load_density": [
+ ["LD_start_test", {
+ "user_detail_dict": {"user": "fast_http_user"},
+ "user_count": 20, "spawn_rate": 10, "test_time": 30,
+ "tasks": [{"method": "get", "request_url": "https://httpbin.org/get"}]
+ }],
+ ["LD_generate_summary_report", {"report_name": "smoke"}]
+ ]}
-.. code-block:: python
+Execute via the CLI::
- from je_load_density import execute_action, read_action_json
+ python -m je_load_density run test_scenario.json
- execute_action(read_action_json("test_scenario.json"))
-
-JSON Script Format
-~~~~~~~~~~~~~~~~~~
-
-Each JSON script is an array of actions. Each action is a list:
-
-* With keyword arguments: ``["action_name", {"param1": "value1"}]``
-* With positional arguments: ``["action_name", ["arg1", "arg2"]]``
-* With no arguments: ``["action_name"]``
-
-Chaining Multiple Actions
-~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Multiple actions can be chained in a single JSON file. For example, run a test and
-generate reports automatically:
-
-.. code-block:: json
-
- [
- ["LD_start_test", {
- "user_detail_dict": {"user": "fast_http_user"},
- "user_count": 10,
- "spawn_rate": 5,
- "test_time": 5,
- "tasks": {"get": {"request_url": "http://httpbin.org/get"}}
- }],
- ["LD_generate_html_report", {"html_name": "my_report"}],
- ["LD_generate_json_report", {"json_file_name": "my_report"}],
- ["LD_generate_xml_report", {"xml_file_name": "my_report"}]
- ]
-
-Dict-based JSON Format
-~~~~~~~~~~~~~~~~~~~~~~~
-
-JSON scripts can also be wrapped in a dict with a ``"load_density"`` key:
-
-.. code-block:: json
-
- {
- "load_density": [
- ["LD_start_test", {
- "user_detail_dict": {"user": "fast_http_user"},
- "user_count": 10,
- "spawn_rate": 5,
- "test_time": 5,
- "tasks": {"get": {"request_url": "http://httpbin.org/get"}}
- }]
- ]
- }
-
-Project Scaffolding
--------------------
-
-LoadDensity can generate a project directory structure with keyword templates and
-executor scripts:
+Or from Python:
.. code-block:: python
- from je_load_density import create_project_dir
-
- create_project_dir(project_path="./my_tests", parent_name="LoadDensity")
-
-Or via CLI:
+ from je_load_density import execute_action, read_action_json
+ execute_action(read_action_json("test_scenario.json"))
-.. code-block:: bash
+JSON script format
+~~~~~~~~~~~~~~~~~~
- python -m je_load_density -c ./my_tests
+Each action is a list:
-This creates the following structure:
+* with keyword arguments: ``["action_name", {"param1": "value1"}]``
+* with positional arguments: ``["action_name", ["arg1", "arg2"]]``
+* with no arguments: ``["action_name"]``
-.. code-block:: text
+The top-level document is either a bare action list or a
+``{"load_density": [...]}`` wrapper.
- my_tests/
- └── LoadDensity/
- ├── keyword/
- │ ├── keyword1.json # FastHttpUser test template
- │ └── keyword2.json # HttpUser test template
- └── executor/
- ├── executor_one_file.py # Execute single keyword file
- └── executor_folder.py # Execute all files in keyword/
+Next steps
+----------
-* ``keyword1.json`` — Template using ``fast_http_user`` with sample GET/POST tasks
-* ``keyword2.json`` — Template using ``http_user`` with sample GET/POST tasks
-* ``executor_one_file.py`` — Python script to execute ``keyword1.json``
-* ``executor_folder.py`` — Python script to execute all JSON files in ``keyword/``
+* Parameterise scripts: see :doc:`../parameter_resolver/parameter_resolver_doc`.
+* Layer scenario flow: see :doc:`../scenarios/scenarios_doc`.
+* Run a distributed master/worker fleet:
+ see :doc:`../distributed/distributed_doc`.
+* Ship metrics to Prometheus / InfluxDB / OTel:
+ see :doc:`../metrics/metrics_doc`.
diff --git a/docs/source/En/doc/grpc_user/grpc_user_doc.rst b/docs/source/En/doc/grpc_user/grpc_user_doc.rst
new file mode 100644
index 0000000..ccdfd0f
--- /dev/null
+++ b/docs/source/En/doc/grpc_user/grpc_user_doc.rst
@@ -0,0 +1,66 @@
+gRPC User
+=========
+
+Overview
+--------
+
+The gRPC user template drives unary calls against operator-supplied
+stub modules. It uses ``grpcio`` (and your own ``*_pb2`` /
+``*_pb2_grpc`` modules), loaded lazily — install with
+``pip install je_load_density[grpc]``.
+
+Task fields
+-----------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 75
+
+ * - Field
+ - Meaning
+ * - ``target`` / ``host``
+ - gRPC endpoint, e.g. ``localhost:50051``.
+ * - ``stub_path``
+ - Dotted path to the stub class (``pkg.greeter_pb2_grpc.GreeterStub``).
+ * - ``request_path``
+ - Dotted path to the request message (``pkg.greeter_pb2.HelloRequest``).
+ * - ``method``
+ - Method name on the stub.
+ * - ``payload``
+ - Dict of fields used to construct the request message.
+ * - ``metadata``
+ - List of ``[key, value]`` pairs or a flat dict.
+ * - ``timeout``
+ - Per-call timeout in seconds (default 10).
+
+The dotted paths are validated against a strict identifier regex
+before ``importlib.import_module`` is called, so traversal-style
+attacks (``../``, ``;``, ``__import__``) are rejected.
+
+Example
+-------
+
+.. code-block:: python
+
+ from je_load_density import start_test
+
+ start_test(
+ user_detail_dict={"user": "grpc_user"},
+ user_count=20,
+ spawn_rate=5,
+ test_time=60,
+ tasks=[
+ {
+ "name": "say_hello",
+ "target": "localhost:50051",
+ "stub_path": "pkg.greeter_pb2_grpc.GreeterStub",
+ "request_path": "pkg.greeter_pb2.HelloRequest",
+ "method": "SayHello",
+ "payload": {"name": "world"},
+ "metadata": [["x-token", "abc"]],
+ "timeout": 5,
+ }
+ ],
+ )
+
+Each call fires a Locust event tagged ``GRPC``.
diff --git a/docs/source/En/doc/gui/gui_doc.rst b/docs/source/En/doc/gui/gui_doc.rst
index 75deabe..4708df3 100644
--- a/docs/source/En/doc/gui/gui_doc.rst
+++ b/docs/source/En/doc/gui/gui_doc.rst
@@ -1,89 +1,88 @@
GUI (Graphical User Interface)
==============================
-LoadDensity includes an optional PySide6-based graphical interface for running load tests
-with a visual form and real-time log display.
+Overview
+--------
-Requirements
-------------
+LoadDensity ships an optional PySide6 graphical front-end. It carries
+the form controls for kicking off a quick HTTP test, a log panel that
+mirrors the framework log, and a live stats panel that polls
+``test_record_instance`` once a second.
-The GUI requires additional dependencies. Install with:
+Install
+-------
.. code-block:: bash
- pip install je_load_density[gui]
+ pip install "je_load_density[gui]"
-This installs:
+Pulls in:
-* **PySide6** (6.10.0) — Qt for Python bindings
-* **qt-material** — Material design theme
+* ``PySide6`` — Qt for Python bindings.
+* ``qt-material`` — Material design theme.
-Launching the GUI
------------------
+Launch
+------
.. code-block:: python
- from je_load_density.gui.main_window import LoadDensityUI
- from PySide6.QtWidgets import QApplication
import sys
+ from PySide6.QtWidgets import QApplication
+ from je_load_density.gui.main_window import LoadDensityUI
app = QApplication(sys.argv)
window = LoadDensityUI()
window.show()
sys.exit(app.exec())
-GUI Features
-------------
+Layout
+------
-The GUI provides:
+* **Test parameter form** — URL, test duration, user count, spawn rate,
+ HTTP method.
+* **Start button** — Launches the load test in a background ``QThread``.
+* **Live stats panel** — Total requests, current rate, average and p95
+ latency, failure count. Refreshes every 1 s.
+* **Log panel** — Real-time framework log feed.
+* **Material Design theme** — ``dark_amber.xml`` from ``qt-material``.
-* **Test Parameter Form** — Input fields for:
+Languages
+---------
- * Target URL
- * Test duration (seconds)
- * User count (number of simulated users)
- * Spawn rate (users per second)
- * HTTP method selection (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS)
+The GUI ships with English, Traditional Chinese, Japanese, and
+Korean translations. Switch via the ``LanguageWrapper.reset_language``
+helper:
-* **Start Button** — Launches the load test in a background thread (non-blocking UI)
-* **Real-time Log Panel** — Displays log messages from the test execution in real-time,
- updated every 50ms via a QTimer
-* **Material Design Theme** — Uses the ``dark_amber.xml`` theme from qt-material
-
-Language Support
-----------------
-
-The GUI supports two languages:
-
-* **English** (default)
-* **Traditional Chinese** (繁體中文)
+.. code-block:: python
-Language strings are managed via the ``language_wrapper`` module under
-``je_load_density/gui/language_wrapper/``.
+ from je_load_density.gui.language_wrapper.multi_language_wrapper import (
+ language_wrapper,
+ )
+ language_wrapper.reset_language("Japanese") # or Korean / Traditional_Chinese / English
Architecture
------------
-The GUI consists of the following components:
-
.. list-table::
:header-rows: 1
- :widths: 35 65
+ :widths: 30 70
* - Component
- Description
* - ``LoadDensityUI``
- - Main window (``QMainWindow``). Applies theme and contains the widget.
+ - ``QMainWindow`` host. Applies theme and wires the central widget.
* - ``LoadDensityWidget``
- - Central widget with form inputs, start button, and log panel.
+ - Form + start button + stats panel + log panel.
+ * - ``StatsPanel``
+ - QTimer-driven panel reading ``test_record_instance``.
* - ``LoadDensityGUIThread``
- - Background ``QThread`` that runs the load test without blocking the UI.
+ - Background ``QThread`` that runs the test without blocking the UI.
* - ``InterceptAllFilter``
- - Log filter that captures log messages into a queue for GUI display.
+ - Captures log records into a thread-safe queue.
* - ``log_message_queue``
- - Thread-safe queue bridging the logger and the GUI log panel.
+ - Bridges the logger and the GUI log panel.
.. note::
- On Windows, the GUI sets ``AppUserModelID`` via ``ctypes`` so the taskbar correctly
- identifies the application.
+ On Windows the main window sets ``AppUserModelID`` via ``ctypes`` so
+ the taskbar correctly identifies the application.
diff --git a/docs/source/En/doc/har_import/har_import_doc.rst b/docs/source/En/doc/har_import/har_import_doc.rst
new file mode 100644
index 0000000..638a538
--- /dev/null
+++ b/docs/source/En/doc/har_import/har_import_doc.rst
@@ -0,0 +1,68 @@
+HAR Record / Replay
+===================
+
+Overview
+--------
+
+The HAR importer turns recorded HTTP traffic (HAR JSON) into a list of
+LoadDensity tasks or a complete runnable action JSON. Capture HAR via
+Chrome / Firefox DevTools, mitmproxy, Charles, or any tool that
+exports the `HAR 1.2 `_
+format.
+
+Python API
+----------
+
+.. code-block:: python
+
+ from je_load_density import load_har, har_to_tasks, har_to_action_json
+
+ har = load_har("recording.har")
+ tasks = har_to_tasks(har, include=[r"example\.com"], exclude=[r"\.svg$"])
+ action_json = har_to_action_json(
+ har,
+ user="fast_http_user",
+ user_count=20,
+ spawn_rate=10,
+ test_time=120,
+ include=[r"api\.example\.com"],
+ )
+
+Filters
+-------
+
+* ``include`` — list of regex patterns; an entry is kept only if its
+ URL matches any pattern.
+* ``exclude`` — list of regex patterns; an entry is dropped if its URL
+ matches any pattern.
+
+Mapping rules
+-------------
+
+* HTTP method, URL, and request headers are copied directly.
+* Hop-by-hop and HTTP/2 pseudo headers
+ (``host``, ``content-length``, ``connection``, ``:authority``, …)
+ are stripped.
+* JSON request bodies (``application/json`` MIME) are parsed into the
+ ``json`` field; form params become ``data`` dicts; raw text bodies
+ fall back to ``data`` strings.
+* The captured response status becomes a ``status_code`` assertion on
+ the generated task.
+
+Action JSON
+-----------
+
+.. code-block:: json
+
+ {"load_density": [
+ ["LD_har_to_action_json", {
+ "har": {"log": {...}},
+ "user": "fast_http_user",
+ "user_count": 20,
+ "spawn_rate": 10,
+ "test_time": 120
+ }]
+ ]}
+
+The result of ``LD_har_to_action_json`` is itself an action JSON that
+can be saved or piped into ``LD_execute_action``.
diff --git a/docs/source/En/doc/http_users/http_users_doc.rst b/docs/source/En/doc/http_users/http_users_doc.rst
new file mode 100644
index 0000000..ac94c93
--- /dev/null
+++ b/docs/source/En/doc/http_users/http_users_doc.rst
@@ -0,0 +1,84 @@
+HTTP Users
+==========
+
+Overview
+--------
+
+LoadDensity ships two HTTP user templates, both wired through the same
+``request_executor`` and ``scenario_runner`` modules:
+
+* ``http_user`` — wraps ``locust.HttpUser`` (``requests`` under the
+ hood).
+* ``fast_http_user`` — wraps ``locust.FastHttpUser`` (geventhttpclient,
+ much higher RPS).
+
+Choose ``fast_http_user`` for high-load scenarios. Use ``http_user``
+when you need ``requests``-specific features or middleware.
+
+Task fields
+-----------
+
+Every HTTP task is a dict; the runner forwards the fields below to the
+underlying client. Anything else is ignored.
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 75
+
+ * - Field
+ - Meaning
+ * - ``method``
+ - ``get`` / ``post`` / ``put`` / ``patch`` / ``delete`` / ``head``
+ / ``options`` (case-insensitive).
+ * - ``request_url`` / ``url``
+ - Target URL (absolute or relative to ``host``).
+ * - ``name``
+ - Locust event name; defaults to the URL.
+ * - ``headers``
+ - Dict of request headers.
+ * - ``params``
+ - Query string parameters (dict or list of pairs).
+ * - ``json``
+ - Body serialised as JSON.
+ * - ``data``
+ - Form-encoded body (dict, list, or str).
+ * - ``cookies``
+ - Dict of cookies.
+ * - ``timeout``
+ - Per-request timeout in seconds.
+ * - ``allow_redirects``, ``verify``, ``files``
+ - Forwarded directly to the client.
+ * - ``auth``
+ - ``{"type": "basic", "username": "...", "password": "..."}`` or
+ ``{"type": "bearer", "token": "..."}``.
+ * - ``assertions``
+ - Response assertions (see :doc:`../assertions/assertions_doc`).
+ * - ``extract``
+ - Response extractors (see :doc:`../parameter_resolver/parameter_resolver_doc`).
+ * - ``weight``, ``run_if``, ``skip_if``
+ - Scenario flow controls (see :doc:`../scenarios/scenarios_doc`).
+
+Example
+-------
+
+.. code-block:: python
+
+ from je_load_density import start_test
+
+ start_test(
+ user_detail_dict={"user": "fast_http_user"},
+ user_count=50,
+ spawn_rate=10,
+ test_time=60,
+ variables={"base": "https://api.example.com"},
+ tasks=[
+ {"method": "post", "request_url": "${var.base}/login",
+ "json": {"email": "u@example.com", "password": "secret"},
+ "extract": [
+ {"var": "auth", "from": "json_path", "path": "data.token"}
+ ]},
+ {"method": "get", "request_url": "${var.base}/profile",
+ "headers": {"Authorization": "Bearer ${var.auth}"},
+ "assertions": [{"type": "status_code", "value": 200}]},
+ ],
+ )
diff --git a/docs/source/En/doc/installation/installation_doc.rst b/docs/source/En/doc/installation/installation_doc.rst
index 9def6ca..04587f7 100644
--- a/docs/source/En/doc/installation/installation_doc.rst
+++ b/docs/source/En/doc/installation/installation_doc.rst
@@ -15,65 +15,83 @@ Supported Platforms
:widths: 30 70
* - Platform
- - Version
- * - Windows
- - 10 / 11
+ - Notes
+ * - Windows 10 / 11
+ - Fully supported
* - macOS
- - 10.15 ~ 11 (Big Sur)
- * - Linux
- - Ubuntu 20.04
+ - Fully supported
+ * - Ubuntu / Linux
+ - Fully supported
* - Raspberry Pi
- - 3B+
+ - Tested on 3B+ and later
-Basic Installation (CLI & Library)
-----------------------------------
-
-Install LoadDensity from PyPI:
+Base install (CLI & library)
+----------------------------
.. code-block:: bash
pip install je_load_density
-This installs the core library and CLI. `Locust `_ is automatically
-installed as a dependency.
-
-Installation with GUI Support
------------------------------
-
-To use the optional PySide6-based graphical interface:
-
-.. code-block:: bash
-
- pip install je_load_density[gui]
-
-This additionally installs:
-
-* `PySide6 `_ — Qt for Python bindings
-* `qt-material `_ — Material design theme
+This pulls in `Locust `_ and ``defusedxml`` —
+nothing else.
-Development Installation
--------------------------
+Optional extras
+---------------
-To install from source for development:
+LoadDensity ships every protocol driver, exporter, recorder, and
+control surface as an opt-in extra. The base package never imports
+these modules eagerly, so the runtime footprint is unchanged for users
+who only need HTTP load testing.
-.. code-block:: bash
-
- git clone https://github.com/Intergration-Automation-Testing/LoadDensity.git
- cd LoadDensity
- pip install -e .
- pip install -r dev_requirements.txt
-
-Verify Installation
+.. list-table::
+ :header-rows: 1
+ :widths: 25 75
+
+ * - Extra
+ - Adds
+ * - ``gui``
+ - PySide6 + qt-material (graphical front-end).
+ * - ``websocket``
+ - ``websocket-client`` (WebSocket user template).
+ * - ``grpc``
+ - ``grpcio`` + ``protobuf`` (gRPC user template).
+ * - ``mqtt``
+ - ``paho-mqtt`` (MQTT user template).
+ * - ``prometheus``
+ - ``prometheus-client`` (Prometheus exporter).
+ * - ``opentelemetry``
+ - OpenTelemetry SDK + OTLP gRPC exporter.
+ * - ``metrics``
+ - ``prometheus`` + ``opentelemetry`` bundle.
+ * - ``faker``
+ - ``Faker`` (powers ``${faker.method}`` placeholders).
+ * - ``mcp``
+ - ``mcp`` SDK (drives the MCP server for Claude).
+ * - ``all``
+ - Everything above.
+
+Examples::
+
+ pip install "je_load_density[gui]"
+ pip install "je_load_density[mqtt,grpc,websocket]"
+ pip install "je_load_density[metrics]"
+ pip install "je_load_density[mcp]"
+ pip install "je_load_density[all]"
+
+Development install
-------------------
-After installation, verify that LoadDensity is correctly installed:
-
.. code-block:: bash
- python -c "from je_load_density import start_test; print('LoadDensity installed successfully')"
+ git clone https://github.com/Integration-Automation/LoadDensity.git
+ cd LoadDensity
+ pip install -e ".[all]"
+ pip install -r requirements.txt
-You can also check the installed version:
+Verify
+------
.. code-block:: bash
+ python -c "from je_load_density import start_test; print('LoadDensity installed')"
pip show je_load_density
diff --git a/docs/source/En/doc/locust_env/locust_env_doc.rst b/docs/source/En/doc/locust_env/locust_env_doc.rst
new file mode 100644
index 0000000..8a16f1e
--- /dev/null
+++ b/docs/source/En/doc/locust_env/locust_env_doc.rst
@@ -0,0 +1,67 @@
+Locust Environment
+==================
+
+Overview
+--------
+
+``prepare_env`` and ``create_env`` wrap ``locust.env.Environment`` and
+hide the boilerplate of wiring up runners, stats printers, and the
+optional Web UI.
+
+create_env
+----------
+
+Builds an ``Environment`` and a runner without starting any users:
+
+.. code-block:: python
+
+ from je_load_density import create_env
+ from je_load_density.wrapper.user_template.fast_http_user_template import (
+ FastHttpUserWrapper,
+ )
+
+ env = create_env(
+ FastHttpUserWrapper,
+ runner_mode="local", # "local" | "master" | "worker"
+ master_bind_host="*",
+ master_bind_port=5557,
+ master_host="127.0.0.1",
+ master_port=5557,
+ )
+
+Use ``create_env`` when you want to attach extra event listeners
+before the runner is started.
+
+prepare_env
+-----------
+
+A complete lifecycle helper: create environment → start runner →
+optionally launch the Locust Web UI → schedule a stop after
+``test_time`` → join.
+
+.. code-block:: python
+
+ from je_load_density import prepare_env
+
+ prepare_env(
+ user_class=FastHttpUserWrapper,
+ user_count=50,
+ spawn_rate=10,
+ test_time=60,
+ web_ui_dict={"host": "127.0.0.1", "port": 8089},
+ )
+
+Web UI
+------
+
+Pass ``web_ui_dict`` to ``prepare_env`` (or to ``start_test``) to enable
+the Locust web UI on the configured host/port. The UI is only started
+in local and master modes; workers never start a UI.
+
+Stats greenlets
+---------------
+
+In local and master modes, ``create_env`` spawns the standard Locust
+``stats_printer`` and ``stats_history`` greenlets so the console keeps
+streaming aggregate stats and the in-memory history is updated for
+charting. Workers skip both because the master collects and prints.
diff --git a/docs/source/En/doc/mcp_claude/mcp_claude_doc.rst b/docs/source/En/doc/mcp_claude/mcp_claude_doc.rst
new file mode 100644
index 0000000..317d751
--- /dev/null
+++ b/docs/source/En/doc/mcp_claude/mcp_claude_doc.rst
@@ -0,0 +1,72 @@
+MCP Server (for Claude)
+=======================
+
+Overview
+--------
+
+LoadDensity ships a `Model Context Protocol
+`_ server that exposes the framework
+as a set of MCP tools. With it, Claude (Desktop, Code, or any MCP
+client) can drive load tests, generate reports, import HAR files, and
+inspect persisted runs without leaving the chat.
+
+Install
+-------
+
+.. code-block:: bash
+
+ pip install "je_load_density[mcp]"
+
+Run the server
+--------------
+
+.. code-block:: bash
+
+ python -m je_load_density.mcp_server
+
+The server speaks MCP over stdio. Wire it into the client of your
+choice (Claude Desktop ``claude_desktop_config.json``, Claude Code,
+etc.):
+
+.. code-block:: json
+
+ {
+ "mcpServers": {
+ "loaddensity": {
+ "command": "python",
+ "args": ["-m", "je_load_density.mcp_server"]
+ }
+ }
+ }
+
+Exposed tools
+-------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 35 65
+
+ * - Tool
+ - Purpose
+ * - ``load_density.run_test``
+ - Run a Locust-backed load test (HTTP / WS / gRPC / MQTT / Socket).
+ * - ``load_density.run_action_json``
+ - Execute an action JSON document.
+ * - ``load_density.create_project``
+ - Scaffold a project skeleton at PATH.
+ * - ``load_density.list_executor_commands``
+ - List every ``LD_*`` command registered in the executor.
+ * - ``load_density.import_har``
+ - Convert a HAR file into a runnable action JSON.
+ * - ``load_density.generate_reports``
+ - Emit any combination of HTML / JSON / XML / CSV / JUnit / summary.
+ * - ``load_density.summary``
+ - Return aggregated stats (totals, per-name p50/p90/p95/p99).
+ * - ``load_density.persist_records``
+ - Save the current records into a SQLite database.
+ * - ``load_density.list_runs``
+ - List recent persisted runs.
+ * - ``load_density.fetch_run``
+ - Fetch records belonging to a saved run.
+ * - ``load_density.clear_records``
+ - Drop in-memory records before a new run.
diff --git a/docs/source/En/doc/metrics/metrics_doc.rst b/docs/source/En/doc/metrics/metrics_doc.rst
new file mode 100644
index 0000000..9a7e07b
--- /dev/null
+++ b/docs/source/En/doc/metrics/metrics_doc.rst
@@ -0,0 +1,94 @@
+Metrics Exporters
+=================
+
+Overview
+--------
+
+LoadDensity ships three observability sinks that hook into Locust's
+``request`` event and emit per-request metrics. All three are loaded
+lazily and ship as optional extras.
+
+Prometheus
+----------
+
+Install: ``pip install je_load_density[prometheus]``.
+
+.. code-block:: python
+
+ from je_load_density import start_prometheus_exporter
+ start_prometheus_exporter(port=9646, addr="127.0.0.1")
+
+Metrics:
+
+* ``loaddensity_requests_total{request_type, name, outcome}`` — counter
+* ``loaddensity_request_latency_ms{request_type, name}`` — histogram
+* ``loaddensity_response_bytes{request_type, name}`` — histogram
+
+The default bind address is loopback. Pass ``addr="0.0.0.0"`` to expose
+the endpoint to a Docker / Kubernetes scrape target.
+
+InfluxDB
+--------
+
+Stdlib only — no extra package needed. Pick UDP for fire-and-forget or
+HTTP for an authenticated cloud endpoint.
+
+.. code-block:: python
+
+ from je_load_density import start_influxdb_sink
+
+ # UDP listener on the InfluxDB box
+ start_influxdb_sink(transport="udp", host="127.0.0.1", port=8089)
+
+ # HTTPS write API
+ start_influxdb_sink(
+ transport="http",
+ url="https://eu-central-1-1.aws.cloud2.influxdata.com/api/v2/write?org=...&bucket=...",
+ token="...",
+ )
+
+The HTTP transport rejects URLs that aren't ``http://`` or ``https://``.
+
+OpenTelemetry
+-------------
+
+Install: ``pip install je_load_density[opentelemetry]``.
+
+.. code-block:: python
+
+ from je_load_density import start_opentelemetry_exporter
+ start_opentelemetry_exporter(
+ endpoint="http://otel-collector:4317",
+ service_name="loaddensity",
+ export_interval_ms=5000,
+ )
+
+Instruments emitted:
+
+* ``loaddensity.requests`` — counter
+* ``loaddensity.request.latency`` — histogram (ms)
+* ``loaddensity.response.size`` — histogram (bytes)
+
+Each instrument carries ``request_type``, ``name``, and ``outcome``
+attributes.
+
+Stop helpers
+------------
+
+Each ``start_*`` has a paired ``stop_*`` that detaches the listener
+(and shuts down the OTel provider). The Prometheus HTTP server itself
+keeps running because ``prometheus_client`` does not expose a stop
+hook.
+
+Action JSON
+-----------
+
+The same exporters are reachable from action JSON:
+
+.. code-block:: json
+
+ {"load_density": [
+ ["LD_start_prometheus_exporter", {"port": 9646, "addr": "127.0.0.1"}],
+ ["LD_start_test", {...}],
+ ["LD_stop_prometheus_exporter", {}]
+ ]}
diff --git a/docs/source/En/doc/mqtt_user/mqtt_user_doc.rst b/docs/source/En/doc/mqtt_user/mqtt_user_doc.rst
new file mode 100644
index 0000000..ab2b09b
--- /dev/null
+++ b/docs/source/En/doc/mqtt_user/mqtt_user_doc.rst
@@ -0,0 +1,60 @@
+MQTT User
+=========
+
+Overview
+--------
+
+The MQTT user template drives ``connect`` / ``publish`` / ``subscribe``
+/ ``disconnect`` against an MQTT broker. It uses ``paho-mqtt``, loaded
+lazily — install with ``pip install je_load_density[mqtt]``.
+
+Task fields
+-----------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 75
+
+ * - Field
+ - Meaning
+ * - ``method``
+ - ``connect`` / ``publish`` / ``subscribe`` / ``disconnect``.
+ * - ``broker`` / ``host``
+ - ``host:port`` of the MQTT broker.
+ * - ``topic``
+ - Topic for publish / subscribe.
+ * - ``payload``
+ - Body for publish (``str`` or ``bytes``).
+ * - ``qos``
+ - 0 / 1 / 2.
+ * - ``retain``
+ - Boolean.
+ * - ``username`` / ``password``
+ - Credentials.
+ * - ``client_id``
+ - Optional client id (defaults to a random hex token).
+ * - ``timeout``
+ - Publish wait timeout (default 5 seconds).
+
+Example
+-------
+
+.. code-block:: python
+
+ from je_load_density import start_test
+
+ start_test(
+ user_detail_dict={"user": "mqtt_user"},
+ user_count=10,
+ spawn_rate=5,
+ test_time=60,
+ tasks=[
+ {"method": "connect", "broker": "127.0.0.1:1883"},
+ {"method": "subscribe", "topic": "telemetry/in", "qos": 1},
+ {"method": "publish", "topic": "telemetry/out",
+ "payload": "ping", "qos": 1},
+ {"method": "disconnect"},
+ ],
+ )
+
+Each step fires a Locust event tagged ``MQTT``.
diff --git a/docs/source/En/doc/parameter_resolver/parameter_resolver_doc.rst b/docs/source/En/doc/parameter_resolver/parameter_resolver_doc.rst
new file mode 100644
index 0000000..5a87389
--- /dev/null
+++ b/docs/source/En/doc/parameter_resolver/parameter_resolver_doc.rst
@@ -0,0 +1,109 @@
+Parameter Resolver
+==================
+
+Overview
+--------
+
+The parameter resolver expands ``${...}`` placeholders inside any
+nested string / list / dict structure. It is invoked automatically on
+every task before the user template touches it, so values flow
+seamlessly between actions.
+
+Supported placeholders
+----------------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 35 65
+
+ * - Placeholder
+ - Resolves to
+ * - ``${var.NAME}``
+ - The value passed to ``register_variable`` / ``register_variables``.
+ * - ``${env.NAME}``
+ - Environment variable ``NAME``.
+ * - ``${csv.SOURCE.COLUMN}``
+ - The next row from CSV source ``SOURCE`` (cycles by default).
+ * - ``${faker.METHOD}``
+ - Calls ``Faker().METHOD()`` (lazy import, optional dependency).
+ * - ``${uuid()}``
+ - A new UUID 4 string.
+ * - ``${now()}``
+ - Local time in ISO-8601 (seconds resolution).
+ * - ``${randint(min, max)}``
+ - Cryptographically-strong random int in ``[min, max]``.
+
+Unknown placeholders are left in place so missing data is visible
+during a dry run.
+
+Registering data
+----------------
+
+.. code-block:: python
+
+ from je_load_density import (
+ register_variable, register_variables,
+ register_csv_source, register_csv_sources,
+ )
+
+ register_variable("base", "https://api.example.com")
+ register_variables({"token": "abc", "tenant": "acme"})
+
+ register_csv_source("users", "users.csv") # cycles
+ register_csv_sources([
+ {"name": "products", "file_path": "products.csv", "cycle": False},
+ ])
+
+CSV files must have a header row. Each call to ``${csv.name.col}``
+returns the value at column ``col`` from the next row.
+
+Action-JSON usage
+-----------------
+
+The same APIs are available through the executor so an entire run can
+be parameterised from JSON:
+
+.. code-block:: json
+
+ {"load_density": [
+ ["LD_register_variables", {"variables": {"base": "https://api.example.com"}}],
+ ["LD_register_csv_sources", {"sources": [
+ {"name": "users", "file_path": "users.csv"}
+ ]}],
+ ["LD_start_test", {
+ "user_detail_dict": {"user": "fast_http_user"},
+ "tasks": [{
+ "method": "post",
+ "request_url": "${var.base}/login",
+ "json": {"email": "${csv.users.email}", "password": "${csv.users.password}"}
+ }]
+ }]
+ ]}
+
+Extracting values from responses
+--------------------------------
+
+HTTP tasks may declare ``extract`` rules; matching values are written
+back into the resolver under the chosen variable name:
+
+.. code-block:: json
+
+ {
+ "method": "post",
+ "request_url": "${var.base}/login",
+ "json": {"email": "u@example.com", "password": "secret"},
+ "extract": [
+ {"var": "auth_token", "from": "json_path", "path": "data.token"},
+ {"var": "request_id", "from": "header", "name": "X-Request-Id"},
+ {"var": "status", "from": "status_code"}
+ ]
+ }
+
+Subsequent tasks can read ``${var.auth_token}`` straight from the
+resolver.
+
+Clearing
+--------
+
+Call ``parameter_resolver.clear()`` (or ``LD_clear_resolver``) between
+runs to discard accumulated state.
diff --git a/docs/source/En/doc/scenarios/scenarios_doc.rst b/docs/source/En/doc/scenarios/scenarios_doc.rst
new file mode 100644
index 0000000..ab772b6
--- /dev/null
+++ b/docs/source/En/doc/scenarios/scenarios_doc.rst
@@ -0,0 +1,96 @@
+Scenario Modes
+==============
+
+Overview
+--------
+
+Tasks for HTTP, FastHttp, and WebSocket users can be bundled into a
+scenario object that controls *which* tasks run on each tick. Three
+modes are supported:
+
+* ``sequence`` — every task runs in order (default).
+* ``weighted`` — one task is picked per tick, weighted by ``weight``.
+* ``conditional`` — each task is gated by ``run_if`` / ``skip_if``
+ predicates evaluated against the parameter resolver.
+
+Shape
+-----
+
+.. code-block:: json
+
+ {
+ "mode": "sequence",
+ "tasks": [
+ {"method": "get", "request_url": "${var.base}/products"},
+ {"method": "post", "request_url": "${var.base}/cart",
+ "json": {"product_id": 1}}
+ ]
+ }
+
+The legacy ``{"get": {...}}`` map and a bare list also still work; the
+runner normalises them to ``{"mode": "sequence", "tasks": [...]}``.
+
+Weighted picks
+--------------
+
+Each task may carry a positive integer ``weight``; the runner picks one
+task per tick with probability proportional to weight. Tasks without a
+``weight`` default to 1.
+
+.. code-block:: json
+
+ {
+ "mode": "weighted",
+ "tasks": [
+ {"method": "get", "request_url": "/", "weight": 3},
+ {"method": "get", "request_url": "/expensive", "weight": 1}
+ ]
+ }
+
+Conditional flow
+----------------
+
+``run_if`` and ``skip_if`` accept the same predicate language; ``run_if``
+must be truthy for the task to run, ``skip_if`` must be falsy.
+
+Predicates
+~~~~~~~~~~
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 70
+
+ * - Form
+ - Meaning
+ * - ``true`` / ``false`` / int
+ - Direct truthy check.
+ * - ``"${var.x}"``
+ - Resolve the placeholder, then truthy check.
+ * - ``{"equals": [a, b]}``
+ - ``a == b`` after resolution.
+ * - ``{"not_equals": [a, b]}``
+ - ``a != b`` after resolution.
+ * - ``{"in": [needle, haystack]}``
+ - ``needle in haystack``.
+ * - ``{"truthy": value}``
+ - Truthy check after resolution.
+
+Example
+~~~~~~~
+
+.. code-block:: json
+
+ {
+ "mode": "sequence",
+ "tasks": [
+ {"method": "post", "request_url": "/login",
+ "json": {"email": "${var.email}"},
+ "extract": [{"var": "auth", "from": "json_path", "path": "token"}]},
+ {"method": "get", "request_url": "/profile",
+ "headers": {"Authorization": "Bearer ${var.auth}"},
+ "run_if": {"truthy": "${var.auth}"}},
+ {"method": "post", "request_url": "/cart",
+ "json": {"product_id": 1},
+ "skip_if": {"equals": ["${var.tenant}", "internal"]}}
+ ]
+ }
diff --git a/docs/source/En/doc/scheduler/scheduler_doc.rst b/docs/source/En/doc/scheduler/scheduler_doc.rst
deleted file mode 100644
index 97eff2a..0000000
--- a/docs/source/En/doc/scheduler/scheduler_doc.rst
+++ /dev/null
@@ -1,117 +0,0 @@
-Scheduler
-=========
-
-LoadDensity includes a built-in scheduler that allows you to schedule recurring test
-execution at defined intervals. The scheduler supports both blocking and non-blocking modes.
-
-Basic Usage
------------
-
-.. code-block:: python
-
- from je_load_density.utils.scheduler.scheduler_manager import SchedulerManager
-
- scheduler = SchedulerManager()
-
- def my_task():
- print("Scheduled task executed")
-
- # Add a job that runs every 5 seconds (blocking mode)
- scheduler.add_interval_blocking_secondly(my_task, seconds=5)
-
- # Start the blocking scheduler
- scheduler.start_block_scheduler()
-
-Blocking vs Non-blocking
--------------------------
-
-The scheduler has two modes:
-
-* **Blocking mode** — ``start_block_scheduler()`` blocks the current thread. Use this for
- standalone scheduler scripts.
-* **Non-blocking mode** — ``start_nonblocking_scheduler()`` runs the scheduler in a background
- thread. Use this when you need to continue executing other code.
-
-Interval Methods (Blocking)
-----------------------------
-
-.. list-table::
- :header-rows: 1
- :widths: 50 50
-
- * - Method
- - Description
- * - ``add_interval_blocking_secondly(func, seconds)``
- - Run every N seconds
- * - ``add_interval_blocking_minutely(func, minutes)``
- - Run every N minutes
- * - ``add_interval_blocking_hourly(func, hours)``
- - Run every N hours
- * - ``add_interval_blocking_daily(func, days)``
- - Run every N days
- * - ``add_interval_blocking_weekly(func, weeks)``
- - Run every N weeks
-
-Interval Methods (Non-blocking)
---------------------------------
-
-.. list-table::
- :header-rows: 1
- :widths: 50 50
-
- * - Method
- - Description
- * - ``add_interval_nonblocking_secondly(func, seconds)``
- - Run every N seconds (non-blocking)
- * - ``add_interval_nonblocking_minutely(func, minutes)``
- - Run every N minutes (non-blocking)
- * - ``add_interval_nonblocking_hourly(func, hours)``
- - Run every N hours (non-blocking)
- * - ``add_interval_nonblocking_daily(func, days)``
- - Run every N days (non-blocking)
- * - ``add_interval_nonblocking_weekly(func, weeks)``
- - Run every N weeks (non-blocking)
-
-Cron Methods
-------------
-
-For cron-like scheduling:
-
-* ``add_cron_blocking(func, **cron_args)`` — Add a cron job in blocking mode
-* ``add_cron_nonblocking(func, **cron_args)`` — Add a cron job in non-blocking mode
-
-Job Management
---------------
-
-* ``remove_blocking_job(job_id)`` — Remove a job from the blocking scheduler
-* ``remove_nonblocking_job(job_id)`` — Remove a job from the non-blocking scheduler
-
-Starting Schedulers
--------------------
-
-* ``start_block_scheduler()`` — Start the blocking scheduler (blocks current thread)
-* ``start_nonblocking_scheduler()`` — Start the non-blocking scheduler (background)
-* ``start_all_scheduler()`` — Start both schedulers
-
-Example: Scheduled Load Test
------------------------------
-
-.. code-block:: python
-
- from je_load_density import start_test
- from je_load_density.utils.scheduler.scheduler_manager import SchedulerManager
-
- scheduler = SchedulerManager()
-
- def run_test():
- start_test(
- user_detail_dict={"user": "fast_http_user"},
- user_count=10,
- spawn_rate=5,
- test_time=5,
- tasks={"get": {"request_url": "http://httpbin.org/get"}},
- )
-
- # Run the test every 60 seconds
- scheduler.add_interval_blocking_secondly(run_test, seconds=60)
- scheduler.start_block_scheduler()
diff --git a/docs/source/En/doc/socket_server/socket_server_doc.rst b/docs/source/En/doc/socket_server/socket_server_doc.rst
index 58f5a82..3b2a430 100644
--- a/docs/source/En/doc/socket_server/socket_server_doc.rst
+++ b/docs/source/En/doc/socket_server/socket_server_doc.rst
@@ -1,110 +1,107 @@
-TCP Socket Server (Remote Execution)
-=====================================
+TCP Control Socket Server
+=========================
-LoadDensity includes a TCP server based on ``gevent`` that accepts JSON commands over the
-network, enabling remote test execution.
+Overview
+--------
-Starting the Server
--------------------
-
-.. code-block:: python
-
- from je_load_density import start_load_density_socket_server
+The control socket server is a gevent-based TCP listener that runs
+LoadDensity action JSON sent over the wire. The hardened protocol adds
+length-prefix framing, optional TLS, and a shared-secret token; the
+legacy unauthenticated mode is preserved for backwards compatibility.
- # Start server (blocking call)
- start_load_density_socket_server(host="localhost", port=9940)
+Modes
+-----
.. list-table::
:header-rows: 1
- :widths: 20 15 15 50
-
- * - Parameter
- - Type
- - Default
- - Description
- * - ``host``
- - ``str``
- - ``"localhost"``
- - Server bind address
- * - ``port``
- - ``int``
- - ``9940``
- - Server bind port
-
-The server starts listening and prints ``Server started on {host}:{port}``. Each incoming
-connection is handled in a separate ``gevent`` greenlet for concurrent request handling.
-
-Sending Commands from a Client
--------------------------------
-
-Commands are sent as JSON-encoded action lists — the same format used in JSON script files.
+ :widths: 25 75
+
+ * - Mode
+ - Notes
+ * - ``legacy``
+ - Single ``recv(8192)``, raw JSON, no auth. Default to keep older
+ clients (e.g. PyBreeze) working.
+ * - ``framed``
+ - 4-byte big-endian length prefix + JSON body. Safer against
+ partial reads and oversized payloads (1 MiB cap).
+ * - ``framed + TLS``
+ - Wrap the connection with ``ssl.create_default_context`` (TLS
+ 1.2+ minimum) using a cert/key on disk.
+
+Auth
+----
+
+Pass ``token=`` (or set ``LOAD_DENSITY_SOCKET_TOKEN``) to require a
+shared secret. Once configured:
+
+* ``quit_server`` is rejected without a valid token.
+* All command payloads must use the envelope
+ ``{"token": "...", "command": [...action JSON...]}`` and may set
+ ``"op": "quit"`` to signal a shutdown.
+
+Tokens are compared with ``hmac.compare_digest`` to avoid timing
+oracles.
+
+Starting the server
+-------------------
-.. code-block:: python
+Python::
- import socket
- import json
-
- # Connect to server
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- sock.connect(("localhost", 9940))
-
- # Send a test command
- command = json.dumps([
- ["LD_start_test", {
- "user_detail_dict": {"user": "fast_http_user"},
- "user_count": 10,
- "spawn_rate": 5,
- "test_time": 5,
- "tasks": {"get": {"request_url": "http://httpbin.org/get"}}
- }]
- ])
- sock.send(command.encode("utf-8"))
-
- # Receive response
- response = sock.recv(8192)
- print(response.decode("utf-8"))
- sock.close()
+ from je_load_density import start_load_density_socket_server
-Server Protocol
----------------
+ start_load_density_socket_server(
+ host="0.0.0.0",
+ port=9940,
+ framed=True,
+ token="ROTATE_ME",
+ certfile="/etc/loaddensity/server.crt",
+ keyfile="/etc/loaddensity/server.key",
+ )
-* **Command format**: JSON-encoded action list (same format as JSON script files)
-* **Response**: Each action's return value is sent back as a line, terminated by
- ``Return_Data_Over_JE\n``
-* **Error handling**: If an error occurs during execution, the error message is sent back
- followed by ``Return_Data_Over_JE\n``
-* **Buffer size**: 8192 bytes per receive
+CLI::
-Shutting Down the Server
-------------------------
+ python -m je_load_density serve \
+ --host 0.0.0.0 --port 9940 --framed \
+ --token "$LOAD_DENSITY_SOCKET_TOKEN"
-Send the string ``"quit_server"`` to gracefully shut down the server:
+Sending commands (framed mode)
+------------------------------
.. code-block:: python
- import socket
-
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- sock.connect(("localhost", 9940))
- sock.send(b"quit_server")
- response = sock.recv(8192)
- print(response.decode("utf-8")) # "Server shutting down"
+ import json, socket, struct
+
+ payload = json.dumps({
+ "token": "ROTATE_ME",
+ "command": {"load_density": [["LD_summary", {}]]}
+ }).encode("utf-8")
+
+ sock = socket.create_connection(("127.0.0.1", 9940))
+ sock.sendall(struct.pack("!I", len(payload)) + payload)
+ while True:
+ header = sock.recv(4)
+ if not header:
+ break
+ (length,) = struct.unpack("!I", header)
+ chunk = sock.recv(length)
+ if chunk == b"Return_Data_Over_JE\n":
+ break
+ print(chunk.decode("utf-8"))
sock.close()
-The server will close all connections and print ``Server shutdown complete``.
-
-Architecture
-------------
+Shutdown
+--------
-The TCP server consists of two components:
+* Legacy mode: send the literal string ``quit_server``.
+* Framed mode (with token): send
+ ``{"token": "...", "op": "quit"}``.
-* **TCPServer** — Main server class based on ``gevent.socket``. Listens for connections
- and spawns greenlets for each client.
-* **start_load_density_socket_server()** — Convenience function that patches the process
- with ``gevent.monkey.patch_all()`` and starts the server.
+The server prints ``Server shutdown complete`` and exits.
-.. note::
+Notes
+-----
- ``gevent.monkey.patch_all()`` is called when starting the socket server. This patches
- standard library modules (socket, threading, etc.) to be gevent-compatible. Be aware
- of this if integrating the socket server into a larger application.
+* ``gevent.monkey.patch_all()`` is invoked on start-up. Plan
+ integration accordingly.
+* The token may be read from the ``LOAD_DENSITY_SOCKET_TOKEN``
+ environment variable so CI secrets stay out of process arguments.
diff --git a/docs/source/En/doc/socket_user/socket_user_doc.rst b/docs/source/En/doc/socket_user/socket_user_doc.rst
new file mode 100644
index 0000000..39999f2
--- /dev/null
+++ b/docs/source/En/doc/socket_user/socket_user_doc.rst
@@ -0,0 +1,57 @@
+Raw TCP / UDP User
+==================
+
+Overview
+--------
+
+The raw socket user template sends arbitrary bytes over TCP or UDP and
+optionally reads back a bounded response. It uses Python's stdlib
+``socket`` module, so no extra dependency is required.
+
+Task fields
+-----------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 75
+
+ * - Field
+ - Meaning
+ * - ``protocol``
+ - ``tcp`` or ``udp``.
+ * - ``target`` / ``host``
+ - ``host:port``.
+ * - ``payload``
+ - Bytes to send. Strings are encoded as UTF-8. Use a
+ ``hex:DEADBEEF`` prefix to send raw bytes from a hex string.
+ * - ``expect_bytes``
+ - Read at most N bytes from the response (0 to skip read).
+ * - ``expect_substring``
+ - Substring assertion on the decoded response.
+ * - ``timeout``
+ - Connect / read timeout in seconds (default 5).
+ * - ``name``
+ - Event name; defaults to ``protocol:target``.
+
+Example
+-------
+
+.. code-block:: python
+
+ from je_load_density import start_test
+
+ start_test(
+ user_detail_dict={"user": "socket_user"},
+ user_count=20,
+ spawn_rate=5,
+ test_time=60,
+ tasks=[
+ {"protocol": "tcp", "target": "127.0.0.1:9000",
+ "payload": "PING\\n", "expect_bytes": 64,
+ "expect_substring": "PONG"},
+ {"protocol": "udp", "target": "127.0.0.1:9000",
+ "payload": "hex:DEADBEEF", "expect_bytes": 4},
+ ],
+ )
+
+Each step fires a Locust event tagged ``TCP`` or ``UDP``.
diff --git a/docs/source/En/doc/sqlite_persistence/sqlite_persistence_doc.rst b/docs/source/En/doc/sqlite_persistence/sqlite_persistence_doc.rst
new file mode 100644
index 0000000..602bb68
--- /dev/null
+++ b/docs/source/En/doc/sqlite_persistence/sqlite_persistence_doc.rst
@@ -0,0 +1,58 @@
+SQLite Persistence
+==================
+
+Overview
+--------
+
+The SQLite sink writes the in-memory ``test_record_instance`` to a
+SQLite database so runs can be compared, regression-checked, or shipped
+to another tool. The schema is created lazily; an empty file is fine.
+
+Python API
+----------
+
+.. code-block:: python
+
+ from je_load_density import (
+ persist_records, list_runs, fetch_run_records,
+ )
+
+ run_id = persist_records(
+ "loadtests.db",
+ label="checkout-2026-04-28",
+ metadata={"branch": "dev", "commit": "abc1234"},
+ )
+
+ for row in list_runs("loadtests.db", limit=10):
+ print(row)
+
+ for record in fetch_run_records("loadtests.db", run_id):
+ print(record)
+
+Schema
+------
+
+* ``load_density_runs(id, started_at, label, metadata_json)``
+* ``load_density_records(id, run_id, outcome, method, test_url, name,
+ status_code, response_time_ms, response_length, error)``
+
+Indexes are created on ``run_id`` and ``name`` to keep cross-run
+queries fast.
+
+Action JSON
+-----------
+
+.. code-block:: json
+
+ {"load_density": [
+ ["LD_clear_records", {}],
+ ["LD_start_test", {...}],
+ ["LD_persist_records", {
+ "database_path": "loadtests.db",
+ "label": "checkout",
+ "metadata": {"branch": "dev"}
+ }]
+ ]}
+
+Use ``LD_list_runs`` and ``LD_fetch_run_records`` from later scripts to
+read back the data.
diff --git a/docs/source/En/doc/start_test/start_test_doc.rst b/docs/source/En/doc/start_test/start_test_doc.rst
new file mode 100644
index 0000000..fd7a5b9
--- /dev/null
+++ b/docs/source/En/doc/start_test/start_test_doc.rst
@@ -0,0 +1,112 @@
+start_test & prepare_env
+========================
+
+Overview
+--------
+
+``start_test`` is the high-level entrypoint that picks a user template,
+seeds the parameter resolver, and asks ``prepare_env`` to build a Locust
+environment in the requested mode (local / master / worker).
+
+Signature
+---------
+
+.. code-block:: python
+
+ from je_load_density import start_test
+
+ start_test(
+ user_detail_dict={"user": "fast_http_user"},
+ user_count=50,
+ spawn_rate=10,
+ test_time=60,
+ web_ui_dict=None, # {"host": "...", "port": ...}
+ runner_mode="local", # "local" | "master" | "worker"
+ master_bind_host="*",
+ master_bind_port=5557,
+ master_host="127.0.0.1",
+ master_port=5557,
+ expected_workers=0,
+ tasks=...,
+ variables={"host": "https://api.example.com"},
+ csv_sources=[{"name": "users", "file_path": "users.csv"}],
+ )
+
+Supported user types
+--------------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 75
+
+ * - ``user``
+ - Template
+ * - ``http_user``
+ - ``locust.HttpUser`` wrapper backed by ``requests``.
+ * - ``fast_http_user``
+ - ``locust.FastHttpUser`` wrapper backed by ``geventhttpclient``.
+ * - ``websocket_user``
+ - WebSocket frame loop (lazy ``websocket-client`` import).
+ * - ``grpc_user``
+ - Unary gRPC calls against operator-supplied stubs.
+ * - ``mqtt_user``
+ - MQTT publish / subscribe loop.
+ * - ``socket_user``
+ - Raw TCP / UDP send-recv.
+
+prepare_env
+-----------
+
+``prepare_env`` is the lower-level layer behind ``start_test``. It is
+useful when you want to build a Locust environment manually, for
+example to integrate with another runner.
+
+.. code-block:: python
+
+ from je_load_density import prepare_env
+ from je_load_density.wrapper.user_template.fast_http_user_template import (
+ FastHttpUserWrapper, set_wrapper_fasthttp_user,
+ )
+
+ set_wrapper_fasthttp_user(
+ {"user": "fast_http_user"},
+ tasks=[{"method": "get", "request_url": "https://example.com/"}],
+ )
+ prepare_env(
+ user_class=FastHttpUserWrapper,
+ user_count=50,
+ spawn_rate=10,
+ test_time=60,
+ runner_mode="local",
+ )
+
+Distributed mode
+----------------
+
+Master::
+
+ start_test(
+ user_detail_dict={"user": "fast_http_user"},
+ runner_mode="master",
+ master_bind_host="0.0.0.0",
+ master_bind_port=5557,
+ expected_workers=4,
+ user_count=200,
+ spawn_rate=20,
+ test_time=300,
+ tasks=[...],
+ )
+
+Worker (run on each node, on the same network as master)::
+
+ start_test(
+ user_detail_dict={"user": "fast_http_user"},
+ runner_mode="worker",
+ master_host="10.0.0.10",
+ master_port=5557,
+ tasks=[...],
+ )
+
+The master waits for ``expected_workers`` workers to register before
+ramping up. Workers join the master and run the requested user count
+proportional to the cluster size.
diff --git a/docs/source/En/doc/test_record/test_record_doc.rst b/docs/source/En/doc/test_record/test_record_doc.rst
new file mode 100644
index 0000000..64ed245
--- /dev/null
+++ b/docs/source/En/doc/test_record/test_record_doc.rst
@@ -0,0 +1,44 @@
+Test Record
+===========
+
+Overview
+--------
+
+``test_record_instance`` is the in-memory store that the Locust
+``request`` hook feeds. Every report generator (HTML / JSON / XML / CSV
+/ JUnit / summary) reads from this object, and the SQLite persistence
+helpers write it to disk.
+
+Record fields
+-------------
+
+Each entry is a dict with the following keys:
+
+* ``Method`` — HTTP method or protocol tag (``GET``, ``POST``, ``WS``,
+ ``GRPC``, ``MQTT``, ``TCP``, ``UDP``).
+* ``test_url`` — Target URL or address.
+* ``name`` — Locust event name (``request_url`` if not overridden).
+* ``status_code`` — Response status (string) or ``None``.
+* ``response_time_ms`` — Locust-reported response time in ms.
+* ``response_length`` — Response size in bytes.
+* ``error`` — ``None`` for success rows; the exception string for
+ failures.
+* ``text``, ``content``, ``headers`` — Optional, only present on HTTP
+ successes.
+
+Clearing between runs
+---------------------
+
+.. code-block:: python
+
+ from je_load_density import test_record_instance
+ test_record_instance.clear_records()
+
+or via the executor::
+
+ ["LD_clear_records", {}]
+
+SQLite persistence
+------------------
+
+See :doc:`../../../En/doc/sqlite_persistence/sqlite_persistence_doc`.
diff --git a/docs/source/En/doc/websocket_user/websocket_user_doc.rst b/docs/source/En/doc/websocket_user/websocket_user_doc.rst
new file mode 100644
index 0000000..e10803c
--- /dev/null
+++ b/docs/source/En/doc/websocket_user/websocket_user_doc.rst
@@ -0,0 +1,53 @@
+WebSocket User
+==============
+
+Overview
+--------
+
+The WebSocket user template drives a connect / send / recv loop against
+a configured ``ws://`` or ``wss://`` URL. It uses the
+``websocket-client`` package, which is loaded lazily — install it with
+``pip install je_load_density[websocket]``.
+
+Task fields
+-----------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 75
+
+ * - Field
+ - Meaning
+ * - ``method``
+ - ``connect`` / ``send`` / ``recv`` / ``sendrecv`` / ``close``.
+ * - ``request_url`` / ``url``
+ - WebSocket URL (required for ``connect``; reused otherwise).
+ * - ``name``
+ - Event name; defaults to URL or method.
+ * - ``payload``
+ - String / bytes to send.
+ * - ``expect``
+ - Substring assertion on the received frame.
+ * - ``timeout``
+ - Recv timeout in seconds (default 5).
+
+Example
+-------
+
+.. code-block:: python
+
+ from je_load_density import start_test
+
+ start_test(
+ user_detail_dict={"user": "websocket_user"},
+ user_count=10,
+ spawn_rate=5,
+ test_time=60,
+ tasks=[
+ {"method": "connect", "request_url": "wss://echo.example.com/socket"},
+ {"method": "sendrecv", "payload": '{"ping": 1}', "expect": "pong"},
+ {"method": "close"},
+ ],
+ )
+
+Each step fires a Locust event tagged ``WS`` for stat aggregation.
diff --git a/docs/source/En/en_index.rst b/docs/source/En/en_index.rst
index 9416629..a65b74f 100644
--- a/docs/source/En/en_index.rst
+++ b/docs/source/En/en_index.rst
@@ -1,20 +1,172 @@
-English Documentation
-=============================================
+====================================
+LoadDensity English Documentation
+====================================
-Welcome to the LoadDensity English documentation. LoadDensity is a load & stress testing
-automation framework built on top of Locust, providing a simplified API, JSON-driven test
-scripts, multi-format report generation, an optional GUI, and remote execution capabilities.
+The English manual is split into chapters that follow a typical reader
+journey: install → run a load test → author actions → scale → integrate.
+Use the table of contents on the left, or jump straight to a chapter
+below.
+
+.. contents:: On this page
+ :local:
+ :depth: 1
+
+----
+
+.. _en-getting-started:
+
+Chapter 1 — Getting Started
+===========================
+
+Install LoadDensity, run your first load test, and scaffold a project.
.. toctree::
- :maxdepth: 4
- :caption: User Guide
+ :maxdepth: 2
+ :caption: Getting Started
doc/installation/installation_doc
doc/getting_started/getting_started_doc
- doc/cli/cli_doc
- doc/generate_report/generate_report_doc
- doc/scheduler/scheduler_doc
- doc/socket_server/socket_server_doc
+ doc/create_project/create_project_doc
+
+.. _en-core-api:
+
+Chapter 2 — Core API
+====================
+
+The Locust-facing facade: environments, runners, and user proxies.
+Read this once and the rest of the framework stops feeling magical.
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Core API
+
+ doc/architecture/architecture_doc
+ doc/start_test/start_test_doc
+ doc/locust_env/locust_env_doc
+
+.. _en-actions:
+
+Chapter 3 — Action Authoring & Execution
+========================================
+
+Compose JSON-driven action scripts, parameterise data, build scenarios,
+and chain post-test callbacks.
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Actions
+
+ doc/action_executor/action_executor_doc
+ doc/parameter_resolver/parameter_resolver_doc
+ doc/scenarios/scenarios_doc
+ doc/assertions/assertions_doc
doc/callback/callback_doc
doc/package_manager/package_manager_doc
+
+.. _en-user-templates:
+
+Chapter 4 — User Templates
+==========================
+
+The protocol drivers: HTTP, FastHttp, WebSocket, gRPC, MQTT, and raw
+TCP/UDP. Each template registers as a Locust user with the same task
+contract.
+
+.. toctree::
+ :maxdepth: 2
+ :caption: User Templates
+
+ doc/http_users/http_users_doc
+ doc/websocket_user/websocket_user_doc
+ doc/grpc_user/grpc_user_doc
+ doc/mqtt_user/mqtt_user_doc
+ doc/socket_user/socket_user_doc
+
+.. _en-reporting:
+
+Chapter 5 — Reporting & Observability
+=====================================
+
+Generate HTML / JSON / XML / CSV / JUnit / percentile-summary reports,
+ship metrics to Prometheus, InfluxDB, or any OTLP backend.
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Reporting
+
+ doc/generate_report/generate_report_doc
+ doc/metrics/metrics_doc
+ doc/test_record/test_record_doc
+
+.. _en-orchestration:
+
+Chapter 6 — Orchestration & Scale
+=================================
+
+Run distributed master/worker fleets, share state through the parameter
+resolver, and gate execution on extracted variables.
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Orchestration
+
+ doc/distributed/distributed_doc
+
+.. _en-recording-data:
+
+Chapter 7 — Recording & Data
+============================
+
+Convert real browser traffic (HAR) into runnable action JSON, persist
+test records to SQLite, and compare runs over time.
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Recording & Data
+
+ doc/har_import/har_import_doc
+ doc/sqlite_persistence/sqlite_persistence_doc
+
+.. _en-tooling:
+
+Chapter 8 — Tooling, CLI & Diagnostics
+======================================
+
+Command-line subcommands, the hardened control socket server, and the
+exception hierarchy you will see in tracebacks.
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Tooling
+
+ doc/cli/cli_doc
+ doc/socket_server/socket_server_doc
+ doc/exception/exception_doc
+
+.. _en-integrations:
+
+Chapter 9 — Integrations
+========================
+
+The optional GUI, the **Model Context Protocol (MCP)** server that lets
+Claude drive LoadDensity, and the downstream PyBreeze IDE integration.
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Integrations
+
doc/gui/gui_doc
+ doc/mcp_claude/mcp_claude_doc
+
+.. _en-reference:
+
+Chapter 10 — API Reference
+==========================
+
+Auto-generated Python API reference.
+
+.. toctree::
+ :maxdepth: 2
+ :caption: Reference
+
+ doc/api_reference/api_reference
diff --git a/docs/source/Zh/doc/action_executor/action_executor_doc.rst b/docs/source/Zh/doc/action_executor/action_executor_doc.rst
new file mode 100644
index 0000000..894279f
--- /dev/null
+++ b/docs/source/Zh/doc/action_executor/action_executor_doc.rst
@@ -0,0 +1,71 @@
+動作 Executor
+=============
+
+概觀
+----
+
+動作 executor 將指令字串對應到 callable。動作腳本是 JSON 列表,所以同一個腳本可以手寫、由 HAR 匯入產生、由 MCP 工具排程,或經由控制 socket 傳送。
+
+所有內建指令以 ``LD_`` 為字首;安全的 Python builtin(``print``、``len``、``range`` 等)也可使用,但 ``eval``、``exec``、``compile``、``__import__``、``breakpoint``、``open``、``input`` 已被明確封鎖。
+
+動作格式
+--------
+
+.. code-block:: python
+
+ ["command_name"] # 無參數
+ ["command_name", {"key": "value"}] # 關鍵字參數
+ ["command_name", [arg1, arg2]] # 位置參數
+
+最上層文件可為:
+
+.. code-block:: json
+
+ {"load_density": [["LD_start_test", {...}], ...]}
+
+或裸列表。
+
+範例
+----
+
+.. code-block:: python
+
+ from je_load_density import execute_action
+
+ execute_action({"load_density": [
+ ["LD_register_variables", {"variables": {"base": "https://api.example.com"}}],
+ ["LD_start_test", {
+ "user_detail_dict": {"user": "fast_http_user"},
+ "user_count": 20,
+ "spawn_rate": 10,
+ "test_time": 30,
+ "tasks": [{"method": "get", "request_url": "${var.base}/health"}],
+ }],
+ ["LD_generate_summary_report", {"report_name": "smoke"}],
+ ]})
+
+LD_* 指令
+---------
+
+下列指令於 executor 註冊。每個對應到 ``je_load_density`` 之下對應模組的實作。詳見 *Reference*。
+
+* **核心**:``LD_start_test``、``LD_execute_action``、``LD_execute_files``、``LD_add_package_to_executor``、``LD_start_socket_server``。
+* **報告**:``LD_generate_html(_report)``、``LD_generate_json(_report)``、``LD_generate_xml(_report)``、``LD_generate_csv_report``、``LD_generate_junit_report``、``LD_generate_summary_report``、``LD_summary``。
+* **持久化**:``LD_persist_records``、``LD_list_runs``、``LD_fetch_run_records``、``LD_clear_records``。
+* **參數解析器**:``LD_register_variable(s)``、``LD_register_csv_source(s)``、``LD_clear_resolver``。
+* **錄製/重放**:``LD_load_har``、``LD_har_to_tasks``、``LD_har_to_action_json``。
+* **指標 exporter**:``LD_start/stop_prometheus_exporter``、``LD_start/stop_influxdb_sink``、``LD_start/stop_opentelemetry_exporter``。
+
+新增自訂指令
+------------
+
+.. code-block:: python
+
+ from je_load_density import add_command_to_executor
+
+ def slack_notify(message: str) -> None:
+ ...
+
+ add_command_to_executor({"LD_slack_notify": slack_notify})
+
+註冊後,新指令即可在任何動作 JSON 中呼叫。
diff --git a/docs/source/Zh/doc/api_reference/api_reference.rst b/docs/source/Zh/doc/api_reference/api_reference.rst
new file mode 100644
index 0000000..a215bc4
--- /dev/null
+++ b/docs/source/Zh/doc/api_reference/api_reference.rst
@@ -0,0 +1,36 @@
+API Reference
+=============
+
+由 Sphinx ``autosummary`` 在每次 build 時自動產生。
+
+.. autosummary::
+ :toctree: _autosummary
+ :recursive:
+
+ je_load_density
+ je_load_density.utils.executor.action_executor
+ je_load_density.utils.parameterization.parameter_resolver
+ je_load_density.utils.recording.har_importer
+ je_load_density.utils.metrics.prometheus_exporter
+ je_load_density.utils.metrics.influxdb_sink
+ je_load_density.utils.metrics.opentelemetry_exporter
+ je_load_density.utils.test_record.test_record_class
+ je_load_density.utils.test_record.sqlite_persistence
+ je_load_density.utils.generate_report.generate_html_report
+ je_load_density.utils.generate_report.generate_json_report
+ je_load_density.utils.generate_report.generate_xml_report
+ je_load_density.utils.generate_report.generate_csv_report
+ je_load_density.utils.generate_report.generate_junit_report
+ je_load_density.utils.generate_report.generate_summary_report
+ je_load_density.utils.socket_server.load_density_socket_server
+ je_load_density.wrapper.create_locust_env.create_locust_env
+ je_load_density.wrapper.start_wrapper.start_test
+ je_load_density.wrapper.user_template.request_executor
+ je_load_density.wrapper.user_template.scenario_runner
+ je_load_density.wrapper.user_template.http_user_template
+ je_load_density.wrapper.user_template.fast_http_user_template
+ je_load_density.wrapper.user_template.websocket_user_template
+ je_load_density.wrapper.user_template.grpc_user_template
+ je_load_density.wrapper.user_template.mqtt_user_template
+ je_load_density.wrapper.user_template.socket_user_template
+ je_load_density.mcp_server.server
diff --git a/docs/source/Zh/doc/architecture/architecture_doc.rst b/docs/source/Zh/doc/architecture/architecture_doc.rst
new file mode 100644
index 0000000..8620bfd
--- /dev/null
+++ b/docs/source/Zh/doc/architecture/architecture_doc.rst
@@ -0,0 +1,77 @@
+架構
+====
+
+概觀
+----
+
+LoadDensity 是 Locust 之上的薄層封裝,加入 JSON 動作執行器、多協定 user 模板註冊、情境流程、資料參數化、可觀測性 sink,以及 MCP 控制介面。
+
+依賴方向永遠是動作層 → Locust,反之不行:你的動作 JSON 描述要做什麼,executor 把每個指令對應到 Python callable,Locust 負責跑壓測。
+
+分層
+----
+
+.. mermaid::
+
+ flowchart TB
+ cli[CLI / MCP / GUI / Socket Server] --> exec[動作 Executor]
+ exec --> start[start_test]
+ start --> proxy[locust_wrapper_proxy]
+ proxy --> userhttp[HTTP / FastHttp]
+ proxy --> userws[WebSocket]
+ proxy --> usergrpc[gRPC]
+ proxy --> usermqtt[MQTT]
+ proxy --> usersock[Raw TCP/UDP]
+ userhttp & userws & usergrpc & usermqtt & usersock --> hooks[Locust 事件]
+ hooks --> records[test_record_instance]
+ hooks --> exporters[Prometheus / Influx / OTel]
+ records --> reports[HTML / JSON / XML / CSV / JUnit / Summary]
+ records --> sqlite[SQLite 持久化]
+
+模組對照表
+----------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 35 65
+
+ * - 模組
+ - 用途
+ * - ``je_load_density.utils.executor``
+ - ``Executor`` 類別、dispatch table、``execute_action`` /
+ ``execute_files`` 進入點。
+ * - ``je_load_density.utils.parameterization``
+ - ``ParameterResolver``,處理 ``${var.x}`` / ``${env.X}`` /
+ ``${csv.s.col}`` / ``${faker.method}`` 與內建 helpers。
+ * - ``je_load_density.utils.recording``
+ - HAR 匯入 → 動作 JSON。
+ * - ``je_load_density.utils.metrics``
+ - Prometheus exporter、InfluxDB sink、OpenTelemetry exporter。
+ * - ``je_load_density.utils.test_record``
+ - 記憶體紀錄清單與選用 SQLite sink。
+ * - ``je_load_density.utils.generate_report``
+ - HTML / JSON / XML / CSV / JUnit / summary 產生器。
+ * - ``je_load_density.utils.socket_server``
+ - 含 framing、選用 TLS 與 token 的 TCP 控制平面。
+ * - ``je_load_density.wrapper.proxy``
+ - 各協定的 proxy,保存對應 user 模板的 task 設定。
+ * - ``je_load_density.wrapper.user_template``
+ - HTTP、FastHttp、WebSocket、gRPC、MQTT、raw socket 的 Locust user 類別。
+ * - ``je_load_density.wrapper.start_wrapper``
+ - ``start_test`` 分派器,挑選 user 模板並轉發 ``prepare_env``。
+ * - ``je_load_density.wrapper.create_locust_env``
+ - ``prepare_env`` / ``create_env`` 建立 local / master / worker 模式的 Locust 環境。
+ * - ``je_load_density.mcp_server``
+ - 提供 11 個工具的 MCP server,可讓 Claude 驅動 LoadDensity。
+ * - ``je_load_density.gui``
+ - 選用的 PySide6 widget(表單 + 即時統計面板)。
+
+動作生命週期
+------------
+
+#. 呼叫端透過 CLI、MCP 工具、socket server 或直接呼叫 ``execute_action(...)`` 提交動作 JSON。
+#. ``Executor.execute_action`` 對 ``event_dict`` 派發每個步驟(``LD_*`` 指令與安全的 builtin)。
+#. 當步驟為 ``LD_start_test`` 時,分派器會挑選 user 模板(``http_user``、``fast_http_user``、``websocket_user``、``grpc_user``、``mqtt_user``、``socket_user``),由 ``variables`` / ``csv_sources`` 種入參數解析器後呼叫 ``prepare_env``。
+#. ``prepare_env`` 以指定模式(local / master / worker)建立 Locust ``Environment`` 並啟動。
+#. 每個 user 每個 tick 跑 ``run_scenario``(或對應的協定執行函式),觸發 Locust 事件並寫入 ``test_record_instance``。
+#. 報告、metrics exporter、SQLite 持久化會吸收累積的紀錄。
diff --git a/docs/source/Zh/doc/assertions/assertions_doc.rst b/docs/source/Zh/doc/assertions/assertions_doc.rst
new file mode 100644
index 0000000..142a082
--- /dev/null
+++ b/docs/source/Zh/doc/assertions/assertions_doc.rst
@@ -0,0 +1,60 @@
+斷言與擷取
+==========
+
+概觀
+----
+
+HTTP / FastHttp task 可附 ``assertions`` 與 ``extract``,會在 Locust 的 ``catch_response`` 之下執行。失敗的斷言會被 Locust 標為 failure,並出現在所有報告中。
+
+斷言
+----
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 75
+
+ * - ``type``
+ - 行為
+ * - ``status_code``
+ - ``int(response.status_code) == int(value)``。
+ * - ``contains``
+ - ``str(value) in response.text``。
+ * - ``not_contains``
+ - ``str(value) not in response.text``。
+ * - ``json_path``
+ - 沿著 ``path``(點分隔,支援 list 索引)解析 ``response.json()`` 後與 ``value`` 比對。
+ * - ``header``
+ - ``response.headers[name] == value``。
+
+範例
+~~~~
+
+.. code-block:: json
+
+ {
+ "method": "get",
+ "request_url": "${var.base}/health",
+ "assertions": [
+ {"type": "status_code", "value": 200},
+ {"type": "json_path", "path": "status", "value": "ok"},
+ {"type": "header", "name": "X-Service", "value": "checkout"}
+ ]
+ }
+
+擷取
+----
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 75
+
+ * - ``from``
+ - 來源
+ * - ``json_path``
+ - 與 ``json_path`` 斷言相同的點分語法。
+ * - ``header``
+ - ``response.headers[name]``。
+ * - ``status_code``
+ - ``response.status_code``。
+
+擷取值會以指定 ``var`` 名寫入參數解析器;後續 task 以 ``${var.NAME}`` 引用。
diff --git a/docs/source/Zh/doc/cli/cli_doc.rst b/docs/source/Zh/doc/cli/cli_doc.rst
index e982be5..f5f6ab7 100644
--- a/docs/source/Zh/doc/cli/cli_doc.rst
+++ b/docs/source/Zh/doc/cli/cli_doc.rst
@@ -1,106 +1,81 @@
-命令列介面(CLI)
-==================
+CLI 命令列介面
+==============
-LoadDensity 提供完整的命令列介面,透過 ``python -m je_load_density`` 使用。
+LoadDensity 採子指令式 CLI。執行 ``python -m je_load_density --help`` 可查看完整介面。
-CLI 參數
---------
+子指令
+------
.. list-table::
:header-rows: 1
- :widths: 25 10 65
-
- * - 參數
- - 簡寫
- - 說明
- * - ``--execute_file``
- - ``-e``
- - 執行單一 JSON 腳本檔案
- * - ``--execute_dir``
- - ``-d``
- - 執行目錄下所有 JSON 檔案
- * - ``--execute_str``
- - —
- - 執行行內 JSON 字串
- * - ``--create_project``
- - ``-c``
- - 建置新專案(包含模板)
-
-執行單一 JSON 檔案
---------------------
-
-執行定義在單一 JSON 關鍵字檔案中的測試:
+ :widths: 25 75
+
+ * - 子指令
+ - 用途
+ * - ``run FILE``
+ - 執行單一動作 JSON 檔。
+ * - ``run-dir DIR``
+ - 執行目錄下所有 ``.json``。
+ * - ``run-str JSON``
+ - 直接執行 inline JSON 字串(Windows 雙重編碼自動處理)。
+ * - ``init PATH``
+ - 建立新的專案骨架。
+ * - ``serve``
+ - 啟動硬化的 TCP 控制 socket server。
+
+``run``
+-------
.. code-block:: bash
- python -m je_load_density -e test_scenario.json
-
-JSON 檔案應遵循動作列表格式:
-
-.. code-block:: json
+ python -m je_load_density run smoke.json
- [
- ["LD_start_test", {
- "user_detail_dict": {"user": "fast_http_user"},
- "user_count": 50,
- "spawn_rate": 10,
- "test_time": 5,
- "tasks": {
- "get": {"request_url": "http://httpbin.org/get"},
- "post": {"request_url": "http://httpbin.org/post"}
- }
- }]
- ]
+``smoke.json`` 內容::
-執行目錄下所有 JSON 檔案
---------------------------
+ {"load_density": [
+ ["LD_start_test", {
+ "user_detail_dict": {"user": "fast_http_user"},
+ "user_count": 20, "spawn_rate": 10, "test_time": 30,
+ "tasks": [{"method": "get", "request_url": "https://httpbin.org/get"}]
+ }],
+ ["LD_generate_summary_report", {"report_name": "smoke"}]
+ ]}
-遞迴執行指定目錄下所有 JSON 關鍵字檔案:
+``run-dir``
+-----------
-.. code-block:: bash
-
- python -m je_load_density -d ./test_scripts/
+對目錄樹下所有 ``.json`` 動作檔執行::
-此命令會掃描目錄中所有 ``.json`` 檔案,依序執行。
+ python -m je_load_density run-dir ./scenarios
-執行行內 JSON 字串
---------------------
+``run-str``
+-----------
-直接以字串形式執行 JSON 動作列表:
+Inline JSON(CI script 友善)::
-.. code-block:: bash
+ python -m je_load_density run-str '{"load_density":[["LD_summary",{}]]}'
- python -m je_load_density --execute_str '[["LD_start_test", {"user_detail_dict": {"user": "fast_http_user"}, "user_count": 10, "spawn_rate": 5, "test_time": 5, "tasks": {"get": {"request_url": "http://httpbin.org/get"}}}]]'
-
-.. note::
-
- 在 **Windows** 平台上,行內 JSON 字串會因為 shell 跳脫字元差異而自動進行雙重解析。
- CLI 會自動處理此差異。
-
-建立專案
+``init``
--------
-建置包含關鍵字模板與執行器腳本的新專案:
+於 PATH 建立專案骨架::
-.. code-block:: bash
+ python -m je_load_density init ./my_load_test
- python -m je_load_density -c MyProject
+``serve``
+---------
-產生的專案目錄結構:
+啟動控制 socket server。詳見 :doc:`../socket_server/socket_server_doc`。
-.. code-block:: text
+.. code-block:: bash
- MyProject/
- └── LoadDensity/
- ├── keyword/
- │ ├── keyword1.json
- │ └── keyword2.json
- └── executor/
- ├── executor_one_file.py
- └── executor_folder.py
+ python -m je_load_density serve \
+ --host 0.0.0.0 --port 9940 \
+ --framed --token "$LOAD_DENSITY_SOCKET_TOKEN" \
+ --tls-cert /etc/loaddensity/server.crt \
+ --tls-key /etc/loaddensity/server.key
-錯誤處理
+舊式旗標
--------
-若未提供有效參數,CLI 會拋出 ``LoadDensityTestExecuteException`` 並以結束碼 1 退出。
-所有錯誤訊息會輸出至 stderr。
+之前版本的扁平旗標 ``-e/-d/-c/--execute_str`` 仍接受(在 ``--help`` 中隱藏),維持與 PyBreeze 等下游工具相容。新腳本應使用子指令。
diff --git a/docs/source/Zh/doc/create_project/create_project_doc.rst b/docs/source/Zh/doc/create_project/create_project_doc.rst
new file mode 100644
index 0000000..3841a39
--- /dev/null
+++ b/docs/source/Zh/doc/create_project/create_project_doc.rst
@@ -0,0 +1,39 @@
+建立專案
+========
+
+概觀
+----
+
+``create_project_dir``(CLI: ``je_load_density init``)在指定路徑建立 LoadDensity 專案骨架。骨架包含一份範例動作 JSON、執行腳本,以及資源放置目錄。
+
+Python API
+----------
+
+.. code-block:: python
+
+ from je_load_density import create_project_dir
+ create_project_dir("./my_load_test")
+
+CLI
+---
+
+.. code-block:: bash
+
+ python -m je_load_density init ./my_load_test
+
+結構
+----
+
+::
+
+ my_load_test/
+ ├── run.py # 讀取動作 JSON 的小執行腳本
+ └── action.json # 範例動作 JSON
+
+建立後,編輯 ``action.json``(見 :doc:`../action_executor/action_executor_doc`)並執行::
+
+ python run.py
+
+或::
+
+ python -m je_load_density run action.json
diff --git a/docs/source/Zh/doc/distributed/distributed_doc.rst b/docs/source/Zh/doc/distributed/distributed_doc.rst
new file mode 100644
index 0000000..9795b46
--- /dev/null
+++ b/docs/source/Zh/doc/distributed/distributed_doc.rst
@@ -0,0 +1,58 @@
+分散式 Master / Worker
+======================
+
+概觀
+----
+
+LoadDensity 透過 ``start_test`` / ``prepare_env`` 的 ``runner_mode`` 參數開放 Locust 的分散式 runner。三種模式:
+
+* ``local`` — 單一程序(預設)。
+* ``master`` — 協調 worker 群,可選擇啟動 Locust Web UI。
+* ``worker`` — 加入 master 並執行指定的 user count。
+
+Master
+------
+
+.. code-block:: python
+
+ from je_load_density import start_test
+
+ start_test(
+ user_detail_dict={"user": "fast_http_user"},
+ runner_mode="master",
+ master_bind_host="0.0.0.0",
+ master_bind_port=5557,
+ expected_workers=4, # 等待 4 個 worker
+ web_ui_dict={"host": "0.0.0.0", "port": 8089},
+ user_count=400,
+ spawn_rate=40,
+ test_time=600,
+ tasks=[...],
+ )
+
+Master 在開始 ramp 前最多等待 60 秒,等待 ``expected_workers`` 個 worker 加入。若僅 N(N < expected)人加入,會記錄警告並照常啟動。
+
+Worker
+------
+
+於每個壓測節點執行:
+
+.. code-block:: python
+
+ start_test(
+ user_detail_dict={"user": "fast_http_user"},
+ runner_mode="worker",
+ master_host="10.0.0.10",
+ master_port=5557,
+ tasks=[...],
+ )
+
+Worker 不啟動 Web UI 並跳過本地 stats greenlet — 由 master 集中收集與發佈整體統計。
+
+提示
+----
+
+* 在防火牆開啟 master 的 ``master_bind_port``。Locust 預設埠 ``5557``。
+* 僅在 master 對 worker 可達時用 ``master_bind_host="0.0.0.0"``;否則綁定私網 IP。
+* Master 與 worker 的 user 模板(``http_user`` / ``fast_http_user`` / ...)需一致 — master 廣播 user class 名稱。
+* 若用 ``${csv.X.col}`` 參數化 task,每個 worker 都需註冊相同 CSV 檔(不共享狀態)。
diff --git a/docs/source/Zh/doc/exception/exception_doc.rst b/docs/source/Zh/doc/exception/exception_doc.rst
new file mode 100644
index 0000000..d59f83d
--- /dev/null
+++ b/docs/source/Zh/doc/exception/exception_doc.rst
@@ -0,0 +1,32 @@
+例外
+====
+
+階層
+----
+
+::
+
+ Exception
+ └── LocustNotFoundException
+ └── LoadDensityTestException
+ ├── LoadDensityTestJsonException
+ ├── LoadDensityGenerateJsonReportException
+ ├── LoadDensityTestExecuteException
+ ├── LoadDensityAssertException
+ ├── LoadDensityHTMLException
+ ├── LoadDensityAddCommandException
+ ├── XMLException
+ │ └── XMLTypeException
+ └── CallbackExecutorException
+
+何時該攔截何者
+--------------
+
+* ``LoadDensityTestExecuteException`` — 動作 JSON 結構錯誤,或引用不存在的指令。攔截以呈現使用者輸入錯誤,不致掩蓋內部錯誤。
+* ``LoadDensityHTMLException`` / ``LoadDensityGenerateJsonReportException`` — 在沒有紀錄時呼叫報告產生器(記憶體 store 為空)。
+* ``LoadDensityAssertException`` — 預留給斷言層;目前 HTTP 斷言會經由 Locust 將 request 標為 fail 而非拋出。
+* ``XMLException`` / ``XMLTypeException`` — XML 格式錯誤或未預期 payload 結構。
+* ``CallbackExecutorException`` — callback executor 收到錯誤的 trigger 或 function。
+* ``LoadDensityAddCommandException`` — ``add_command_to_executor`` 收到非 callable。
+
+所有自訂例外皆繼承自 ``LoadDensityTestException``,攔截該類別即可達成全面錯誤處理。
diff --git a/docs/source/Zh/doc/generate_report/generate_report_doc.rst b/docs/source/Zh/doc/generate_report/generate_report_doc.rst
index d663dc4..4f9f536 100644
--- a/docs/source/Zh/doc/generate_report/generate_report_doc.rst
+++ b/docs/source/Zh/doc/generate_report/generate_report_doc.rst
@@ -1,160 +1,82 @@
-報告產生
+產生報告
========
-LoadDensity 可以產生三種格式的測試報告:**HTML**、**JSON** 和 **XML**。
-報告是根據測試執行期間由 request hook 收集的測試紀錄產生的。
+概觀
+----
-.. note::
+LoadDensity 可從 ``test_record_instance`` 產生六種報告:HTML、JSON、XML、CSV、JUnit XML、百分位摘要 JSON。
- 報告只能在測試執行後產生。若不存在測試紀錄,會拋出
- ``LoadDensityHTMLException`` 或 ``LoadDensityGenerateJsonReportException``。
+.. note::
-HTML 報告
----------
+ 報告需要至少一筆紀錄;對空 store 呼叫產生器會拋出 ``LoadDensityHTMLException`` / ``LoadDensityGenerateJsonReportException``。
-產生帶有樣式的 HTML 檔案,以表格顯示成功與失敗紀錄。
+HTML
+----
.. code-block:: python
from je_load_density import generate_html_report
+ generate_html_report("my_report") # 寫出 my_report.html
- # 產生 "my_report.html"
- generate_html_report("my_report")
-
-HTML 報告包含:
-
-* **成功紀錄** — 以青色表頭的表格顯示,包含 Method、URL、name、status_code、
- 回應內文、content 和 headers
-* **失敗紀錄** — 以紅色表頭的表格顯示,包含 Method、URL、name、status_code 和錯誤訊息
-
-若要取得原始 HTML 片段而不寫入檔案:
-
-.. code-block:: python
-
- from je_load_density import generate_html
-
- success_fragments, failure_fragments = generate_html()
- # success_fragments: List[str] — 每筆成功紀錄的 HTML 表格字串
- # failure_fragments: List[str] — 每筆失敗紀錄的 HTML 表格字串
-
-JSON 報告
----------
-
-產生結構化的 JSON 檔案,供程式化使用。
+JSON(依結果分檔)
+------------------
.. code-block:: python
from je_load_density import generate_json_report
-
- # 產生 "my_report_success.json" 和 "my_report_failure.json"
success_path, failure_path = generate_json_report("my_report")
-**成功 JSON 格式:**
+XML(依結果分檔)
+-----------------
-.. code-block:: json
-
- {
- "Success_Test1": {
- "Method": "GET",
- "test_url": "http://httpbin.org/get",
- "name": "/get",
- "status_code": "200",
- "text": "...",
- "content": "...",
- "headers": "..."
- },
- "Success_Test2": {}
- }
-
-**失敗 JSON 格式:**
-
-.. code-block:: json
+.. code-block:: python
- {
- "Failure_Test1": {
- "Method": "POST",
- "test_url": "http://httpbin.org/status/500",
- "name": "/status/500",
- "status_code": "500",
- "error": "..."
- }
- }
+ from je_load_density import generate_xml_report
+ success_path, failure_path = generate_xml_report("my_report")
-若要取得原始 JSON 資料結構而不寫入檔案:
+CSV(每筆請求一列)
+-------------------
.. code-block:: python
- from je_load_density import generate_json
-
- success_dict, failure_dict = generate_json()
+ from je_load_density import generate_csv_report
+ generate_csv_report("my_report") # 寫出 my_report.csv
-XML 報告
---------
+欄位:``outcome, Method, test_url, name, status_code, response_time_ms, response_length, error``。
-產生 XML 檔案,適用於 CI/CD 整合。
+JUnit XML(CI 友善)
+--------------------
.. code-block:: python
- from je_load_density import generate_xml_report
-
- # 產生 "my_report_success.xml" 和 "my_report_failure.xml"
- success_path, failure_path = generate_xml_report("my_report")
+ from je_load_density import generate_junit_report
+ generate_junit_report("loaddensity-junit") # 寫出 loaddensity-junit.xml
-XML 輸出使用 ``xml.dom.minidom`` 進行格式化。每筆測試紀錄包裝在 ```` 根節點下。
+每筆請求變成 ````;失敗附上 ```` 節點。可餵 Jenkins、GitHub Actions、GitLab 等。
-若要取得原始 XML 字串而不寫入檔案:
+Summary(百分位)
+-----------------
.. code-block:: python
- from je_load_density import generate_xml
+ from je_load_density import generate_summary_report, build_summary
- success_xml_str, failure_xml_str = generate_xml()
+ summary = build_summary() # 記憶體 dict
+ generate_summary_report("loaddensity-summary")
-在 JSON 腳本中使用
---------------------
+包含 totals、per-name 計數、min / max / mean / 百分位(p50 / p90 / p95 / p99)延遲與整體區塊。便於繪圖與跨次回歸檢查。
+
+動作 JSON
+---------
-報告產生可以與測試執行在 JSON 腳本中串連:
-
-.. code-block:: json
-
- [
- ["LD_start_test", {
- "user_detail_dict": {"user": "fast_http_user"},
- "user_count": 10,
- "spawn_rate": 5,
- "test_time": 5,
- "tasks": {"get": {"request_url": "http://httpbin.org/get"}}
- }],
- ["LD_generate_html_report", {"html_name": "report"}],
- ["LD_generate_json_report", {"json_file_name": "report"}],
- ["LD_generate_xml_report", {"xml_file_name": "report"}]
- ]
-
-報告函式總覽
-------------
-
-.. list-table::
- :header-rows: 1
- :widths: 35 25 40
-
- * - 函式
- - 回傳值
- - 說明
- * - ``generate_html()``
- - ``Tuple[List[str], List[str]]``
- - 成功與失敗紀錄的 HTML 片段
- * - ``generate_html_report(html_name)``
- - ``str``
- - 寫入 HTML 報告檔案,回傳檔案路徑
- * - ``generate_json()``
- - ``Tuple[Dict, Dict]``
- - 成功與失敗紀錄的 JSON 字典
- * - ``generate_json_report(json_file_name)``
- - ``Tuple[str, str]``
- - 寫入 JSON 報告檔案,回傳路徑
- * - ``generate_xml()``
- - ``Tuple[str, str]``
- - 成功與失敗紀錄的 XML 字串
- * - ``generate_xml_report(xml_file_name)``
- - ``Tuple[str, str]``
- - 寫入 XML 報告檔案,回傳路徑
+把報告串入測試::
+
+ {"load_density": [
+ ["LD_start_test", {...}],
+ ["LD_generate_html_report", {"html_name": "report"}],
+ ["LD_generate_json_report", {"json_file_name": "report"}],
+ ["LD_generate_xml_report", {"xml_file_name": "report"}],
+ ["LD_generate_csv_report", {"csv_name": "report"}],
+ ["LD_generate_junit_report", {"report_name": "report-junit"}],
+ ["LD_generate_summary_report",{"report_name": "report-summary"}]
+ ]}
diff --git a/docs/source/Zh/doc/getting_started/getting_started_doc.rst b/docs/source/Zh/doc/getting_started/getting_started_doc.rst
index c4066dd..f40dd92 100644
--- a/docs/source/Zh/doc/getting_started/getting_started_doc.rst
+++ b/docs/source/Zh/doc/getting_started/getting_started_doc.rst
@@ -1,238 +1,83 @@
-開始使用
-========
+入門
+====
-本指南將帶您了解如何使用 LoadDensity 執行第一個負載測試。
+本指南帶你跑出第一支 LoadDensity 壓測。
-使用者類型
-----------
+User 類型
+---------
-LoadDensity 支援兩種 Locust 使用者類型:
+LoadDensity 提供六種 user 類型:
-.. list-table::
- :header-rows: 1
- :widths: 25 25 50
+* ``fast_http_user`` — 高吞吐 HTTP(``locust.FastHttpUser`` + geventhttpclient)。
+* ``http_user`` — ``locust.HttpUser`` + ``requests``。
+* ``websocket_user``、``grpc_user``、``mqtt_user``、``socket_user`` — 詳見第 4 章。
- * - 使用者類型鍵值
- - Locust 類別
- - 說明
- * - ``fast_http_user``
- - ``FastHttpUser``
- - 使用 ``geventhttpclient``,效能較高。建議大多數情況使用。
- * - ``http_user``
- - ``HttpUser``
- - 使用 Python ``requests`` 函式庫。相容性較佳,效能較低。
-
-支援的 HTTP 方法
------------------
-
-LoadDensity 支援以下 HTTP 方法:
-
-* ``get``
-* ``post``
-* ``put``
-* ``patch``
-* ``delete``
-* ``head``
-* ``options``
-
-使用 Python API 執行測試
--------------------------
-
-最簡單的方式是呼叫 ``start_test()``:
+以 Python API 執行
+------------------
.. code-block:: python
from je_load_density import start_test
- result = start_test(
+ start_test(
user_detail_dict={"user": "fast_http_user"},
user_count=50,
spawn_rate=10,
- test_time=10,
- tasks={
- "get": {"request_url": "http://httpbin.org/get"},
- "post": {"request_url": "http://httpbin.org/post"},
- }
+ test_time=30,
+ variables={"base": "https://httpbin.org"},
+ tasks=[
+ {"method": "get", "request_url": "${var.base}/get"},
+ {"method": "post", "request_url": "${var.base}/post",
+ "json": {"hello": "world"},
+ "assertions": [{"type": "status_code", "value": 200}]},
+ ],
)
-``start_test()`` 參數說明
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-.. list-table::
- :header-rows: 1
- :widths: 20 15 10 55
-
- * - 參數
- - 類型
- - 預設值
- - 說明
- * - ``user_detail_dict``
- - ``dict``
- - (必填)
- - 使用者類型設定。``{"user": "fast_http_user"}`` 或 ``{"user": "http_user"}``
- * - ``user_count``
- - ``int``
- - ``50``
- - 模擬使用者總數
- * - ``spawn_rate``
- - ``int``
- - ``10``
- - 每秒生成使用者數量
- * - ``test_time``
- - ``int`` 或 ``None``
- - ``60``
- - 測試持續時間(秒)。傳入 ``None`` 則無限制
- * - ``web_ui_dict``
- - ``dict`` 或 ``None``
- - ``None``
- - 啟用 Locust Web UI。例如 ``{"host": "127.0.0.1", "port": 8089}``
-
-回傳值
-~~~~~~
-
-``start_test()`` 回傳一個測試設定摘要字典:
-
-.. code-block:: python
-
- {
- "user_detail": {"user": "fast_http_user"},
- "user_count": 50,
- "spawn_rate": 10,
- "test_time": 10,
- "web_ui": None,
- }
-
-啟用 Locust Web UI
--------------------
-
-若要透過 Locust Web UI 即時監控測試:
+啟動 Locust Web UI
+------------------
.. code-block:: python
- from je_load_density import start_test
-
- result = start_test(
- user_detail_dict={"user": "http_user"},
- user_count=100,
- spawn_rate=20,
- test_time=30,
+ start_test(
+ user_detail_dict={"user": "fast_http_user"},
+ user_count=50, spawn_rate=10, test_time=30,
web_ui_dict={"host": "127.0.0.1", "port": 8089},
- tasks={
- "get": {"request_url": "http://httpbin.org/get"},
- }
+ tasks=[{"method": "get", "request_url": "https://httpbin.org/get"}],
)
-然後在瀏覽器開啟 ``http://127.0.0.1:8089`` 即可查看即時統計資料。
-
-使用 JSON 腳本檔案執行測試
-----------------------------
-
-可以將測試情境定義為 JSON 檔案,無需撰寫 Python 程式碼即可執行。
-
-建立 ``test_scenario.json`` 檔案:
-
-.. code-block:: json
-
- [
- ["LD_start_test", {
- "user_detail_dict": {"user": "fast_http_user"},
- "user_count": 50,
- "spawn_rate": 10,
- "test_time": 5,
- "tasks": {
- "get": {"request_url": "http://httpbin.org/get"},
- "post": {"request_url": "http://httpbin.org/post"}
- }
- }]
- ]
-
-從 Python 執行:
+之後在瀏覽器開啟 ``http://127.0.0.1:8089``。
-.. code-block:: python
-
- from je_load_density import execute_action, read_action_json
-
- execute_action(read_action_json("test_scenario.json"))
-
-JSON 腳本格式
-~~~~~~~~~~~~~~
+以 JSON 動作腳本執行
+--------------------
-每個 JSON 腳本是一個動作陣列。每個動作是一個列表:
-
-* 使用關鍵字參數:``["action_name", {"param1": "value1"}]``
-* 使用位置參數:``["action_name", ["arg1", "arg2"]]``
-* 無參數:``["action_name"]``
-
-串連多個動作
-~~~~~~~~~~~~
-
-多個動作可以在單一 JSON 檔案中串連。例如,執行測試並自動產生報告:
+建立 ``test_scenario.json``:
.. code-block:: json
- [
- ["LD_start_test", {
- "user_detail_dict": {"user": "fast_http_user"},
- "user_count": 10,
- "spawn_rate": 5,
- "test_time": 5,
- "tasks": {"get": {"request_url": "http://httpbin.org/get"}}
- }],
- ["LD_generate_html_report", {"html_name": "my_report"}],
- ["LD_generate_json_report", {"json_file_name": "my_report"}],
- ["LD_generate_xml_report", {"xml_file_name": "my_report"}]
- ]
+ {"load_density": [
+ ["LD_start_test", {
+ "user_detail_dict": {"user": "fast_http_user"},
+ "user_count": 20, "spawn_rate": 10, "test_time": 30,
+ "tasks": [{"method": "get", "request_url": "https://httpbin.org/get"}]
+ }],
+ ["LD_generate_summary_report", {"report_name": "smoke"}]
+ ]}
-字典格式 JSON
-~~~~~~~~~~~~~~
+執行::
-JSON 腳本也可以用字典包裝,使用 ``"load_density"`` 鍵值:
-
-.. code-block:: json
+ python -m je_load_density run test_scenario.json
- {
- "load_density": [
- ["LD_start_test", {
- "user_detail_dict": {"user": "fast_http_user"},
- "user_count": 10,
- "spawn_rate": 5,
- "test_time": 5,
- "tasks": {"get": {"request_url": "http://httpbin.org/get"}}
- }]
- ]
- }
-
-專案建置
---------
-
-LoadDensity 可以自動產生專案目錄結構,包含關鍵字模板與執行器腳本:
+或於 Python:
.. code-block:: python
- from je_load_density import create_project_dir
-
- create_project_dir(project_path="./my_tests", parent_name="LoadDensity")
-
-或透過 CLI:
-
-.. code-block:: bash
-
- python -m je_load_density -c ./my_tests
-
-產生的結構如下:
-
-.. code-block:: text
+ from je_load_density import execute_action, read_action_json
+ execute_action(read_action_json("test_scenario.json"))
- my_tests/
- └── LoadDensity/
- ├── keyword/
- │ ├── keyword1.json # FastHttpUser 測試模板
- │ └── keyword2.json # HttpUser 測試模板
- └── executor/
- ├── executor_one_file.py # 執行單一關鍵字檔案
- └── executor_folder.py # 執行 keyword/ 下所有檔案
+下一步
+------
-* ``keyword1.json`` — 使用 ``fast_http_user`` 的模板,包含範例 GET/POST 任務
-* ``keyword2.json`` — 使用 ``http_user`` 的模板,包含範例 GET/POST 任務
-* ``executor_one_file.py`` — 執行 ``keyword1.json`` 的 Python 腳本
-* ``executor_folder.py`` — 執行 ``keyword/`` 目錄下所有 JSON 檔案的 Python 腳本
+* 為動作腳本參數化:見 :doc:`../parameter_resolver/parameter_resolver_doc`。
+* 改用情境流程:見 :doc:`../scenarios/scenarios_doc`。
+* 升級到分散式 master/worker:見 :doc:`../distributed/distributed_doc`。
+* 啟動 Prometheus / InfluxDB / OTel 指標 sink:見 :doc:`../metrics/metrics_doc`。
diff --git a/docs/source/Zh/doc/grpc_user/grpc_user_doc.rst b/docs/source/Zh/doc/grpc_user/grpc_user_doc.rst
new file mode 100644
index 0000000..6a1a45f
--- /dev/null
+++ b/docs/source/Zh/doc/grpc_user/grpc_user_doc.rst
@@ -0,0 +1,61 @@
+gRPC 使用者
+===========
+
+概觀
+----
+
+gRPC user 模板對 operator 提供的 stub 進行 unary 呼叫。底層使用 ``grpcio`` 與你自己的 ``*_pb2`` / ``*_pb2_grpc``,皆 lazy import — 以 ``pip install je_load_density[grpc]`` 安裝。
+
+Task 欄位
+---------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 75
+
+ * - 欄位
+ - 意義
+ * - ``target`` / ``host``
+ - gRPC 端點,如 ``localhost:50051``。
+ * - ``stub_path``
+ - Stub 類別的 dotted path(``pkg.greeter_pb2_grpc.GreeterStub``)。
+ * - ``request_path``
+ - 請求訊息的 dotted path(``pkg.greeter_pb2.HelloRequest``)。
+ * - ``method``
+ - Stub 上的 method 名。
+ * - ``payload``
+ - 用以建構 request message 的 dict。
+ * - ``metadata``
+ - ``[key, value]`` pair 列表或扁平 dict。
+ * - ``timeout``
+ - 單呼叫 timeout(秒),預設 10。
+
+dotted path 在 ``importlib.import_module`` 之前會通過嚴格識別符 regex 驗證;traversal 攻擊(``../``、``;``、``__import__``)皆被拒絕。
+
+範例
+----
+
+.. code-block:: python
+
+ from je_load_density import start_test
+
+ start_test(
+ user_detail_dict={"user": "grpc_user"},
+ user_count=20,
+ spawn_rate=5,
+ test_time=60,
+ tasks=[
+ {
+ "name": "say_hello",
+ "target": "localhost:50051",
+ "stub_path": "pkg.greeter_pb2_grpc.GreeterStub",
+ "request_path": "pkg.greeter_pb2.HelloRequest",
+ "method": "SayHello",
+ "payload": {"name": "world"},
+ "metadata": [["x-token", "abc"]],
+ "timeout": 5,
+ }
+ ],
+ )
+
+每次呼叫會觸發標記為 ``GRPC`` 的 Locust 事件。
diff --git a/docs/source/Zh/doc/gui/gui_doc.rst b/docs/source/Zh/doc/gui/gui_doc.rst
index 04e4c37..32033ce 100644
--- a/docs/source/Zh/doc/gui/gui_doc.rst
+++ b/docs/source/Zh/doc/gui/gui_doc.rst
@@ -1,87 +1,80 @@
-GUI(圖形化使用者介面)
-======================
+GUI 圖形介面
+============
-LoadDensity 包含一個可選的 PySide6 圖形化介面,可透過視覺化表單執行負載測試,
-並即時顯示日誌。
+概觀
+----
-安裝需求
---------
+LoadDensity 內含選用的 PySide6 圖形前端。提供啟動快速 HTTP 測試的表單控制元件、鏡像框架日誌的 log panel,以及每秒輪詢 ``test_record_instance`` 的即時統計面板。
-GUI 需要額外的相依套件。請使用以下方式安裝:
+安裝
+----
.. code-block:: bash
- pip install je_load_density[gui]
+ pip install "je_load_density[gui]"
-這會安裝:
+引入:
-* **PySide6** (6.10.0) — Qt for Python 綁定
-* **qt-material** — Material Design 主題
+* ``PySide6`` — Qt for Python bindings。
+* ``qt-material`` — Material design 主題。
-啟動 GUI
---------
+啟動
+----
.. code-block:: python
- from je_load_density.gui.main_window import LoadDensityUI
- from PySide6.QtWidgets import QApplication
import sys
+ from PySide6.QtWidgets import QApplication
+ from je_load_density.gui.main_window import LoadDensityUI
app = QApplication(sys.argv)
window = LoadDensityUI()
window.show()
sys.exit(app.exec())
-GUI 功能
---------
-
-GUI 提供以下功能:
-
-* **測試參數表單** — 輸入欄位包含:
-
- * 目標 URL
- * 測試持續時間(秒)
- * 使用者數量(模擬使用者總數)
- * 生成速率(每秒生成使用者數量)
- * HTTP 方法選擇(GET、POST、PUT、PATCH、DELETE、HEAD、OPTIONS)
+版面
+----
-* **啟動按鈕** — 在背景執行緒中啟動負載測試(不會阻塞 UI)
-* **即時日誌面板** — 每 50 毫秒更新一次,即時顯示測試執行的日誌訊息
-* **Material Design 主題** — 使用 qt-material 的 ``dark_amber.xml`` 主題
+* **測試參數表單** — URL、測試時間、user 數、spawn rate、HTTP method。
+* **開始按鈕** — 在背景 ``QThread`` 啟動壓測。
+* **即時統計面板** — 總請求、目前速率、平均與 p95 延遲、失敗數。每 1 秒重新整理。
+* **Log panel** — 即時框架日誌。
+* **Material Design 主題** — ``qt-material`` 的 ``dark_amber.xml``。
-語言支援
---------
+語言
+----
-GUI 支援兩種語言:
+GUI 內含英文、繁體中文、日文、韓文翻譯。透過 ``LanguageWrapper.reset_language`` 切換:
-* **英文**(預設)
-* **繁體中文**
+.. code-block:: python
-語言字串由 ``je_load_density/gui/language_wrapper/`` 下的 ``language_wrapper`` 模組管理。
+ from je_load_density.gui.language_wrapper.multi_language_wrapper import (
+ language_wrapper,
+ )
+ language_wrapper.reset_language("Japanese") # 或 Korean / Traditional_Chinese / English
架構
----
-GUI 由以下元件組成:
-
.. list-table::
:header-rows: 1
- :widths: 35 65
+ :widths: 30 70
* - 元件
- 說明
* - ``LoadDensityUI``
- - 主視窗(``QMainWindow``)。套用主題並包含 widget。
+ - ``QMainWindow`` 主機。套用主題並嵌入中央 widget。
* - ``LoadDensityWidget``
- - 中央 widget,包含表單輸入、啟動按鈕和日誌面板。
+ - 表單 + 開始按鈕 + 統計面板 + log panel。
+ * - ``StatsPanel``
+ - 由 QTimer 驅動、讀取 ``test_record_instance`` 的面板。
* - ``LoadDensityGUIThread``
- - 背景 ``QThread``,在不阻塞 UI 的情況下執行負載測試。
+ - 在背景跑測試的 ``QThread``,避免阻擋 UI。
* - ``InterceptAllFilter``
- - 日誌過濾器,將日誌訊息擷取到佇列中供 GUI 顯示。
+ - 將 log records 攔截至 thread-safe queue。
* - ``log_message_queue``
- - 執行緒安全的佇列,連接日誌系統與 GUI 日誌面板。
+ - 連接 logger 與 GUI log panel 的橋接 queue。
.. note::
- 在 Windows 平台上,GUI 會透過 ``ctypes`` 設定 ``AppUserModelID``,
- 讓工作列能正確識別應用程式。
+ 在 Windows 上,主視窗會以 ``ctypes`` 設定 ``AppUserModelID``,工作列才會顯示正確的應用名稱。
diff --git a/docs/source/Zh/doc/har_import/har_import_doc.rst b/docs/source/Zh/doc/har_import/har_import_doc.rst
new file mode 100644
index 0000000..a0de5e3
--- /dev/null
+++ b/docs/source/Zh/doc/har_import/har_import_doc.rst
@@ -0,0 +1,56 @@
+HAR 錄製/重放
+==============
+
+概觀
+----
+
+HAR 匯入器把錄製的 HTTP 流量(HAR JSON)轉換為 LoadDensity tasks 列表或完整可執行的動作 JSON。可從 Chrome / Firefox DevTools、mitmproxy、Charles 等工具匯出 `HAR 1.2 `_ 格式。
+
+Python API
+----------
+
+.. code-block:: python
+
+ from je_load_density import load_har, har_to_tasks, har_to_action_json
+
+ har = load_har("recording.har")
+ tasks = har_to_tasks(har, include=[r"example\.com"], exclude=[r"\.svg$"])
+ action_json = har_to_action_json(
+ har,
+ user="fast_http_user",
+ user_count=20,
+ spawn_rate=10,
+ test_time=120,
+ include=[r"api\.example\.com"],
+ )
+
+過濾
+----
+
+* ``include`` — regex 列表;URL 必須命中其一才保留。
+* ``exclude`` — regex 列表;URL 命中其一即丟棄。
+
+對應規則
+--------
+
+* HTTP method、URL、請求 headers 直接複製。
+* 移除 hop-by-hop 與 HTTP/2 pseudo header(``host``、``content-length``、``connection``、``:authority`` 等)。
+* JSON 請求 body(``application/json`` MIME)解析為 ``json`` 欄位;form params 變成 ``data`` dict;純文字 body 退回 ``data`` 字串。
+* 擷取的 response status 變成生成 task 上的 ``status_code`` 斷言。
+
+動作 JSON
+---------
+
+.. code-block:: json
+
+ {"load_density": [
+ ["LD_har_to_action_json", {
+ "har": {"log": {...}},
+ "user": "fast_http_user",
+ "user_count": 20,
+ "spawn_rate": 10,
+ "test_time": 120
+ }]
+ ]}
+
+``LD_har_to_action_json`` 的結果本身是動作 JSON,可儲存或餵給 ``LD_execute_action``。
diff --git a/docs/source/Zh/doc/http_users/http_users_doc.rst b/docs/source/Zh/doc/http_users/http_users_doc.rst
new file mode 100644
index 0000000..ce74266
--- /dev/null
+++ b/docs/source/Zh/doc/http_users/http_users_doc.rst
@@ -0,0 +1,79 @@
+HTTP 使用者
+===========
+
+概觀
+----
+
+LoadDensity 內含兩個 HTTP user 模板,皆透過 ``request_executor`` 與 ``scenario_runner`` 連線:
+
+* ``http_user`` — 封裝 ``locust.HttpUser``(底層 ``requests``)。
+* ``fast_http_user`` — 封裝 ``locust.FastHttpUser``(底層 geventhttpclient,吞吐高得多)。
+
+高負載情境選 ``fast_http_user``。需要 ``requests`` 特性或 middleware 時用 ``http_user``。
+
+Task 欄位
+---------
+
+每個 HTTP task 是 dict;runner 將下列欄位轉送底層 client,其餘欄位忽略。
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 75
+
+ * - 欄位
+ - 意義
+ * - ``method``
+ - ``get`` / ``post`` / ``put`` / ``patch`` / ``delete`` / ``head`` /
+ ``options``(不分大小寫)。
+ * - ``request_url`` / ``url``
+ - 目標 URL(絕對或相對 ``host``)。
+ * - ``name``
+ - Locust 事件名;預設等同 URL。
+ * - ``headers``
+ - 請求 headers dict。
+ * - ``params``
+ - Query string 參數(dict 或 list of pairs)。
+ * - ``json``
+ - 以 JSON 序列化的 body。
+ * - ``data``
+ - Form-encoded body(dict / list / str)。
+ * - ``cookies``
+ - cookies dict。
+ * - ``timeout``
+ - 單請求 timeout(秒)。
+ * - ``allow_redirects``、``verify``、``files``
+ - 直接轉送 client。
+ * - ``auth``
+ - ``{"type": "basic", "username": "...", "password": "..."}`` 或
+ ``{"type": "bearer", "token": "..."}``。
+ * - ``assertions``
+ - 回應斷言(見 :doc:`../assertions/assertions_doc`)。
+ * - ``extract``
+ - 回應擷取(見 :doc:`../parameter_resolver/parameter_resolver_doc`)。
+ * - ``weight``、``run_if``、``skip_if``
+ - 情境流程控制(見 :doc:`../scenarios/scenarios_doc`)。
+
+範例
+----
+
+.. code-block:: python
+
+ from je_load_density import start_test
+
+ start_test(
+ user_detail_dict={"user": "fast_http_user"},
+ user_count=50,
+ spawn_rate=10,
+ test_time=60,
+ variables={"base": "https://api.example.com"},
+ tasks=[
+ {"method": "post", "request_url": "${var.base}/login",
+ "json": {"email": "u@example.com", "password": "secret"},
+ "extract": [
+ {"var": "auth", "from": "json_path", "path": "data.token"}
+ ]},
+ {"method": "get", "request_url": "${var.base}/profile",
+ "headers": {"Authorization": "Bearer ${var.auth}"},
+ "assertions": [{"type": "status_code", "value": 200}]},
+ ],
+ )
diff --git a/docs/source/Zh/doc/installation/installation_doc.rst b/docs/source/Zh/doc/installation/installation_doc.rst
index ebf5b6f..07f7c84 100644
--- a/docs/source/Zh/doc/installation/installation_doc.rst
+++ b/docs/source/Zh/doc/installation/installation_doc.rst
@@ -1,11 +1,11 @@
安裝
====
-系統需求
---------
+需求
+----
-* Python **3.10** 或更新版本
-* pip 19.3 或更新版本
+* Python **3.10** 以上
+* pip 19.3 以上
支援平台
~~~~~~~~
@@ -15,64 +15,79 @@
:widths: 30 70
* - 平台
- - 版本
- * - Windows
- - 10 / 11
+ - 註記
+ * - Windows 10 / 11
+ - 完整支援
* - macOS
- - 10.15 ~ 11 (Big Sur)
- * - Linux
- - Ubuntu 20.04
+ - 完整支援
+ * - Ubuntu / Linux
+ - 完整支援
* - Raspberry Pi
- - 3B+
+ - 已測 3B+ 以上
基本安裝(CLI 與函式庫)
---------------------------
-
-從 PyPI 安裝 LoadDensity:
+------------------------
.. code-block:: bash
pip install je_load_density
-這會安裝核心函式庫與 CLI 工具。`Locust `_ 會作為相依套件自動安裝。
-
-安裝 GUI 支援
---------------
-
-若要使用可選的 PySide6 圖形化介面:
-
-.. code-block:: bash
-
- pip install je_load_density[gui]
-
-這會額外安裝:
-
-* `PySide6 `_ — Qt for Python 綁定
-* `qt-material `_ — Material Design 主題
+僅引入 `Locust `_ 與 ``defusedxml`` — 其餘皆為選用。
-開發者安裝
-----------
+選用 extras
+-----------
-從原始碼安裝進行開發:
+LoadDensity 將每個協定驅動、exporter、錄製器與控制介面都拆成可選 extras。基礎套件不會 eager import 這些模組,僅做 HTTP 壓測者執行期不受影響。
-.. code-block:: bash
-
- git clone https://github.com/Intergration-Automation-Testing/LoadDensity.git
- cd LoadDensity
- pip install -e .
- pip install -r dev_requirements.txt
-
-驗證安裝
+.. list-table::
+ :header-rows: 1
+ :widths: 25 75
+
+ * - Extra
+ - 加入
+ * - ``gui``
+ - PySide6 + qt-material(圖形介面)。
+ * - ``websocket``
+ - ``websocket-client``(WebSocket user 模板)。
+ * - ``grpc``
+ - ``grpcio`` + ``protobuf``(gRPC user 模板)。
+ * - ``mqtt``
+ - ``paho-mqtt``(MQTT user 模板)。
+ * - ``prometheus``
+ - ``prometheus-client``(Prometheus exporter)。
+ * - ``opentelemetry``
+ - OpenTelemetry SDK + OTLP gRPC exporter。
+ * - ``metrics``
+ - 結合 ``prometheus`` 與 ``opentelemetry``。
+ * - ``faker``
+ - ``Faker``(驅動 ``${faker.method}`` 占位符)。
+ * - ``mcp``
+ - ``mcp`` SDK(驅動 Claude 用的 MCP server)。
+ * - ``all``
+ - 上述全部。
+
+範例::
+
+ pip install "je_load_density[gui]"
+ pip install "je_load_density[mqtt,grpc,websocket]"
+ pip install "je_load_density[metrics]"
+ pip install "je_load_density[mcp]"
+ pip install "je_load_density[all]"
+
+開發安裝
--------
-安裝後,驗證 LoadDensity 是否正確安裝:
-
.. code-block:: bash
- python -c "from je_load_density import start_test; print('LoadDensity 安裝成功')"
+ git clone https://github.com/Integration-Automation/LoadDensity.git
+ cd LoadDensity
+ pip install -e ".[all]"
+ pip install -r requirements.txt
-也可以檢查已安裝的版本:
+驗證
+----
.. code-block:: bash
+ python -c "from je_load_density import start_test; print('LoadDensity installed')"
pip show je_load_density
diff --git a/docs/source/Zh/doc/locust_env/locust_env_doc.rst b/docs/source/Zh/doc/locust_env/locust_env_doc.rst
new file mode 100644
index 0000000..5f7aec7
--- /dev/null
+++ b/docs/source/Zh/doc/locust_env/locust_env_doc.rst
@@ -0,0 +1,57 @@
+Locust 環境
+===========
+
+概觀
+----
+
+``prepare_env`` 與 ``create_env`` 封裝 ``locust.env.Environment``,把 wiring runner、stats printer、可選 Web UI 的樣板程式都隱藏起來。
+
+create_env
+----------
+
+建立 ``Environment`` 與 runner,但不啟動任何 user:
+
+.. code-block:: python
+
+ from je_load_density import create_env
+ from je_load_density.wrapper.user_template.fast_http_user_template import (
+ FastHttpUserWrapper,
+ )
+
+ env = create_env(
+ FastHttpUserWrapper,
+ runner_mode="local", # "local" | "master" | "worker"
+ master_bind_host="*",
+ master_bind_port=5557,
+ master_host="127.0.0.1",
+ master_port=5557,
+ )
+
+當你需要在 runner 啟動前掛上額外事件監聽器時使用。
+
+prepare_env
+-----------
+
+完整生命週期 helper:建立 environment → 啟動 runner → 視情況啟動 Locust Web UI → 在 ``test_time`` 後停止 → join。
+
+.. code-block:: python
+
+ from je_load_density import prepare_env
+
+ prepare_env(
+ user_class=FastHttpUserWrapper,
+ user_count=50,
+ spawn_rate=10,
+ test_time=60,
+ web_ui_dict={"host": "127.0.0.1", "port": 8089},
+ )
+
+Web UI
+------
+
+傳入 ``web_ui_dict`` 即可啟動 Locust web UI。只有 local 與 master 模式會啟動 UI;workers 永遠不啟動。
+
+Stats greenlets
+---------------
+
+local 與 master 模式下,``create_env`` 會 spawn Locust 標準 ``stats_printer`` 與 ``stats_history`` greenlet。Workers 兩者皆跳過,因為由 master 收集並列印。
diff --git a/docs/source/Zh/doc/mcp_claude/mcp_claude_doc.rst b/docs/source/Zh/doc/mcp_claude/mcp_claude_doc.rst
new file mode 100644
index 0000000..f4c00b7
--- /dev/null
+++ b/docs/source/Zh/doc/mcp_claude/mcp_claude_doc.rst
@@ -0,0 +1,66 @@
+MCP Server(給 Claude)
+=======================
+
+概觀
+----
+
+LoadDensity 內含一個 `Model Context Protocol `_ server,將框架功能以 MCP 工具暴露。Claude(Desktop、Code 或任何 MCP 客戶端)可藉此驅動壓力測試、產生報告、匯入 HAR、檢視持久化資料,無需離開對話。
+
+安裝
+----
+
+.. code-block:: bash
+
+ pip install "je_load_density[mcp]"
+
+啟動
+----
+
+.. code-block:: bash
+
+ python -m je_load_density.mcp_server
+
+Server 透過 stdio 講 MCP。請接到你選用的客戶端(Claude Desktop ``claude_desktop_config.json``、Claude Code 等):
+
+.. code-block:: json
+
+ {
+ "mcpServers": {
+ "loaddensity": {
+ "command": "python",
+ "args": ["-m", "je_load_density.mcp_server"]
+ }
+ }
+ }
+
+提供的工具
+----------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 35 65
+
+ * - Tool
+ - 用途
+ * - ``load_density.run_test``
+ - 跑一個 Locust 壓測(HTTP / WS / gRPC / MQTT / Socket)。
+ * - ``load_density.run_action_json``
+ - 執行動作 JSON 文件。
+ * - ``load_density.create_project``
+ - 在 PATH 建立專案骨架。
+ * - ``load_density.list_executor_commands``
+ - 列出 executor 註冊的所有 ``LD_*`` 指令。
+ * - ``load_density.import_har``
+ - 將 HAR 檔轉成可執行的動作 JSON。
+ * - ``load_density.generate_reports``
+ - 產生 HTML / JSON / XML / CSV / JUnit / summary 任意組合。
+ * - ``load_density.summary``
+ - 回傳彙整統計(totals、per-name p50/p90/p95/p99)。
+ * - ``load_density.persist_records``
+ - 將目前紀錄寫入 SQLite 資料庫。
+ * - ``load_density.list_runs``
+ - 列出近期持久化的 runs。
+ * - ``load_density.fetch_run``
+ - 取出某次 run 的所有紀錄。
+ * - ``load_density.clear_records``
+ - 開始新一輪前清除記憶體中的紀錄。
diff --git a/docs/source/Zh/doc/metrics/metrics_doc.rst b/docs/source/Zh/doc/metrics/metrics_doc.rst
new file mode 100644
index 0000000..de7a217
--- /dev/null
+++ b/docs/source/Zh/doc/metrics/metrics_doc.rst
@@ -0,0 +1,84 @@
+指標 Exporter
+=============
+
+概觀
+----
+
+LoadDensity 內含三個可觀測性 sink,掛上 Locust 的 ``request`` 事件,逐請求送出指標。三者皆 lazy load,並以選用 extras 提供。
+
+Prometheus
+----------
+
+安裝:``pip install je_load_density[prometheus]``。
+
+.. code-block:: python
+
+ from je_load_density import start_prometheus_exporter
+ start_prometheus_exporter(port=9646, addr="127.0.0.1")
+
+指標:
+
+* ``loaddensity_requests_total{request_type, name, outcome}`` — counter
+* ``loaddensity_request_latency_ms{request_type, name}`` — histogram
+* ``loaddensity_response_bytes{request_type, name}`` — histogram
+
+預設僅綁 loopback。要對 Docker / Kubernetes scraping target 開放,請改傳 ``addr="0.0.0.0"``。
+
+InfluxDB
+--------
+
+僅用標準函式庫,無需額外套件。可選 UDP(fire-and-forget)或 HTTP(含 token)。
+
+.. code-block:: python
+
+ from je_load_density import start_influxdb_sink
+
+ # InfluxDB UDP listener
+ start_influxdb_sink(transport="udp", host="127.0.0.1", port=8089)
+
+ # HTTPS write API
+ start_influxdb_sink(
+ transport="http",
+ url="https://eu-central-1-1.aws.cloud2.influxdata.com/api/v2/write?org=...&bucket=...",
+ token="...",
+ )
+
+HTTP transport 會拒絕非 ``http://`` / ``https://`` 的 URL。
+
+OpenTelemetry
+-------------
+
+安裝:``pip install je_load_density[opentelemetry]``。
+
+.. code-block:: python
+
+ from je_load_density import start_opentelemetry_exporter
+ start_opentelemetry_exporter(
+ endpoint="http://otel-collector:4317",
+ service_name="loaddensity",
+ export_interval_ms=5000,
+ )
+
+送出的儀器:
+
+* ``loaddensity.requests`` — counter
+* ``loaddensity.request.latency`` — histogram(ms)
+* ``loaddensity.response.size`` — histogram(bytes)
+
+每個儀器皆攜帶 ``request_type``、``name``、``outcome`` 屬性。
+
+Stop helpers
+------------
+
+每個 ``start_*`` 都有對應的 ``stop_*``,會卸下 listener(並 shutdown OTel provider)。Prometheus HTTP server 因 ``prometheus_client`` 沒提供 stop hook,只能繼續執行。
+
+動作 JSON
+---------
+
+.. code-block:: json
+
+ {"load_density": [
+ ["LD_start_prometheus_exporter", {"port": 9646, "addr": "127.0.0.1"}],
+ ["LD_start_test", {...}],
+ ["LD_stop_prometheus_exporter", {}]
+ ]}
diff --git a/docs/source/Zh/doc/mqtt_user/mqtt_user_doc.rst b/docs/source/Zh/doc/mqtt_user/mqtt_user_doc.rst
new file mode 100644
index 0000000..18bead3
--- /dev/null
+++ b/docs/source/Zh/doc/mqtt_user/mqtt_user_doc.rst
@@ -0,0 +1,58 @@
+MQTT 使用者
+===========
+
+概觀
+----
+
+MQTT user 模板對 MQTT broker 進行 ``connect`` / ``publish`` / ``subscribe`` / ``disconnect``。底層使用 ``paho-mqtt``,lazy import — 以 ``pip install je_load_density[mqtt]`` 安裝。
+
+Task 欄位
+---------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 75
+
+ * - 欄位
+ - 意義
+ * - ``method``
+ - ``connect`` / ``publish`` / ``subscribe`` / ``disconnect``。
+ * - ``broker`` / ``host``
+ - MQTT broker 的 ``host:port``。
+ * - ``topic``
+ - 發佈/訂閱主題。
+ * - ``payload``
+ - publish body(``str`` 或 ``bytes``)。
+ * - ``qos``
+ - 0 / 1 / 2。
+ * - ``retain``
+ - 布林。
+ * - ``username`` / ``password``
+ - 憑證。
+ * - ``client_id``
+ - 選用 client id(預設為隨機十六進位字串)。
+ * - ``timeout``
+ - publish 等待 timeout(預設 5 秒)。
+
+範例
+----
+
+.. code-block:: python
+
+ from je_load_density import start_test
+
+ start_test(
+ user_detail_dict={"user": "mqtt_user"},
+ user_count=10,
+ spawn_rate=5,
+ test_time=60,
+ tasks=[
+ {"method": "connect", "broker": "127.0.0.1:1883"},
+ {"method": "subscribe", "topic": "telemetry/in", "qos": 1},
+ {"method": "publish", "topic": "telemetry/out",
+ "payload": "ping", "qos": 1},
+ {"method": "disconnect"},
+ ],
+ )
+
+每個步驟會觸發標記為 ``MQTT`` 的 Locust 事件。
diff --git a/docs/source/Zh/doc/parameter_resolver/parameter_resolver_doc.rst b/docs/source/Zh/doc/parameter_resolver/parameter_resolver_doc.rst
new file mode 100644
index 0000000..c4454bb
--- /dev/null
+++ b/docs/source/Zh/doc/parameter_resolver/parameter_resolver_doc.rst
@@ -0,0 +1,98 @@
+參數解析器
+==========
+
+概觀
+----
+
+參數解析器會展開任何巢狀 string / list / dict 結構中的 ``${...}`` 占位符。在每個 task 被 user 模板處理之前自動套用,讓資料能在動作之間順暢流動。
+
+支援的占位符
+------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 35 65
+
+ * - 占位符
+ - 解析為
+ * - ``${var.NAME}``
+ - 由 ``register_variable`` / ``register_variables`` 設定的值。
+ * - ``${env.NAME}``
+ - 環境變數 ``NAME``。
+ * - ``${csv.SOURCE.COLUMN}``
+ - CSV 來源 ``SOURCE`` 的下一筆資料中欄位 ``COLUMN`` 的值(預設循環)。
+ * - ``${faker.METHOD}``
+ - 呼叫 ``Faker().METHOD()``(lazy import,選用相依)。
+ * - ``${uuid()}``
+ - 新的 UUID 4 字串。
+ * - ``${now()}``
+ - 本地 ISO-8601 時間(秒)。
+ * - ``${randint(min, max)}``
+ - 介於 ``[min, max]`` 之間、密碼學強度的隨機整數。
+
+未知占位符會原樣保留,便於 dry run 時偵測缺值。
+
+註冊資料
+--------
+
+.. code-block:: python
+
+ from je_load_density import (
+ register_variable, register_variables,
+ register_csv_source, register_csv_sources,
+ )
+
+ register_variable("base", "https://api.example.com")
+ register_variables({"token": "abc", "tenant": "acme"})
+
+ register_csv_source("users", "users.csv") # 循環
+ register_csv_sources([
+ {"name": "products", "file_path": "products.csv", "cycle": False},
+ ])
+
+CSV 必須有 header;每次呼叫 ``${csv.name.col}`` 取下一行對應欄位。
+
+動作 JSON 用法
+--------------
+
+.. code-block:: json
+
+ {"load_density": [
+ ["LD_register_variables", {"variables": {"base": "https://api.example.com"}}],
+ ["LD_register_csv_sources", {"sources": [
+ {"name": "users", "file_path": "users.csv"}
+ ]}],
+ ["LD_start_test", {
+ "user_detail_dict": {"user": "fast_http_user"},
+ "tasks": [{
+ "method": "post",
+ "request_url": "${var.base}/login",
+ "json": {"email": "${csv.users.email}", "password": "${csv.users.password}"}
+ }]
+ }]
+ ]}
+
+從回應擷取值
+------------
+
+HTTP task 可以宣告 ``extract`` 規則;命中的值會寫回解析器:
+
+.. code-block:: json
+
+ {
+ "method": "post",
+ "request_url": "${var.base}/login",
+ "json": {"email": "u@example.com", "password": "secret"},
+ "extract": [
+ {"var": "auth_token", "from": "json_path", "path": "data.token"},
+ {"var": "request_id", "from": "header", "name": "X-Request-Id"},
+ {"var": "status", "from": "status_code"}
+ ]
+ }
+
+後續 task 即可用 ``${var.auth_token}`` 取用。
+
+清除
+----
+
+呼叫 ``parameter_resolver.clear()``(或 ``LD_clear_resolver``)以清除累積狀態。
diff --git a/docs/source/Zh/doc/scenarios/scenarios_doc.rst b/docs/source/Zh/doc/scenarios/scenarios_doc.rst
new file mode 100644
index 0000000..617e4ea
--- /dev/null
+++ b/docs/source/Zh/doc/scenarios/scenarios_doc.rst
@@ -0,0 +1,89 @@
+情境模式
+========
+
+概觀
+----
+
+HTTP / FastHttp / WebSocket user 的 tasks 可包成情境物件,控制每個 tick *要跑哪些 task*。三種模式:
+
+* ``sequence`` — 每個 task 依序執行(預設)。
+* ``weighted`` — 每 tick 依 ``weight`` 加權挑一個。
+* ``conditional`` — 以 ``run_if`` / ``skip_if`` 預測式(透過參數解析器評估)控制。
+
+格式
+----
+
+.. code-block:: json
+
+ {
+ "mode": "sequence",
+ "tasks": [
+ {"method": "get", "request_url": "${var.base}/products"},
+ {"method": "post", "request_url": "${var.base}/cart",
+ "json": {"product_id": 1}}
+ ]
+ }
+
+舊式 ``{"get": {...}}`` map 與裸列表也仍可用,runner 會 normalise 成 ``{"mode": "sequence", "tasks": [...]}``。
+
+加權挑選
+--------
+
+每個 task 可帶正整數 ``weight``;runner 每 tick 挑一個,機率與 weight 成正比。未提供 ``weight`` 預設 1。
+
+.. code-block:: json
+
+ {
+ "mode": "weighted",
+ "tasks": [
+ {"method": "get", "request_url": "/", "weight": 3},
+ {"method": "get", "request_url": "/expensive", "weight": 1}
+ ]
+ }
+
+條件流程
+--------
+
+``run_if`` 與 ``skip_if`` 皆使用相同預測式語言;``run_if`` 必須為真才執行該 task,``skip_if`` 必須為假才執行。
+
+預測式
+~~~~~~
+
+.. list-table::
+ :header-rows: 1
+ :widths: 30 70
+
+ * - 形式
+ - 意義
+ * - ``true`` / ``false`` / int
+ - 直接 truthy 檢查。
+ * - ``"${var.x}"``
+ - 解析占位符後 truthy 檢查。
+ * - ``{"equals": [a, b]}``
+ - 解析後 ``a == b``。
+ * - ``{"not_equals": [a, b]}``
+ - 解析後 ``a != b``。
+ * - ``{"in": [needle, haystack]}``
+ - ``needle in haystack``。
+ * - ``{"truthy": value}``
+ - 解析後 truthy 檢查。
+
+範例
+~~~~
+
+.. code-block:: json
+
+ {
+ "mode": "sequence",
+ "tasks": [
+ {"method": "post", "request_url": "/login",
+ "json": {"email": "${var.email}"},
+ "extract": [{"var": "auth", "from": "json_path", "path": "token"}]},
+ {"method": "get", "request_url": "/profile",
+ "headers": {"Authorization": "Bearer ${var.auth}"},
+ "run_if": {"truthy": "${var.auth}"}},
+ {"method": "post", "request_url": "/cart",
+ "json": {"product_id": 1},
+ "skip_if": {"equals": ["${var.tenant}", "internal"]}}
+ ]
+ }
diff --git a/docs/source/Zh/doc/scheduler/scheduler_doc.rst b/docs/source/Zh/doc/scheduler/scheduler_doc.rst
deleted file mode 100644
index ac6c0f9..0000000
--- a/docs/source/Zh/doc/scheduler/scheduler_doc.rst
+++ /dev/null
@@ -1,116 +0,0 @@
-排程器
-======
-
-LoadDensity 內建排程器,可讓您在指定的時間間隔排程重複執行測試。
-排程器支援阻塞(blocking)與非阻塞(non-blocking)兩種模式。
-
-基本用法
---------
-
-.. code-block:: python
-
- from je_load_density.utils.scheduler.scheduler_manager import SchedulerManager
-
- scheduler = SchedulerManager()
-
- def my_task():
- print("排程任務已執行")
-
- # 新增每 5 秒執行一次的工作(阻塞模式)
- scheduler.add_interval_blocking_secondly(my_task, seconds=5)
-
- # 啟動阻塞排程器
- scheduler.start_block_scheduler()
-
-阻塞 vs 非阻塞
-----------------
-
-排程器有兩種模式:
-
-* **阻塞模式** — ``start_block_scheduler()`` 會阻塞當前執行緒。適用於獨立的排程腳本。
-* **非阻塞模式** — ``start_nonblocking_scheduler()`` 在背景執行緒中執行排程器。
- 適用於需要繼續執行其他程式碼的情境。
-
-間隔方法(阻塞)
-~~~~~~~~~~~~~~~~~
-
-.. list-table::
- :header-rows: 1
- :widths: 50 50
-
- * - 方法
- - 說明
- * - ``add_interval_blocking_secondly(func, seconds)``
- - 每 N 秒執行一次
- * - ``add_interval_blocking_minutely(func, minutes)``
- - 每 N 分鐘執行一次
- * - ``add_interval_blocking_hourly(func, hours)``
- - 每 N 小時執行一次
- * - ``add_interval_blocking_daily(func, days)``
- - 每 N 天執行一次
- * - ``add_interval_blocking_weekly(func, weeks)``
- - 每 N 週執行一次
-
-間隔方法(非阻塞)
-~~~~~~~~~~~~~~~~~~~~
-
-.. list-table::
- :header-rows: 1
- :widths: 50 50
-
- * - 方法
- - 說明
- * - ``add_interval_nonblocking_secondly(func, seconds)``
- - 每 N 秒執行一次(非阻塞)
- * - ``add_interval_nonblocking_minutely(func, minutes)``
- - 每 N 分鐘執行一次(非阻塞)
- * - ``add_interval_nonblocking_hourly(func, hours)``
- - 每 N 小時執行一次(非阻塞)
- * - ``add_interval_nonblocking_daily(func, days)``
- - 每 N 天執行一次(非阻塞)
- * - ``add_interval_nonblocking_weekly(func, weeks)``
- - 每 N 週執行一次(非阻塞)
-
-Cron 方法
----------
-
-用於類似 cron 的排程:
-
-* ``add_cron_blocking(func, **cron_args)`` — 以阻塞模式新增 cron 工作
-* ``add_cron_nonblocking(func, **cron_args)`` — 以非阻塞模式新增 cron 工作
-
-工作管理
---------
-
-* ``remove_blocking_job(job_id)`` — 從阻塞排程器移除工作
-* ``remove_nonblocking_job(job_id)`` — 從非阻塞排程器移除工作
-
-啟動排程器
-----------
-
-* ``start_block_scheduler()`` — 啟動阻塞排程器(阻塞當前執行緒)
-* ``start_nonblocking_scheduler()`` — 啟動非阻塞排程器(背景執行)
-* ``start_all_scheduler()`` — 啟動兩種排程器
-
-範例:排程負載測試
--------------------
-
-.. code-block:: python
-
- from je_load_density import start_test
- from je_load_density.utils.scheduler.scheduler_manager import SchedulerManager
-
- scheduler = SchedulerManager()
-
- def run_test():
- start_test(
- user_detail_dict={"user": "fast_http_user"},
- user_count=10,
- spawn_rate=5,
- test_time=5,
- tasks={"get": {"request_url": "http://httpbin.org/get"}},
- )
-
- # 每 60 秒執行一次測試
- scheduler.add_interval_blocking_secondly(run_test, seconds=60)
- scheduler.start_block_scheduler()
diff --git a/docs/source/Zh/doc/socket_server/socket_server_doc.rst b/docs/source/Zh/doc/socket_server/socket_server_doc.rst
index 5cc1011..f9bd67b 100644
--- a/docs/source/Zh/doc/socket_server/socket_server_doc.rst
+++ b/docs/source/Zh/doc/socket_server/socket_server_doc.rst
@@ -1,106 +1,94 @@
-TCP Socket 伺服器(遠端執行)
-==============================
+TCP 控制 Socket Server
+======================
-LoadDensity 內建基於 ``gevent`` 的 TCP 伺服器,可透過網路接收 JSON 指令,實現遠端測試執行。
-
-啟動伺服器
-----------
-
-.. code-block:: python
+概觀
+----
- from je_load_density import start_load_density_socket_server
+控制 socket server 是 gevent 為基礎的 TCP listener,將收到的 LoadDensity 動作 JSON 透過網路執行。硬化版協定加入 length-prefix framing、選用 TLS,以及共享密鑰 token;舊版未驗證模式仍保留以維持相容。
- # 啟動伺服器(阻塞呼叫)
- start_load_density_socket_server(host="localhost", port=9940)
+模式
+----
.. list-table::
:header-rows: 1
- :widths: 20 15 15 50
+ :widths: 25 75
+
+ * - 模式
+ - 註記
+ * - ``legacy``
+ - 單次 ``recv(8192)``、純 JSON、無驗證。預設模式以維持舊客戶端(如 PyBreeze)相容。
+ * - ``framed``
+ - 4-byte big-endian 長度前綴 + JSON body。對 partial read 與超大 payload 較安全(1 MiB 上限)。
+ * - ``framed + TLS``
+ - 以 ``ssl.create_default_context``(TLS 1.2+)包裝連線,需 cert/key 檔案。
+
+驗證
+----
- * - 參數
- - 類型
- - 預設值
- - 說明
- * - ``host``
- - ``str``
- - ``"localhost"``
- - 伺服器綁定位址
- * - ``port``
- - ``int``
- - ``9940``
- - 伺服器綁定埠號
+傳入 ``token=``(或設定 ``LOAD_DENSITY_SOCKET_TOKEN``)即可要求共享密鑰。一旦設定:
-伺服器啟動後會輸出 ``Server started on {host}:{port}``。每個連入的連線會在獨立的
-``gevent`` greenlet 中處理,支援並行請求。
+* ``quit_server`` 沒有正確 token 將被拒絕。
+* 所有指令 payload 必須使用 envelope ``{"token": "...", "command": [...action JSON...]}``,可以 ``"op": "quit"`` 表示停機。
-從客戶端發送指令
------------------
+Token 以 ``hmac.compare_digest`` 比對,避免 timing oracle。
-指令以 JSON 編碼的動作列表發送 — 與 JSON 腳本檔案使用相同的格式。
+啟動 server
+-----------
-.. code-block:: python
+Python::
- import socket
- import json
-
- # 連接到伺服器
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- sock.connect(("localhost", 9940))
-
- # 發送測試指令
- command = json.dumps([
- ["LD_start_test", {
- "user_detail_dict": {"user": "fast_http_user"},
- "user_count": 10,
- "spawn_rate": 5,
- "test_time": 5,
- "tasks": {"get": {"request_url": "http://httpbin.org/get"}}
- }]
- ])
- sock.send(command.encode("utf-8"))
-
- # 接收回應
- response = sock.recv(8192)
- print(response.decode("utf-8"))
- sock.close()
+ from je_load_density import start_load_density_socket_server
-伺服器協定
-----------
+ start_load_density_socket_server(
+ host="0.0.0.0",
+ port=9940,
+ framed=True,
+ token="ROTATE_ME",
+ certfile="/etc/loaddensity/server.crt",
+ keyfile="/etc/loaddensity/server.key",
+ )
-* **指令格式**:JSON 編碼的動作列表(與 JSON 腳本檔案格式相同)
-* **回應**:每個動作的回傳值以一行傳回,最後以 ``Return_Data_Over_JE\n`` 結尾
-* **錯誤處理**:若執行過程中發生錯誤,錯誤訊息會傳回,後接 ``Return_Data_Over_JE\n``
-* **緩衝區大小**:每次接收 8192 bytes
+CLI::
-關閉伺服器
-----------
+ python -m je_load_density serve \
+ --host 0.0.0.0 --port 9940 --framed \
+ --token "$LOAD_DENSITY_SOCKET_TOKEN"
-發送字串 ``"quit_server"`` 即可優雅地關閉伺服器:
+傳送指令(framed 模式)
+-----------------------
.. code-block:: python
- import socket
-
- sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- sock.connect(("localhost", 9940))
- sock.send(b"quit_server")
- response = sock.recv(8192)
- print(response.decode("utf-8")) # "Server shutting down"
+ import json, socket, struct
+
+ payload = json.dumps({
+ "token": "ROTATE_ME",
+ "command": {"load_density": [["LD_summary", {}]]}
+ }).encode("utf-8")
+
+ sock = socket.create_connection(("127.0.0.1", 9940))
+ sock.sendall(struct.pack("!I", len(payload)) + payload)
+ while True:
+ header = sock.recv(4)
+ if not header:
+ break
+ (length,) = struct.unpack("!I", header)
+ chunk = sock.recv(length)
+ if chunk == b"Return_Data_Over_JE\n":
+ break
+ print(chunk.decode("utf-8"))
sock.close()
-伺服器會關閉所有連線並輸出 ``Server shutdown complete``。
-
-架構
+關閉
----
-TCP 伺服器由兩個元件組成:
+* Legacy 模式:傳送字面字串 ``quit_server``。
+* Framed 模式(含 token):傳送 ``{"token": "...", "op": "quit"}``。
-* **TCPServer** — 基於 ``gevent.socket`` 的主伺服器類別。監聽連線並為每個客戶端產生 greenlet。
-* **start_load_density_socket_server()** — 便利函式,呼叫
- ``gevent.monkey.patch_all()`` 並啟動伺服器。
+Server 列印 ``Server shutdown complete`` 後結束。
-.. note::
+注意事項
+--------
- 啟動 socket 伺服器時會呼叫 ``gevent.monkey.patch_all()``。這會修補標準函式庫模組
- (socket、threading 等)以相容 gevent。若將 socket 伺服器整合到較大的應用程式中,
- 請注意此行為。
+* 啟動時會呼叫 ``gevent.monkey.patch_all()``,整合時請留意。
+* token 可由環境變數 ``LOAD_DENSITY_SOCKET_TOKEN`` 讀取,避免將密鑰寫進指令參數。
diff --git a/docs/source/Zh/doc/socket_user/socket_user_doc.rst b/docs/source/Zh/doc/socket_user/socket_user_doc.rst
new file mode 100644
index 0000000..67a3c67
--- /dev/null
+++ b/docs/source/Zh/doc/socket_user/socket_user_doc.rst
@@ -0,0 +1,54 @@
+原生 TCP / UDP 使用者
+=====================
+
+概觀
+----
+
+原生 socket user 模板透過 TCP 或 UDP 收送任意 bytes,並可選擇讀取有限長度的回應。使用 Python 內建 ``socket`` 模組,無需額外相依。
+
+Task 欄位
+---------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 75
+
+ * - 欄位
+ - 意義
+ * - ``protocol``
+ - ``tcp`` 或 ``udp``。
+ * - ``target`` / ``host``
+ - ``host:port``。
+ * - ``payload``
+ - 要送出的 bytes。字串會以 UTF-8 編碼;以 ``hex:DEADBEEF`` 字首傳入十六進位字串可送出原始 bytes。
+ * - ``expect_bytes``
+ - 至多讀取 N bytes 的回應(0 表示略過讀取)。
+ * - ``expect_substring``
+ - 對解碼後回應的 substring 斷言。
+ * - ``timeout``
+ - 連線/讀取 timeout(秒),預設 5。
+ * - ``name``
+ - 事件名;預設 ``protocol:target``。
+
+範例
+----
+
+.. code-block:: python
+
+ from je_load_density import start_test
+
+ start_test(
+ user_detail_dict={"user": "socket_user"},
+ user_count=20,
+ spawn_rate=5,
+ test_time=60,
+ tasks=[
+ {"protocol": "tcp", "target": "127.0.0.1:9000",
+ "payload": "PING\\n", "expect_bytes": 64,
+ "expect_substring": "PONG"},
+ {"protocol": "udp", "target": "127.0.0.1:9000",
+ "payload": "hex:DEADBEEF", "expect_bytes": 4},
+ ],
+ )
+
+每個步驟會觸發標記為 ``TCP`` 或 ``UDP`` 的 Locust 事件。
diff --git a/docs/source/Zh/doc/sqlite_persistence/sqlite_persistence_doc.rst b/docs/source/Zh/doc/sqlite_persistence/sqlite_persistence_doc.rst
new file mode 100644
index 0000000..86ee90d
--- /dev/null
+++ b/docs/source/Zh/doc/sqlite_persistence/sqlite_persistence_doc.rst
@@ -0,0 +1,54 @@
+SQLite 持久化
+=============
+
+概觀
+----
+
+SQLite sink 將記憶體 ``test_record_instance`` 寫入 SQLite 資料庫,便於跨次比對、回歸檢查或匯出至其他工具。Schema 採延遲建立;空檔案即可使用。
+
+Python API
+----------
+
+.. code-block:: python
+
+ from je_load_density import (
+ persist_records, list_runs, fetch_run_records,
+ )
+
+ run_id = persist_records(
+ "loadtests.db",
+ label="checkout-2026-04-28",
+ metadata={"branch": "dev", "commit": "abc1234"},
+ )
+
+ for row in list_runs("loadtests.db", limit=10):
+ print(row)
+
+ for record in fetch_run_records("loadtests.db", run_id):
+ print(record)
+
+Schema
+------
+
+* ``load_density_runs(id, started_at, label, metadata_json)``
+* ``load_density_records(id, run_id, outcome, method, test_url, name,
+ status_code, response_time_ms, response_length, error)``
+
+``run_id`` 與 ``name`` 上建立索引以加速跨次查詢。
+
+動作 JSON
+---------
+
+.. code-block:: json
+
+ {"load_density": [
+ ["LD_clear_records", {}],
+ ["LD_start_test", {...}],
+ ["LD_persist_records", {
+ "database_path": "loadtests.db",
+ "label": "checkout",
+ "metadata": {"branch": "dev"}
+ }]
+ ]}
+
+之後的腳本以 ``LD_list_runs`` 與 ``LD_fetch_run_records`` 讀取資料。
diff --git a/docs/source/Zh/doc/start_test/start_test_doc.rst b/docs/source/Zh/doc/start_test/start_test_doc.rst
new file mode 100644
index 0000000..63f16d6
--- /dev/null
+++ b/docs/source/Zh/doc/start_test/start_test_doc.rst
@@ -0,0 +1,106 @@
+start_test 與 prepare_env
+=========================
+
+概觀
+----
+
+``start_test`` 是高層進入點,挑選 user 模板、種入參數解析器、請 ``prepare_env`` 以指定模式(local / master / worker)建立 Locust 環境。
+
+簽章
+----
+
+.. code-block:: python
+
+ from je_load_density import start_test
+
+ start_test(
+ user_detail_dict={"user": "fast_http_user"},
+ user_count=50,
+ spawn_rate=10,
+ test_time=60,
+ web_ui_dict=None, # {"host": "...", "port": ...}
+ runner_mode="local", # "local" | "master" | "worker"
+ master_bind_host="*",
+ master_bind_port=5557,
+ master_host="127.0.0.1",
+ master_port=5557,
+ expected_workers=0,
+ tasks=...,
+ variables={"host": "https://api.example.com"},
+ csv_sources=[{"name": "users", "file_path": "users.csv"}],
+ )
+
+支援的 user 類型
+----------------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 75
+
+ * - ``user``
+ - 模板
+ * - ``http_user``
+ - ``locust.HttpUser`` 封裝(以 ``requests`` 為底)。
+ * - ``fast_http_user``
+ - ``locust.FastHttpUser`` 封裝(以 ``geventhttpclient`` 為底)。
+ * - ``websocket_user``
+ - WebSocket 框架收送迴圈(lazy import ``websocket-client``)。
+ * - ``grpc_user``
+ - 對 operator 提供的 stub 進行 unary gRPC 呼叫。
+ * - ``mqtt_user``
+ - MQTT 發佈/訂閱迴圈。
+ * - ``socket_user``
+ - 原生 TCP / UDP 收送。
+
+prepare_env
+-----------
+
+``prepare_env`` 是 ``start_test`` 之下的較低階 API。當你想自行整合至其他 runner 時較有用。
+
+.. code-block:: python
+
+ from je_load_density import prepare_env
+ from je_load_density.wrapper.user_template.fast_http_user_template import (
+ FastHttpUserWrapper, set_wrapper_fasthttp_user,
+ )
+
+ set_wrapper_fasthttp_user(
+ {"user": "fast_http_user"},
+ tasks=[{"method": "get", "request_url": "https://example.com/"}],
+ )
+ prepare_env(
+ user_class=FastHttpUserWrapper,
+ user_count=50,
+ spawn_rate=10,
+ test_time=60,
+ runner_mode="local",
+ )
+
+分散式模式
+----------
+
+Master::
+
+ start_test(
+ user_detail_dict={"user": "fast_http_user"},
+ runner_mode="master",
+ master_bind_host="0.0.0.0",
+ master_bind_port=5557,
+ expected_workers=4,
+ user_count=200,
+ spawn_rate=20,
+ test_time=300,
+ tasks=[...],
+ )
+
+Worker(在每個壓測節點執行,與 master 同網段)::
+
+ start_test(
+ user_detail_dict={"user": "fast_http_user"},
+ runner_mode="worker",
+ master_host="10.0.0.10",
+ master_port=5557,
+ tasks=[...],
+ )
+
+Master 在開始 ramp 前會等待 ``expected_workers`` 個 worker 註冊完成。Workers 加入 master 後,會依群集規模分擔 user count。
diff --git a/docs/source/Zh/doc/test_record/test_record_doc.rst b/docs/source/Zh/doc/test_record/test_record_doc.rst
new file mode 100644
index 0000000..33d85b0
--- /dev/null
+++ b/docs/source/Zh/doc/test_record/test_record_doc.rst
@@ -0,0 +1,38 @@
+測試紀錄
+========
+
+概觀
+----
+
+``test_record_instance`` 為 Locust ``request`` hook 寫入的記憶體紀錄。所有報告產生器(HTML / JSON / XML / CSV / JUnit / summary)都從此物件讀取,SQLite 持久化 helper 將其寫入磁碟。
+
+紀錄欄位
+--------
+
+每筆紀錄為 dict,欄位如下:
+
+* ``Method`` — HTTP method 或協定標籤(``GET``、``POST``、``WS``、``GRPC``、``MQTT``、``TCP``、``UDP``)。
+* ``test_url`` — 目標 URL 或位址。
+* ``name`` — Locust 事件名(未指定時為 ``request_url``)。
+* ``status_code`` — 回應 status(字串)或 ``None``。
+* ``response_time_ms`` — Locust 回報的回應時間(ms)。
+* ``response_length`` — 回應大小(bytes)。
+* ``error`` — 成功為 ``None``;失敗為 exception 字串。
+* ``text``、``content``、``headers`` — 選用,僅 HTTP 成功才有。
+
+清除
+----
+
+.. code-block:: python
+
+ from je_load_density import test_record_instance
+ test_record_instance.clear_records()
+
+或透過 executor::
+
+ ["LD_clear_records", {}]
+
+SQLite 持久化
+-------------
+
+見 :doc:`../sqlite_persistence/sqlite_persistence_doc`。
diff --git a/docs/source/Zh/doc/websocket_user/websocket_user_doc.rst b/docs/source/Zh/doc/websocket_user/websocket_user_doc.rst
new file mode 100644
index 0000000..3b7d494
--- /dev/null
+++ b/docs/source/Zh/doc/websocket_user/websocket_user_doc.rst
@@ -0,0 +1,50 @@
+WebSocket 使用者
+================
+
+概觀
+----
+
+WebSocket user 模板對指定的 ``ws://`` / ``wss://`` URL 做 connect / send / recv 迴圈。底層使用 ``websocket-client``,採 lazy import — 以 ``pip install je_load_density[websocket]`` 安裝。
+
+Task 欄位
+---------
+
+.. list-table::
+ :header-rows: 1
+ :widths: 25 75
+
+ * - 欄位
+ - 意義
+ * - ``method``
+ - ``connect`` / ``send`` / ``recv`` / ``sendrecv`` / ``close``。
+ * - ``request_url`` / ``url``
+ - WebSocket URL(``connect`` 必填,其餘步驟可重用上次連線)。
+ * - ``name``
+ - 事件名;預設為 URL 或 method。
+ * - ``payload``
+ - 要送出的字串 / bytes。
+ * - ``expect``
+ - 對接收到的 frame 做 substring 斷言。
+ * - ``timeout``
+ - 接收 timeout(秒),預設 5。
+
+範例
+----
+
+.. code-block:: python
+
+ from je_load_density import start_test
+
+ start_test(
+ user_detail_dict={"user": "websocket_user"},
+ user_count=10,
+ spawn_rate=5,
+ test_time=60,
+ tasks=[
+ {"method": "connect", "request_url": "wss://echo.example.com/socket"},
+ {"method": "sendrecv", "payload": '{"ping": 1}', "expect": "pong"},
+ {"method": "close"},
+ ],
+ )
+
+每個步驟會觸發標記為 ``WS`` 的 Locust 事件,供統計彙整。
diff --git a/docs/source/Zh/zh_index.rst b/docs/source/Zh/zh_index.rst
index f15f817..bece33e 100644
--- a/docs/source/Zh/zh_index.rst
+++ b/docs/source/Zh/zh_index.rst
@@ -1,19 +1,160 @@
-繁體中文文件
-=============================================
+====================================
+LoadDensity 繁體中文手冊
+====================================
-歡迎來到 LoadDensity 繁體中文文件。LoadDensity 是一個建構於 Locust 之上的負載與壓力測試自動化框架,
-提供簡化的 API、JSON 驅動的測試腳本、多格式報告產生、可選的 GUI 介面,以及遠端執行功能。
+繁體中文手冊依照讀者使用順序分為十章:安裝 → 執行壓測 → 撰寫動作腳本 → 擴展 → 整合。可使用左側目次,或直接跳到下方章節。
+
+.. contents:: 本頁目次
+ :local:
+ :depth: 1
+
+----
+
+.. _zh-getting-started:
+
+第 1 章 — 入門
+==============
+
+安裝 LoadDensity、執行第一次壓測,並建立專案骨架。
.. toctree::
- :maxdepth: 4
- :caption: 使用指南
+ :maxdepth: 2
+ :caption: 入門
doc/installation/installation_doc
doc/getting_started/getting_started_doc
- doc/cli/cli_doc
- doc/generate_report/generate_report_doc
- doc/scheduler/scheduler_doc
- doc/socket_server/socket_server_doc
+ doc/create_project/create_project_doc
+
+.. _zh-core-api:
+
+第 2 章 — 核心 API
+==================
+
+面向 Locust 的封裝:環境、Runner、使用者代理。讀過這章後,整個框架就不再神秘。
+
+.. toctree::
+ :maxdepth: 2
+ :caption: 核心 API
+
+ doc/architecture/architecture_doc
+ doc/start_test/start_test_doc
+ doc/locust_env/locust_env_doc
+
+.. _zh-actions:
+
+第 3 章 — 動作撰寫與執行
+========================
+
+組合 JSON 動作腳本、參數化資料、建立情境流程,串接測試後 callback。
+
+.. toctree::
+ :maxdepth: 2
+ :caption: 動作
+
+ doc/action_executor/action_executor_doc
+ doc/parameter_resolver/parameter_resolver_doc
+ doc/scenarios/scenarios_doc
+ doc/assertions/assertions_doc
doc/callback/callback_doc
doc/package_manager/package_manager_doc
+
+.. _zh-user-templates:
+
+第 4 章 — 使用者模板
+====================
+
+協定驅動程式:HTTP、FastHttp、WebSocket、gRPC、MQTT,以及原生 TCP/UDP。每個模板皆以 Locust 使用者註冊,採用相同的 task 契約。
+
+.. toctree::
+ :maxdepth: 2
+ :caption: 使用者模板
+
+ doc/http_users/http_users_doc
+ doc/websocket_user/websocket_user_doc
+ doc/grpc_user/grpc_user_doc
+ doc/mqtt_user/mqtt_user_doc
+ doc/socket_user/socket_user_doc
+
+.. _zh-reporting:
+
+第 5 章 — 報告與可觀測性
+========================
+
+產生 HTML / JSON / XML / CSV / JUnit / 百分位摘要報告,將指標送至 Prometheus、InfluxDB,或任何 OTLP 後端。
+
+.. toctree::
+ :maxdepth: 2
+ :caption: 報告
+
+ doc/generate_report/generate_report_doc
+ doc/metrics/metrics_doc
+ doc/test_record/test_record_doc
+
+.. _zh-orchestration:
+
+第 6 章 — 編排與擴展
+====================
+
+執行分散式 master/worker 群集、透過參數解析器共享狀態、依擷取變數控制執行流程。
+
+.. toctree::
+ :maxdepth: 2
+ :caption: 編排
+
+ doc/distributed/distributed_doc
+
+.. _zh-recording-data:
+
+第 7 章 — 錄製與資料
+====================
+
+將真實瀏覽流量(HAR)轉換為可執行的動作 JSON,將測試紀錄持久化到 SQLite,並比對歷次執行結果。
+
+.. toctree::
+ :maxdepth: 2
+ :caption: 錄製與資料
+
+ doc/har_import/har_import_doc
+ doc/sqlite_persistence/sqlite_persistence_doc
+
+.. _zh-tooling:
+
+第 8 章 — 工具、CLI 與診斷
+==========================
+
+命令列子指令、硬化的控制 socket server,以及 traceback 中可能出現的例外階層。
+
+.. toctree::
+ :maxdepth: 2
+ :caption: 工具
+
+ doc/cli/cli_doc
+ doc/socket_server/socket_server_doc
+ doc/exception/exception_doc
+
+.. _zh-integrations:
+
+第 9 章 — 整合
+==============
+
+選用的 GUI、可讓 Claude 驅動 LoadDensity 的 **Model Context Protocol (MCP)** server,以及下游 PyBreeze IDE 整合。
+
+.. toctree::
+ :maxdepth: 2
+ :caption: 整合
+
doc/gui/gui_doc
+ doc/mcp_claude/mcp_claude_doc
+
+.. _zh-reference:
+
+第 10 章 — API Reference
+========================
+
+自動產生的 Python API reference。
+
+.. toctree::
+ :maxdepth: 2
+ :caption: 參考
+
+ doc/api_reference/api_reference
diff --git a/docs/source/conf.py b/docs/source/conf.py
index da1537c..e5bef3d 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -6,30 +6,65 @@
import os
import sys
-# -- Path setup --------------------------------------------------------------
-sys.path.insert(0, os.path.abspath('../..'))
+sys.path.insert(0, os.path.abspath('.'))
+# Reach the repo root so ``import je_load_density`` works inside autodoc.
+sys.path.insert(0, os.path.abspath(os.path.join(os.pardir, os.pardir)))
# -- Project information -----------------------------------------------------
+
project = 'LoadDensity'
-project_copyright = '2022, JE-Chen'
+project_copyright = '2022 ~ 2025, JE-Chen'
author = 'JE-Chen'
-
-# The full version, including alpha/beta/rc tags
release = '0.0.65'
# -- General configuration ---------------------------------------------------
-extensions = []
-templates_path = ['_templates']
+extensions = [
+ "sphinx.ext.autodoc",
+ "sphinx.ext.autosummary",
+ "sphinx.ext.autosectionlabel",
+ "sphinx.ext.napoleon",
+ "sphinxcontrib.mermaid",
+]
+
+# Autosummary writes per-module reference pages on every build.
+autosummary_generate = True
+# autosectionlabel collides on common section titles repeated across
+# language manuals; prefix every label with the document path so
+# duplicates become unique.
+autosectionlabel_prefix_document = True
+autodoc_default_options = {
+ "members": True,
+ "undoc-members": False,
+ "show-inheritance": True,
+}
+# Autodoc imports the modules it documents; some carry soft deps
+# that aren't installed in the docs build environment, so silence them.
+autodoc_mock_imports = [
+ "locust",
+ "gevent",
+ "PySide6",
+ "qt_material",
+ "defusedxml",
+ "websocket",
+ "grpc",
+ "paho",
+ "prometheus_client",
+ "opentelemetry",
+ "faker",
+ "mcp",
+]
+mermaid_version = "10.9.0"
+
+templates_path = ['_templates']
exclude_patterns = []
# -- Options for HTML output -------------------------------------------------
-html_theme = 'sphinx_rtd_theme'
+html_theme = 'sphinx_rtd_theme'
html_static_path = ['_static']
-# -- Options for HTML theme --------------------------------------------------
html_theme_options = {
'navigation_depth': 4,
'collapse_navigation': False,
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 5ffc108..dccf857 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -1,22 +1,49 @@
-Welcome to LoadDensity's documentation!
-=============================================
+==================
+LoadDensity
+==================
-**LoadDensity** is a high-performance load & stress testing automation framework built on top of
-`Locust `_. It provides a simplified wrapper around Locust's core functionality,
-enabling fast user spawning, flexible test configuration via templates and JSON-driven scripts,
-report generation in multiple formats (HTML / JSON / XML), a built-in GUI, remote execution
-via TCP socket server, and a callback mechanism for post-test workflows.
+**A multi-protocol load and stress automation framework built on Locust**
-.. note::
+LoadDensity (``je_load_density``) wraps Locust with a JSON-driven action
+executor and adds first-class support for HTTP, WebSocket, gRPC, MQTT,
+and raw TCP/UDP user templates. Every executor command has a
+deterministic name (``LD_*``) and a single dispatch point, so an action
+JSON can mix protocols, parameterised data, scenario flow, reports, and
+metrics exporters in the same script.
- - **PyPI**: https://pypi.org/project/je_load_density/
- - **Source Code**: https://github.com/Intergration-Automation-Testing/LoadDensity
- - **License**: MIT
+* **PyPI**: https://pypi.org/project/je_load_density/
+* **GitHub**: https://github.com/Integration-Automation/LoadDensity
+* **License**: MIT
+
+----
+
+The documentation is split by language and by content type. Each
+language manual is organised into chapters (Getting Started, Core API,
+Actions, User Templates, Reporting, Orchestration, Recording & Data,
+Tooling, Integrations, Reference); the API book holds the
+auto-generated Python reference.
+
+.. toctree::
+ :maxdepth: 2
+ :caption: English manual
+
+ En/en_index.rst
.. toctree::
- :maxdepth: 4
- :caption: Contents
+ :maxdepth: 2
+ :caption: 繁體中文手冊
+
+ Zh/zh_index.rst
+
+.. toctree::
+ :maxdepth: 2
+ :caption: API reference
+
+ api/api_index.rst
+
+----
+
+RoadMap
+-------
- En/en_index
- Zh/zh_index
- api/api_index
+* Project Kanban: https://github.com/orgs/Integration-Automation/projects