locust 2.29.2.dev10__py3-none-any.whl → 2.29.2.dev15__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/event.py +32 -0
- locust/runners.py +13 -22
- locust/test/test_runners.py +145 -11
- {locust-2.29.2.dev10.dist-info → locust-2.29.2.dev15.dist-info}/METADATA +1 -1
- {locust-2.29.2.dev10.dist-info → locust-2.29.2.dev15.dist-info}/RECORD +10 -10
- {locust-2.29.2.dev10.dist-info → locust-2.29.2.dev15.dist-info}/LICENSE +0 -0
- {locust-2.29.2.dev10.dist-info → locust-2.29.2.dev15.dist-info}/WHEEL +0 -0
- {locust-2.29.2.dev10.dist-info → locust-2.29.2.dev15.dist-info}/entry_points.txt +0 -0
- {locust-2.29.2.dev10.dist-info → locust-2.29.2.dev15.dist-info}/top_level.txt +0 -0
locust/_version.py
CHANGED
@@ -12,5 +12,5 @@ __version__: str
|
|
12
12
|
__version_tuple__: VERSION_TUPLE
|
13
13
|
version_tuple: VERSION_TUPLE
|
14
14
|
|
15
|
-
__version__ = version = '2.29.2.
|
16
|
-
__version_tuple__ = version_tuple = (2, 29, 2, '
|
15
|
+
__version__ = version = '2.29.2.dev15'
|
16
|
+
__version_tuple__ = version_tuple = (2, 29, 2, 'dev15')
|
locust/event.py
CHANGED
@@ -235,6 +235,38 @@ class Events:
|
|
235
235
|
Fired when the CPU usage exceeds runners.CPU_WARNING_THRESHOLD (90% by default)
|
236
236
|
"""
|
237
237
|
|
238
|
+
heartbeat_sent: EventHook
|
239
|
+
"""
|
240
|
+
Fired when a heartbeat is sent by master to a worker.
|
241
|
+
|
242
|
+
Event arguments:
|
243
|
+
|
244
|
+
:param client_id: worker client id
|
245
|
+
:param timestamp: time in seconds since the epoch (float) when the event occured
|
246
|
+
"""
|
247
|
+
|
248
|
+
heartbeat_received: EventHook
|
249
|
+
"""
|
250
|
+
Fired when a heartbeat is received by a worker from master.
|
251
|
+
|
252
|
+
Event arguments:
|
253
|
+
|
254
|
+
:param client_id: worker client id
|
255
|
+
:param timestamp: time in seconds since the epoch (float) when the event occured
|
256
|
+
"""
|
257
|
+
|
258
|
+
usage_monitor: EventHook
|
259
|
+
"""
|
260
|
+
Fired every runners.CPU_MONITOR_INTERVAL (5.0 seconds by default) with information about
|
261
|
+
current CPU and memory usage.
|
262
|
+
|
263
|
+
Event arguments:
|
264
|
+
|
265
|
+
:param environment: locust environment
|
266
|
+
:param cpu_usage: current CPU usage in percent
|
267
|
+
:param memory_usage: current memory usage (RSS) in bytes
|
268
|
+
"""
|
269
|
+
|
238
270
|
def __init__(self):
|
239
271
|
# For backward compatibility use also values of class attributes
|
240
272
|
for name, value in vars(type(self)).items():
|
locust/runners.py
CHANGED
@@ -15,19 +15,9 @@ import traceback
|
|
15
15
|
from abc import abstractmethod
|
16
16
|
from collections import defaultdict
|
17
17
|
from collections.abc import Iterator, MutableMapping, ValuesView
|
18
|
-
from operator import
|
19
|
-
itemgetter,
|
20
|
-
methodcaller,
|
21
|
-
)
|
18
|
+
from operator import itemgetter, methodcaller
|
22
19
|
from types import TracebackType
|
23
|
-
from typing import
|
24
|
-
TYPE_CHECKING,
|
25
|
-
Any,
|
26
|
-
Callable,
|
27
|
-
NoReturn,
|
28
|
-
TypedDict,
|
29
|
-
cast,
|
30
|
-
)
|
20
|
+
from typing import TYPE_CHECKING, Any, Callable, NoReturn, TypedDict, cast
|
31
21
|
from uuid import uuid4
|
32
22
|
|
33
23
|
import gevent
|
@@ -40,15 +30,8 @@ from . import argument_parser
|
|
40
30
|
from .dispatch import UsersDispatcher
|
41
31
|
from .exception import RPCError, RPCReceiveError, RPCSendError
|
42
32
|
from .log import get_logs, greenlet_exception_logger
|
43
|
-
from .rpc import
|
44
|
-
|
45
|
-
rpc,
|
46
|
-
)
|
47
|
-
from .stats import (
|
48
|
-
RequestStats,
|
49
|
-
StatsError,
|
50
|
-
setup_distributed_stats_event_listeners,
|
51
|
-
)
|
33
|
+
from .rpc import Message, rpc
|
34
|
+
from .stats import RequestStats, StatsError, setup_distributed_stats_event_listeners
|
52
35
|
|
53
36
|
if TYPE_CHECKING:
|
54
37
|
from . import User
|
@@ -106,7 +89,7 @@ class Runner:
|
|
106
89
|
self.spawning_greenlet: gevent.Greenlet | None = None
|
107
90
|
self.shape_greenlet: gevent.Greenlet | None = None
|
108
91
|
self.shape_last_tick: tuple[int, float] | tuple[int, float, list[type[User]] | None] | None = None
|
109
|
-
self.current_cpu_usage:
|
92
|
+
self.current_cpu_usage: float = 0.0
|
110
93
|
self.cpu_warning_emitted: bool = False
|
111
94
|
self.worker_cpu_warning_emitted: bool = False
|
112
95
|
self.current_memory_usage: int = 0
|
@@ -308,6 +291,10 @@ class Runner:
|
|
308
291
|
f"CPU usage above {CPU_WARNING_THRESHOLD}%! This may constrain your throughput and may even give inconsistent response time measurements! See https://docs.locust.io/en/stable/running-distributed.html for how to distribute the load over multiple CPU cores or machines"
|
309
292
|
)
|
310
293
|
self.cpu_warning_emitted = True
|
294
|
+
|
295
|
+
self.environment.events.usage_monitor.fire(
|
296
|
+
environment=self.environment, cpu_usage=self.current_cpu_usage, memory_usage=self.current_memory_usage
|
297
|
+
)
|
311
298
|
gevent.sleep(CPU_MONITOR_INTERVAL)
|
312
299
|
|
313
300
|
@abstractmethod
|
@@ -1102,6 +1089,7 @@ class MasterRunner(DistributedRunner):
|
|
1102
1089
|
)
|
1103
1090
|
if "current_memory_usage" in msg.data:
|
1104
1091
|
c.memory_usage = msg.data["current_memory_usage"]
|
1092
|
+
self.environment.events.heartbeat_sent.fire(client_id=msg.node_id, timestamp=time.time())
|
1105
1093
|
self.server.send_to_client(Message("heartbeat", None, msg.node_id))
|
1106
1094
|
else:
|
1107
1095
|
logging.debug(f"Got heartbeat message from unknown worker {msg.node_id}")
|
@@ -1399,6 +1387,9 @@ class WorkerRunner(DistributedRunner):
|
|
1399
1387
|
self.reset_connection()
|
1400
1388
|
elif msg.type == "heartbeat":
|
1401
1389
|
self.last_heartbeat_timestamp = time.time()
|
1390
|
+
self.environment.events.heartbeat_received.fire(
|
1391
|
+
client_id=msg.node_id, timestamp=self.last_heartbeat_timestamp
|
1392
|
+
)
|
1402
1393
|
elif msg.type == "update_user_class":
|
1403
1394
|
self.environment.update_user_class(msg.data)
|
1404
1395
|
elif msg.type == "spawning_complete":
|
locust/test/test_runners.py
CHANGED
@@ -1,12 +1,7 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import locust
|
4
|
-
from locust import
|
5
|
-
LoadTestShape,
|
6
|
-
__version__,
|
7
|
-
constant,
|
8
|
-
runners,
|
9
|
-
)
|
4
|
+
from locust import LoadTestShape, __version__, constant, runners
|
10
5
|
from locust.argument_parser import parse_options
|
11
6
|
from locust.dispatch import UsersDispatcher
|
12
7
|
from locust.env import Environment
|
@@ -26,11 +21,7 @@ from locust.runners import (
|
|
26
21
|
WorkerRunner,
|
27
22
|
)
|
28
23
|
from locust.stats import RequestStats
|
29
|
-
from locust.user import
|
30
|
-
TaskSet,
|
31
|
-
User,
|
32
|
-
task,
|
33
|
-
)
|
24
|
+
from locust.user import TaskSet, User, task
|
34
25
|
|
35
26
|
import json
|
36
27
|
import logging
|
@@ -2136,6 +2127,149 @@ class TestMasterWorkerRunners(LocustTestCase):
|
|
2136
2127
|
|
2137
2128
|
self.assertEqual(test_start_exec_count, 1)
|
2138
2129
|
|
2130
|
+
def test_heartbeat_event(self) -> None:
|
2131
|
+
"""
|
2132
|
+
Tests that heartbeat event is fired during a test
|
2133
|
+
"""
|
2134
|
+
|
2135
|
+
class TestUser(User):
|
2136
|
+
wait_time = constant(0.1)
|
2137
|
+
|
2138
|
+
@task
|
2139
|
+
def noop(self) -> None:
|
2140
|
+
pass
|
2141
|
+
|
2142
|
+
with mock.patch("locust.runners.HEARTBEAT_INTERVAL", new=1):
|
2143
|
+
# start a Master runner
|
2144
|
+
master_env = Environment(user_classes=[TestUser])
|
2145
|
+
worker_connect_events = []
|
2146
|
+
timestamp_start: list[float] = [time.time() + 3600.0]
|
2147
|
+
|
2148
|
+
def on_connect(client_id: str) -> None:
|
2149
|
+
worker_connect_events.append(client_id)
|
2150
|
+
timestamp_start[0] = time.time()
|
2151
|
+
|
2152
|
+
master_env.events.worker_connect.add_listener(on_connect)
|
2153
|
+
master = master_env.create_master_runner("*", 0)
|
2154
|
+
sleep(0)
|
2155
|
+
worker_env = Environment(user_classes=[TestUser])
|
2156
|
+
worker: WorkerRunner = worker_env.create_worker_runner("127.0.0.1", master.server.port)
|
2157
|
+
|
2158
|
+
with (
|
2159
|
+
mock.patch.object(
|
2160
|
+
worker.environment.events.heartbeat_received,
|
2161
|
+
"fire",
|
2162
|
+
wraps=worker.environment.events.heartbeat_received.fire,
|
2163
|
+
) as worker_heartbeat_received_mock,
|
2164
|
+
mock.patch.object(
|
2165
|
+
master.environment.events.heartbeat_sent,
|
2166
|
+
"fire",
|
2167
|
+
wraps=master.environment.events.heartbeat_sent.fire,
|
2168
|
+
) as master_heartbeat_sent_mock,
|
2169
|
+
):
|
2170
|
+
# give workers time to connect
|
2171
|
+
sleep(0.1)
|
2172
|
+
# issue start command that should trigger TestUsers to be spawned in the Workers
|
2173
|
+
master.start(2, spawn_rate=2)
|
2174
|
+
sleep(0.1)
|
2175
|
+
# check that worker nodes have started locusts
|
2176
|
+
self.assertEqual(2, worker.user_count)
|
2177
|
+
|
2178
|
+
# give time for nodes to send and receive 5 heartbeats, HEARTBEAT_INTERVAL mocked to 1 second, so
|
2179
|
+
# sleep 5 seconds - 1 second that represents the overhead from connecting
|
2180
|
+
sleep(5 - 1)
|
2181
|
+
master.quit()
|
2182
|
+
|
2183
|
+
# make sure users are killed
|
2184
|
+
self.assertEqual(0, worker.user_count)
|
2185
|
+
# make sure events happened correctly
|
2186
|
+
self.assertIn(worker.client_id, worker_connect_events)
|
2187
|
+
|
2188
|
+
timestamp_stop = time.time()
|
2189
|
+
|
2190
|
+
self.assertEqual(worker_heartbeat_received_mock.call_count, 5)
|
2191
|
+
self.assertEqual(master_heartbeat_sent_mock.call_count, 5)
|
2192
|
+
|
2193
|
+
for call_args, call_kwargs in [
|
2194
|
+
*worker_heartbeat_received_mock.call_args_list,
|
2195
|
+
*master_heartbeat_sent_mock.call_args_list,
|
2196
|
+
]:
|
2197
|
+
self.assertEqual(call_args, ()) # args
|
2198
|
+
self.assertEqual(call_kwargs, {"client_id": worker.client_id, "timestamp": mock.ANY}) # kwargs
|
2199
|
+
self.assertGreaterEqual(call_kwargs["timestamp"], timestamp_start[0])
|
2200
|
+
self.assertLessEqual(call_kwargs["timestamp"], timestamp_stop)
|
2201
|
+
|
2202
|
+
def test_usage_monitor_event(self) -> None:
|
2203
|
+
"""
|
2204
|
+
Tests that usage_monitor event is fired during a test
|
2205
|
+
"""
|
2206
|
+
|
2207
|
+
class TestUser(User):
|
2208
|
+
wait_time = constant(0.1)
|
2209
|
+
|
2210
|
+
@task
|
2211
|
+
def noop(self) -> None:
|
2212
|
+
pass
|
2213
|
+
|
2214
|
+
with mock.patch("locust.runners.CPU_MONITOR_INTERVAL", new=1):
|
2215
|
+
# start a Master runner
|
2216
|
+
master_env = Environment(user_classes=[TestUser])
|
2217
|
+
worker_connect_events = []
|
2218
|
+
|
2219
|
+
def on_connect(client_id: str) -> None:
|
2220
|
+
worker_connect_events.append(client_id)
|
2221
|
+
|
2222
|
+
master_env.events.worker_connect.add_listener(on_connect)
|
2223
|
+
master = master_env.create_master_runner("*", 0)
|
2224
|
+
sleep(0)
|
2225
|
+
worker_env = Environment(user_classes=[TestUser])
|
2226
|
+
worker: WorkerRunner = worker_env.create_worker_runner("127.0.0.1", master.server.port)
|
2227
|
+
|
2228
|
+
with (
|
2229
|
+
mock.patch.object(
|
2230
|
+
worker.environment.events.usage_monitor, "fire", wraps=worker.environment.events.usage_monitor.fire
|
2231
|
+
) as worker_usage_monitor_mock,
|
2232
|
+
mock.patch.object(
|
2233
|
+
master.environment.events.usage_monitor, "fire", wraps=master.environment.events.usage_monitor.fire
|
2234
|
+
) as master_usage_monitor_mock,
|
2235
|
+
):
|
2236
|
+
# give workers time to connect
|
2237
|
+
sleep(0.1)
|
2238
|
+
# issue start command that should trigger TestUsers to be spawned in the Workers
|
2239
|
+
master.start(2, spawn_rate=2)
|
2240
|
+
sleep(0.1)
|
2241
|
+
# check that worker nodes have started locusts
|
2242
|
+
self.assertEqual(2, worker.user_count)
|
2243
|
+
|
2244
|
+
# give time for nodes to send 5 usage_monitor events, CPU_MONITOR_INTERVAL mocked to 1 second, so
|
2245
|
+
# sleep 5 seconds
|
2246
|
+
sleep(5)
|
2247
|
+
master.quit()
|
2248
|
+
|
2249
|
+
# make sure users are killed
|
2250
|
+
self.assertEqual(0, worker.user_count)
|
2251
|
+
# make sure events happened correctly
|
2252
|
+
self.assertIn(worker.client_id, worker_connect_events)
|
2253
|
+
|
2254
|
+
self.assertEqual(worker_usage_monitor_mock.call_count, 5)
|
2255
|
+
self.assertEqual(master_usage_monitor_mock.call_count, 5)
|
2256
|
+
|
2257
|
+
for call_args, call_kwargs in master_usage_monitor_mock:
|
2258
|
+
self.assertEqual(call_args, ()) # args
|
2259
|
+
self.assertEqual(
|
2260
|
+
call_kwargs, {"environment": master_env, "cpu_usage": mock.ANY, "memory_usage": mock.ANY}
|
2261
|
+
) # kwargs
|
2262
|
+
self.assertTrue(isinstance(call_kwargs["cpu_usage"], float))
|
2263
|
+
self.assertTrue(isinstance(call_kwargs["memory_usage"], int))
|
2264
|
+
|
2265
|
+
for call_args, call_kwargs in worker_usage_monitor_mock:
|
2266
|
+
self.assertEqual(call_args, ()) # args
|
2267
|
+
self.assertEqual(
|
2268
|
+
call_kwargs, {"environment": worker_env, "cpu_usage": mock.ANY, "memory_usage": mock.ANY}
|
2269
|
+
) # kwargs
|
2270
|
+
self.assertTrue(isinstance(call_kwargs["cpu_usage"], float))
|
2271
|
+
self.assertTrue(isinstance(call_kwargs["memory_usage"], int))
|
2272
|
+
|
2139
2273
|
|
2140
2274
|
class TestMasterRunner(LocustRunnerTestCase):
|
2141
2275
|
def setUp(self):
|
@@ -1,19 +1,19 @@
|
|
1
1
|
locust/__init__.py,sha256=Hmw2vNf75eLQ1mQIPXAwlQrJ_XFY65MOb92fGsNCukQ,1458
|
2
2
|
locust/__main__.py,sha256=vBQ82334kX06ImDbFlPFgiBRiLIinwNk3z8Khs6hd74,31
|
3
|
-
locust/_version.py,sha256=
|
3
|
+
locust/_version.py,sha256=ZoGVgC2lL7aipXStKMVEIu1JxbIXfEYmFmNyXQQY1RM,428
|
4
4
|
locust/argument_parser.py,sha256=sjQoJ1NTac9LdNYT7zn8RajlWqBQs8YFNv6uRExb2gg,28941
|
5
5
|
locust/clients.py,sha256=OHPv6hBAt4gt3HI67yqyT1qrSsF8uMdCwIRu0kIsRWI,19491
|
6
6
|
locust/debug.py,sha256=We6Z9W0btkKSc7PxWmrZx-xMynvOOsKhG6jmDgQin0g,5134
|
7
7
|
locust/dispatch.py,sha256=vYh0QEDFgJ3hY0HgSk-EiNO7IP9ffzXF_Et8wB9JvsI,16995
|
8
8
|
locust/env.py,sha256=sP-fCnZs0e2xodRemLHgTgyyUt5eezwtdA9WsCoqJkQ,12767
|
9
|
-
locust/event.py,sha256=
|
9
|
+
locust/event.py,sha256=iXEwIYFzra-j1WRldXB9SUibydtD8q8EIKaFPGTTIjk,8729
|
10
10
|
locust/exception.py,sha256=jGgJ32ubuf4pWdlaVOkbh2Y0LlG0_DHi-lv3ib8ppOE,1791
|
11
11
|
locust/html.py,sha256=_n3aB3fxiYzSeE_7RqHF3iiEPjPnbQ3e2Pw9P8AVtPU,3920
|
12
12
|
locust/input_events.py,sha256=ZIyePyAMuA_YFYWg18g_pE4kwuQV3RbEB250MzXRwjY,3314
|
13
13
|
locust/log.py,sha256=Wrkn0Ibugh5Sqjm4hGQ2-jUsy1tNMBdTctp4FyXQI24,3457
|
14
14
|
locust/main.py,sha256=NGjL5QqakU5aeyUzwu2Fh00xVZfC3eoBE3DtfOmRtcM,27854
|
15
15
|
locust/py.typed,sha256=gkWLl8yD4mIZnNYYAIRM8g9VarLvWmTAFeUfEbxJLBw,65
|
16
|
-
locust/runners.py,sha256=
|
16
|
+
locust/runners.py,sha256=otH-ZxTygBRXN46Nmocs5ac8R4b0MsnKAUcHRwVnD1E,69869
|
17
17
|
locust/shape.py,sha256=t-lwBS8LOjWcKXNL7j2U3zroIXJ1b0fazUwpRYQOKXw,1973
|
18
18
|
locust/stats.py,sha256=5jx9aD9Sky-kCZ3E-MjRT3xbwvxo9xyDtfcfP56zclo,45875
|
19
19
|
locust/web.py,sha256=rN1NVeZ9LKSEeDwvpRbOJ0bcy8U1U4VjP-7vK7ejlwM,27367
|
@@ -39,7 +39,7 @@ locust/test/test_log.py,sha256=YPY6vgTAy1KaNU2qoVvQrTH5x_mzRrljEHrkSBy3yxs,7553
|
|
39
39
|
locust/test/test_main.py,sha256=7OuH2-7noD_rbeoKJD9hIZsylSugu7ze3XFIrU1u0HI,85016
|
40
40
|
locust/test/test_old_wait_api.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
41
41
|
locust/test/test_parser.py,sha256=-2VO5Dopg-VoWvIgXrmr7GN40cqrnjUoctBHmVlyewg,17826
|
42
|
-
locust/test/test_runners.py,sha256=
|
42
|
+
locust/test/test_runners.py,sha256=jvyv5JCD4sJ_8nmB8ZzqGINLkepIAKyIgwdVTzMtG7I,169468
|
43
43
|
locust/test/test_sequential_taskset.py,sha256=QjVMWWfGHn9hU5AvPxRDU7Vo5DcVW1VkMVfDA0k9OPE,3398
|
44
44
|
locust/test/test_stats.py,sha256=F51VkL3k3y4OhYBlRyV6vWzisenSAOmSWKy2IPVrnWM,33929
|
45
45
|
locust/test/test_tags.py,sha256=mzhGLPMizSnSItTHLHizYvloxDfuIDAOgelwInyrf28,13138
|
@@ -70,9 +70,9 @@ locust/webui/dist/index.html,sha256=S78UvAUZbQ-sH0wBxqFKrT2ZSfRxUFGx5xwQY6FaVMk,
|
|
70
70
|
locust/webui/dist/report.html,sha256=sOdZZVgZbqgu86BBCSQf3uQUYXgmgSnXF32JpnyAII8,513
|
71
71
|
locust/webui/dist/assets/favicon.ico,sha256=IUl-rYqfpHdV38e-s0bkmFIeLS-n3Ug0DQxk-h202hI,8348
|
72
72
|
locust/webui/dist/assets/logo.png,sha256=EIVPqr6wE_yqguHaqFHIsH0ZACLSrvNWyYO7PbyIj4w,19299
|
73
|
-
locust-2.29.2.
|
74
|
-
locust-2.29.2.
|
75
|
-
locust-2.29.2.
|
76
|
-
locust-2.29.2.
|
77
|
-
locust-2.29.2.
|
78
|
-
locust-2.29.2.
|
73
|
+
locust-2.29.2.dev15.dist-info/LICENSE,sha256=78XGpIn3fHVBfaxlPNUfjVufSN7QsdhpJMRJHv2AFpo,1095
|
74
|
+
locust-2.29.2.dev15.dist-info/METADATA,sha256=HwQcBqK2CgqJBGYsEhEuyg2Uxftjm0Q4Z1cnLe-2czM,7390
|
75
|
+
locust-2.29.2.dev15.dist-info/WHEEL,sha256=mguMlWGMX-VHnMpKOjjQidIo1ssRlCFu4a4mBpz1s2M,91
|
76
|
+
locust-2.29.2.dev15.dist-info/entry_points.txt,sha256=RAdt8Ku-56m7bFjmdj-MBhbF6h4NX7tVODR9QNnOg0E,44
|
77
|
+
locust-2.29.2.dev15.dist-info/top_level.txt,sha256=XSsjgPA8Ggf9TqKVbkwSqZFuPlZ085X13M9orDycE20,7
|
78
|
+
locust-2.29.2.dev15.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|