locust 2.46.5.dev8__tar.gz → 2.46.6.dev4__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.
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/PKG-INFO +1 -1
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/__init__.py +2 -1
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/_version.py +2 -2
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/clients.py +4 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/contrib/fasthttp.py +3 -67
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/user/users.py +79 -3
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/user/wait_time.py +27 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/.gitignore +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/LICENSE +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/README.md +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/hatch_build.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/__main__.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/argument_parser.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/contrib/__init__.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/contrib/csv_request_logger.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/contrib/dns.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/contrib/milvus.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/contrib/mongodb.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/contrib/mqtt.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/contrib/oai.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/contrib/postgres.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/contrib/qdrant.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/contrib/socketio.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/debug.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/dispatch.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/env.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/event.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/exception.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/html.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/input_events.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/log.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/main.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/opentelemetry.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/py.typed +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/rpc/__init__.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/rpc/protocol.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/rpc/zmqrpc.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/runners.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/shape.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/stats.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/user/__init__.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/user/inspectuser.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/user/markov_taskset.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/user/sequential_taskset.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/user/task.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/util/__init__.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/util/cache.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/util/date.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/util/deprecation.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/util/directory.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/util/exception_handler.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/util/load_locustfile.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/util/rounding.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/util/timespan.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/util/url.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/web.py +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/webui/dist/assets/favicon-dark.png +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/webui/dist/assets/favicon-light.png +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/webui/dist/assets/graphs-dark.png +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/webui/dist/assets/graphs-light.png +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/webui/dist/assets/src-pSlzKFX1.js +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/webui/dist/assets/terminal.gif +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/webui/dist/assets/testruns-dark.png +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/webui/dist/assets/testruns-light.png +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/webui/dist/auth.html +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/webui/dist/index.html +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/locust/webui/dist/report.html +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/pyproject.toml +0 -0
- {locust-2.46.5.dev8 → locust-2.46.6.dev4}/pytest_locust/plugin.py +0 -0
|
@@ -31,7 +31,7 @@ from .user.markov_taskset import MarkovTaskSet, transition, transitions
|
|
|
31
31
|
from .user.sequential_taskset import SequentialTaskSet
|
|
32
32
|
from .user.task import TaskSet, tag, task
|
|
33
33
|
from .user.users import HttpUser, User
|
|
34
|
-
from .user.wait_time import between, constant, constant_pacing, constant_throughput
|
|
34
|
+
from .user.wait_time import between, constant, constant_pacing, constant_throughput, poisson
|
|
35
35
|
|
|
36
36
|
events = Events()
|
|
37
37
|
|
|
@@ -51,6 +51,7 @@ __all__ = (
|
|
|
51
51
|
"constant",
|
|
52
52
|
"constant_pacing",
|
|
53
53
|
"constant_throughput",
|
|
54
|
+
"poisson",
|
|
54
55
|
"events",
|
|
55
56
|
"LoadTestShape",
|
|
56
57
|
"run_single_user",
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '2.46.
|
|
22
|
-
__version_tuple__ = version_tuple = (2, 46,
|
|
21
|
+
__version__ = version = '2.46.6.dev4'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 46, 6, 'dev4')
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -478,6 +478,10 @@ class ResponseContextManager(Response):
|
|
|
478
478
|
self._manual_result = exc
|
|
479
479
|
|
|
480
480
|
|
|
481
|
+
class RestResponseContextManager(ResponseContextManager):
|
|
482
|
+
js: dict # This is technically an Optional, but I dont want to force everyone to check it
|
|
483
|
+
|
|
484
|
+
|
|
481
485
|
class LocustHttpAdapter(HTTPAdapter):
|
|
482
486
|
def __init__(self, pool_manager: PoolManager | None, *args, **kwargs):
|
|
483
487
|
self.poolmanager = pool_manager
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from locust.exception import CatchResponseError, LocustError, ResponseError, StopTest
|
|
4
4
|
from locust.user import User
|
|
5
|
+
from locust.user.users import RestMixin
|
|
5
6
|
from locust.util.deprecation import DeprecatedFastHttpLocustClass as FastHttpLocust # noqa: F401
|
|
6
7
|
|
|
7
8
|
import json
|
|
@@ -9,14 +10,11 @@ import json as unshadowed_json # some methods take a named parameter called jso
|
|
|
9
10
|
import re
|
|
10
11
|
import socket
|
|
11
12
|
import time
|
|
12
|
-
import traceback
|
|
13
13
|
import zlib
|
|
14
14
|
from base64 import b64encode
|
|
15
|
-
from contextlib import contextmanager
|
|
16
15
|
from http.cookiejar import CookieJar
|
|
17
|
-
from json.decoder import JSONDecodeError
|
|
18
16
|
from ssl import SSLError
|
|
19
|
-
from typing import TYPE_CHECKING
|
|
17
|
+
from typing import TYPE_CHECKING
|
|
20
18
|
from urllib.parse import urlparse, urlunparse
|
|
21
19
|
|
|
22
20
|
import gevent
|
|
@@ -355,7 +353,7 @@ class FastHttpSession:
|
|
|
355
353
|
return self.request("PUT", url, data=data, **kwargs)
|
|
356
354
|
|
|
357
355
|
|
|
358
|
-
class FastHttpUser(User):
|
|
356
|
+
class FastHttpUser(User, RestMixin["RestResponseContextManager"]):
|
|
359
357
|
"""
|
|
360
358
|
FastHttpUser provides the same API as HttpUser, but uses geventhttpclient instead of python-requests
|
|
361
359
|
as its underlying client. It uses considerably less CPU on the load generator, and should work
|
|
@@ -413,8 +411,6 @@ class FastHttpUser(User):
|
|
|
413
411
|
abstract = True
|
|
414
412
|
"""Dont register this as a User class that can be run by itself"""
|
|
415
413
|
|
|
416
|
-
_callstack_regex = re.compile(r' File "(\/.[^"]*)", line (\d*),(.*)')
|
|
417
|
-
|
|
418
414
|
def __init__(self, environment) -> None:
|
|
419
415
|
super().__init__(environment)
|
|
420
416
|
if self.host is None:
|
|
@@ -443,66 +439,6 @@ class FastHttpUser(User):
|
|
|
443
439
|
The client support cookies, and therefore keeps the session between HTTP requests.
|
|
444
440
|
"""
|
|
445
441
|
|
|
446
|
-
@contextmanager
|
|
447
|
-
def rest(self, method, url, headers: dict | None = None, **kwargs) -> Generator[RestResponseContextManager]:
|
|
448
|
-
"""
|
|
449
|
-
A wrapper for self.client.request that:
|
|
450
|
-
|
|
451
|
-
* Parses the JSON response to a dict called ``js`` in the response object. Marks the request as failed if the response was not valid JSON.
|
|
452
|
-
* Defaults ``Content-Type`` and ``Accept`` headers to ``application/json``
|
|
453
|
-
* Sets ``catch_response=True`` (so always use a :ref:`with-block <catch-response>`)
|
|
454
|
-
* Catches any unhandled exceptions thrown inside your with-block, marking the sample as failed (instead of exiting the task immediately without even firing the request event)
|
|
455
|
-
"""
|
|
456
|
-
headers = headers or {}
|
|
457
|
-
if not ("Content-Type" in headers or "content-type" in headers):
|
|
458
|
-
headers["Content-Type"] = "application/json"
|
|
459
|
-
if not ("Accept" in headers or "accept" in headers):
|
|
460
|
-
headers["Accept"] = "application/json"
|
|
461
|
-
with self.client.request(method, url, catch_response=True, headers=headers, **kwargs) as r:
|
|
462
|
-
resp = cast(RestResponseContextManager, r)
|
|
463
|
-
resp.js = None # type: ignore
|
|
464
|
-
if resp.content is None:
|
|
465
|
-
resp.failure(str(resp.error))
|
|
466
|
-
elif resp.text:
|
|
467
|
-
try:
|
|
468
|
-
resp.js = resp.json()
|
|
469
|
-
except JSONDecodeError as e:
|
|
470
|
-
resp.failure(
|
|
471
|
-
f"Could not parse response as JSON. {resp.text[:250]}, response code {resp.status_code}, error {e}"
|
|
472
|
-
)
|
|
473
|
-
try:
|
|
474
|
-
yield resp
|
|
475
|
-
except AssertionError as e:
|
|
476
|
-
if e.args:
|
|
477
|
-
if e.args[0].endswith(","):
|
|
478
|
-
short_resp = resp.text[:200] if resp.text else resp.text
|
|
479
|
-
resp.failure(f"{e.args[0][:-1]}, response was {short_resp}")
|
|
480
|
-
else:
|
|
481
|
-
resp.failure(e.args[0])
|
|
482
|
-
else:
|
|
483
|
-
resp.failure("Assertion failed")
|
|
484
|
-
|
|
485
|
-
except Exception as e:
|
|
486
|
-
error_lines = []
|
|
487
|
-
for l in traceback.format_exc().split("\n"):
|
|
488
|
-
if m := self._callstack_regex.match(l):
|
|
489
|
-
filename = re.sub(r"/((home|Users)/\w*)/", "~/", m.group(1))
|
|
490
|
-
error_lines.append(filename + ":" + m.group(2) + m.group(3))
|
|
491
|
-
short_resp = resp.text[:200] if resp.text else resp.text
|
|
492
|
-
resp.failure(f"{e.__class__.__name__}: {e} at {', '.join(error_lines)}. Response was {short_resp}")
|
|
493
|
-
|
|
494
|
-
@contextmanager
|
|
495
|
-
def rest_(self, method, url, name=None, **kwargs) -> Generator[RestResponseContextManager]:
|
|
496
|
-
"""
|
|
497
|
-
Some REST api:s use a timestamp as part of their query string (mainly to break through caches).
|
|
498
|
-
This is a convenience method for that, appending a _=<timestamp> parameter automatically
|
|
499
|
-
"""
|
|
500
|
-
separator = "&" if "?" in url else "?"
|
|
501
|
-
if name is None:
|
|
502
|
-
name = url + separator + "_=..."
|
|
503
|
-
with self.rest(method, f"{url}{separator}_={int(time.time() * 1000)}", name=name, **kwargs) as resp:
|
|
504
|
-
yield resp
|
|
505
|
-
|
|
506
442
|
|
|
507
443
|
class FastRequest(CompatRequest):
|
|
508
444
|
payload: str | None = None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from locust.clients import HttpSession
|
|
3
|
+
from locust.clients import HttpSession, RestResponseContextManager
|
|
4
4
|
from locust.exception import CatchResponseError, StopTest, StopUser
|
|
5
5
|
from locust.user.task import (
|
|
6
6
|
LOCUST_STATE_RUNNING,
|
|
@@ -14,11 +14,14 @@ from locust.user.wait_time import constant
|
|
|
14
14
|
from locust.util import deprecation
|
|
15
15
|
|
|
16
16
|
import logging
|
|
17
|
+
import re
|
|
17
18
|
import sys
|
|
18
19
|
import time
|
|
19
20
|
import traceback
|
|
20
21
|
from collections.abc import Callable
|
|
21
|
-
from
|
|
22
|
+
from contextlib import contextmanager
|
|
23
|
+
from json.decoder import JSONDecodeError
|
|
24
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar, final
|
|
22
25
|
|
|
23
26
|
from gevent import GreenletExit, greenlet
|
|
24
27
|
from gevent.pool import Group
|
|
@@ -32,6 +35,8 @@ else:
|
|
|
32
35
|
from typing_extensions import override
|
|
33
36
|
|
|
34
37
|
if TYPE_CHECKING:
|
|
38
|
+
from collections.abc import Generator
|
|
39
|
+
|
|
35
40
|
import pytest
|
|
36
41
|
|
|
37
42
|
logger = logging.getLogger(__name__)
|
|
@@ -250,7 +255,78 @@ class User(metaclass=UserMeta):
|
|
|
250
255
|
return ".".join(filter(lambda x: x != "<locals>", (cls.__module__ + "." + cls.__qualname__).split(".")))
|
|
251
256
|
|
|
252
257
|
|
|
253
|
-
|
|
258
|
+
RestResponseT = TypeVar("RestResponseT")
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class RestMixin(Generic[RestResponseT]):
|
|
262
|
+
"""
|
|
263
|
+
Provides the ``rest`` and ``rest_`` methods for User classes that have an HTTP ``client`` (HttpUser and FastHttpUser)
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
_callstack_regex = re.compile(r' File "(\/.[^"]*)", line (\d*),(.*)')
|
|
267
|
+
|
|
268
|
+
@contextmanager
|
|
269
|
+
def rest(self, method, url, headers: dict | None = None, **kwargs) -> Generator[RestResponseT]:
|
|
270
|
+
"""
|
|
271
|
+
A wrapper for self.client.request that:
|
|
272
|
+
|
|
273
|
+
* Parses the JSON response to a dict called ``js`` in the response object. Marks the request as failed if the response was not valid JSON.
|
|
274
|
+
* Defaults ``Content-Type`` and ``Accept`` headers to ``application/json``
|
|
275
|
+
* Sets ``catch_response=True`` (so always use a :ref:`with-block <catch-response>`)
|
|
276
|
+
* Catches any unhandled exceptions thrown inside your with-block, marking the sample as failed (instead of exiting the task immediately without even firing the request event)
|
|
277
|
+
"""
|
|
278
|
+
headers = headers or {}
|
|
279
|
+
if not ("Content-Type" in headers or "content-type" in headers):
|
|
280
|
+
headers["Content-Type"] = "application/json"
|
|
281
|
+
if not ("Accept" in headers or "accept" in headers):
|
|
282
|
+
headers["Accept"] = "application/json"
|
|
283
|
+
with self.client.request(method, url, catch_response=True, headers=headers, **kwargs) as r: # type: ignore[attr-defined]
|
|
284
|
+
resp: Any = r
|
|
285
|
+
resp.js = None
|
|
286
|
+
if resp.content is None:
|
|
287
|
+
resp.failure(str(resp.error))
|
|
288
|
+
elif resp.text:
|
|
289
|
+
try:
|
|
290
|
+
resp.js = resp.json()
|
|
291
|
+
except JSONDecodeError as e:
|
|
292
|
+
resp.failure(
|
|
293
|
+
f"Could not parse response as JSON. {resp.text[:250]}, response code {resp.status_code}, error {e}"
|
|
294
|
+
)
|
|
295
|
+
try:
|
|
296
|
+
yield resp
|
|
297
|
+
except AssertionError as e:
|
|
298
|
+
if e.args:
|
|
299
|
+
if e.args[0].endswith(","):
|
|
300
|
+
short_resp = resp.text[:200] if resp.text else resp.text
|
|
301
|
+
resp.failure(f"{e.args[0][:-1]}, response was {short_resp}")
|
|
302
|
+
else:
|
|
303
|
+
resp.failure(e.args[0])
|
|
304
|
+
else:
|
|
305
|
+
resp.failure("Assertion failed")
|
|
306
|
+
|
|
307
|
+
except Exception as e:
|
|
308
|
+
error_lines = []
|
|
309
|
+
for l in traceback.format_exc().split("\n"):
|
|
310
|
+
if m := self._callstack_regex.match(l):
|
|
311
|
+
filename = re.sub(r"/((home|Users)/\w*)/", "~/", m.group(1))
|
|
312
|
+
error_lines.append(filename + ":" + m.group(2) + m.group(3))
|
|
313
|
+
short_resp = resp.text[:200] if resp.text else resp.text
|
|
314
|
+
resp.failure(f"{e.__class__.__name__}: {e} at {', '.join(error_lines)}. Response was {short_resp}")
|
|
315
|
+
|
|
316
|
+
@contextmanager
|
|
317
|
+
def rest_(self, method, url, name=None, **kwargs) -> Generator[RestResponseT]:
|
|
318
|
+
"""
|
|
319
|
+
Some REST api:s use a timestamp as part of their query string (mainly to break through caches).
|
|
320
|
+
This is a convenience method for that, appending a _=<timestamp> parameter automatically
|
|
321
|
+
"""
|
|
322
|
+
separator = "&" if "?" in url else "?"
|
|
323
|
+
if name is None:
|
|
324
|
+
name = url + separator + "_=..."
|
|
325
|
+
with self.rest(method, f"{url}{separator}_={int(time.time() * 1000)}", name=name, **kwargs) as resp:
|
|
326
|
+
yield resp
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
class HttpUser(User, RestMixin[RestResponseContextManager]):
|
|
254
330
|
"""
|
|
255
331
|
Represents an HTTP "user" which is to be spawned and attack the system that is to be load tested.
|
|
256
332
|
|
|
@@ -84,3 +84,30 @@ def constant_throughput(task_runs_per_second: float) -> Callable[["User"], float
|
|
|
84
84
|
the next task.
|
|
85
85
|
"""
|
|
86
86
|
return constant_pacing(1 / task_runs_per_second)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def poisson(rate: float) -> Callable[["User"], float]:
|
|
90
|
+
"""
|
|
91
|
+
Returns a function that will return a random wait time sampled from an exponential
|
|
92
|
+
distribution with mean ``1/rate`` seconds. Tasks will therefore arrive according to a
|
|
93
|
+
Poisson process with mean rate ``rate`` tasks per second, which is a good approximation
|
|
94
|
+
of how real users arrive.
|
|
95
|
+
|
|
96
|
+
The average number of task runs per second will be ``rate``, but unlike
|
|
97
|
+
:py:func:`constant_throughput <locust.wait_time.constant_throughput>` the individual
|
|
98
|
+
intervals vary randomly, so bursts and quiet periods occur naturally.
|
|
99
|
+
|
|
100
|
+
Example::
|
|
101
|
+
|
|
102
|
+
class MyUser(User):
|
|
103
|
+
wait_time = poisson(2) # on average, each user runs 2 tasks per second
|
|
104
|
+
@task
|
|
105
|
+
def my_task(self):
|
|
106
|
+
...
|
|
107
|
+
|
|
108
|
+
If you have multiple requests in a task your RPS will of course be higher than the
|
|
109
|
+
specified throughput.
|
|
110
|
+
"""
|
|
111
|
+
if rate <= 0:
|
|
112
|
+
raise ValueError(f"poisson() requires a positive rate, got: {rate}")
|
|
113
|
+
return lambda instance: random.expovariate(rate)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|