{"id":13728106,"url":"https://github.com/FutureSharks/invokust","last_synced_at":"2025-05-08T00:31:05.218Z","repository":{"id":45610532,"uuid":"86588294","full_name":"FutureSharks/invokust","owner":"FutureSharks","description":"A wrapper for locust to allow running load tests in python or on AWS Lambda","archived":false,"fork":false,"pushed_at":"2023-07-30T18:46:49.000Z","size":308,"stargazers_count":158,"open_issues_count":9,"forks_count":42,"subscribers_count":7,"default_branch":"master","last_synced_at":"2024-11-09T17:51:42.121Z","etag":null,"topics":["aws-lambda","load","locust","locustfile","python","python-packages","testing"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/FutureSharks.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2017-03-29T13:53:38.000Z","updated_at":"2024-10-14T14:48:56.000Z","dependencies_parsed_at":"2024-01-07T16:32:15.673Z","dependency_job_id":"c724267f-9b5c-4ea5-b696-3ac6abd41131","html_url":"https://github.com/FutureSharks/invokust","commit_stats":{"total_commits":53,"total_committers":13,"mean_commits":4.076923076923077,"dds":0.4716981132075472,"last_synced_commit":"2482cd5511a6ccd9aad98bc2fe0dcb5bedf58c48"},"previous_names":[],"tags_count":20,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FutureSharks%2Finvokust","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FutureSharks%2Finvokust/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FutureSharks%2Finvokust/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FutureSharks%2Finvokust/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/FutureSharks","download_url":"https://codeload.github.com/FutureSharks/invokust/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224679819,"owners_count":17351873,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws-lambda","load","locust","locustfile","python","python-packages","testing"],"created_at":"2024-08-03T02:00:37.299Z","updated_at":"2024-11-14T19:30:37.275Z","avatar_url":"https://github.com/FutureSharks.png","language":"Python","funding_links":[],"categories":["Tools \u0026 Integrations"],"sub_categories":["Wrappers"],"readme":"# invokust\n\nA tool for running [Locust](http://locust.io/) load tests from within Python without the need to use the locust command line. This gives more flexibility for automation such as QA/CI/CD tests and also makes it possible to run locust on [AWS Lambda](https://aws.amazon.com/lambda/) for ultimate scalability.\n\n## Installation\n\nInstall via pip:\n\n```\npip3 install invokust\n```\n\n## Examples\n\nRunning a load test using a locust file:\n\n```python\nimport invokust\n\nsettings = invokust.create_settings(\n    locustfile='locustfile_example.py',\n    host='http://www.iana.org',\n    num_users=1,\n    spawn_rate=1,\n    run_time='3m'\n    )\n\nloadtest = invokust.LocustLoadTest(settings)\nloadtest.run()\nloadtest.stats()\n\"{'requests': {'GET_/': {'request_type': 'GET', 'num_requests': 923, 'min_response_time': 113.54585000000128, 'median_response_time': 120.0, 'avg_response_time': 145.68631223510297, 'max_response_time': 331.89674199999786, 'response_times': {270.0: 2, 120.0: 479, 150.0: 17, 200.0: 83, 210.0: 80, 160.0: 20, 190.0: 55, 220.0: 9, 130.0: 30, 170.0: 22, 230.0: 5, 110.0: 69, 140.0: 19, 180.0: 27, 240.0: 2, 320.0: 3, 330.0: 1}, 'response_time_percentiles': {55: 120.0, 65: 150.0, 75: 190.0, 85: 200.0, 95: 210.0}, 'total_rps': 5.136500841568583, 'total_rpm': 308.190050494115}, 'GET_/about': {'request_type': 'GET', 'num_requests': 308, 'min_response_time': 113.23035299999873, 'median_response_time': 120.0, 'avg_response_time': 146.04534828246747, 'max_response_time': 290.40608500000076, 'response_times': {120.0: 147, 200.0: 36, 190.0: 25, 110.0: 27, 160.0: 12, 150.0: 6, 180.0: 13, 210.0: 12, 170.0: 13, 220.0: 2, 130.0: 8, 140.0: 6, 290.0: 1}, 'response_time_percentiles': {55: 120.0, 65: 160.0, 75: 180.0, 85: 200.0, 95: 200.0}, 'total_rps': 1.7140219492991589, 'total_rpm': 102.84131695794953}}, 'failures': {}, 'num_requests': 1231, 'num_requests_fail': 0, 'start_time': 1608207776.312684, 'end_time': 1608207956.070369}\"\n```\n\nRunning a load test without locust file:\n\n```python\nimport invokust\n\nfrom locust import HttpUser, between, task\n\nclass WebsiteUser(HttpUser):\n    wait_time = between(1, 3)\n\n    @task()\n    def get_home_page(self):\n        '''\n        Gets /\n        '''\n        self.client.get(\"/\")\n\nsettings = invokust.create_settings(\n    classes=[WebsiteUser],\n    host='http://www.iana.org',\n    num_users=1,\n    spawn_rate=1,\n    run_time='3m'\n)\n\nloadtest = invokust.LocustLoadTest(settings)\nloadtest.run()\nloadtest.stats()\n\"{'requests': {'GET_/': {'request_type': 'GET', 'num_requests': 71, 'min_response_time': 138.60819600000696, 'median_response_time': 360.0, 'avg_response_time': 327.0060322394364, 'max_response_time': 603.2539320000012, 'response_times': {590.0: 1, 210.0: 3, 370.0: 2, 230.0: 5, 200.0: 2, 490.0: 2, 420.0: 4, 480.0: 1, 190.0: 5, 180.0: 6, 400.0: 3, 270.0: 1, 260.0: 3, 280.0: 2, 360.0: 4, 470.0: 2, 460.0: 3, 350.0: 1, 250.0: 1, 380.0: 4, 410.0: 2, 140.0: 1, 440.0: 1, 600.0: 1, 390.0: 2, 450.0: 1, 430.0: 3, 290.0: 1, 240.0: 2, 340.0: 1, 220.0: 1}, 'response_time_percentiles': {55: 370.0, 65: 390.0, 75: 420.0, 85: 450.0, 95: 490.0}, 'total_rps': 0.4443058717398536, 'total_rpm': 26.658352304391215}}, 'failures': {'GET_/': {'method': 'GET', 'name': '/', 'error': \"ConnectionError(ProtocolError('Connection aborted.', RemoteDisconnected('Remote end closed connection without response')))\", 'occurrences': 1}}, 'num_requests': 71, 'num_requests_fail': 1, 'start_time': 1608208114.321394, 'end_time': 1608208276.0525749}\"\n```\n\n## Running Locust on AWS Lambda\n\n\u003cimg src=\"http://d0.awsstatic.com/Graphics/lambda-icon-smallr1.png\" alt=\"Lambda logo\" height=\"100\"\u003e\u003cimg src=\"http://locust.io/static/img/logo.png\" alt=\"Locust logo\" height=\"100\"\u003e\n\n[AWS Lambda](https://aws.amazon.com/lambda/) is a great tool for load testing as it is very cheap (or free) and highly scalable.\n\nThere are many load testing tools such as [ab](https://httpd.apache.org/docs/2.4/programs/ab.html) and [wrk](https://github.com/wg/wrk). Then there are other cloud based load testing options such as [BlazeMeter](https://www.blazemeter.com/) or [Loader](https://loader.io/) and some more DIY solutions that use AWS Lambda too such as [Goad](https://goad.io/) or [serverless-artillery](https://github.com/Nordstrom/serverless-artillery). But these all have the same drawback: They are too simplistic. They can perform simple GET or POST requests but can't accurately emulate more complex behaviour. e.g. browsing a website, selecting random items, filling a shopping cart and checking out. But with [Locust](http://locust.io/) this is possible.\n\nIncluded is an example function for running Locust on AWS Lambda, `lambda_locust.py`.\n\n### Creating a Lambda function\n\nThe process for running a locust test on Lambda involves [creating a zip file](http://docs.aws.amazon.com/lambda/latest/dg/lambda-python-how-to-create-deployment-package.html) of the locust load test, creating a Lambda function and then triggering the function.\n\nInstall invokust (and its dependencies) python packages locally:\n\n```\npip3 install invokust --target=python-packages\n```\n\nOr if running on a Mac (python packages need to be compiled for 64 bit Linux) you can use docker:\n\n```\ndocker run -it --volume=$PWD:/temp python:3.7 bash -c \"pip install /temp --target=/temp/python-packages\"\n```\n\nCreate the zip file:\n\n```\nzip -q -r lambda_locust.zip lambda_locust.py locustfile_example.py python-packages\n```\n\nThen create the Lambda function using using the AWS CLI:\n\n```\naws lambda create-function --function-name lambda_locust --timeout 300 --runtime python3.7 --role arn:aws:iam::9999999999:role/lambda_basic_execution --handler lambda_locust.handler --zip-file fileb://lambda_locust.zip\n```\n\nOr [Terraform](https://www.terraform.io/) and the example [main.tf](main.tf) file:\n\n```\nterraform apply\n...\n```\n\n### Invoking the function\n\nThe Locust settings can be passed to the Lambda function or can be set from environment variables. The environment variables are:\n\n  - LOCUST_LOCUSTFILE: Locust file to use for the load test\n  - LOCUST_CLASSES: Names of locust classes to use for the load test (instead of a locustfile). If more than one, separate with comma.\n  - LOCUST_HOST: The host to run the load test against\n  - LOCUST_NUM_CLIENTS: Number of clients to simulate\n  - LOCUST_HATCH_RATE: Number of clients per second to start\n  - LOCUST_RUN_TIME: The time the test should run for\n  - LOCUST_LOGLEVEL: Level of logging\n\n[AWS CLI](https://aws.amazon.com/cli/) example with Locust settings in a payload:\n\n```\naws lambda invoke --function-name lambda_locust --invocation-type RequestResponse --payload '{\"locustfile\": \"locustfile_example.py\", \"host\":\"http://www.iana.org\", \"num_users\": \"1\", \"spawn_rate\": \"1\", \"run_time\":\"3m\"}' --cli-binary-format raw-in-base64-out output.txt\n{\n    \"StatusCode\": 200\n}\ncat output.txt\n\"{\\\"success\\\": {\\\"GET_/\\\": {\\\"request_type\\\": \\\"GET\\\", \\\"num_requests\\\": 20, \\\"min_response_time\\\": 86, \\\"median_response_time\\\": 93 ...\n```\n\nPython boto3 example:\n\n```python\nimport json\nfrom boto3.session import Session\nfrom botocore.client import Config\n\nsession = Session()\nconfig = Config(connect_timeout=10, read_timeout=310)\nclient = session.client('lambda', config=config)\n\nlambda_payload = {\n    'locustfile': 'locustfile_example.py',\n    'host': 'https://example.com',\n    'num_users': '1',\n    'spawn_rate': 1,\n    'run_time':'3m'\n}\n\nresponse = client.invoke(FunctionName='lambda_locust', Payload=json.dumps(lambda_payload))\njson.loads(response['Payload'].read())\n'{\"success\": {\"GET_/\": {\"request_type\": \"GET\", \"num_requests\": 20, \"min_response_time\": 87, \"median_response_time\": 99, \"avg_response_time\": 97.35 ...\n```\n\n### Running a real load test\n\nLambda function execution time is limited to a maximum of 15 minutes. To run a real load test the function will need to be invoked repeatedly and likely in parallel to generate enough load. To manage this there is a class called `LambdaLoadTest` that can manage invoking the function in parallel loops and collecting the statistics.\n\n```python\nimport logging\nfrom invokust.aws_lambda import LambdaLoadTest\n\nlogging.basicConfig(level=logging.INFO)\n\nlambda_payload = {\n    'locustfile': 'locustfile_example.py',\n    'host': 'https://example.com',\n    'num_users': 1,\n    'spawn_rate': 1,\n    'run_time':'3m'\n}\n\nload_test = LambdaLoadTest(\n  lambda_function_name='lambda_locust',\n  threads=2,\n  ramp_time=0,\n  time_limit=30,\n  lambda_payload=lambda_payload\n)\n\nload_test.run()\nprint(load_test.get_summary_stats())\n```\n\nThe output:\n```\nINFO:root:\nStarting load test...\nFunction name: lambda_locust\nRamp time: 0s\nThreads: 2\nLambda payload: {'locustfile': 'locustfile_example.py', 'host': 'https://example.com', 'num_users': '1', 'spawn_rate': 1, 'run_time': '3m'}\nStart ramping down after: 30s\nINFO:root:thread started\nINFO:root:Invoking lambda...\nINFO:root:threads: 1, rpm: 0, time elapsed: 0s, total requests from finished threads: 0, request fail ratio: 0, invocation error ratio: 0\nINFO:root:threads: 1, rpm: 0, time elapsed: 3s, total requests from finished threads: 0, request fail ratio: 0, invocation error ratio: 0\nINFO:root:thread started\nINFO:root:Invoking lambda...\nINFO:root:threads: 2, rpm: 0, time elapsed: 6s, total requests from finished threads: 0, request fail ratio: 0, invocation error ratio: 0\nINFO:root:threads: 2, rpm: 0, time elapsed: 9s, total requests from finished threads: 0, request fail ratio: 0, invocation error ratio: 0\nINFO:root:threads: 2, rpm: 0, time elapsed: 12s, total requests from finished threads: 0, request fail ratio: 0, invocation error ratio: 0\nINFO:root:threads: 2, rpm: 0, time elapsed: 15s, total requests from finished threads: 0, request fail ratio: 0, invocation error ratio: 0\nINFO:root:threads: 2, rpm: 0, time elapsed: 18s, total requests from finished threads: 0, request fail ratio: 0, invocation error ratio: 0\nINFO:root:threads: 2, rpm: 0, time elapsed: 21s, total requests from finished threads: 0, request fail ratio: 0, invocation error ratio: 0\nINFO:root:threads: 2, rpm: 0, time elapsed: 24s, total requests from finished threads: 0, request fail ratio: 0, invocation error ratio: 0\nINFO:root:threads: 2, rpm: 0, time elapsed: 27s, total requests from finished threads: 0, request fail ratio: 0, invocation error ratio: 0\nINFO:root:threads: 2, rpm: 0, time elapsed: 30s, total requests from finished threads: 0, request fail ratio: 0, invocation error ratio: 0\nINFO:root:threads: 2, rpm: 0, time elapsed: 33s, total requests from finished threads: 0, request fail ratio: 0, invocation error ratio: 0\nINFO:root:Time limit reached. Starting ramp down...\nINFO:root:Waiting for all Lambdas to return. This may take up to 3m.\nINFO:invokust.aws_lambda.lambda_load_test:Lambda invocation complete. Requests (errors): 1867 (0), execution time: 180066ms, sleeping: 0s\nINFO:root:thread finished\nINFO:invokust.aws_lambda.lambda_load_test:Lambda invocation complete. Requests (errors): 1884 (0), execution time: 180065ms, sleeping: 0s\nINFO:root:thread finished\n{'lambda_invocation_count': 2, 'total_lambda_execution_time': 360131, 'requests_total': 3751, 'request_fail_ratio': 0.0, 'invocation_error_ratio': 0.0}\n```\n\nThere is also an example CLI tool for running a load test, `invokr.py`:\n\n```\n$ ./invokr.py --function_name=lambda_locust --locust_file=locustfile_example.py --locust_host=https://example.com --threads=1 --time_limit=15 --locust_users=2\n2017-05-22 20:16:22,432 INFO   MainThread\nStarting load test\nFunction: lambda_locust\nRamp time: 0\nThreads: 1\nLambda payload: {'locustfile': 'locustfile_example.py', 'host': 'https://example.com', 'num_users': 2, 'spawn_rate': 10, 'run_time': '15s'}\n\n[2020-06-28 19:58:22,103] pudli/INFO/root: thread started\n[2020-06-28 19:58:22,107] pudli/INFO/root: threads: 1, rpm: 0, run_time: 0, requests_total: 0, request_fail_ratio: 0, invocation_error_ratio: 0\n[2020-06-28 19:58:25,108] pudli/INFO/root: threads: 1, rpm: 0, run_time: 3, requests_total: 0, request_fail_ratio: 0, invocation_error_ratio: 0\n[2020-06-28 19:58:28,109] pudli/INFO/root: threads: 1, rpm: 0, run_time: 6, requests_total: 0, request_fail_ratio: 0, invocation_error_ratio: 0\n[2020-06-28 19:58:31,110] pudli/INFO/root: threads: 1, rpm: 0, run_time: 9, requests_total: 0, request_fail_ratio: 0, invocation_error_ratio: 0\n[2020-06-28 19:58:34,112] pudli/INFO/root: threads: 1, rpm: 0, run_time: 12, requests_total: 0, request_fail_ratio: 0, invocation_error_ratio: 0\n[2020-06-28 19:58:37,113] pudli/INFO/root: threads: 1, rpm: 0, run_time: 15, requests_total: 0, request_fail_ratio: 0, invocation_error_ratio: 0\n[2020-06-28 19:58:39,001] pudli/INFO/invokust.aws_lambda.lambda_load_test: Invocation complete. Requests (errors): 224 (120), execution time: 15066, sleeping: 0\n[2020-06-28 19:58:40,116] pudli/INFO/root: threads: 1, rpm: 795, run_time: 18, requests_total: 224, request_fail_ratio: 0.5357142857142857, invocation_error_ratio: 0.0\n[2020-06-28 19:58:40,117] pudli/ERROR/root: Error limit reached, invocation error ratio: 0.0, request fail ratio: 0.5357142857142857\n[2020-06-28 19:58:40,117] pudli/INFO/root: Waiting for threads to exit...\n[2020-06-28 19:58:54,086] pudli/INFO/invokust.aws_lambda.lambda_load_test: Invocation complete. Requests (errors): 242 (131), execution time: 15052, sleeping: 0\n[2020-06-28 19:58:54,086] pudli/INFO/root: thread finished\n[2020-06-28 19:58:54,142] pudli/INFO/root: Aggregated results: {\"requests\": {\"GET_/\": {\"median_response_time\": 92.0, \"total_rps\": 7.18569301694931, \"avg_response_time\": 91.08271769409947, \"max_response_time\": 114.66264724731445, \"min_response_time\": 84.4886302947998, \"response_times\": {\"histogram\": [85, 45, 4, 6, 7, 47, 11, 0, 0, 10], \"bins\": [84.0, 86.6, 89.2, 91.8, 94.4, 97.0, 99.6, 102.2, 104.8, 107.4, 110.0]}, \"total_rpm\": 431.1415810169586, \"num_requests\": 215}, \"POST_/post\": {\"median_response_time\": 150.0, \"total_rps\": 8.38878329746517, \"avg_response_time\": 157.73737294831653, \"max_response_time\": 1087.4686241149902, \"min_response_time\": 142.15636253356934, \"response_times\": {\"histogram\": [247, 0, 0, 1, 2, 0, 0, 0, 0, 1], \"bins\": [140.0, 236.0, 332.0, 428.0, 524.0, 620.0, 716.0, 812.0, 908.0, 1004.0, 1100.0]}, \"total_rpm\": 503.32699784791026, \"num_requests\": 251}}, \"failures\": {\"POST_/post\": {\"method\": \"POST\", \"name\": \"/post\", \"error\": \"HTTPError('404 Client Error: Not Found for url: https://example.com/post',)\", \"occurrences\": 251}}, \"num_requests\": 466, \"num_requests_fail\": 251, \"total_lambda_execution_time\": 30118, \"lambda_invocations\": 2, \"approximate_cost\": 6.3008e-05, \"request_fail_ratio\": 0.5386266094420601, \"invocation_error_ratio\": 0.0, \"locust_settings\": {\"locustfile\": \"locustfile_example.py\", \"host\": \"https://example.com\", \"num_users\": 2, \"spawn_rate\": 10, \"run_time\": \"15s\"}, \"lambda_function_name\": \"lambda_locust\", \"threads\": 1, \"ramp_time\": 0, \"time_limit\": 15}\n[2020-06-28 19:58:54,142] pudli/INFO/root: ===========================================================================================================================\n[2020-06-28 19:58:54,143] pudli/INFO/root: TYPE    NAME                                                #REQUESTS    MEDIAN   AVERAGE       MIN       MAX  #REQS/SEC\nScratch\n[2020-06-28 19:58:54,143] pudli/INFO/root: ===========================================================================================================================\n[2020-06-28 19:58:54,143] pudli/INFO/root: GET     /                                                         215      92.0     91.08     84.49    114.66       7.19\n[2020-06-28 19:58:54,144] pudli/INFO/root: POST    /post                                                     251     150.0    157.74    142.16   1087.47       8.39\n[2020-06-28 19:58:54,144] pudli/INFO/root: Exiting...\n```\n\n### Occasional errors\n\n*  ERROR : `xxxxx-3f19-11e7-a1d1-xxxxxxx Process exited before completing request\"`\n    - SOLUTION: Double the size of the memory for the function.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FFutureSharks%2Finvokust","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FFutureSharks%2Finvokust","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FFutureSharks%2Finvokust/lists"}