locust 2.24.2.dev19__py3-none-any.whl → 2.25.0__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.24.2.dev19'
16
- __version_tuple__ = version_tuple = (2, 24, 2, 'dev19')
15
+ __version__ = version = '2.25.0'
16
+ __version_tuple__ = version_tuple = (2, 25, 0)
locust/dispatch.py CHANGED
@@ -1,8 +1,10 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import contextlib
4
+ import functools
4
5
  import itertools
5
6
  import math
7
+ import sys
6
8
  import time
7
9
  from collections import defaultdict
8
10
  from operator import attrgetter
@@ -16,6 +18,20 @@ if TYPE_CHECKING:
16
18
  from locust.runners import WorkerNode
17
19
 
18
20
 
21
+ def compatible_math_gcd(*args: int) -> int:
22
+ """
23
+ This function is a workaround for the fact that `math.gcd` in:
24
+ - 3.5 <= Python < 3.9 doesn't accept more than two arguments.
25
+ - 3.9 <= Python can accept more than two arguments.
26
+ See more at https://docs.python.org/3.9/library/math.html#math.gcd
27
+ """
28
+ if (3, 5) <= sys.version_info < (3, 9):
29
+ return functools.reduce(math.gcd, args)
30
+ elif sys.version_info >= (3, 9):
31
+ return math.gcd(*args)
32
+ raise NotImplementedError("This function is only implemented for Python from 3.5")
33
+
34
+
19
35
  # To profile line-by-line, uncomment the code below (i.e. `import line_profiler ...`) and
20
36
  # place `@profile` on the functions/methods you wish to profile. Then, in the unit test you are
21
37
  # running, use `from locust.dispatch import profile; profile.print_stats()` at the end of the unit test.
@@ -366,18 +382,37 @@ class UsersDispatcher(Iterator):
366
382
  if not users:
367
383
  return itertools.cycle([None])
368
384
 
369
- # Normalize the weights so that the smallest weight will be equal to "target_min_weight".
370
- # The value "2" was experimentally determined because it gave a better distribution especially
371
- # when dealing with weights which are close to each others, e.g. 1.5, 2, 2.4, etc.
372
- target_min_weight = 2
373
-
374
- # 'Value' here means weight or fixed count
385
+ def _get_order_of_magnitude(n: float) -> int:
386
+ """Get how many times we need to multiply `n` to get an integer-like number.
387
+ For example:
388
+ 0.1 would return 10,
389
+ 0.04 would return 100,
390
+ 0.0007 would return 10000.
391
+ """
392
+ if n <= 0:
393
+ raise ValueError("To get the order of magnitude, the number must be greater than 0.")
394
+
395
+ counter = 0
396
+ while n < 1:
397
+ n *= 10
398
+ counter += 1
399
+ return 10**counter
400
+
401
+ # Get maximum order of magnitude to "normalize the weights".
402
+ # "Normalizing the weights" is to multiply all weights by the same number so that
403
+ # they become integers. Then we can find the largest common divisor of all the
404
+ # weights, divide them by it and get the smallest possible numbers with the same
405
+ # ratio as the numbers originally had.
406
+ max_order_of_magnitude = _get_order_of_magnitude(min(abs(u[1]) for u in users))
407
+ weights = tuple(int(u[1] * max_order_of_magnitude) for u in users)
408
+
409
+ greatest_common_divisor = compatible_math_gcd(*weights)
375
410
  normalized_values = [
376
411
  (
377
- user.__name__,
378
- round(target_min_weight * value / min(u[1] for u in users)),
412
+ user[0].__name__,
413
+ normalized_weight // greatest_common_divisor,
379
414
  )
380
- for user, value in users
415
+ for user, normalized_weight in zip(users, weights)
381
416
  ]
