locust 2.43.4.dev1__tar.gz → 2.43.4.dev5__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.
Files changed (67) hide show
  1. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/PKG-INFO +1 -1
  2. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/_version.py +2 -2
  3. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/contrib/mqtt.py +95 -0
  4. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/.gitignore +0 -0
  5. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/LICENSE +0 -0
  6. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/README.md +0 -0
  7. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/hatch_build.py +0 -0
  8. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/__init__.py +0 -0
  9. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/__main__.py +0 -0
  10. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/argument_parser.py +0 -0
  11. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/clients.py +0 -0
  12. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/contrib/__init__.py +0 -0
  13. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/contrib/dns.py +0 -0
  14. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/contrib/fasthttp.py +0 -0
  15. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/contrib/milvus.py +0 -0
  16. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/contrib/mongodb.py +0 -0
  17. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/contrib/oai.py +0 -0
  18. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/contrib/postgres.py +0 -0
  19. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/contrib/socketio.py +0 -0
  20. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/debug.py +0 -0
  21. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/dispatch.py +0 -0
  22. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/env.py +0 -0
  23. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/event.py +0 -0
  24. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/exception.py +0 -0
  25. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/html.py +0 -0
  26. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/input_events.py +0 -0
  27. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/log.py +0 -0
  28. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/main.py +0 -0
  29. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/opentelemetry.py +0 -0
  30. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/py.typed +0 -0
  31. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/rpc/__init__.py +0 -0
  32. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/rpc/protocol.py +0 -0
  33. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/rpc/zmqrpc.py +0 -0
  34. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/runners.py +0 -0
  35. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/shape.py +0 -0
  36. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/stats.py +0 -0
  37. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/user/__init__.py +0 -0
  38. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/user/inspectuser.py +0 -0
  39. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/user/markov_taskset.py +0 -0
  40. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/user/sequential_taskset.py +0 -0
  41. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/user/task.py +0 -0
  42. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/user/users.py +0 -0
  43. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/user/wait_time.py +0 -0
  44. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/util/__init__.py +0 -0
  45. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/util/cache.py +0 -0
  46. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/util/date.py +0 -0
  47. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/util/deprecation.py +0 -0
  48. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/util/directory.py +0 -0
  49. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/util/exception_handler.py +0 -0
  50. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/util/load_locustfile.py +0 -0
  51. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/util/rounding.py +0 -0
  52. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/util/timespan.py +0 -0
  53. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/util/url.py +0 -0
  54. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/web.py +0 -0
  55. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/webui/dist/assets/favicon-dark.png +0 -0
  56. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/webui/dist/assets/favicon-light.png +0 -0
  57. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/webui/dist/assets/graphs-dark.png +0 -0
  58. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/webui/dist/assets/graphs-light.png +0 -0
  59. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/webui/dist/assets/index-Bl8icIRq.js +0 -0
  60. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/webui/dist/assets/terminal.gif +0 -0
  61. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/webui/dist/assets/testruns-dark.png +0 -0
  62. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/webui/dist/assets/testruns-light.png +0 -0
  63. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/webui/dist/auth.html +0 -0
  64. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/webui/dist/index.html +0 -0
  65. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/locust/webui/dist/report.html +0 -0
  66. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/pyproject.toml +0 -0
  67. {locust-2.43.4.dev1 → locust-2.43.4.dev5}/pytest_locust/plugin.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: locust
3
- Version: 2.43.4.dev1
3
+ Version: 2.43.4.dev5
4
4
  Summary: Developer-friendly load testing framework
5
5
  Project-URL: homepage, https://locust.io/
6
6
  Project-URL: repository, https://github.com/locustio/locust
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '2.43.4.dev1'
32
- __version_tuple__ = version_tuple = (2, 43, 4, 'dev1')
31
+ __version__ = version = '2.43.4.dev5'
32
+ __version_tuple__ = version_tuple = (2, 43, 4, 'dev5')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -4,10 +4,13 @@ from locust import User
4
4
  from locust.env import Environment
5
5
 
6
6
  import random
7
+ import selectors
7
8
  import time
8
9
  import typing
10
+ from contextlib import suppress
9
11
 
10
12
  import paho.mqtt.client as mqtt
13
+ from paho.mqtt.enums import MQTTErrorCode
11
14
 
12
15
  if typing.TYPE_CHECKING:
13
16
  from paho.mqtt.client import MQTTMessageInfo
@@ -73,6 +76,7 @@ class MqttClient(mqtt.Client):
73
76
  environment: Environment,
74
77
  client_id: str | None = None,
75
78
  protocol: MQTTProtocolVersion = mqtt.MQTTv311,
79
+ use_loop_selectors: bool = False,
76
80
  **kwargs,
77
81
  ):
