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 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.dev10'
16
- __version_tuple__ = version_tuple = (2, 29, 2, 'dev10')
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
- Message,
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: int = 0
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":
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: locust
3
- Version: 2.29.2.dev10
3
+ Version: 2.29.2.dev15
4
4
  Summary: Developer-friendly load testing framework
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/locustio/locust
@@ -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=YavhO18vJKba7ORlLdQVf4D1EDgW-jjeaJqzbttnXjY,428
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=H2SuxyadPMsRY-LQ6MuK1okHls9lYsS9GgXIDSxpi50,7864
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=wqf9eIPUGrqz7m-EZtXKdMJ43cainHm0bS-3Cj4haBo,69471
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=LNZusqKx8brYB30Uk_2dxJMjBMawUmhYwDn0S7EcNH8,163088
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.dev10.dist-info/LICENSE,sha256=78XGpIn3fHVBfaxlPNUfjVufSN7QsdhpJMRJHv2AFpo,1095
74
- locust-2.29.2.dev10.dist-info/METADATA,sha256=GOOGcZMIDpZWsdNzHeM5B_oGHm492YESszucZMKEIzk,7390
75
- locust-2.29.2.dev10.dist-info/WHEEL,sha256=mguMlWGMX-VHnMpKOjjQidIo1ssRlCFu4a4mBpz1s2M,91
76
- locust-2.29.2.dev10.dist-info/entry_points.txt,sha256=RAdt8Ku-56m7bFjmdj-MBhbF6h4NX7tVODR9QNnOg0E,44
77
- locust-2.29.2.dev10.dist-info/top_level.txt,sha256=XSsjgPA8Ggf9TqKVbkwSqZFuPlZ085X13M9orDycE20,7
78
- locust-2.29.2.dev10.dist-info/RECORD,,
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,,