Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
746 changes: 505 additions & 241 deletions README.md

Large diffs are not rendered by default.

724 changes: 478 additions & 246 deletions README/README_zh-CN.md

Large diffs are not rendered by default.

734 changes: 483 additions & 251 deletions README/README_zh-TW.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
sphinx-rtd-theme
sphinx-rtd-theme
sphinxcontrib-mermaid
176 changes: 176 additions & 0 deletions docs/source/En/doc/action_executor/action_executor_doc.rst
Original file line number Diff line number Diff line change
@@ -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.
37 changes: 37 additions & 0 deletions docs/source/En/doc/api_reference/api_reference.rst
Original file line number Diff line number Diff line change
@@ -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
95 changes: 95 additions & 0 deletions docs/source/En/doc/architecture/architecture_doc.rst
Original file line number Diff line number Diff line change
@@ -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.
76 changes: 76 additions & 0 deletions docs/source/En/doc/assertions/assertions_doc.rst
Original file line number Diff line number Diff line change
@@ -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"}
]
}
Loading
Loading