locust 2.32.2.dev19__py3-none-any.whl → 2.32.2.dev34__py3-none-any.whl
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/_version.py +2 -2
- locust/argument_parser.py +8 -0
- locust/env.py +2 -0
- locust/main.py +12 -4
- locust/runners.py +3 -2
- locust/web.py +25 -20
- {locust-2.32.2.dev19.dist-info → locust-2.32.2.dev34.dist-info}/METADATA +1 -1
- {locust-2.32.2.dev19.dist-info → locust-2.32.2.dev34.dist-info}/RECORD +11 -11
- {locust-2.32.2.dev19.dist-info → locust-2.32.2.dev34.dist-info}/LICENSE +0 -0
- {locust-2.32.2.dev19.dist-info → locust-2.32.2.dev34.dist-info}/WHEEL +0 -0
- {locust-2.32.2.dev19.dist-info → locust-2.32.2.dev34.dist-info}/entry_points.txt +0 -0
locust/_version.py
CHANGED
@@ -14,7 +14,7 @@ __version_tuple__: VERSION_TUPLE
|
|
14
14
|
version_tuple: VERSION_TUPLE
|
15
15
|
|
16
16
|
|
17
|
-
__version__ = "2.32.2.
|
17
|
+
__version__ = "2.32.2.dev34"
|
18
18
|
version = __version__
|
19
|
-
__version_tuple__ = (2, 32, 2, "
|
19
|
+
__version_tuple__ = (2, 32, 2, "dev34")
|
20
20
|
version_tuple = __version_tuple__
|
locust/argument_parser.py
CHANGED
@@ -612,6 +612,14 @@ Typically ONLY these options (and --locustfile) need to be specified on workers,
|
|
612
612
|
env_var="LOCUST_MASTER_NODE_PORT",
|
613
613
|
)
|
614
614
|
|
615
|
+
web_ui_group.add_argument(
|
616
|
+
"--web-base-path",
|
617
|
+
type=str,
|
618
|
+
default="",
|
619
|
+
help="Base path for the web interface (e.g., '/locust'). Default is empty (root path).",
|
620
|
+
env_var="LOCUST_web_base_path",
|
621
|
+
)
|
622
|
+
|
615
623
|
tag_group = parser.add_argument_group(
|
616
624
|
"Tag options",
|
617
625
|
"Locust tasks can be tagged using the @tag decorator. These options let specify which tasks to include or exclude during a test.",
|
locust/env.py
CHANGED
@@ -165,6 +165,7 @@ class Environment:
|
|
165
165
|
self,
|
166
166
|
host="",
|
167
167
|
port=8089,
|
168
|
+
web_base_path: str | None = None,
|
168
169
|
web_login: bool = False,
|
169
170
|
tls_cert: str | None = None,
|
170
171
|
tls_key: str | None = None,
|
@@ -199,6 +200,7 @@ class Environment:
|
|
199
200
|
delayed_start=delayed_start,
|
200
201
|
userclass_picker_is_active=userclass_picker_is_active,
|
201
202
|
build_path=build_path,
|
203
|
+
web_base_path=web_base_path,
|
202
204
|
)
|
203
205
|
return self.web_ui
|
204
206
|
|
locust/main.py
CHANGED
@@ -454,7 +454,9 @@ See https://github.com/locustio/locust/wiki/Installation#increasing-maximum-numb
|
|
454
454
|
elif options.worker:
|
455
455
|
try:
|
456
456
|
runner = environment.create_worker_runner(options.master_host, options.master_port)
|
457
|
-
logger.debug(
|
457
|
+
logger.debug(
|
458
|
+
"Connected to locust master: %s:%s%s", options.master_host, options.master_port, options.web_base_path
|
459
|
+
)
|
458
460
|
except OSError as e:
|
459
461
|
logger.error("Failed to connect to the Locust master: %s", e)
|
460
462
|
sys.exit(-1)
|
@@ -490,26 +492,32 @@ See https://github.com/locustio/locust/wiki/Installation#increasing-maximum-numb
|
|
490
492
|
if not options.headless and not options.worker:
|
491
493
|
protocol = "https" if options.tls_cert and options.tls_key else "http"
|
492
494
|
|
495
|
+
if options.web_base_path and options.web_base_path[0] != "/":
|
496
|
+
logger.error(
|
497
|
+
f"Invalid format for --web-base-path argument ({options.web_base_path}): the url path must start with a slash."
|
498
|
+
)
|
499
|
+
sys.exit(1)
|
493
500
|
if options.web_host == "*":
|
494
501
|
# special check for "*" so that we're consistent with --master-bind-host
|
495
502
|
web_host = ""
|
496
503
|
else:
|
497
504
|
web_host = options.web_host
|
498
505
|
if web_host:
|
499
|
-
logger.info(f"Starting web interface at {protocol}://{web_host}:{options.web_port}")
|
506
|
+
logger.info(f"Starting web interface at {protocol}://{web_host}:{options.web_port}{options.web_base_path}")
|
500
507
|
if options.web_host_display_name:
|
501
508
|
logger.info(f"Starting web interface at {options.web_host_display_name}")
|
502
509
|
else:
|
503
510
|
if os.name == "nt":
|
504
511
|
logger.info(
|
505
|
-
f"Starting web interface at {protocol}://localhost:{options.web_port} (accepting connections from all network interfaces)"
|
512
|
+
f"Starting web interface at {protocol}://localhost:{options.web_port}{options.web_base_path} (accepting connections from all network interfaces)"
|
506
513
|
)
|
507
514
|
else:
|
508
|
-
logger.info(f"Starting web interface at {protocol}://0.0.0.0:{options.web_port}")
|
515
|
+
logger.info(f"Starting web interface at {protocol}://0.0.0.0:{options.web_port}{options.web_base_path}")
|
509
516
|
|
510
517
|
web_ui = environment.create_web_ui(
|
511
518
|
host=web_host,
|
512
519
|
port=options.web_port,
|
520
|
+
web_base_path=options.web_base_path,
|
513
521
|
web_login=options.web_login,
|
514
522
|
tls_cert=options.tls_cert,
|
515
523
|
tls_key=options.tls_key,
|
locust/runners.py
CHANGED
@@ -1216,6 +1216,7 @@ class WorkerRunner(DistributedRunner):
|
|
1216
1216
|
self.client_id = socket.gethostname() + "_" + uuid4().hex
|
1217
1217
|
self.master_host = master_host
|
1218
1218
|
self.master_port = master_port
|
1219
|
+
self.web_base_path = environment.parsed_options.web_base_path if environment.parsed_options else ""
|
1219
1220
|
self.logs: list[str] = []
|
1220
1221
|
self.worker_cpu_warning_emitted = False
|
1221
1222
|
self._users_dispatcher: UsersDispatcher | None = None
|
@@ -1475,11 +1476,11 @@ class WorkerRunner(DistributedRunner):
|
|
1475
1476
|
if not success:
|
1476
1477
|
if self.retry < 3:
|
1477
1478
|
logger.debug(
|
1478
|
-
f"Failed to connect to master {self.master_host}:{self.master_port}, retry {self.retry}/{CONNECT_RETRY_COUNT}."
|
1479
|
+
f"Failed to connect to master {self.master_host}:{self.master_port}{self.web_base_path}, retry {self.retry}/{CONNECT_RETRY_COUNT}."
|
1479
1480
|
)
|
1480
1481
|
else:
|
1481
1482
|
logger.warning(
|
1482
|
-
f"Failed to connect to master {self.master_host}:{self.master_port}, retry {self.retry}/{CONNECT_RETRY_COUNT}."
|
1483
|
+
f"Failed to connect to master {self.master_host}:{self.master_port}{self.web_base_path}, retry {self.retry}/{CONNECT_RETRY_COUNT}."
|
1483
1484
|
)
|
1484
1485
|
if self.retry > CONNECT_RETRY_COUNT:
|
1485
1486
|
raise ConnectionError()
|
locust/web.py
CHANGED
@@ -15,6 +15,7 @@ from typing import TYPE_CHECKING, Any, TypedDict
|
|
15
15
|
|
16
16
|
import gevent
|
17
17
|
from flask import (
|
18
|
+
Blueprint,
|
18
19
|
Flask,
|
19
20
|
Response,
|
20
21
|
jsonify,
|
@@ -120,6 +121,7 @@ class WebUI:
|
|
120
121
|
environment: Environment,
|
121
122
|
host: str,
|
122
123
|
port: int,
|
124
|
+
web_base_path: str | None = None,
|
123
125
|
web_login: bool = False,
|
124
126
|
tls_cert: str | None = None,
|
125
127
|
tls_key: str | None = None,
|
@@ -161,20 +163,21 @@ class WebUI:
|
|
161
163
|
self.auth_args = {}
|
162
164
|
self.app.template_folder = build_path or DEFAULT_BUILD_PATH
|
163
165
|
self.app.static_url_path = "/assets/"
|
166
|
+
|
167
|
+
app_blueprint = Blueprint("locust", __name__, url_prefix=web_base_path)
|
164
168
|
# ensures static js files work on Windows
|
165
169
|
mimetypes.add_type("application/javascript", ".js")
|
166
|
-
|
167
170
|
if self.web_login:
|
168
171
|
self._login_manager = LoginManager()
|
169
172
|
self._login_manager.init_app(self.app)
|
170
|
-
self._login_manager.login_view = "login"
|
173
|
+
self._login_manager.login_view = "locust.login"
|
171
174
|
|
172
175
|
if environment.runner:
|
173
176
|
self.update_template_args()
|
174
177
|
if not delayed_start:
|
175
178
|
self.start()
|
176
179
|
|
177
|
-
@
|
180
|
+
@app_blueprint.errorhandler(Exception)
|
178
181
|
def handle_exception(error):
|
179
182
|
error_message = str(error)
|
180
183
|
error_code = getattr(error, "code", 500)
|
@@ -184,7 +187,7 @@ class WebUI:
|
|
184
187
|
)
|
185
188
|
return make_response(error_message, error_code)
|
186
189
|
|
187
|
-
@
|
190
|
+
@app_blueprint.route("/assets/<path:path>")
|
188
191
|
def send_assets(path):
|
189
192
|
directory = (
|
190
193
|
os.path.join(self.app.template_folder, "assets")
|
@@ -194,7 +197,7 @@ class WebUI:
|
|
194
197
|
|
195
198
|
return send_from_directory(directory, path)
|
196
199
|
|
197
|
-
@
|
200
|
+
@app_blueprint.route("/")
|
198
201
|
@self.auth_required_if_enabled
|
199
202
|
def index() -> str | Response:
|
200
203
|
if not environment.runner:
|
@@ -203,7 +206,7 @@ class WebUI:
|
|
203
206
|
|
204
207
|
return render_template("index.html", template_args=self.template_args)
|
205
208
|
|
206
|
-
@
|
209
|
+
@app_blueprint.route("/swarm", methods=["POST"])
|
207
210
|
@self.auth_required_if_enabled
|
208
211
|
def swarm() -> Response:
|
209
212
|
assert request.method == "POST"
|
@@ -317,7 +320,7 @@ class WebUI:
|
|
317
320
|
else:
|
318
321
|
return jsonify({"success": False, "message": "No runner", "host": environment.host})
|
319
322
|
|
320
|
-
@
|
323
|
+
@app_blueprint.route("/stop")
|
321
324
|
@self.auth_required_if_enabled
|
322
325
|
def stop() -> Response:
|
323
326
|
if self._swarm_greenlet is not None:
|
@@ -327,7 +330,7 @@ class WebUI:
|
|
327
330
|
environment.runner.stop()
|
328
331
|
return jsonify({"success": True, "message": "Test stopped"})
|
329
332
|
|
330
|
-
@
|
333
|
+
@app_blueprint.route("/stats/reset")
|
331
334
|
@self.auth_required_if_enabled
|
332
335
|
def reset_stats() -> str:
|
333
336
|
environment.events.reset_stats.fire()
|
@@ -336,7 +339,7 @@ class WebUI:
|
|
336
339
|
environment.runner.exceptions = {}
|
337
340
|
return "ok"
|
338
341
|
|
339
|
-
@
|
342
|
+
@app_blueprint.route("/stats/report")
|
340
343
|
@self.auth_required_if_enabled
|
341
344
|
def stats_report() -> Response:
|
342
345
|
theme = request.args.get("theme", "")
|
@@ -382,7 +385,7 @@ class WebUI:
|
|
382
385
|
)
|
383
386
|
return response
|
384
387
|
|
385
|
-
@
|
388
|
+
@app_blueprint.route("/stats/requests/csv")
|
386
389
|
@self.auth_required_if_enabled
|
387
390
|
def request_stats_csv() -> Response:
|
388
391
|
data = StringIO()
|
@@ -390,7 +393,7 @@ class WebUI:
|
|
390
393
|
self.stats_csv_writer.requests_csv(writer)
|
391
394
|
return _download_csv_response(data.getvalue(), "requests")
|
392
395
|
|
393
|
-
@
|
396
|
+
@app_blueprint.route("/stats/requests_full_history/csv")
|
394
397
|
@self.auth_required_if_enabled
|
395
398
|
def request_stats_full_history_csv() -> Response:
|
396
399
|
options = self.environment.parsed_options
|
@@ -408,7 +411,7 @@ class WebUI:
|
|
408
411
|
|
409
412
|
return make_response("Error: Server was not started with option to generate full history.", 404)
|
410
413
|
|
411
|
-
@
|
414
|
+
@app_blueprint.route("/stats/failures/csv")
|
412
415
|
@self.auth_required_if_enabled
|
413
416
|
def failures_stats_csv() -> Response:
|
414
417
|
data = StringIO()
|
@@ -416,7 +419,7 @@ class WebUI:
|
|
416
419
|
self.stats_csv_writer.failures_csv(writer)
|
417
420
|
return _download_csv_response(data.getvalue(), "failures")
|
418
421
|
|
419
|
-
@
|
422
|
+
@app_blueprint.route("/stats/requests")
|
420
423
|
@self.auth_required_if_enabled
|
421
424
|
@memoize(timeout=DEFAULT_CACHE_TIME, dynamic_timeout=True)
|
422
425
|
def request_stats() -> Response:
|
@@ -486,7 +489,7 @@ class WebUI:
|
|
486
489
|
|
487
490
|
return jsonify(report)
|
488
491
|
|
489
|
-
@
|
492
|
+
@app_blueprint.route("/exceptions")
|
490
493
|
@self.auth_required_if_enabled
|
491
494
|
def exceptions() -> Response:
|
492
495
|
return jsonify(
|
@@ -503,7 +506,7 @@ class WebUI:
|
|
503
506
|
}
|
504
507
|
)
|
505
508
|
|
506
|
-
@
|
509
|
+
@app_blueprint.route("/exceptions/csv")
|
507
510
|
@self.auth_required_if_enabled
|
508
511
|
def exceptions_csv() -> Response:
|
509
512
|
data = StringIO()
|
@@ -511,7 +514,7 @@ class WebUI:
|
|
511
514
|
self.stats_csv_writer.exceptions_csv(writer)
|
512
515
|
return _download_csv_response(data.getvalue(), "exceptions")
|
513
516
|
|
514
|
-
@
|
517
|
+
@app_blueprint.route("/tasks")
|
515
518
|
@self.auth_required_if_enabled
|
516
519
|
def tasks() -> dict[str, dict[str, dict[str, float]]]:
|
517
520
|
runner = self.environment.runner
|
@@ -531,15 +534,15 @@ class WebUI:
|
|
531
534
|
}
|
532
535
|
return task_data
|
533
536
|
|
534
|
-
@
|
537
|
+
@app_blueprint.route("/logs")
|
535
538
|
@self.auth_required_if_enabled
|
536
539
|
def logs():
|
537
540
|
return jsonify({"master": get_logs(), "workers": self.environment.worker_logs})
|
538
541
|
|
539
|
-
@
|
542
|
+
@app_blueprint.route("/login")
|
540
543
|
def login():
|
541
544
|
if not self.web_login:
|
542
|
-
return redirect(url_for("index"))
|
545
|
+
return redirect(url_for("locust.index"))
|
543
546
|
|
544
547
|
self.auth_args["error"] = session.get("auth_error", None)
|
545
548
|
self.auth_args["info"] = session.get("auth_info", None)
|
@@ -549,7 +552,7 @@ class WebUI:
|
|
549
552
|
auth_args=self.auth_args,
|
550
553
|
)
|
551
554
|
|
552
|
-
@
|
555
|
+
@app_blueprint.route("/user", methods=["POST"])
|
553
556
|
def update_user():
|
554
557
|
assert request.method == "POST"
|
555
558
|
|
@@ -558,6 +561,8 @@ class WebUI:
|
|
558
561
|
|
559
562
|
return {}, 201
|
560
563
|
|
564
|
+
app.register_blueprint(app_blueprint)
|
565
|
+
|
561
566
|
@property
|
562
567
|
def login_manager(self):
|
563
568
|
if self.web_login:
|
@@ -1,7 +1,7 @@
|
|
1
1
|
locust/__init__.py,sha256=Jit8eNUrwuMLqavyFvMZr69e61DILq_KB4yT4MciScw,1681
|
2
2
|
locust/__main__.py,sha256=vBQ82334kX06ImDbFlPFgiBRiLIinwNk3z8Khs6hd74,31
|
3
|
-
locust/_version.py,sha256=
|
4
|
-
locust/argument_parser.py,sha256=
|
3
|
+
locust/_version.py,sha256=yJ7AQeYbmnb_G0YVzPTbL51vIUG21tYcikti09oKDv8,460
|
4
|
+
locust/argument_parser.py,sha256=QA8A-VHrAp4LAl2zhOpAozHcsw0Act8N_9Q9zsy2Ln4,29424
|
5
5
|
locust/clients.py,sha256=XK-xabq2_5GZKMEjebDobvEjeBTtCs8h2EelL7s68Qs,19346
|
6
6
|
locust/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
7
|
locust/contrib/fasthttp.py,sha256=Fs83qq8zqT4_aTnA8JUQaIWo2IBj-LNi2AKflx8yGtg,28858
|
@@ -9,18 +9,18 @@ locust/contrib/mongodb.py,sha256=1seUYgJOaNKwybYOP9PUEVhgl8hGy-G33f8lFj3R8W8,124
|
|
9
9
|
locust/contrib/postgres.py,sha256=OuMWnGYN10K65Tq2axVESEW25Y0g5gJb0rK90jkcCJg,1230
|
10
10
|
locust/debug.py,sha256=7CCm8bIg44uGH2wqBlo1rXBzV2VzwPicLxLewz8r5CQ,5099
|
11
11
|
locust/dispatch.py,sha256=prdwtb9EoN4A9klgiKgWuwQmvFB8hEuFHOK6ot62AJI,16202
|
12
|
-
locust/env.py,sha256=
|
12
|
+
locust/env.py,sha256=URPRltUBdOdCCO1yb2WmE9JeUlI_SAm9l7cvR9j1c_s,13083
|
13
13
|
locust/event.py,sha256=AWoeG2q11Vpupv1eW9Rf2N0EI4dDDAldKe6zY1ajC7I,8717
|
14
14
|
locust/exception.py,sha256=jGgJ32ubuf4pWdlaVOkbh2Y0LlG0_DHi-lv3ib8ppOE,1791
|
15
15
|
locust/html.py,sha256=TCwCa93t7l-M6SEAxDA2hIw18JztQw46eDkXxgbs254,3614
|
16
16
|
locust/input_events.py,sha256=ZIyePyAMuA_YFYWg18g_pE4kwuQV3RbEB250MzXRwjY,3314
|
17
17
|
locust/log.py,sha256=Wrkn0Ibugh5Sqjm4hGQ2-jUsy1tNMBdTctp4FyXQI24,3457
|
18
|
-
locust/main.py,sha256=
|
18
|
+
locust/main.py,sha256=LEr4WaPCraEOF8mt0-FY8r1MveF6G9y6lsnRxm66PNc,29933
|
19
19
|
locust/py.typed,sha256=gkWLl8yD4mIZnNYYAIRM8g9VarLvWmTAFeUfEbxJLBw,65
|
20
20
|
locust/rpc/__init__.py,sha256=nVGoHWFQxZjnhCDWjbgXIbmFbN9sizAjkhvSs9_642c,58
|
21
21
|
locust/rpc/protocol.py,sha256=n-rb3GZQcAlldYDj4E4GuFGylYj_26GSS5U29meft5Y,1282
|
22
22
|
locust/rpc/zmqrpc.py,sha256=_zl0DKazQLD2YyqxvyuskVApsDCaUnhloHvV8SkJxjw,3157
|
23
|
-
locust/runners.py,sha256=
|
23
|
+
locust/runners.py,sha256=vO_2Nv7WHcHnUguIpNCBstSAln4Zg1S4ExfFoHQpo4A,70514
|
24
24
|
locust/shape.py,sha256=t-lwBS8LOjWcKXNL7j2U3zroIXJ1b0fazUwpRYQOKXw,1973
|
25
25
|
locust/stats.py,sha256=z_s98ClnSfryvRi-VS0c7u5g87B_o4qL8nkunkPuhPI,45989
|
26
26
|
locust/user/__init__.py,sha256=S2yvmI_AU9kXirtTIVqiV_Hs7yXzqXvaSgkNo9ig-fk,71
|
@@ -39,7 +39,7 @@ locust/util/load_locustfile.py,sha256=hn70KcIG8jHmZyuKv2pcEmwgWtOEu24Efeji1KRYNU
|
|
39
39
|
locust/util/rounding.py,sha256=5haxR8mKhATqag6WvPby-MSRRgIw5Ob6thbyvMYZM7o,92
|
40
40
|
locust/util/timespan.py,sha256=Y0LtnhUq2Mq19p04u0XtBlYQ_-S2cRvwRdgru8W9WhA,986
|
41
41
|
locust/util/url.py,sha256=s_W2PCxvxTWxWX0yUvp-8VBuQm881KwI5X9iifogZG4,321
|
42
|
-
locust/web.py,sha256=
|
42
|
+
locust/web.py,sha256=iV0VM6nIhals1seZSkDoKMOdAQciv8cbz_Uaq2OF2hI,29731
|
43
43
|
locust/webui/dist/assets/favicon-dark.png,sha256=6zVkRtiRfU45qQGvEhf1cq2nNauFs_JW5SI79wT0YkM,2437
|
44
44
|
locust/webui/dist/assets/favicon-light.png,sha256=VdG2GZyeTCOML7xfievupBP4EhmHoUqlVCw-tXqkvxU,2468
|
45
45
|
locust/webui/dist/assets/index-CV_-ndKF.js,sha256=YNaB15qUPWDD41yMWEB-9ZuyQIPVVW2bzACpKmowu-c,1674517
|
@@ -47,8 +47,8 @@ locust/webui/dist/auth.html,sha256=Na04RsfJNXyscodaY3CjreTxUmeYnXaOONhSxPB-59Y,6
|
|
47
47
|
locust/webui/dist/index.html,sha256=ozpUdS047Z-rFgrKq1J-40FNHZlkR36tbhzipbxkS5o,654
|
48
48
|
locust/webui/dist/report.html,sha256=Ok3WzJmly4E0pyBuHmzIVJx7e36zdyqgJ0VwmwVQnuE,1470993
|
49
49
|
poetry.lock,sha256=NUOC8hV7YCL5kHXj3GF2Kb16DLRCXoZUjiWXPghYEyg,213450
|
50
|
-
locust-2.32.2.
|
51
|
-
locust-2.32.2.
|
52
|
-
locust-2.32.2.
|
53
|
-
locust-2.32.2.
|
54
|
-
locust-2.32.2.
|
50
|
+
locust-2.32.2.dev34.dist-info/LICENSE,sha256=78XGpIn3fHVBfaxlPNUfjVufSN7QsdhpJMRJHv2AFpo,1095
|
51
|
+
locust-2.32.2.dev34.dist-info/METADATA,sha256=xQFxLnO25rHnbPp_y4xWuX2r10IykrzdBe5SG_5pqpk,7877
|
52
|
+
locust-2.32.2.dev34.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
53
|
+
locust-2.32.2.dev34.dist-info/entry_points.txt,sha256=RhIxlLwU_Ae_WjimS5COUDLagdCh6IOMyLtgaQxNmlM,43
|
54
|
+
locust-2.32.2.dev34.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|