locust-plugins 4.2.0__tar.gz → 4.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/.pylintrc +1 -1
- {locust-plugins-4.2.0/locust_plugins.egg-info → locust-plugins-4.3.1}/PKG-INFO +26 -2
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/README.md +2 -2
- locust-plugins-4.3.1/examples/distributor_ex.py +41 -0
- locust-plugins-4.3.1/examples/products.csv +2 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/_version.py +2 -2
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/csvreader.py +4 -3
- locust-plugins-4.3.1/locust_plugins/distributor.py +59 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/mongoreader.py +52 -3
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/users/socketio.py +2 -2
- {locust-plugins-4.2.0 → locust-plugins-4.3.1/locust_plugins.egg-info}/PKG-INFO +26 -2
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins.egg-info/SOURCES.txt +3 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins.egg-info/requires.txt +1 -1
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/setup.py +2 -1
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/tox.ini +4 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/.github/workflows/tests.yml +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/.gitignore +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/.vscode/launch.json +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/.vscode/settings.json +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/CONTRIBUTING.md +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/LICENSE +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/MANIFEST.in +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/Makefile +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/bin/locust-compose +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/appinsights_listener_ex.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/cloudwatch_listener_ex.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/cmd_line_examples.sh +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/connection_pool_ex.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/constant_total_ips_ex.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/csvreader_ex.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/embedded_resource_manager_ex.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/jmeter_listener_example.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/kafka_ex.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/mongoreader_ex.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/mqtt_ex.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/playwright-recording.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/playwright_ex.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/reschedule_on_fail_ex.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/rest_ex.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/socketio_ex.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/ssn.csv +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/ssn.tsv +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/transaction_example.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/transaction_example_as_library_local.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/transaction_example_as_library_master.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/transaction_example_as_library_worker.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/tsvreader_ex.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/webdriver_ex.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/__init__.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/connection_pools.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/Makefile +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/README.md +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/docker-compose.yml +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/locust-grafana/Dockerfile +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/locust-grafana/Makefile +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/locust-grafana/create_datasource.sh +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/locust-grafana/grafana_setup.sh +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/locust-grafana/import_dashboards.sh +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/locust-timescale/Dockerfile +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/locust-timescale/Makefile +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/locust-timescale/timescale_schema.sql +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/locust-timescale/zz_hypertable.sql +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/screenshots/main_dashboard.png +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/screenshots/main_dashboard_by_request_graphs.png +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/screenshots/requests_table.png +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/screenshots/scatter_plot.png +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/screenshots/testruns.png +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/listeners/__init__.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/listeners/appinsights.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/listeners/cloudwatch.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/listeners/jmeter.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/listeners/timescale.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/py.typed +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/transaction_manager.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/users/__init__.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/users/kafka.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/users/mqtt.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/users/playwright.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/users/resource.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/users/rest.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/users/webdriver.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/utils.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/wait_time.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins.egg-info/dependency_links.txt +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins.egg-info/not-zip-safe +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins.egg-info/top_level.txt +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/mypy.ini +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/pyproject.toml +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/setup.cfg +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/test/test.csv +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/test/test_all.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/test/test_cloudwatch_plugin.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/test/test_missing_extras.py +0 -0
- {locust-plugins-4.2.0 → locust-plugins-4.3.1}/tusk.yaml +0 -0
|
@@ -11,7 +11,7 @@ attr-rgx=[a-z0-9_]{1,30}$
|
|
|
11
11
|
ignored-argument-names=_.*|^ignored_|^unused_|^kwargs|^environment
|
|
12
12
|
|
|
13
13
|
[MESSAGES CONTROL]
|
|
14
|
-
disable=logging-not-lazy,logging-fstring-interpolation,missing-docstring,wrong-import-position,wrong-import-order,too-few-public-methods,invalid-name,protected-access,logging-format-interpolation,dangerous-default-value,global-statement,too-many-locals,too-many-arguments,too-many-instance-attributes,blacklisted-name,attribute-defined-outside-init,broad-except,bare-except,consider-using-with,too-many-branches,unspecified-encoding,arguments-differ,broad-exception-raised
|
|
14
|
+
disable=logging-not-lazy,logging-fstring-interpolation,missing-docstring,wrong-import-position,wrong-import-order,too-few-public-methods,invalid-name,protected-access,logging-format-interpolation,dangerous-default-value,global-statement,too-many-locals,too-many-arguments,too-many-instance-attributes,blacklisted-name,attribute-defined-outside-init,broad-except,bare-except,consider-using-with,too-many-branches,unspecified-encoding,arguments-differ,broad-exception-raised,wildcard-import,keyword-arg-before-vararg
|
|
15
15
|
|
|
16
16
|
[MASTER]
|
|
17
17
|
ignore=locustio,examples
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: locust-plugins
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.3.1
|
|
4
4
|
Summary: Useful plugins/extensions for Locust
|
|
5
5
|
Home-page: https://github.com/SvenskaSpel/locust-plugins
|
|
6
6
|
Author: Lars Holmberg
|
|
@@ -16,20 +16,44 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.9
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
20
|
Classifier: Intended Audience :: Developers
|
|
20
21
|
Classifier: Intended Audience :: System Administrators
|
|
21
22
|
Requires-Python: >=3.8, <4
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: locust>=2.20.0
|
|
25
|
+
Requires-Dist: typing-extensions
|
|
22
26
|
Provides-Extra: websocket
|
|
27
|
+
Requires-Dist: websocket-client; extra == "websocket"
|
|
23
28
|
Provides-Extra: playwright
|
|
29
|
+
Requires-Dist: playwright>=1.34.0; extra == "playwright"
|
|
24
30
|
Provides-Extra: dashboards
|
|
31
|
+
Requires-Dist: psycogreen; extra == "dashboards"
|
|
32
|
+
Requires-Dist: psycopg2-binary; extra == "dashboards"
|
|
25
33
|
Provides-Extra: kafka
|
|
34
|
+
Requires-Dist: confluent-kafka; extra == "kafka"
|
|
26
35
|
Provides-Extra: mongo
|
|
36
|
+
Requires-Dist: pymongo; extra == "mongo"
|
|
27
37
|
Provides-Extra: mqtt
|
|
38
|
+
Requires-Dist: paho-mqtt>=1.5.0; extra == "mqtt"
|
|
28
39
|
Provides-Extra: appinsights
|
|
40
|
+
Requires-Dist: opencensus-ext-azure; extra == "appinsights"
|
|
29
41
|
Provides-Extra: resource
|
|
42
|
+
Requires-Dist: lxml; extra == "resource"
|
|
30
43
|
Provides-Extra: boto3
|
|
44
|
+
Requires-Dist: boto3; extra == "boto3"
|
|
31
45
|
Provides-Extra: all
|
|
46
|
+
Requires-Dist: websocket-client; extra == "all"
|
|
47
|
+
Requires-Dist: playwright>=1.34.0; extra == "all"
|
|
48
|
+
Requires-Dist: psycogreen; extra == "all"
|
|
49
|
+
Requires-Dist: psycopg2-binary; extra == "all"
|
|
50
|
+
Requires-Dist: confluent-kafka; extra == "all"
|
|
51
|
+
Requires-Dist: pymongo; extra == "all"
|
|
52
|
+
Requires-Dist: paho-mqtt>=1.5.0; extra == "all"
|
|
53
|
+
Requires-Dist: opencensus-ext-azure; extra == "all"
|
|
54
|
+
Requires-Dist: lxml; extra == "all"
|
|
55
|
+
Requires-Dist: boto3; extra == "all"
|
|
32
56
|
Provides-Extra: webdriver
|
|
33
|
-
|
|
57
|
+
Requires-Dist: selenium>=4.0.0; extra == "webdriver"
|
|
34
58
|
|
|
35
59
|
https://github.com/SvenskaSpel/locust-plugins
|
|
@@ -55,8 +55,8 @@ locust --help
|
|
|
55
55
|
## Wait time
|
|
56
56
|
- Custom wait time functions ([example](examples/constant_total_ips_ex.py), [source](locust_plugins/wait_time.py))
|
|
57
57
|
|
|
58
|
-
##
|
|
59
|
-
- Support for
|
|
58
|
+
## Distributing test data
|
|
59
|
+
- Support for distributing test data from master to workers while maintaining test data order ([example](examples/distributor_ex.py), [source](locust_plugins/distributor.py))
|
|
60
60
|
|
|
61
61
|
## Transaction manager
|
|
62
62
|
- Support for logging transactions (aggregating multiple requests or other actions) ([example](examples/transaction_example.py), [source](locust_plugins/transaction_manager.py))
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# this example is a little more complex than it needs to be, but I wanted to highlight
|
|
2
|
+
# that it is entirely possible to have multiple distributors at the same time
|
|
3
|
+
|
|
4
|
+
from typing import Dict, List
|
|
5
|
+
from locust_plugins.mongoreader import MongoLRUReader
|
|
6
|
+
from locust_plugins.csvreader import CSVDictReader, CSVReader
|
|
7
|
+
from locust_plugins.distributor import Distributor
|
|
8
|
+
from locust import HttpUser, task, run_single_user, events
|
|
9
|
+
from locust.runners import WorkerRunner
|
|
10
|
+
|
|
11
|
+
distributors = {}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@events.init.add_listener
|
|
15
|
+
def on_locust_init(environment, **_kwargs):
|
|
16
|
+
ssn_reader = None
|
|
17
|
+
product_reader = None
|
|
18
|
+
if not isinstance(environment.runner, WorkerRunner):
|
|
19
|
+
product_reader = CSVReader("products.csv")
|
|
20
|
+
csv = True
|
|
21
|
+
if csv:
|
|
22
|
+
ssn_reader = CSVDictReader("ssn.tsv", delimiter="\t")
|
|
23
|
+
else:
|
|
24
|
+
ssn_reader = MongoLRUReader({"foo": "bar"}, "last_login")
|
|
25
|
+
product_reader = CSVReader("products.csv")
|
|
26
|
+
distributors["customers"] = Distributor(environment, ssn_reader, "customers")
|
|
27
|
+
distributors["products"] = Distributor(environment, product_reader, "products")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class MyUser(HttpUser):
|
|
31
|
+
host = "http://www.example.com"
|
|
32
|
+
|
|
33
|
+
@task
|
|
34
|
+
def my_task(self) -> None:
|
|
35
|
+
customer: Dict = next(distributors["customers"])
|
|
36
|
+
product: List[str] = next(distributors["products"])
|
|
37
|
+
self.client.get(f"/?customer={customer['ssn']}&product={product[0]}")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
run_single_user(MyUser)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import csv
|
|
2
|
+
from typing import Iterator, Dict
|
|
2
3
|
|
|
3
4
|
|
|
4
|
-
class CSVReader:
|
|
5
|
+
class CSVReader(Iterator):
|
|
5
6
|
"Read test data from csv file using an iterator"
|
|
6
7
|
|
|
7
8
|
def __init__(self, file, **kwargs):
|
|
@@ -21,8 +22,8 @@ class CSVReader:
|
|
|
21
22
|
return next(self.reader)
|
|
22
23
|
|
|
23
24
|
|
|
24
|
-
class CSVDictReader:
|
|
25
|
-
"Read test data from csv file using an iterator"
|
|
25
|
+
class CSVDictReader(Iterator[Dict]):
|
|
26
|
+
"Read test data from csv file using an iterator, returns rows as dicts"
|
|
26
27
|
|
|
27
28
|
def __init__(self, file, **kwargs):
|
|
28
29
|
try:
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from typing import Dict, Iterator, Optional
|
|
2
|
+
import logging
|
|
3
|
+
from gevent.event import AsyncResult
|
|
4
|
+
import greenlet
|
|
5
|
+
from locust.env import Environment
|
|
6
|
+
from locust.runners import WorkerRunner
|
|
7
|
+
|
|
8
|
+
_results: Dict[int, AsyncResult] = {}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Distributor:
|
|
12
|
+
def __init__(self, environment: Environment, iterator: Optional[Iterator], name="distributor"):
|
|
13
|
+
"""Register distributor method handlers and tie them to use the iterator that you pass.
|
|
14
|
+
|
|
15
|
+
iterator is not used on workers, so you can leave it as None there.
|
|
16
|
+
"""
|
|
17
|
+
self.iterator = iterator
|
|
18
|
+
self.name = name
|
|
19
|
+
self.runner = environment.runner
|
|
20
|
+
assert iterator or isinstance(
|
|
21
|
+
self.runner, WorkerRunner
|
|
22
|
+
), "iterator is a mandatory parameter when not on a worker runner"
|
|
23
|
+
if self.runner:
|
|
24
|
+
# received on master
|
|
25
|
+
def _distributor_request(environment: Environment, msg, **kwargs):
|
|
26
|
+
item = next(self.iterator)
|
|
27
|
+
self.runner.send_message(
|
|
28
|
+
f"_{name}_response",
|
|
29
|
+
{"item": item, "gid": msg.data["gid"]},
|
|
30
|
+
client_id=msg.data["client_id"],
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# received on worker
|
|
34
|
+
def _distributor_response(environment: Environment, msg, **kwargs):
|
|
35
|
+
_results[msg.data["gid"]].set(msg.data)
|
|
36
|
+
|
|
37
|
+
self.runner.register_message(f"_{name}_request", _distributor_request)
|
|
38
|
+
self.runner.register_message(f"_{name}_response", _distributor_response)
|
|
39
|
+
|
|
40
|
+
def __next__(self):
|
|
41
|
+
"""Get the next data dict from iterator
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
user (User): current user object (we use the object id of the User to keep track of who's waiting for which data)
|
|
45
|
+
"""
|
|
46
|
+
if not self.runner: # no need to do anything clever if there is no runner
|
|
47
|
+
assert self.iterator
|
|
48
|
+
return next(self.iterator)
|
|
49
|
+
|
|
50
|
+
gid = greenlet.getcurrent().minimal_ident # type: ignore
|
|
51
|
+
|
|
52
|
+
if gid in _results:
|
|
53
|
+
logging.warning("This user was already waiting for data. Strange.")
|
|
54
|
+
|
|
55
|
+
_results[gid] = AsyncResult()
|
|
56
|
+
self.runner.send_message(f"_{self.name}_request", {"gid": gid, "client_id": self.runner.client_id})
|
|
57
|
+
item = _results[gid].get()["item"] # this waits for the reply
|
|
58
|
+
del _results[gid]
|
|
59
|
+
return item
|
|
@@ -1,23 +1,71 @@
|
|
|
1
|
-
from typing import Iterator
|
|
1
|
+
from typing import Iterator, Optional, Dict
|
|
2
2
|
from pymongo import MongoClient
|
|
3
|
-
|
|
3
|
+
from pymongo.collection import Collection
|
|
4
|
+
from pymongo.cursor import Cursor
|
|
4
5
|
from datetime import datetime, timezone
|
|
5
6
|
import logging
|
|
6
7
|
import time
|
|
7
8
|
from contextlib import contextmanager
|
|
8
9
|
import os
|
|
10
|
+
from os import environ as env
|
|
9
11
|
from abc import ABC, abstractmethod
|
|
10
12
|
from gevent.lock import Semaphore
|
|
11
13
|
|
|
12
14
|
dblock = Semaphore()
|
|
13
15
|
|
|
14
16
|
|
|
17
|
+
class MongoLRUReader(Iterator[Dict]):
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
filter: Dict, # pylint: disable=redefined-builtin
|
|
21
|
+
timestamp_field: str,
|
|
22
|
+
coll: Optional[Collection] = None,
|
|
23
|
+
):
|
|
24
|
+
"""A thread safe iterator to read test data from a mongo collection sorted by least-recently-used.
|
|
25
|
+
|
|
26
|
+
Order is ensured even if Locust is restarted, because the timestamp_field is updated on every iteration step.
|
|
27
|
+
|
|
28
|
+
Iteration is quite fast, but the first query can be slow if you dont have an index that covers the filter and sort fields.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
filter (Dict): Query filter statement
|
|
32
|
+
sort_field (str): Time stamp field, e.g. "last_used"
|
|
33
|
+
collection (pymongo.collection.Collection, optional): By default, we use LOCUST_MONGO, LOCUST_MONGO_DATABASE and LOCUST_MONGO_COLLECTION env vars to get the collection, but you can also pass a pre-existing Collection.
|
|
34
|
+
|
|
35
|
+
"""
|
|
36
|
+
self.timestamp_field = timestamp_field
|
|
37
|
+
self.coll: Collection = (
|
|
38
|
+
coll or MongoClient(env["LOCUST_MONGO"])[env["LOCUST_MONGO_DATABASE"]][env["LOCUST_MONGO_COLLECTION"]]
|
|
39
|
+
)
|
|
40
|
+
self.cursor: Cursor = self.coll.find(filter, sort=[(self.timestamp_field, 1)])
|
|
41
|
+
records_in_buffer = self.cursor._refresh() # trigger fetch immediately instead of waiting for the first next()
|
|
42
|
+
if not records_in_buffer:
|
|
43
|
+
logging.warning(f"No records returned from query {filter}")
|
|
44
|
+
|
|
45
|
+
def __next__(self) -> dict:
|
|
46
|
+
try:
|
|
47
|
+
with dblock:
|
|
48
|
+
doc: dict = next(self.cursor)
|
|
49
|
+
self.coll.find_one_and_update(
|
|
50
|
+
{"_id": doc["_id"]},
|
|
51
|
+
{"$set": {self.timestamp_field: datetime.now(tz=timezone.utc)}},
|
|
52
|
+
)
|
|
53
|
+
return doc
|
|
54
|
+
except StopIteration:
|
|
55
|
+
with dblock:
|
|
56
|
+
self.cursor.rewind()
|
|
57
|
+
return next(self.cursor)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
### Legacy
|
|
61
|
+
|
|
62
|
+
|
|
15
63
|
class NoUserException(Exception):
|
|
16
64
|
pass
|
|
17
65
|
|
|
18
66
|
|
|
19
67
|
class User(dict):
|
|
20
|
-
def __init__(self, coll:
|
|
68
|
+
def __init__(self, coll: Collection, query: dict):
|
|
21
69
|
self.coll = coll
|
|
22
70
|
with dblock:
|
|
23
71
|
data = self.coll.find_one_and_update(
|
|
@@ -55,6 +103,7 @@ class MongoReader(AbstractReader):
|
|
|
55
103
|
self.reduced_filters = []
|
|
56
104
|
self.delay_warning = 0
|
|
57
105
|
self.query = {"$and": filters + [{"logged_in": False}]}
|
|
106
|
+
logging.warning("MongoReader is deprecated, please switch to MongoReaderLRU")
|
|
58
107
|
|
|
59
108
|
@contextmanager
|
|
60
109
|
def user(self, query: dict = None):
|
|
@@ -88,7 +88,7 @@ class SocketIOUser(User):
|
|
|
88
88
|
logging.debug(f"WSR: {message}")
|
|
89
89
|
self.on_message(message)
|
|
90
90
|
|
|
91
|
-
def send(self, body, name=None, context={}):
|
|
91
|
+
def send(self, body, name=None, context={}, opcode=websocket.ABNF.OPCODE_TEXT):
|
|
92
92
|
if not name:
|
|
93
93
|
if body == "2":
|
|
94
94
|
name = "2 heartbeat"
|
|
@@ -112,7 +112,7 @@ class SocketIOUser(User):
|
|
|
112
112
|
context={**self.context(), **context},
|
|
113
113
|
)
|
|
114
114
|
logging.debug(f"WSS: {body}")
|
|
115
|
-
self.ws.send(body)
|
|
115
|
+
self.ws.send(body, opcode)
|
|
116
116
|
|
|
117
117
|
def sleep_with_heartbeat(self, seconds):
|
|
118
118
|
while seconds >= 0:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: locust-plugins
|
|
3
|
-
Version: 4.
|
|
3
|
+
Version: 4.3.1
|
|
4
4
|
Summary: Useful plugins/extensions for Locust
|
|
5
5
|
Home-page: https://github.com/SvenskaSpel/locust-plugins
|
|
6
6
|
Author: Lars Holmberg
|
|
@@ -16,20 +16,44 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.9
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.10
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
20
|
Classifier: Intended Audience :: Developers
|
|
20
21
|
Classifier: Intended Audience :: System Administrators
|
|
21
22
|
Requires-Python: >=3.8, <4
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: locust>=2.20.0
|
|
25
|
+
Requires-Dist: typing-extensions
|
|
22
26
|
Provides-Extra: websocket
|
|
27
|
+
Requires-Dist: websocket-client; extra == "websocket"
|
|
23
28
|
Provides-Extra: playwright
|
|
29
|
+
Requires-Dist: playwright>=1.34.0; extra == "playwright"
|
|
24
30
|
Provides-Extra: dashboards
|
|
31
|
+
Requires-Dist: psycogreen; extra == "dashboards"
|
|
32
|
+
Requires-Dist: psycopg2-binary; extra == "dashboards"
|
|
25
33
|
Provides-Extra: kafka
|
|
34
|
+
Requires-Dist: confluent-kafka; extra == "kafka"
|
|
26
35
|
Provides-Extra: mongo
|
|
36
|
+
Requires-Dist: pymongo; extra == "mongo"
|
|
27
37
|
Provides-Extra: mqtt
|
|
38
|
+
Requires-Dist: paho-mqtt>=1.5.0; extra == "mqtt"
|
|
28
39
|
Provides-Extra: appinsights
|
|
40
|
+
Requires-Dist: opencensus-ext-azure; extra == "appinsights"
|
|
29
41
|
Provides-Extra: resource
|
|
42
|
+
Requires-Dist: lxml; extra == "resource"
|
|
30
43
|
Provides-Extra: boto3
|
|
44
|
+
Requires-Dist: boto3; extra == "boto3"
|
|
31
45
|
Provides-Extra: all
|
|
46
|
+
Requires-Dist: websocket-client; extra == "all"
|
|
47
|
+
Requires-Dist: playwright>=1.34.0; extra == "all"
|
|
48
|
+
Requires-Dist: psycogreen; extra == "all"
|
|
49
|
+
Requires-Dist: psycopg2-binary; extra == "all"
|
|
50
|
+
Requires-Dist: confluent-kafka; extra == "all"
|
|
51
|
+
Requires-Dist: pymongo; extra == "all"
|
|
52
|
+
Requires-Dist: paho-mqtt>=1.5.0; extra == "all"
|
|
53
|
+
Requires-Dist: opencensus-ext-azure; extra == "all"
|
|
54
|
+
Requires-Dist: lxml; extra == "all"
|
|
55
|
+
Requires-Dist: boto3; extra == "all"
|
|
32
56
|
Provides-Extra: webdriver
|
|
33
|
-
|
|
57
|
+
Requires-Dist: selenium>=4.0.0; extra == "webdriver"
|
|
34
58
|
|
|
35
59
|
https://github.com/SvenskaSpel/locust-plugins
|
|
@@ -20,6 +20,7 @@ examples/cmd_line_examples.sh
|
|
|
20
20
|
examples/connection_pool_ex.py
|
|
21
21
|
examples/constant_total_ips_ex.py
|
|
22
22
|
examples/csvreader_ex.py
|
|
23
|
+
examples/distributor_ex.py
|
|
23
24
|
examples/embedded_resource_manager_ex.py
|
|
24
25
|
examples/jmeter_listener_example.py
|
|
25
26
|
examples/kafka_ex.py
|
|
@@ -27,6 +28,7 @@ examples/mongoreader_ex.py
|
|
|
27
28
|
examples/mqtt_ex.py
|
|
28
29
|
examples/playwright-recording.py
|
|
29
30
|
examples/playwright_ex.py
|
|
31
|
+
examples/products.csv
|
|
30
32
|
examples/reschedule_on_fail_ex.py
|
|
31
33
|
examples/rest_ex.py
|
|
32
34
|
examples/socketio_ex.py
|
|
@@ -42,6 +44,7 @@ locust_plugins/__init__.py
|
|
|
42
44
|
locust_plugins/_version.py
|
|
43
45
|
locust_plugins/connection_pools.py
|
|
44
46
|
locust_plugins/csvreader.py
|
|
47
|
+
locust_plugins/distributor.py
|
|
45
48
|
locust_plugins/mongoreader.py
|
|
46
49
|
locust_plugins/py.typed
|
|
47
50
|
locust_plugins/transaction_manager.py
|
|
@@ -32,6 +32,7 @@ setup(
|
|
|
32
32
|
"Programming Language :: Python :: 3.9",
|
|
33
33
|
"Programming Language :: Python :: 3.10",
|
|
34
34
|
"Programming Language :: Python :: 3.11",
|
|
35
|
+
"Programming Language :: Python :: 3.12",
|
|
35
36
|
"Intended Audience :: Developers",
|
|
36
37
|
"Intended Audience :: System Administrators",
|
|
37
38
|
],
|
|
@@ -45,7 +46,7 @@ setup(
|
|
|
45
46
|
package_data={"locust_plugins": ["py.typed"]},
|
|
46
47
|
zip_safe=False,
|
|
47
48
|
install_requires=[
|
|
48
|
-
"locust>=2.
|
|
49
|
+
"locust>=2.20.0",
|
|
49
50
|
"typing-extensions",
|
|
50
51
|
],
|
|
51
52
|
extras_require={
|
|
@@ -32,6 +32,10 @@ commands =
|
|
|
32
32
|
grep -m 1 'Manual' output.txt
|
|
33
33
|
grep -m 1 'LCP' output.txt
|
|
34
34
|
bash -ec "! grep 'object has no attribute' output.txt"
|
|
35
|
+
bash -ec "(cd examples && PYTHONUNBUFFERED=1 locust -f distributor_ex.py --headless -t 5 -u 4 --processes 4) |& tee output.txt || true"
|
|
36
|
+
grep -m 1 'customer=2099010101-1111' output.txt
|
|
37
|
+
grep -m 1 'product=asdf1' output.txt
|
|
38
|
+
bash -ec "! grep -q Traceback output.txt"
|
|
35
39
|
locust -f examples/jmeter_listener_example.py --headless -t 1
|
|
36
40
|
locust-compose up -d
|
|
37
41
|
sleep 15
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/transaction_example_as_library_local.py
RENAMED
|
File without changes
|
{locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/transaction_example_as_library_master.py
RENAMED
|
File without changes
|
{locust-plugins-4.2.0 → locust-plugins-4.3.1}/examples/transaction_example_as_library_worker.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/locust-grafana/Dockerfile
RENAMED
|
File without changes
|
{locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/locust-grafana/Makefile
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/locust-timescale/Dockerfile
RENAMED
|
File without changes
|
{locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/locust-timescale/Makefile
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/screenshots/scatter_plot.png
RENAMED
|
File without changes
|
{locust-plugins-4.2.0 → locust-plugins-4.3.1}/locust_plugins/dashboards/screenshots/testruns.png
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|