382
417
  generation_length_to_get_proper_distribution = sum(
383
418
  normalized_val[1] for normalized_val in normalized_values
@@ -847,6 +847,75 @@ class TestRampUpUsersFromZero(unittest.TestCase):
847
847
  delta = time.perf_counter() - ts
848
848
  self.assertTrue(0 <= delta <= _TOLERANCE, delta)
849
849
 
850
+ def test_implementation_of_dispatch_distribution_with_gcd(self):
851
+ class User1(User):
852
+ weight = 4
853
+
854
+ class User2(User):
855
+ weight = 5
856
+
857
+ user_classes = [User1, User2]
858
+ worker_node1 = WorkerNode("1")
859
+
860
+ sleep_time = 0.2 # Speed-up test
861
+
862
+ users_dispatcher = UsersDispatcher(worker_nodes=[worker_node1], user_classes=user_classes)
863
+ users_dispatcher.new_dispatch(target_user_count=9, spawn_rate=9)
864
+
865
+ users_dispatcher._wait_between_dispatch = sleep_time
866
+
867
+ ts = time.perf_counter()
868
+ self.assertDictEqual(
869
+ next(users_dispatcher),
870
+ {
871
+ "1": {"User1": 4, "User2": 5},
872
+ },
873
+ )
874
+ delta = time.perf_counter() - ts
875
+ self.assertTrue(0 <= delta <= _TOLERANCE, delta)
876
+
877
+ ts = time.perf_counter()
878
+ self.assertRaises(StopIteration, lambda: next(users_dispatcher))
879
+ delta = time.perf_counter() - ts
880
+ self.assertTrue(0 <= delta <= _TOLERANCE, delta)
881
+
882
+ def test_implementation_of_dispatch_distribution_with_gcd_float_weight(self):
883
+ class User1(User):
884
+ weight = 0.8
885
+
886
+ class User2(User):
887
+ weight = 1
888
+
889
+ normalized_weights_to_min_int = 5 # User1: 0.8 * 5 = 4; User2: 1 * 5 = 5
890
+
891
+ user_classes = [User1, User2]
892
+ worker_node1 = WorkerNode("1")
893
+
894
+ sleep_time = 0.2 # Speed-up test
895
+
896
+ users_dispatcher = UsersDispatcher(worker_nodes=[worker_node1], user_classes=user_classes)
897
+ users_dispatcher.new_dispatch(target_user_count=18, spawn_rate=18)
898
+
899
+ users_dispatcher._wait_between_dispatch = sleep_time
900
+
901
+ ts = time.perf_counter()
902
+ self.assertDictEqual(
903
+ next(users_dispatcher),
904
+ {
905
+ "1": {
906
+ "User1": int(normalized_weights_to_min_int * User1.weight * 2),
907
+ "User2": int(normalized_weights_to_min_int * User2.weight * 2),
908
+ },
909
+ },
910
+ )
911
+ delta = time.perf_counter() - ts
912
+ self.assertTrue(0 <= delta <= _TOLERANCE, delta)
913
+
914
+ ts = time.perf_counter()
915
+ self.assertRaises(StopIteration, lambda: next(users_dispatcher))
916
+ delta = time.perf_counter() - ts
917
+ self.assertTrue(0 <= delta <= _TOLERANCE, delta)
918
+
850
919
 
851
920
  class TestWaitBetweenDispatch(unittest.TestCase):
852
921
  def test_wait_between_dispatch(self):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: locust
3
- Version: 2.24.2.dev19
3
+ Version: 2.25.0
4
4
  Summary: Developer friendly load testing framework
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/locustio/locust
@@ -1,10 +1,10 @@
1
1
  locust/__init__.py,sha256=g6oA-Ba_hs3gLWVf5MKJ1mvfltI8MFnDWG8qslqm8yg,1402
2
2
  locust/__main__.py,sha256=vBQ82334kX06ImDbFlPFgiBRiLIinwNk3z8Khs6hd74,31
3
- locust/_version.py,sha256=5NBvVO4oj5bwsW9o1m12F0zB6RR_oGxoklErgHdVzNA,428
3
+ locust/_version.py,sha256=SDnfHVyhb0TZHC7alKpeyDaxXMP92afFlRNDEq2iBEs,413
4
4
  locust/argument_parser.py,sha256=W0X5t-9BFq1DtOBgTBbMoDYBIXfWEqUD7CZ5Rn-i6LQ,32016
5
5
  locust/clients.py,sha256=o3G9welWb-zhgDUM5TKnMVFMJckr_m0FI8Dxn3OtBUA,14810
6
6
  locust/debug.py,sha256=We6Z9W0btkKSc7PxWmrZx-xMynvOOsKhG6jmDgQin0g,5134
7
- locust/dispatch.py,sha256=mn-xVqjLUzRtN91hEqWE2VnVvieQNo97fTVSdMwCDV8,18605
7
+ locust/dispatch.py,sha256=sPtRRer_dJVOaLaIvVriIf4gLduvCcRf11O-E7ohzSc,20101
8
8
  locust/env.py,sha256=nd6ui1bv6n-kkLkP3r61ZkskDY627dsKOAkYHhtOW7o,12472
9
9
  locust/event.py,sha256=Dip3aRKyd4MhAkfd5nPYmWcGKtQEX8NH1mHT74INZT4,7713
10
10
  locust/exception.py,sha256=jGgJ32ubuf4pWdlaVOkbh2Y0LlG0_DHi-lv3ib8ppOE,1791
@@ -53,7 +53,7 @@ locust/test/fake_module2_for_env_test.py,sha256=dzGYWCr1SSkd8Yyo68paUNrCNW7YY_Qg
53
53
  locust/test/mock_locustfile.py,sha256=N9sGjW-BmJ-J_x-5bEOR82VQ0DhR1hki313BHPOWq4g,1273
54
54
  locust/test/mock_logging.py,sha256=dvfXaY2eb0ZHx7Bjn7R69BarnRq9uu0z4HD8hCiRNmU,819
55
55
  locust/test/test_debugging.py,sha256=w5Lx6h101novinKjTJJDg5AqqY28U2Wg1W4hgCk6Sx8,1069
56
- locust/test/test_dispatch.py,sha256=SdnPFdyW0eRj7Bp4mAI8JRkmcwah6Z_b6oaZ_D98QPE,166009
56
+ locust/test/test_dispatch.py,sha256=IVfoBTErABqpRs9B1_D5IP9BrfSBz9qToLiN03Kt9B4,168304
57
57
  locust/test/test_env.py,sha256=ggyx7ZbS7sZPae2wb6JzrvA0p0H7c9HhcBVJn7u1HTw,8956
58
58
  locust/test/test_fasthttp.py,sha256=-UyGdz2Po1wOjcSHs2xPqBtMoJWMaaVrqbx6SaQNAac,30864
59
59
  locust/test/test_http.py,sha256=R23BWBNLjU3OOWCAgIAW8F9H5kqtBx43gPaXC-hhM2g,12570
@@ -95,9 +95,9 @@ locust/webui/dist/report.html,sha256=sOdZZVgZbqgu86BBCSQf3uQUYXgmgSnXF32JpnyAII8
95
95
  locust/webui/dist/assets/favicon.ico,sha256=IUl-rYqfpHdV38e-s0bkmFIeLS-n3Ug0DQxk-h202hI,8348
96
96
  locust/webui/dist/assets/index-941b6e82.js,sha256=G3n5R81Svt0HzbWaV3AV20jLWGLr4X50UZ-Adu2KcxU,1645614
97
97
  locust/webui/dist/assets/logo.png,sha256=EIVPqr6wE_yqguHaqFHIsH0ZACLSrvNWyYO7PbyIj4w,19299
98
- locust-2.24.2.dev19.dist-info/LICENSE,sha256=78XGpIn3fHVBfaxlPNUfjVufSN7QsdhpJMRJHv2AFpo,1095
99
- locust-2.24.2.dev19.dist-info/METADATA,sha256=6l_ZV4AdLiT2K9xxaKGV8ooeLqxM3iPBbYhKjjRFD8Q,7212
100
- locust-2.24.2.dev19.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
101
- locust-2.24.2.dev19.dist-info/entry_points.txt,sha256=RAdt8Ku-56m7bFjmdj-MBhbF6h4NX7tVODR9QNnOg0E,44
102
- locust-2.24.2.dev19.dist-info/top_level.txt,sha256=XSsjgPA8Ggf9TqKVbkwSqZFuPlZ085X13M9orDycE20,7
103
- locust-2.24.2.dev19.dist-info/RECORD,,
98
+ locust-2.25.0.dist-info/LICENSE,sha256=78XGpIn3fHVBfaxlPNUfjVufSN7QsdhpJMRJHv2AFpo,1095
99
+ locust-2.25.0.dist-info/METADATA,sha256=DGfEVV0hzfd550S-Nxi5WNTxsZOoySgfb-xRJ4gFHtw,7206
100
+ locust-2.25.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
101
+ locust-2.25.0.dist-info/entry_points.txt,sha256=RAdt8Ku-56m7bFjmdj-MBhbF6h4NX7tVODR9QNnOg0E,44
102
+ locust-2.25.0.dist-info/top_level.txt,sha256=XSsjgPA8Ggf9TqKVbkwSqZFuPlZ085X13M9orDycE20,7
103
+ locust-2.25.0.dist-info/RECORD,,