locust 2.20.1.dev26__py3-none-any.whl → 2.20.2__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/__init__.py +7 -7
- locust/_version.py +2 -2
- locust/argument_parser.py +35 -18
- locust/clients.py +7 -6
- locust/contrib/fasthttp.py +42 -37
- locust/debug.py +14 -8
- locust/dispatch.py +25 -25
- locust/env.py +45 -37
- locust/event.py +13 -10
- locust/exception.py +0 -9
- locust/html.py +9 -8
- locust/input_events.py +7 -5
- locust/main.py +95 -47
- locust/rpc/protocol.py +4 -2
- locust/rpc/zmqrpc.py +6 -4
- locust/runners.py +69 -76
- locust/shape.py +7 -4
- locust/stats.py +73 -71
- locust/templates/index.html +8 -23
- locust/test/mock_logging.py +7 -5
- locust/test/test_debugging.py +4 -4
- locust/test/test_dispatch.py +10 -9
- locust/test/test_env.py +30 -1
- locust/test/test_fasthttp.py +9 -8
- locust/test/test_http.py +4 -3
- locust/test/test_interruptable_task.py +4 -4
- locust/test/test_load_locustfile.py +6 -5
- locust/test/test_locust_class.py +11 -5
- locust/test/test_log.py +5 -4
- locust/test/test_main.py +37 -7
- locust/test/test_parser.py +11 -15
- locust/test/test_runners.py +22 -21
- locust/test/test_sequential_taskset.py +3 -2
- locust/test/test_stats.py +16 -18
- locust/test/test_tags.py +3 -2
- locust/test/test_taskratio.py +3 -3
- locust/test/test_users.py +3 -3
- locust/test/test_util.py +3 -2
- locust/test/test_wait_time.py +3 -3
- locust/test/test_web.py +142 -27
- locust/test/test_zmqrpc.py +5 -3
- locust/test/testcases.py +7 -7
- locust/test/util.py +6 -7
- locust/user/__init__.py +1 -1
- locust/user/inspectuser.py +6 -5
- locust/user/sequential_taskset.py +3 -1
- locust/user/task.py +22 -27
- locust/user/users.py +17 -7
- locust/util/deprecation.py +0 -1
- locust/util/exception_handler.py +1 -1
- locust/util/load_locustfile.py +4 -2
- locust/web.py +102 -53
- locust/webui/dist/assets/auth-5e21717c.js.map +1 -0
- locust/webui/dist/assets/{index-01afe4fa.js → index-a83a5dd9.js} +85 -84
- locust/webui/dist/assets/index-a83a5dd9.js.map +1 -0
- locust/webui/dist/auth.html +20 -0
- locust/webui/dist/index.html +1 -1
- {locust-2.20.1.dev26.dist-info → locust-2.20.2.dist-info}/METADATA +2 -2
- locust-2.20.2.dist-info/RECORD +105 -0
- locust-2.20.1.dev26.dist-info/RECORD +0 -102
- {locust-2.20.1.dev26.dist-info → locust-2.20.2.dist-info}/LICENSE +0 -0
- {locust-2.20.1.dev26.dist-info → locust-2.20.2.dist-info}/WHEEL +0 -0
- {locust-2.20.1.dev26.dist-info → locust-2.20.2.dist-info}/entry_points.txt +0 -0
- {locust-2.20.1.dev26.dist-info → locust-2.20.2.dist-info}/top_level.txt +0 -0
locust/env.py
CHANGED
@@ -1,24 +1,18 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
from operator import methodcaller
|
2
|
-
from typing import
|
3
|
-
Callable,
|
4
|
-
Dict,
|
5
|
-
List,
|
6
|
-
Type,
|
7
|
-
TypeVar,
|
8
|
-
Optional,
|
9
|
-
)
|
4
|
+
from typing import Callable, TypeVar
|
10
5
|
|
11
6
|
from configargparse import Namespace
|
12
7
|
|
13
8
|
from .event import Events
|
14
9
|
from .exception import RunnerAlreadyExistsError
|
10
|
+
from .runners import LocalRunner, MasterRunner, Runner, WorkerRunner
|
11
|
+
from .shape import LoadTestShape
|
15
12
|
from .stats import RequestStats, StatsCSV
|
16
|
-
from .runners import Runner, LocalRunner, MasterRunner, WorkerRunner
|
17
|
-
from .web import WebUI
|
18
13
|
from .user import User
|
19
|
-
from .user.task import
|
20
|
-
from .
|
21
|
-
|
14
|
+
from .user.task import TaskHolder, TaskSet, filter_tasks_by_tags
|
15
|
+
from .web import WebUI
|
22
16
|
|
23
17
|
RunnerType = TypeVar("RunnerType", bound=Runner)
|
24
18
|
|
@@ -27,27 +21,28 @@ class Environment:
|
|
27
21
|
def __init__(
|
28
22
|
self,
|
29
23
|
*,
|
30
|
-
user_classes:
|
31
|
-
shape_class:
|
32
|
-
tags:
|
33
|
-
locustfile:
|
34
|
-
exclude_tags:
|
35
|
-
events:
|
36
|
-
host:
|
24
|
+
user_classes: list[type[User]] | None = None,
|
25
|
+
shape_class: LoadTestShape | None = None,
|
26
|
+
tags: list[str] | None = None,
|
27
|
+
locustfile: str | None = None,
|
28
|
+
exclude_tags: list[str] | None = None,
|
29
|
+
events: Events | None = None,
|
30
|
+
host: str | None = None,
|
37
31
|
reset_stats=False,
|
38
|
-
stop_timeout:
|
32
|
+
stop_timeout: float | None = None,
|
39
33
|
catch_exceptions=True,
|
40
|
-
parsed_options:
|
41
|
-
available_user_classes:
|
42
|
-
available_shape_classes:
|
34
|
+
parsed_options: Namespace | None = None,
|
35
|
+
available_user_classes: dict[str, User] | None = None,
|
36
|
+
available_shape_classes: dict[str, LoadTestShape] | None = None,
|
37
|
+
available_user_tasks: dict[str, list[TaskSet | Callable]] | None = None,
|
43
38
|
):
|
44
|
-
self.runner:
|
39
|
+
self.runner: Runner | None = None
|
45
40
|
"""Reference to the :class:`Runner <locust.runners.Runner>` instance"""
|
46
41
|
|
47
|
-
self.web_ui:
|
42
|
+
self.web_ui: WebUI | None = None
|
48
43
|
"""Reference to the WebUI instance"""
|
49
44
|
|
50
|
-
self.process_exit_code:
|
45
|
+
self.process_exit_code: int | None = None
|
51
46
|
"""
|
52
47
|
If set it'll be the exit code of the Locust process
|
53
48
|
"""
|
@@ -63,7 +58,7 @@ class Environment:
|
|
63
58
|
|
64
59
|
self.locustfile = locustfile
|
65
60
|
"""Filename (not path) of locustfile"""
|
66
|
-
self.user_classes:
|
61
|
+
self.user_classes: list[type[User]] = user_classes or []
|
67
62
|
"""User classes that the runner will run"""
|
68
63
|
self.shape_class = shape_class
|
69
64
|
"""A shape class to control the shape of the load test"""
|
@@ -98,6 +93,8 @@ class Environment:
|
|
98
93
|
"""List of the available User Classes to pick from in the UserClass Picker"""
|
99
94
|
self.available_shape_classes = available_shape_classes
|
100
95
|
"""List of the available Shape Classes to pick from in the ShapeClass Picker"""
|
96
|
+
self.available_user_tasks = available_user_tasks
|
97
|
+
"""List of the available Tasks per User Classes to pick from in the Task Picker"""
|
101
98
|
|
102
99
|
self._remove_user_classes_with_weight_zero()
|
103
100
|
self._validate_user_class_name_uniqueness()
|
@@ -105,7 +102,7 @@ class Environment:
|
|
105
102
|
|
106
103
|
def _create_runner(
|
107
104
|
self,
|
108
|
-
runner_class:
|
105
|
+
runner_class: type[RunnerType],
|
109
106
|
*args,
|
110
107
|
**kwargs,
|
111
108
|
) -> RunnerType:
|
@@ -159,10 +156,10 @@ class Environment:
|
|
159
156
|
self,
|
160
157
|
host="",
|
161
158
|
port=8089,
|
162
|
-
|
163
|
-
tls_cert:
|
164
|
-
tls_key:
|
165
|
-
stats_csv_writer:
|
159
|
+
web_login: bool = False,
|
160
|
+
tls_cert: str | None = None,
|
161
|
+
tls_key: str | None = None,
|
162
|
+
stats_csv_writer: StatsCSV | None = None,
|
166
163
|
delayed_start=False,
|
167
164
|
userclass_picker_is_active=False,
|
168
165
|
modern_ui=False,
|
@@ -173,7 +170,7 @@ class Environment:
|
|
173
170
|
:param host: Host/interface that the web server should accept connections to. Defaults to ""
|
174
171
|
which means all interfaces
|
175
172
|
:param port: Port that the web server should listen to
|
176
|
-
:param
|
173
|
+
:param web_login: If provided, an authentication page will protect the app
|
177
174
|
:param tls_cert: An optional path (str) to a TLS cert. If this is provided the web UI will be
|
178
175
|
served over HTTPS
|
179
176
|
:param tls_key: An optional path (str) to a TLS private key. If this is provided the web UI will be
|
@@ -186,7 +183,7 @@ class Environment:
|
|
186
183
|
self,
|
187
184
|
host,
|
188
185
|
port,
|
189
|
-
|
186
|
+
web_login=web_login,
|
190
187
|
tls_cert=tls_cert,
|
191
188
|
tls_key=tls_key,
|
192
189
|
stats_csv_writer=stats_csv_writer,
|
@@ -196,6 +193,17 @@ class Environment:
|
|
196
193
|
)
|
197
194
|
return self.web_ui
|
198
195
|
|
196
|
+
def update_user_class(self, user_settings):
|
197
|
+
user_class_name = user_settings.get("user_class_name")
|
198
|
+
user_class = self.available_user_classes[user_class_name]
|
199
|
+
user_tasks = self.available_user_tasks[user_class_name]
|
200
|
+
|
201
|
+
for key, value in user_settings.items():
|
202
|
+
if key not in ["user_class_name", "tasks"]:
|
203
|
+
setattr(user_class, key, value)
|
204
|
+
if key == "tasks":
|
205
|
+
user_class.tasks = [task for task in user_tasks if task.__name__ in value]
|
206
|
+
|
199
207
|
def _filter_tasks_by_tags(self) -> None:
|
200
208
|
"""
|
201
209
|
Filter the tasks on all the user_classes recursively, according to the tags and
|
@@ -244,7 +252,7 @@ class Environment:
|
|
244
252
|
"""
|
245
253
|
for u in self.user_classes:
|
246
254
|
u.weight = 1
|
247
|
-
user_tasks:
|
255
|
+
user_tasks: list[TaskSet | Callable] = []
|
248
256
|
tasks_frontier = u.tasks
|
249
257
|
while len(tasks_frontier) != 0:
|
250
258
|
t = tasks_frontier.pop()
|
@@ -274,5 +282,5 @@ class Environment:
|
|
274
282
|
)
|
275
283
|
|
276
284
|
@property
|
277
|
-
def user_classes_by_name(self) ->
|
285
|
+
def user_classes_by_name(self) -> dict[str, type[User]]:
|
278
286
|
return {u.__name__: u for u in self.user_classes}
|
locust/event.py
CHANGED
@@ -1,10 +1,13 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import logging
|
2
|
-
|
4
|
+
import time
|
3
5
|
import traceback
|
4
6
|
from contextlib import contextmanager
|
5
|
-
from typing import
|
6
|
-
|
7
|
-
from .
|
7
|
+
from typing import Any, Generator
|
8
|
+
|
9
|
+
from . import log
|
10
|
+
from .exception import InterruptTaskSet, RescheduleTask, RescheduleTaskImmediately, StopUser
|
8
11
|
|
9
12
|
|
10
13
|
class EventHook:
|
@@ -52,7 +55,7 @@ class EventHook:
|
|
52
55
|
@contextmanager
|
53
56
|
def measure(
|
54
57
|
self, request_type: str, name: str, response_length: int = 0, context=None
|
55
|
-
) -> Generator[
|
58
|
+
) -> Generator[dict[str, Any], None, None]:
|
56
59
|
"""Convenience method for firing the event with automatically calculated response time and automatically marking the request as failed if an exception is raised (this is really only useful for the *request* event)
|
57
60
|
|
58
61
|
Example usage (in a task):
|
@@ -230,11 +233,11 @@ class Events:
|
|
230
233
|
"""
|
231
234
|
|
232
235
|
def __init__(self):
|
233
|
-
# For
|
236
|
+
# For backward compatibility use also values of class attributes
|
234
237
|
for name, value in vars(type(self)).items():
|
235
|
-
if value == EventHook:
|
236
|
-
setattr(self, name,
|
238
|
+
if value == "EventHook":
|
239
|
+
setattr(self, name, EventHook())
|
237
240
|
|
238
241
|
for name, value in self.__annotations__.items():
|
239
|
-
if value == EventHook:
|
240
|
-
setattr(self, name,
|
242
|
+
if value == "EventHook":
|
243
|
+
setattr(self, name, EventHook())
|
locust/exception.py
CHANGED
@@ -74,14 +74,5 @@ class RPCReceiveError(Exception):
|
|
74
74
|
self.addr = addr
|
75
75
|
|
76
76
|
|
77
|
-
class AuthCredentialsError(ValueError):
|
78
|
-
"""
|
79
|
-
Exception when the auth credentials provided
|
80
|
-
are not in the correct format
|
81
|
-
"""
|
82
|
-
|
83
|
-
pass
|
84
|
-
|
85
|
-
|
86
77
|
class RunnerAlreadyExistsError(Exception):
|
87
78
|
pass
|
locust/html.py
CHANGED
@@ -1,16 +1,17 @@
|
|
1
|
-
|
2
|
-
import os
|
1
|
+
import datetime
|
3
2
|
import glob
|
3
|
+
import os
|
4
4
|
import pathlib
|
5
|
-
import datetime
|
6
|
-
from itertools import chain
|
7
|
-
from .stats import sort_stats
|
8
|
-
from . import stats as stats_module
|
9
|
-
from .user.inspectuser import get_ratio
|
10
5
|
from html import escape
|
6
|
+
from itertools import chain
|
11
7
|
from json import dumps
|
12
|
-
from .runners import MasterRunner, STATE_STOPPED, STATE_STOPPING
|
13
8
|
|
9
|
+
from jinja2 import Environment, FileSystemLoader
|
10
|
+
|
11
|
+
from . import stats as stats_module
|
12
|
+
from .runners import STATE_STOPPED, STATE_STOPPING, MasterRunner
|
13
|
+
from .stats import sort_stats
|
14
|
+
from .user.inspectuser import get_ratio
|
14
15
|
|
15
16
|
PERCENTILES_FOR_HTML_REPORT = [0.50, 0.6, 0.7, 0.8, 0.9, 0.95, 0.99, 1.0]
|
16
17
|
|
locust/input_events.py
CHANGED
@@ -1,18 +1,20 @@
|
|
1
|
-
from
|
1
|
+
from __future__ import annotations
|
2
2
|
|
3
|
-
import gevent
|
4
3
|
import logging
|
5
4
|
import os
|
6
5
|
import sys
|
6
|
+
from typing import Callable
|
7
|
+
|
8
|
+
import gevent
|
7
9
|
|
8
10
|
if os.name == "nt":
|
9
11
|
from win32api import STD_INPUT_HANDLE
|
10
12
|
from win32console import (
|
11
|
-
GetStdHandle,
|
12
|
-
KEY_EVENT,
|
13
13
|
ENABLE_ECHO_INPUT,
|
14
14
|
ENABLE_LINE_INPUT,
|
15
15
|
ENABLE_PROCESSED_INPUT,
|
16
|
+
KEY_EVENT,
|
17
|
+
GetStdHandle,
|
16
18
|
)
|
17
19
|
else:
|
18
20
|
import select
|
@@ -88,7 +90,7 @@ def get_poller():
|
|
88
90
|
return UnixKeyPoller()
|
89
91
|
|
90
92
|
|
91
|
-
def input_listener(key_to_func_map:
|
93
|
+
def input_listener(key_to_func_map: dict[str, Callable]):
|
92
94
|
def input_listener_func():
|
93
95
|
try:
|
94
96
|
poller = get_poller()
|
locust/main.py
CHANGED
@@ -1,35 +1,40 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import locust
|
4
|
+
|
5
|
+
import atexit
|
1
6
|
import errno
|
7
|
+
import gc
|
8
|
+
import inspect
|
9
|
+
import json
|
2
10
|
import logging
|
3
11
|
import os
|
4
12
|
import signal
|
5
13
|
import sys
|
6
14
|
import time
|
7
|
-
import
|
8
|
-
|
15
|
+
import traceback
|
16
|
+
|
9
17
|
import gevent
|
10
|
-
|
11
|
-
from
|
12
|
-
from . import log
|
18
|
+
|
19
|
+
from . import log, stats
|
13
20
|
from .argument_parser import parse_locustfile_option, parse_options
|
14
21
|
from .env import Environment
|
15
|
-
from .
|
16
|
-
from . import
|
22
|
+
from .html import get_html_report
|
23
|
+
from .input_events import input_listener
|
24
|
+
from .log import greenlet_exception_logger, setup_logging
|
17
25
|
from .stats import (
|
26
|
+
StatsCSV,
|
27
|
+
StatsCSVFileWriter,
|
18
28
|
print_error_report,
|
19
29
|
print_percentile_stats,
|
20
30
|
print_stats,
|
21
31
|
print_stats_json,
|
22
|
-
stats_printer,
|
23
32
|
stats_history,
|
33
|
+
stats_printer,
|
24
34
|
)
|
25
|
-
from .stats import StatsCSV, StatsCSVFileWriter
|
26
35
|
from .user.inspectuser import print_task_ratio, print_task_ratio_json
|
27
|
-
from .util.timespan import parse_timespan
|
28
|
-
from .exception import AuthCredentialsError
|
29
|
-
from .input_events import input_listener
|
30
|
-
from .html import get_html_report
|
31
36
|
from .util.load_locustfile import load_locustfile
|
32
|
-
import
|
37
|
+
from .util.timespan import parse_timespan
|
33
38
|
|
34
39
|
try:
|
35
40
|
# import locust_plugins if it is installed, to allow it to register custom arguments etc
|
@@ -56,6 +61,7 @@ def create_environment(
|
|
56
61
|
locustfile=None,
|
57
62
|
available_user_classes=None,
|
58
63
|
available_shape_classes=None,
|
64
|
+
available_user_tasks=None,
|
59
65
|
):
|
60
66
|
"""
|
61
67
|
Create an Environment instance from options
|
@@ -70,6 +76,7 @@ def create_environment(
|
|
70
76
|
parsed_options=options,
|
71
77
|
available_user_classes=available_user_classes,
|
72
78
|
available_shape_classes=available_shape_classes,
|
79
|
+
available_user_tasks=available_user_tasks,
|
73
80
|
)
|
74
81
|
|
75
82
|
|
@@ -84,9 +91,10 @@ def main():
|
|
84
91
|
locustfile = locustfiles[0] if locustfiles_length == 1 else None
|
85
92
|
|
86
93
|
# Importing Locustfile(s) - setting available UserClasses and ShapeClasses to choose from in UI
|
87
|
-
user_classes:
|
94
|
+
user_classes: dict[str, locust.User] = {}
|
88
95
|
available_user_classes = {}
|
89
96
|
available_shape_classes = {}
|
97
|
+
available_user_tasks = {}
|
90
98
|
shape_class = None
|
91
99
|
for _locustfile in locustfiles:
|
92
100
|
docstring, _user_classes, shape_classes = load_locustfile(_locustfile)
|
@@ -118,6 +126,7 @@ def main():
|
|
118
126
|
|
119
127
|
user_classes[key] = value
|
120
128
|
available_user_classes[key] = value
|
129
|
+
available_user_tasks[key] = value.tasks or None
|
121
130
|
|
122
131
|
if len(stats.PERCENTILES_TO_CHART) != 2:
|
123
132
|
logging.error("stats.PERCENTILES_TO_CHART parameter should be 2 parameters \n")
|
@@ -137,6 +146,14 @@ def main():
|
|
137
146
|
"stats.PERCENTILES_TO_CHART parameter need to be float and value between. 0 < percentile < 1 Eg 0.95\n"
|
138
147
|
)
|
139
148
|
sys.exit(1)
|
149
|
+
|
150
|
+
for percentile in stats.PERCENTILES_TO_STATISTICS:
|
151
|
+
if not is_valid_percentile(percentile):
|
152
|
+
logging.error(
|
153
|
+
"stats.PERCENTILES_TO_STATISTICS parameter need to be float and value between. 0 < percentile < 1 Eg 0.95\n"
|
154
|
+
)
|
155
|
+
sys.exit(1)
|
156
|
+
|
140
157
|
# parse all command line options
|
141
158
|
options = parse_options()
|
142
159
|
|
@@ -147,6 +164,12 @@ def main():
|
|
147
164
|
sys.stderr.write("The --slave/--expect-slaves parameters have been renamed --worker/--expect-workers\n")
|
148
165
|
sys.exit(1)
|
149
166
|
|
167
|
+
if options.web_auth:
|
168
|
+
sys.stderr.write(
|
169
|
+
"The --web-auth parameters has been replaced with --web-login. See https://docs.locust.io/en/stable/extending-locust.html#authentication for details\n"
|
170
|
+
)
|
171
|
+
sys.exit(1)
|
172
|
+
|
150
173
|
if options.autoquit != -1 and not options.autostart:
|
151
174
|
sys.stderr.write("--autoquit is only meaningful in combination with --autostart\n")
|
152
175
|
sys.exit(1)
|
@@ -182,6 +205,9 @@ def main():
|
|
182
205
|
"--master cannot be combined with --processes. Remove --master, as it is implicit as long as --worker is not set.\n"
|
183
206
|
)
|
184
207
|
sys.exit(1)
|
208
|
+
# Optimize copy-on-write-behavior to save some memory (aprx 26MB -> 15MB rss) in child processes
|
209
|
+
gc.collect() # avoid freezing garbage
|
210
|
+
gc.freeze() # move all objects to perm gen so ref counts dont get updated
|
185
211
|
for _ in range(options.processes):
|
186
212
|
child_pid = gevent.fork()
|
187
213
|
if child_pid:
|
@@ -337,8 +363,36 @@ See https://github.com/locustio/locust/wiki/Installation#increasing-maximum-numb
|
|
337
363
|
locustfile=locustfile_path,
|
338
364
|
available_user_classes=available_user_classes,
|
339
365
|
available_shape_classes=available_shape_classes,
|
366
|
+
available_user_tasks=available_user_tasks,
|
340
367
|
)
|
341
368
|
|
369
|
+
if options.config_users:
|
370
|
+
for json_user_config in options.config_users:
|
371
|
+
try:
|
372
|
+
if json_user_config.endswith(".json"):
|
373
|
+
with open(json_user_config) as file:
|
374
|
+
user_config = json.load(file)
|
375
|
+
else:
|
376
|
+
user_config = json.loads(json_user_config)
|
377
|
+
|
378
|
+
def ensure_user_class_name(config):
|
379
|
+
if "user_class_name" not in config:
|
380
|
+
logger.error("The user config must specify a user_class_name")
|
381
|
+
sys.exit(-1)
|
382
|
+
|
383
|
+
if isinstance(user_config, list):
|
384
|
+
for config in user_config:
|
385
|
+
ensure_user_class_name(config)
|
386
|
+
|
387
|
+
environment.update_user_class(config)
|
388
|
+
else:
|
389
|
+
ensure_user_class_name(user_config)
|
390
|
+
|
391
|
+
environment.update_user_class(user_config)
|
392
|
+
except Exception as e:
|
393
|
+
logger.error(f"The --config-users arugment must be in valid JSON string or file: {e}")
|
394
|
+
sys.exit(-1)
|
395
|
+
|
342
396
|
if (
|
343
397
|
shape_class
|
344
398
|
and not shape_class.use_common_options
|
@@ -412,39 +466,33 @@ See https://github.com/locustio/locust/wiki/Installation#increasing-maximum-numb
|
|
412
466
|
if not options.headless and not options.worker:
|
413
467
|
# spawn web greenlet
|
414
468
|
protocol = "https" if options.tls_cert and options.tls_key else "http"
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
)
|
428
|
-
else:
|
429
|
-
logger.info(f"Starting web interface at {protocol}://0.0.0.0:{options.web_port}")
|
430
|
-
if options.web_auth:
|
431
|
-
logging.warning(
|
432
|
-
"BasicAuth support is deprecated, it will be removed in a future release, unless someone reimplements it in a more modern way! See https://github.com/locustio/locust/issues/2517"
|
469
|
+
|
470
|
+
if options.web_host == "*":
|
471
|
+
# special check for "*" so that we're consistent with --master-bind-host
|
472
|
+
web_host = ""
|
473
|
+
else:
|
474
|
+
web_host = options.web_host
|
475
|
+
if web_host:
|
476
|
+
logger.info(f"Starting web interface at {protocol}://{web_host}:{options.web_port}")
|
477
|
+
else:
|
478
|
+
if os.name == "nt":
|
479
|
+
logger.info(
|
480
|
+
f"Starting web interface at {protocol}://localhost:{options.web_port} (accepting connections from all network interfaces)"
|
433
481
|
)
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
482
|
+
else:
|
483
|
+
logger.info(f"Starting web interface at {protocol}://0.0.0.0:{options.web_port}")
|
484
|
+
|
485
|
+
web_ui = environment.create_web_ui(
|
486
|
+
host=web_host,
|
487
|
+
port=options.web_port,
|
488
|
+
web_login=options.web_login,
|
489
|
+
tls_cert=options.tls_cert,
|
490
|
+
tls_key=options.tls_key,
|
491
|
+
stats_csv_writer=stats_csv_writer,
|
492
|
+
delayed_start=True,
|
493
|
+
userclass_picker_is_active=options.class_picker,
|
494
|
+
modern_ui=options.modern_ui,
|
495
|
+
)
|
448
496
|
else:
|
449
497
|
web_ui = None
|
450
498
|
|
locust/rpc/protocol.py
CHANGED
locust/rpc/zmqrpc.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
-
|
2
|
-
from .protocol import Message
|
1
|
+
from locust.exception import RPCError, RPCReceiveError, RPCSendError
|
3
2
|
from locust.util.exception_handler import retry
|
4
|
-
|
5
|
-
import zmq.error as zmqerr
|
3
|
+
|
6
4
|
import msgpack.exceptions as msgerr
|
5
|
+
import zmq.error as zmqerr
|
6
|
+
import zmq.green as zmq
|
7
|
+
|
8
|
+
from .protocol import Message
|
7
9
|
|
8
10
|
|
9
11
|
class BaseSocket:
|