diff --git a/docs/api.rst b/docs/api.rst index f256c0b4d2..f39a9be676 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -7,20 +7,20 @@ Locust class ============ .. autoclass:: locust.core.Locust - :members: min_wait, max_wait, wait_function, task_set, weight + :members: wait_time, task_set, weight HttpLocust class ================ .. autoclass:: locust.core.HttpLocust - :members: min_wait, max_wait, wait_function, task_set, client + :members: wait_time, task_set, client TaskSet class ============= .. autoclass:: locust.core.TaskSet - :members: locust, parent, min_wait, max_wait, wait_function, client, tasks, interrupt, schedule_task + :members: locust, parent, wait_time, client, tasks, interrupt, schedule_task task decorator ============== @@ -31,13 +31,19 @@ TaskSequence class ================== .. autoclass:: locust.core.TaskSequence - :members: locust, parent, min_wait, max_wait, wait_function, client, tasks, interrupt, schedule_task + :members: locust, parent, wait_time, client, tasks, interrupt, schedule_task seq_task decorator ================== .. autofunction:: locust.core.seq_task +Built in wait_time functions +============================ + +.. automodule:: locust.wait_time + :members: between, constant, constant_pacing + HttpSession class ================= diff --git a/docs/increase-performance.rst b/docs/increase-performance.rst index 3442905999..3fc394154d 100644 --- a/docs/increase-performance.rst +++ b/docs/increase-performance.rst @@ -24,7 +24,7 @@ How to use FastHttpLocust Subclass FastHttpLocust instead of HttpLocust:: - from locust import TaskSet, task + from locust import TaskSet, task, between from locust.contrib.fasthttp import FastHttpLocust class MyTaskSet(TaskSet): @@ -34,8 +34,7 @@ Subclass FastHttpLocust instead of HttpLocust:: class MyLocust(FastHttpLocust): task_set = MyTaskSet - min_wait = 1000 - max_wait = 60000 + wait_time = between(1, 60) .. note:: diff --git a/docs/quickstart.rst b/docs/quickstart.rst index ca152c0460..ca79c88b78 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -10,7 +10,7 @@ Below is a quick little example of a simple **locustfile.py**: .. code-block:: python - from locust import HttpLocust, TaskSet + from locust import HttpLocust, TaskSet, between def login(l): l.client.post("/login", {"username":"ellen_key", "password":"education"}) @@ -35,8 +35,7 @@ Below is a quick little example of a simple **locustfile.py**: class WebsiteUser(HttpLocust): task_set = UserBehavior - min_wait = 5000 - max_wait = 9000 + wait_time = between(5.0, 9.0) Here we define a number of Locust tasks, which are normal Python callables that take one argument @@ -60,7 +59,7 @@ Another way we could declare tasks, which is usually more convenient, is to use .. code-block:: python - from locust import HttpLocust, TaskSet, task + from locust import HttpLocust, TaskSet, task, between class UserBehavior(TaskSet): def on_start(self): @@ -87,14 +86,13 @@ Another way we could declare tasks, which is usually more convenient, is to use class WebsiteUser(HttpLocust): task_set = UserBehavior - min_wait = 5000 - max_wait = 9000 + wait_time = between(5, 9) The :py:class:`Locust ` class (as well as :py:class:`HttpLocust ` -since it's a subclass) also allows one to specify minimum and maximum wait time in milliseconds—per simulated -user—between the execution of tasks (*min_wait* and *max_wait*) as well as other user behaviours. -By default the time is randomly chosen uniformly between *min_wait* and *max_wait*, but any user-defined -time distributions can be used by setting *wait_function* to any arbitrary function. +since it's a subclass) also allows one to specify the wait time between the execution of tasks +(:code:`wait_time = between(5, 9)`) as well as other user behaviours. +With the between function the time is randomly chosen uniformly between the specified min and max values, +but any user-defined time distributions can be used by setting *wait_time* to any arbitrary function. For example, for an exponentially distributed wait time with average of 1 second: .. code-block:: python @@ -103,7 +101,7 @@ For example, for an exponentially distributed wait time with average of 1 second class WebsiteUser(HttpLocust): task_set = UserBehaviour - wait_function = lambda self: random.expovariate(1)*1000 + wait_time = lambda self: random.expovariate(1)*1000 Start Locust diff --git a/docs/writing-a-locustfile.rst b/docs/writing-a-locustfile.rst index 591fa67a70..45f7e707f8 100644 --- a/docs/writing-a-locustfile.rst +++ b/docs/writing-a-locustfile.rst @@ -12,38 +12,58 @@ A locust class represents one user (or a swarming locust if you will). Locust wi instance of the locust class for each user that is being simulated. There are a few attributes that a locust class should typically define. -The :py:attr:`task_set ` attribute ---------------------------------------------------------------- +The *task_set* attribute +------------------------ The :py:attr:`task_set ` attribute should point to a :py:class:`TaskSet ` class which defines the behaviour of the user and is described in more detail below. -The *min_wait* and *max_wait* attributes ----------------------------------------- +The *wait_time* attribute +------------------------- + +In addition to the *task_set* attribute, one should also declare a +:py:attr:`wait_time ` method. It's used to determine +for how long a simulated user will wait between executing tasks. Locust comes with a few built in +functions that return a few common wait_time methods. -In addition to the task_set attribute, one usually wants to declare the *min_wait* and *max_wait* -attributes. These are the minimum and maximum time respectively, in milliseconds, that a simulated user will wait -between executing each task. *min_wait* and *max_wait* default to 1000, and therefore a locust will -always wait 1 second between each task if *min_wait* and *max_wait* are not declared. +The most common one is :py:attr:`between `. It's used to make the simulated +users wait a random time between a min and max value after each task execution. Other built in +wait time functions are :py:attr:`constant ` and +:py:attr:`constant_pacing `. With the following locustfile, each user would wait between 5 and 15 seconds between tasks: .. code-block:: python - from locust import Locust, TaskSet, task + from locust import Locust, TaskSet, task, between class MyTaskSet(TaskSet): @task def my_task(self): print("executing my_task") + class User(Locust): + task_set = MyTaskSet + wait_time = between(5, 15) + +The wait_time method should return a number of seconds (or fraction of a second) and can also +be declared on a TaskSet class, in which case it will only be used for that TaskSet. + +It's also possible to declare your own wait_time method directly on a Locust or TaskSet class. The +following locust class would start sleeping for one second and then one, two, three, etc. + +.. code-block:: python + class MyLocust(Locust): task_set = MyTaskSet - min_wait = 5000 - max_wait = 15000 + last_wait_time = 0 + + def wait_time(self): + self.last_wait_time += 1 + return self.last_wait_time + -The *min_wait* and *max_wait* attributes can also be overridden in a TaskSet class. The *weight* attribute ---------------------- @@ -94,9 +114,9 @@ and—if we were load-testing an auction website—could do stuff like "loading When a load test is started, each instance of the spawned Locust classes will start executing their TaskSet. What happens then is that each TaskSet will pick one of its tasks and call it. It will then -wait a number of milliseconds, chosen at random between the Locust class' *min_wait* and *max_wait* attributes -(unless min_wait/max_wait have been defined directly under the TaskSet, in which case it will use -its own values instead). Then it will again pick a new task to be called, wait again, and so on. +wait a number of seconds, specified by the Locust class' *wait_time* method (unless a *wait_time* +method has been declared directly on the TaskSet, in which case it will use its own method instead). +Then it will again pick a new task to be called, wait again, and so on. Declaring tasks --------------- @@ -123,10 +143,10 @@ the following example *task2* will be executed twice as much as *task1*: .. code-block:: python from locust import Locust, TaskSet, task + from locust.wait_time import between class MyTaskSet(TaskSet): - min_wait = 5000 - max_wait = 15000 + wait_time = between(5, 15) @task(3) def task1(self): @@ -343,7 +363,7 @@ with two URLs; **/** and **/about/**: .. code-block:: python - from locust import HttpLocust, TaskSet, task + from locust import HttpLocust, TaskSet, task, between class MyTaskSet(TaskSet): @task(2) @@ -356,8 +376,7 @@ with two URLs; **/** and **/about/**: class MyLocust(HttpLocust): task_set = MyTaskSet - min_wait = 5000 - max_wait = 15000 + wait_time = between(5, 15) Using the above Locust class, each simulated user will wait between 5 and 15 seconds between the requests, and **/** will be requested twice as much as **/about/**. diff --git a/examples/basic.py b/examples/basic.py index c34610e8df..86d94e1efa 100644 --- a/examples/basic.py +++ b/examples/basic.py @@ -1,4 +1,4 @@ -from locust import HttpLocust, TaskSet, task +from locust import HttpLocust, TaskSet, task, between def index(l): @@ -21,6 +21,5 @@ class WebsiteUser(HttpLocust): Locust user class that does requests to the locust web server running on localhost """ host = "http://127.0.0.1:8089" - min_wait = 2000 - max_wait = 5000 + wait_time = between(2, 5) task_set = UserTasks diff --git a/examples/browse_docs_sequence_test.py b/examples/browse_docs_sequence_test.py index 56b24230d5..f3a7b4651a 100644 --- a/examples/browse_docs_sequence_test.py +++ b/examples/browse_docs_sequence_test.py @@ -2,7 +2,7 @@ # browsing the Locust documentation on https://docs.locust.io/ import random -from locust import HttpLocust, TaskSequence, seq_task, task +from locust import HttpLocust, TaskSequence, seq_task, task, between from pyquery import PyQuery @@ -46,5 +46,4 @@ class AwesomeUser(HttpLocust): # generally has a quite long waiting time (between # 20 and 600 seconds), since there's a bunch of text # on each page - min_wait = 20 * 1000 - max_wait = 600 * 1000 + wait_time = between(20, 600) diff --git a/examples/browse_docs_test.py b/examples/browse_docs_test.py index b8a42a7c2b..9f2401e4a6 100644 --- a/examples/browse_docs_test.py +++ b/examples/browse_docs_test.py @@ -2,7 +2,7 @@ # browsing the Locust documentation on https://docs.locust.io/ import random -from locust import HttpLocust, TaskSet, task +from locust import HttpLocust, TaskSet, task, between from pyquery import PyQuery @@ -45,5 +45,4 @@ class AwesomeUser(HttpLocust): # generally has a quite long waiting time (between # 20 and 600 seconds), since there's a bunch of text # on each page - min_wait = 20 * 1000 - max_wait = 600 * 1000 + wait_time = between(20, 600) diff --git a/examples/custom_wait_function.py b/examples/custom_wait_function.py index fad47c3174..9bbc8105dc 100644 --- a/examples/custom_wait_function.py +++ b/examples/custom_wait_function.py @@ -23,7 +23,7 @@ class WebsiteUser(HttpLocust): host = "http://127.0.0.1:8089" # Most task inter-arrival times approximate to exponential distributions # We will model this wait time as exponentially distributed with a mean of 1 second - wait_function = lambda self: random.expovariate(1)*1000 # *1000 to convert to milliseconds + wait_time = lambda self: random.expovariate(1) task_set = UserTasks def strictExp(min_wait,max_wait,mu=1): @@ -43,7 +43,7 @@ class StrictWebsiteUser(HttpLocust): Locust user class that makes exponential requests but strictly between two bounds. """ host = "http://127.0.0.1:8089" - wait_function = lambda self: strictExp(self.min_wait, self.max_wait)*1000 + wait_time = lambda self: strictExp(3, 7) task_set = UserTasks diff --git a/examples/custom_xmlrpc_client/xmlrpc_locustfile.py b/examples/custom_xmlrpc_client/xmlrpc_locustfile.py index f117acd779..c7dfca01b8 100644 --- a/examples/custom_xmlrpc_client/xmlrpc_locustfile.py +++ b/examples/custom_xmlrpc_client/xmlrpc_locustfile.py @@ -1,7 +1,7 @@ import time import xmlrpclib -from locust import Locust, TaskSet, events, task +from locust import Locust, TaskSet, events, task, between class XmlRpcClient(xmlrpclib.ServerProxy): @@ -41,8 +41,7 @@ def __init__(self, *args, **kwargs): class ApiUser(XmlRpcLocust): host = "http://127.0.0.1:8877/" - min_wait = 100 - max_wait = 1000 + wait_time = between(0.1, 1) class task_set(TaskSet): @task(10) diff --git a/examples/dynamice_user_credentials.py b/examples/dynamice_user_credentials.py index 6f8f66baa5..21029e12e4 100644 --- a/examples/dynamice_user_credentials.py +++ b/examples/dynamice_user_credentials.py @@ -1,6 +1,6 @@ # locustfile.py -from locust import HttpLocust, TaskSet, task +from locust import HttpLocust, TaskSet, task, between USER_CREDENTIALS = [ ("user1", "password"), @@ -21,5 +21,4 @@ def some_task(self): class User(HttpLocust): task_set = UserBehaviour - min_wait = 5000 - max_wait = 60000 + wait_time = between(5, 60) diff --git a/examples/events.py b/examples/events.py index 7b1de7fafe..fca5c11673 100644 --- a/examples/events.py +++ b/examples/events.py @@ -5,7 +5,7 @@ track the sum of the content-length header in all successful HTTP responses """ -from locust import HttpLocust, TaskSet, events, task, web +from locust import HttpLocust, TaskSet, events, task, web, between class MyTaskSet(TaskSet): @@ -19,8 +19,7 @@ def stats(l): class WebsiteUser(HttpLocust): host = "http://127.0.0.1:8089" - min_wait = 2000 - max_wait = 5000 + between(2, 5) task_set = MyTaskSet diff --git a/examples/fast_http_locust.py b/examples/fast_http_locust.py index 3cc47b3d73..95eaf01f34 100644 --- a/examples/fast_http_locust.py +++ b/examples/fast_http_locust.py @@ -1,4 +1,4 @@ -from locust import HttpLocust, TaskSet, task +from locust import HttpLocust, TaskSet, task, between from locust.contrib.fasthttp import FastHttpLocust @@ -18,7 +18,6 @@ class WebsiteUser(FastHttpLocust): using the fast HTTP client """ host = "http://127.0.0.1:8089" - min_wait = 2000 - max_wait = 5000 + wait_time = between(2, 5) task_set = UserTasks diff --git a/examples/multiple_hosts.py b/examples/multiple_hosts.py index b30585b37c..4101611c75 100644 --- a/examples/multiple_hosts.py +++ b/examples/multiple_hosts.py @@ -1,6 +1,6 @@ import os -from locust import HttpLocust, TaskSet, task +from locust import HttpLocust, TaskSet, task, between from locust.clients import HttpSession class MultipleHostsLocust(HttpLocust): @@ -26,6 +26,5 @@ class WebsiteUser(MultipleHostsLocust): Locust user class that does requests to the locust web server running on localhost """ host = "http://127.0.0.1:8089" - min_wait = 2000 - max_wait = 5000 + wait_time = between(2, 5) task_set = UserTasks diff --git a/examples/nested_inline_tasksets.py b/examples/nested_inline_tasksets.py index 01dc40f2ac..2e08bf63a7 100644 --- a/examples/nested_inline_tasksets.py +++ b/examples/nested_inline_tasksets.py @@ -1,4 +1,4 @@ -from locust import HttpLocust, TaskSet, task +from locust import HttpLocust, TaskSet, task, between class WebsiteUser(HttpLocust): @@ -6,8 +6,7 @@ class WebsiteUser(HttpLocust): Example of the ability of inline nested TaskSet classes """ host = "http://127.0.0.1:8089" - min_wait = 2000 - max_wait = 5000 + wait_time = between(2, 5) class task_set(TaskSet): @task diff --git a/examples/semaphore_wait.py b/examples/semaphore_wait.py index 8665b9d851..24df036196 100644 --- a/examples/semaphore_wait.py +++ b/examples/semaphore_wait.py @@ -1,4 +1,4 @@ -from locust import HttpLocust, TaskSet, task, events +from locust import HttpLocust, TaskSet, task, events, between from gevent.lock import Semaphore @@ -21,6 +21,5 @@ def index(self): class WebsiteUser(HttpLocust): host = "http://127.0.0.1:8089" - min_wait = 2000 - max_wait = 5000 + wait_time = between(2, 5) task_set = UserTasks diff --git a/locust/__init__.py b/locust/__init__.py index d85072a123..78c7db9b59 100644 --- a/locust/__init__.py +++ b/locust/__init__.py @@ -1,4 +1,5 @@ from .core import HttpLocust, Locust, TaskSet, TaskSequence, task, seq_task from .exception import InterruptTaskSet, ResponseError, RescheduleTaskImmediately +from .wait_time import between, constant, constant_pacing __version__ = "0.12.2" diff --git a/locust/core.py b/locust/core.py index 4af35bc2e6..e5236d328e 100644 --- a/locust/core.py +++ b/locust/core.py @@ -19,8 +19,10 @@ from . import events from .clients import HttpSession from .exception import (InterruptTaskSet, LocustError, RescheduleTask, - RescheduleTaskImmediately, StopLocust) + RescheduleTaskImmediately, StopLocust, MissingWaitTimeError) from .runners import STATE_CLEANUP, LOCUST_STATE_RUNNING, LOCUST_STATE_STOPPING, LOCUST_STATE_WAITING +from .util import deprecation + logger = logging.getLogger(__name__) @@ -111,14 +113,32 @@ class Locust(object): host = None """Base hostname to swarm. i.e: http://127.0.0.1:1234""" - min_wait = 1000 - """Minimum waiting time between the execution of locust tasks""" + min_wait = None + """Deprecated: Use wait_time instead. Minimum waiting time between the execution of locust tasks""" - max_wait = 1000 - """Maximum waiting time between the execution of locust tasks""" - - wait_function = lambda self: random.randint(self.min_wait,self.max_wait) - """Function used to calculate waiting time between the execution of locust tasks in milliseconds""" + max_wait = None + """Deprecated: Use wait_time instead. Maximum waiting time between the execution of locust tasks""" + + wait_time = None + """ + Method that returns the time (in seconds) between the execution of locust tasks. + Can be overridden for individual TaskSets. + + Example:: + + from locust import Locust, between + class User(Locust): + wait_time = between(3, 25) + """ + + wait_function = None + """ + .. warning:: + + DEPRECATED: Use wait_time instead. Note that the new wait_time method should return seconds and not milliseconds. + + Method that returns the time between the execution of locust tasks in milliseconds + """ task_set = None """TaskSet class that defines the execution behaviour of this locust""" @@ -135,6 +155,9 @@ class Locust(object): def __init__(self): super(Locust, self).__init__() + # check if deprecated wait API is used + deprecation.check_for_deprecated_wait_api(self) + self._lock.acquire() if hasattr(self, "setup") and self._setup_has_run is False: self._set_setup_flag() @@ -273,6 +296,7 @@ class ForumPage(TaskSet): min_wait = None """ + Deprecated: Use wait_time instead. Minimum waiting time between the execution of locust tasks. Can be used to override the min_wait defined in the root Locust class, which will be used if not set on the TaskSet. @@ -280,6 +304,7 @@ class ForumPage(TaskSet): max_wait = None """ + Deprecated: Use wait_time instead. Maximum waiting time between the execution of locust tasks. Can be used to override the max_wait defined in the root Locust class, which will be used if not set on the TaskSet. @@ -287,6 +312,7 @@ class ForumPage(TaskSet): wait_function = None """ + Deprecated: Use wait_time instead. Function used to calculate waiting time betwen the execution of locust tasks in milliseconds. Can be used to override the wait_function defined in the root Locust class, which will be used if not set on the TaskSet. @@ -306,6 +332,9 @@ class ForumPage(TaskSet): _lock = gevent.lock.Semaphore() # Lock to make sure setup is only run once def __init__(self, parent): + # check if deprecated wait API is used + deprecation.check_for_deprecated_wait_api(self) + self._task_queue = [] self._time_start = time() @@ -428,13 +457,29 @@ def schedule_task(self, task_callable, args=None, kwargs=None, first=False): def get_next_task(self): return random.choice(self.tasks) - def get_wait_secs(self): - millis = self.wait_function() - return millis / 1000.0 - + def wait_time(self): + """ + Method that returns the time (in seconds) between the execution of tasks. + + Example:: + + from locust import TaskSet, between + class Tasks(TaskSet): + wait_time = between(3, 25) + """ + if self.locust.wait_time: + return self.locust.wait_time() + elif self.min_wait and self.max_wait: + return random.randint(self.min_wait, self.max_wait) / 1000.0 + else: + raise MissingWaitTimeError("You must define a wait_time method on either the %s or %s class" % ( + type(self.locust).__name__, + type(self).__name__, + )) + def wait(self): self.locust._state = LOCUST_STATE_WAITING - self._sleep(self.get_wait_secs()) + self._sleep(self.wait_time()) self.locust._state = LOCUST_STATE_RUNNING def _sleep(self, seconds): diff --git a/locust/exception.py b/locust/exception.py index fd997ab9a4..3d6a825bff 100644 --- a/locust/exception.py +++ b/locust/exception.py @@ -7,6 +7,9 @@ class ResponseError(Exception): class CatchResponseError(Exception): pass +class MissingWaitTimeError(LocustError): + pass + class InterruptTaskSet(Exception): """ Exception that will interrupt a Locust when thrown inside a task diff --git a/locust/main.py b/locust/main.py index 6e7fe75feb..20551358ed 100644 --- a/locust/main.py +++ b/locust/main.py @@ -24,6 +24,7 @@ _internals = [Locust, HttpLocust] version = locust.__version__ + def parse_options(): """ Handle command-line options with argparse.ArgumentParser. diff --git a/locust/test/test_locust_class.py b/locust/test/test_locust_class.py index 2fb98c0f58..00ccd28087 100644 --- a/locust/test/test_locust_class.py +++ b/locust/test/test_locust_class.py @@ -5,7 +5,8 @@ from locust.exception import (CatchResponseError, LocustError, RescheduleTask, RescheduleTaskImmediately) -from locust.test.testcases import LocustTestCase, WebserverTestCase +from locust.wait_time import between, constant +from .testcases import LocustTestCase, WebserverTestCase class TestTaskSet(LocustTestCase): @@ -179,16 +180,15 @@ def t1(self): def test_wait_function(self): class MyTaskSet(TaskSet): - min_wait = 1000 - max_wait = 2000 - wait_function = lambda self: 1000 + (self.max_wait-self.min_wait) + a = 1 + b = 2 + wait_time = lambda self: 1 + (self.b-self.a) taskset = MyTaskSet(self.locust) - self.assertEqual(taskset.get_wait_secs(), 2.0) + self.assertEqual(taskset.wait_time(), 2.0) def test_sub_taskset(self): class MySubTaskSet(TaskSet): - min_wait = 1 - max_wait = 1 + constant(1) @task() def a_task(self): self.locust.sub_locust_task_executed = True @@ -207,8 +207,7 @@ def test_sub_taskset_tasks_decorator(self): class MyTaskSet(TaskSet): @task class MySubTaskSet(TaskSet): - min_wait = 1 - max_wait = 1 + wait_time = constant(0.001) @task() def a_task(self): self.locust.sub_locust_task_executed = True @@ -222,8 +221,7 @@ def a_task(self): def test_sub_taskset_arguments(self): class MySubTaskSet(TaskSet): - min_wait = 1 - max_wait = 1 + wait_time = constant(0.001) @task() def a_task(self): self.locust.sub_taskset_args = self.args diff --git a/locust/test/test_main.py b/locust/test/test_main.py index ee1fa08ace..ff0afa845f 100644 --- a/locust/test/test_main.py +++ b/locust/test/test_main.py @@ -33,7 +33,7 @@ class TestLoadLocustfile(LocustTestCase): mock_docstring = 'This is a mock locust file for unit testing.' mock_locust_file_content = """\"\"\"{}\"\"\" -from locust import HttpLocust, TaskSet, task +from locust import HttpLocust, TaskSet, task, between def index(l): @@ -50,8 +50,7 @@ class UserTasks(TaskSet): class LocustSubclass(HttpLocust): host = "http://127.0.0.1:8089" - min_wait = 2000 - max_wait = 5000 + wait_time = between(2, 5) task_set = UserTasks diff --git a/locust/test/test_old_wait_api.py b/locust/test/test_old_wait_api.py new file mode 100644 index 0000000000..96d32f2b32 --- /dev/null +++ b/locust/test/test_old_wait_api.py @@ -0,0 +1,89 @@ +import warnings + +from locust import InterruptTaskSet, ResponseError +from locust.core import HttpLocust, Locust, TaskSet, events, task +from locust.exception import (CatchResponseError, LocustError, RescheduleTask, + RescheduleTaskImmediately) +from locust.wait_time import between, constant + +from .testcases import LocustTestCase, WebserverTestCase + + +class TestOldWaitApi(LocustTestCase): + def setUp(self): + super(TestOldWaitApi, self).setUp() + + def test_wait_function(self): + with warnings.catch_warnings(record=True) as w: + class User(Locust): + wait_function = lambda self: 5000 + class MyTaskSet(TaskSet): + pass + taskset = MyTaskSet(User()) + self.assertEqual(5, taskset.wait_time()) + self.assertEqual(1, len(w)) + self.assertTrue(issubclass(w[0].category, DeprecationWarning)) + self.assertIn("wait_function", str(w[0].message)) + + def test_wait_function_on_taskset(self): + with warnings.catch_warnings(record=True) as w: + class User(Locust): + pass + class MyTaskSet(TaskSet): + wait_function = lambda self: 5000 + taskset = MyTaskSet(User()) + self.assertEqual(5, taskset.wait_time()) + self.assertEqual(1, len(w)) + self.assertTrue(issubclass(w[0].category, DeprecationWarning)) + self.assertIn("wait_function", str(w[0].message)) + + def test_min_max_wait(self): + with warnings.catch_warnings(record=True) as w: + class User(Locust): + min_wait = 1000 + max_wait = 1000 + class TS(TaskSet): + @task + def t(self): + pass + taskset = TS(User()) + self.assertEqual(1, taskset.wait_time()) + self.assertEqual(1, len(w)) + self.assertTrue(issubclass(w[0].category, DeprecationWarning)) + self.assertIn("min_wait", str(w[0].message)) + self.assertIn("max_wait", str(w[0].message)) + + def test_min_max_wait_combined_with_wait_time(self): + with warnings.catch_warnings(record=True) as w: + class User(Locust): + min_wait = 1000 + max_wait = 1000 + class TS(TaskSet): + wait_time = constant(3) + @task + def t(self): + pass + taskset = TS(User()) + self.assertEqual(3, taskset.wait_time()) + self.assertEqual(1, len(w)) + self.assertTrue(issubclass(w[0].category, DeprecationWarning)) + self.assertIn("min_wait", str(w[0].message)) + self.assertIn("max_wait", str(w[0].message)) + + def test_min_max_wait_on_taskset(self): + with warnings.catch_warnings(record=True) as w: + class User(Locust): + wait_time = constant(3) + class TS(TaskSet): + min_wait = 1000 + max_wait = 1000 + @task + def t(self): + pass + taskset = TS(User()) + self.assertEqual(3, taskset.wait_time()) + self.assertEqual(1, len(w)) + self.assertTrue(issubclass(w[0].category, DeprecationWarning)) + self.assertIn("min_wait", str(w[0].message)) + self.assertIn("max_wait", str(w[0].message)) + diff --git a/locust/test/test_runners.py b/locust/test/test_runners.py index ec34e4f064..292dc86c0f 100644 --- a/locust/test/test_runners.py +++ b/locust/test/test_runners.py @@ -13,6 +13,7 @@ STATE_INIT, STATE_HATCHING, STATE_RUNNING, STATE_MISSING from locust.stats import global_stats, RequestStats from locust.test.testcases import LocustTestCase +from locust.wait_time import between, constant def mocked_rpc_server(): @@ -115,6 +116,7 @@ class L3(BaseLocust): def test_kill_locusts(self): triggered = [False] class BaseLocust(Locust): + wait_time = constant(1) class task_set(TaskSet): @task def trigger(self): @@ -355,8 +357,7 @@ def my_task(self): class MyTestLocust(Locust): task_set = MyTaskSet - min_wait = 100 - max_wait = 100 + wait_time = constant(0.1) runner = LocalLocustRunner([MyTestLocust], self.options) @@ -457,8 +458,7 @@ def will_stop(self): self.interrupt() class MyLocust(Locust): - min_wait = 10 - max_wait = 10 + wait_time = constant(0.01) task_set = MyTaskSet runner = LocalLocustRunner([MyLocust], self.options) @@ -502,8 +502,7 @@ def my_task(self): class MyTestLocust(Locust): task_set = MyTaskSet - min_wait = 0 - max_wait = 0 + wait_time = constant(0) options = mocked_options() runner = LocalLocustRunner([MyTestLocust], options) @@ -571,8 +570,7 @@ def my_task(self): class MyTestLocust(Locust): task_set = MyTaskSet - min_wait = 1000 - max_wait = 1000 + wait_time = between(1, 1) options = mocked_options() options.stop_timeout = short_time diff --git a/locust/test/test_task_sequence_class.py b/locust/test/test_task_sequence_class.py index b42011207e..6e395d297b 100644 --- a/locust/test/test_task_sequence_class.py +++ b/locust/test/test_task_sequence_class.py @@ -4,7 +4,7 @@ from locust.core import HttpLocust, Locust, TaskSequence, events, seq_task, task from locust.exception import (CatchResponseError, LocustError, RescheduleTask, RescheduleTaskImmediately) - +from locust.wait_time import between, constant from .testcases import LocustTestCase, WebserverTestCase @@ -14,8 +14,7 @@ def setUp(self): class User(Locust): host = "127.0.0.1" - min_wait = 1 - max_wait = 10 + wait_time = between(0.001, 0.1) self.locust = User() def test_task_sequence_with_list(self): diff --git a/locust/test/test_wait_time.py b/locust/test/test_wait_time.py new file mode 100644 index 0000000000..359c536dc8 --- /dev/null +++ b/locust/test/test_wait_time.py @@ -0,0 +1,79 @@ +import random +import time + +from locust.core import HttpLocust, Locust, TaskSet, events, task +from locust.exception import MissingWaitTimeError +from locust.wait_time import between, constant, constant_pacing + +from .testcases import LocustTestCase, WebserverTestCase + + +class TestWaitTime(LocustTestCase): + def test_between(self): + class User(Locust): + wait_time = between(3, 9) + class TaskSet1(TaskSet): + pass + class TaskSet2(TaskSet): + wait_time = between(20.0, 21.0) + + u = User() + ts1 = TaskSet1(u) + ts2 = TaskSet2(u) + for i in range(100): + w = u.wait_time() + self.assertGreaterEqual(w, 3) + self.assertLessEqual(w, 9) + w = ts1.wait_time() + self.assertGreaterEqual(w, 3) + self.assertLessEqual(w, 9) + for i in range(100): + w = ts2.wait_time() + self.assertGreaterEqual(w, 20) + self.assertLessEqual(w, 21) + + def test_constant(self): + class User(Locust): + wait_time = constant(13) + class TaskSet1(TaskSet): + pass + self.assertEqual(13, User().wait_time()) + self.assertEqual(13, TaskSet1(User()).wait_time()) + + def test_constant_zero(self): + class User(Locust): + wait_time = constant(0) + class TaskSet1(TaskSet): + pass + self.assertEqual(0, User().wait_time()) + self.assertEqual(0, TaskSet1(User()).wait_time()) + start_time = time.time() + TaskSet1(User()).wait() + self.assertLess(time.time() - start_time, 0.002) + + def test_constant_pacing(self): + class User(Locust): + wait_time = constant_pacing(0.1) + class TS(TaskSet): + pass + ts = TS(User()) + + ts2 = TS(User()) + + previous_time = time.time() + for i in range(7): + ts.wait() + since_last_run = time.time() - previous_time + self.assertLess(abs(0.1 - since_last_run), 0.02) + previous_time = time.time() + time.sleep(random.random() * 0.1) + _ = ts2.wait_time() + _ = ts2.wait_time() + + def test_missing_wait_time(self): + class User(Locust): + pass + class TS(TaskSet): + pass + self.assertRaises(MissingWaitTimeError, lambda: TS(User()).wait_time()) + diff --git a/locust/util/deprecation.py b/locust/util/deprecation.py new file mode 100644 index 0000000000..e2c02f2ade --- /dev/null +++ b/locust/util/deprecation.py @@ -0,0 +1,36 @@ +import six +import warnings + + +# Show deprecation warnings +warnings.filterwarnings('always', category=DeprecationWarning, module="locust") + + +def get_class_func(f): + if six.PY2: + return f.__func__ + else: + return f + +def check_for_deprecated_wait_api(locust_or_taskset): + # check if deprecated wait API is used + if locust_or_taskset.wait_function: + warnings.warn("Usage of wait_function is deprecated since version 0.13. Declare a %s.wait_time method instead " + "(should return seconds and not milliseconds)" % type(locust_or_taskset).__name__, DeprecationWarning) + from locust.core import TaskSet + if not locust_or_taskset.wait_time or locust_or_taskset.wait_time.__func__ == get_class_func(TaskSet.wait_time): + # If wait_function has been declared, and custom wait_time has NOT been declared, + # we'll add a wait_time function that just calls wait_function and divides the + # returned value by 1000.0 + locust_or_taskset.wait_time = lambda: locust_or_taskset.wait_function() / 1000.0 + if locust_or_taskset.min_wait and locust_or_taskset.max_wait: + def format_min_max_wait(i): + float_value = i / 1000.0 + if float_value == int(float_value): + return "%i" % int(float_value) + else: + return "%.3f" % float_value + warnings.warn("Usage of min_wait and max_wait is deprecated since version 0.13. Instead use: wait_time = between(%s, %s)" % ( + format_min_max_wait(locust_or_taskset.min_wait), + format_min_max_wait(locust_or_taskset.max_wait), + ), DeprecationWarning) diff --git a/locust/wait_time.py b/locust/wait_time.py new file mode 100644 index 0000000000..1399f37810 --- /dev/null +++ b/locust/wait_time.py @@ -0,0 +1,59 @@ +import random +from time import time + + +def between(min_wait, max_wait): + """ + Returns a function that will return a random number between min_wait and max_wait. + + Example:: + + class User(Locust): + # wait between 3.0 and 10.5 seconds after each task + wait_time = between(3.0, 10.5) + """ + return lambda instance: min_wait + random.random() * (max_wait - min_wait) + + +def constant(wait_time): + """ + Returns a function that just returns the number specified by the wait_time argument + + Example:: + + class User(Locust): + wait_time = constant(3) + """ + return lambda instance: wait_time + + +def constant_pacing(wait_time): + """ + Returns a function that will track the run time of the tasks, and for each time it's + called it will return a wait time that will try to make the total time between task + execution equal to the time specified by the wait_time argument. + + In the following example the task will always be executed once every second, no matter + the task execution time:: + + class User(Locust): + wait_time = constant_pacing(1) + class task_set(TaskSet): + @task + def my_task(self): + time.sleep(random.random()) + + If a task execution exceeds the specified wait_time, the wait will be 0 before starting + the next task. + """ + def wait_time_func(self): + if not hasattr(self,"_cp_last_run"): + self._cp_last_wait_time = wait_time + self._cp_last_run = time() + return wait_time + else: + run_time = time() - self._cp_last_run - self._cp_last_wait_time + self._cp_last_wait_time = max(0, wait_time - run_time) + self._cp_last_run = time() + return self._cp_last_wait_time + return wait_time_func