78
82
  """Initializes a paho.mqtt.Client for use in Locust swarms.
@@ -120,6 +124,8 @@ class MqttClient(mqtt.Client):
120
124
  self._publish_requests: dict[int, PublishedMessageContext] = {}
121
125
  self._subscribe_requests: dict[int, tuple[int, str, float]] = {}
122
126
 
127
+ self._use_loop_selectors = use_loop_selectors
128
+
123
129
  def _generate_event_name(self, event_type: str, qos: int, topic: str):
124
130
  return _generate_mqtt_event_name(event_type, qos, topic)
125
131
 
@@ -323,6 +329,93 @@ class MqttClient(mqtt.Client):
323
329
  ) -> None:
324
330
  self._on_connect_cb(client, userdata, {}, reasoncode)
325
331
 
332
+ def _loop(self, timeout: float = 1.0) -> MQTTErrorCode:
333
+ """Override the parent's _loop method to optionally use selectors.
334
+
335
+ When use_loop_selectors is True, this uses a selector-based implementation that allows more than 340 connections.
336
+ Otherwise, it falls back to the parent's implementation.
337
+ """
338
+ if self._use_loop_selectors:
339
+ return self._loop_selectors(timeout)
340
+ else:
341
+ return super()._loop(timeout)
342
+
343
+ def _loop_selectors(self, timeout: float = 1.0) -> MQTTErrorCode:
344
+ if timeout < 0.0:
345
+ raise ValueError("Invalid timeout.")
346
+
347
+ sel = selectors.DefaultSelector()
348
+
349
+ eventmask = selectors.EVENT_READ
350
+
351
+ with suppress(IndexError):
352
+ packet = self._out_packet.popleft()
353
+ self._out_packet.appendleft(packet)
354
+ eventmask = selectors.EVENT_WRITE | eventmask
355
+
356
+ # used to check if there are any bytes left in the (SSL) socket
357
+ pending_bytes = 0
358
+ if hasattr(self._sock, "pending"):
359
+ pending_bytes = self._sock.pending() # type: ignore
360
+
361
+ # if bytes are pending do not wait in select
362
+ if pending_bytes > 0:
363
+ timeout = 0.0
364
+
365
+ try:
366
+ if self._sockpairR is None:
367
+ sel.register(self._sock, eventmask) # type: ignore
368
+ else:
369
+ sel.register(self._sock, eventmask) # type: ignore
370
+ sel.register(self._sockpairR, selectors.EVENT_READ)
371
+
372
+ events = sel.select(timeout)
373
+
374
+ except TypeError:
375
+ # Socket isn't correct type, in likelihood connection is lost
376
+ return mqtt.MQTT_ERR_CONN_LOST
377
+ except ValueError:
378
+ # Can occur if we just reconnected but rlist/wlist contain a -1 for
379
+ # some reason.
380
+ return mqtt.MQTT_ERR_CONN_LOST
381
+ except Exception:
382
+ # Note that KeyboardInterrupt, etc. can still terminate since they
383
+ # are not derived from Exception
384
+ return mqtt.MQTT_ERR_UNKNOWN
385
+
386
+ socklist: list[list] = [[], []]
387
+
388
+ for key, _event in events:
389
+ if key.events & selectors.EVENT_READ:
390
+ socklist[0].append(key.fileobj)
391
+
392
+ if key.events & selectors.EVENT_WRITE:
393
+ socklist[1].append(key.fileobj)
394
+
395
+ if self._sock in socklist[0] or pending_bytes > 0:
396
+ rc = self.loop_read()
397
+ if rc or self._sock is None:
398
+ return rc
399
+
400
+ if self._sockpairR and self._sockpairR in socklist[0]:
401
+ # Stimulate output write even though we didn't ask for it, because
402
+ # at that point the publish or other command wasn't present.
403
+ socklist[1].insert(0, self._sock)
404
+ # Clear sockpairR - only ever a single byte written.
405
+ with suppress(BlockingIOError):
406
+ # Read many bytes at once - this allows up to 10000 calls to
407
+ # publish() inbetween calls to loop().
408
+ self._sockpairR.recv(10000)
409
+
410
+ if self._sock in socklist[1]:
411
+ rc = self.loop_write()
412
+ if rc or self._sock is None:
413
+ return rc
414
+
415
+ sel.close()
416
+
417
+ return self.loop_misc()
418
+
326
419
  def publish(
327
420
  self,
328
421
  topic: str,
@@ -433,6 +526,7 @@ class MqttUser(User):
433
526
  username = None
434
527
  password = None
435
528
  protocol = mqtt.MQTTv311
529
+ use_loop_selectors: bool = False
436
530
 
437
531
  def __init__(self, environment: Environment):
438
532
  super().__init__(environment)
@@ -441,6 +535,7 @@ class MqttUser(User):
441
535
  transport=self.transport,
442
536
  client_id=self.client_id,
443
537
  protocol=self.protocol,
538
+ use_loop_selectors=self.use_loop_selectors,
444
539
  )
445
540
 
446
541
  if self.tls_context:
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes