-
Notifications
You must be signed in to change notification settings - Fork 142
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #152 from SvenskaSpel/introduce-synchronizer
Introduce synchronizer, a way for workers to easily get data from master in an ordered way (using any iterator)
- Loading branch information
Showing
6 changed files
with
149 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
from typing import Iterator | ||
from locust_plugins.mongoreader import MongoLRUReader | ||
from locust_plugins.csvreader import CSVDictReader | ||
from locust_plugins import synchronizer | ||
from locust import HttpUser, task, run_single_user | ||
|
||
|
||
reader: Iterator | ||
csv = True | ||
if csv: | ||
reader = CSVDictReader("ssn.tsv", delimiter="\t") | ||
else: | ||
reader = MongoLRUReader({"foo": "bar"}, "last_login") | ||
synchronizer.register(reader) | ||
# optionally replace this with lazy initalization of Reader to avoid unnecessarily doing it on workers: | ||
# synchronizer.register(None, MongoLRUReader, {"env": "test", "tb": False, "lb": True}, "last_login") | ||
|
||
|
||
class MyUser(HttpUser): | ||
host = "http://www.example.com" | ||
|
||
@task | ||
def my_task(self): | ||
customer = synchronizer.getdata(self) | ||
self.client.get(f"/?{customer['ssn']}") | ||
|
||
|
||
if __name__ == "__main__": | ||
run_single_user(MyUser) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
from typing import Dict, Iterator, Type, Optional | ||
import logging | ||
from gevent.event import AsyncResult | ||
from locust import User, events | ||
|
||
from locust.env import Environment | ||
from locust.runners import MasterRunner, WorkerRunner | ||
|
||
test_data: Dict[int, AsyncResult] = {} | ||
iterator: Optional[Iterator[Dict]] = None | ||
|
||
|
||
def register(i: Optional[Iterator[dict]], reader_class: Optional[Type[Iterator[Dict]]] = None, *args, **kwargs): | ||
"""Register synchronizer methods and tie them to use the iterator that you pass. | ||
To avoid unnecessarily instantiating the iterator on workers (where it isnt used), | ||
you can pass an iterator class and initialization parameters instead of an object instance. | ||
""" | ||
global iterator | ||
iterator = i | ||
|
||
@events.test_start.add_listener | ||
def test_start(environment, **_kw): | ||
global iterator | ||
runner = environment.runner | ||
if not i and not isinstance(runner, WorkerRunner): | ||
assert reader_class | ||
logging.debug(f"about to initialize reader class {reader_class}") | ||
iterator = reader_class(*args, **kwargs) | ||
if runner: | ||
# called on master | ||
def user_request(environment: Environment, msg, **kwargs): | ||
assert iterator # should have been instantiated by now... | ||
data = next(iterator) | ||
# data["_id"] = str(data["_id"]) # this is an ObjectId, msgpack doesnt know how to serialize it | ||
environment.runner.send_message( | ||
"synchronizer_response", | ||
{"payload": data, "user_id": msg.data["user_id"]}, | ||
client_id=msg.data["client_id"], | ||
) | ||
|
||
# called on worker | ||
def user_response(environment: Environment, msg, **kwargs): | ||
test_data[msg.data["user_id"]].set(msg.data) | ||
|
||
if not isinstance(runner, WorkerRunner): | ||
runner.register_message("synchronizer_request", user_request) | ||
if not isinstance(runner, MasterRunner): | ||
runner.register_message("synchronizer_response", user_response) | ||
|
||
|
||
def getdata(u: User) -> Dict: | ||
if not u.environment.runner: # no need to do anything clever if there is no runner | ||
return next(iterator) | ||
|
||
if id(u) in test_data: | ||
logging.warning("This user was already waiting for data. Not sure how to handle this nicely...") | ||
|
||
test_data[id(u)] = AsyncResult() | ||
runner = u.environment.runner | ||
runner.send_message("synchronizer_request", {"user_id": id(u), "client_id": runner.client_id}) | ||
data = test_data[id(u)].get()["payload"] | ||
del test_data[id(u)] | ||
return data |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters