locust 2.24.2.dev19__tar.gz → 2.25.0__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.24.2.dev19 → locust-2.25.0}/PKG-INFO +1 -1
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/changelog.rst +7 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/extending-locust.rst +14 -3
- locust-2.25.0/docs/images/extend_modern_web_ui_cache_stats.png +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/quickstart.rst +2 -2
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/writing-a-locustfile.rst +11 -9
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/extend_web_ui/extend.py +7 -0
- locust-2.25.0/examples/web_ui_cache_stats.py +210 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/_version.py +2 -2
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/dispatch.py +44 -9
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/test_dispatch.py +69 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust.egg-info/PKG-INFO +1 -1
- {locust-2.24.2.dev19 → locust-2.25.0}/locust.egg-info/SOURCES.txt +2 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/.coveragerc +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/.dockerignore +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/.git-blame-ignore-revs +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/.gitattributes +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/.github/CONTRIBUTING.md +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/.github/workflows/stale.yml +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/.github/workflows/tests.yml +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/.gitignore +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/.readthedocs.yaml +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/.vscode/extensions.json +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/.vscode/launch.json +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/.vscode/launch_locust.json +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/.vscode/settings.json +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/CHANGELOG.md +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/Dockerfile +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/LICENSE +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/MANIFEST.in +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/Makefile +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/README +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/README.md +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/SECURITY.md +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/Vagrantfile +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/benchmarks/dispatch.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/_static/theme-overrides.css +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/api.rst +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/conf.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/configuration.rst +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/custom-load-shape.rst +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/developing-locust.rst +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/further-reading.rst +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/history.rst +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/images/locust_workers.png +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/images/number_of_users.png +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/images/response_times.png +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/images/total_requests_per_second.png +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/images/userclass_picker_example.png +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/images/webui-running-statistics.png +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/images/webui-splash-screenshot.png +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/increase-performance.rst +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/index.rst +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/installation.rst +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/logging.rst +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/requirements.txt +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/retrieving-stats.rst +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/running-cloud-integration.rst +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/running-distributed.rst +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/running-in-debugger.rst +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/running-in-docker.rst +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/running-without-web-ui.rst +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/tasksets.rst +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/testing-other-systems.rst +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/use-as-lib.rst +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/docs/what-is-locust.rst +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/add_command_line_argument.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/basic.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/browse_docs_sequence_test.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/browse_docs_test.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/custom_messages.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/custom_shape/double_wave.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/custom_shape/stages.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/custom_shape/staging_user_classes.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/custom_shape/step_load.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/custom_shape/wait_user_count.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/custom_wait_function.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/custom_xmlrpc_client/server.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/custom_xmlrpc_client/xmlrpc_locustfile.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/debugging.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/debugging_advanced.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/docker-compose/docker-compose.yml +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/dynamic_user_credentials.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/events.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/extend_modern_web_ui.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/extend_web_ui/static/custom-stats-table.css +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/extend_web_ui/static/extend.js +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/extend_web_ui/templates/extend.html +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/fast_http_locust.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/grpc/grpc_user.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/grpc/hello.proto +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/grpc/hello_pb2.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/grpc/hello_pb2_grpc.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/grpc/hello_server.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/grpc/locustfile.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/locustfile.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/manual_stats_reporting.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/multiple_hosts.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/nested_inline_tasksets.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/rest.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/sdk_session_patching/session_patch_locustfile.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/semaphore_wait.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/stop_on_threshold.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/terraform/aws/README.md +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/terraform/aws/data_subnet.tf +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/terraform/aws/main.tf +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/terraform/aws/output.tf +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/terraform/aws/plan/basic.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/terraform/aws/provisioner.tf +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/terraform/aws/variables.tf +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/test_data_management.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/use_as_lib.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/vagrant/README.md +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/vagrant/supervisord.conf +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/web_ui_auth.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/examples/worker_index.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/generate_changelog.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/__init__.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/__main__.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/argument_parser.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/clients.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/contrib/__init__.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/contrib/fasthttp.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/debug.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/env.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/event.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/exception.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/html.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/input_events.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/log.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/main.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/py.typed +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/rpc/__init__.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/rpc/protocol.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/rpc/zmqrpc.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/runners.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/shape.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/static/chart.js +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/static/css/application.css +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/static/css/application.css.map +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/static/css/tables.css +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/static/css/tables.css.map +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/static/echarts.common.min.js +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/static/img/favicon.ico +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/static/img/logo.png +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/static/img/ui-screenshot-charts.png +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/static/img/ui-screenshot-start-test.png +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/static/img/ui-screenshot-stats.png +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/static/img/ui-screenshot-workers.png +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/static/jquery-1.11.3.min.js +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/static/jquery.jqote2.min.js +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/static/jquery.tools.min.js +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/static/locust.js +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/static/sass/_base.sass +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/static/sass/_mixins.sass +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/static/sass/application.sass +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/static/sass/tables.sass +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/static/tasks.js +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/static/vintage.js +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/stats.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/templates/index.html +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/templates/report.html +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/templates/stats_data.html +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/__init__.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/fake_module1_for_env_test.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/fake_module2_for_env_test.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/mock_locustfile.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/mock_logging.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/test_debugging.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/test_env.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/test_fasthttp.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/test_http.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/test_interruptable_task.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/test_load_locustfile.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/test_locust_class.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/test_log.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/test_main.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/test_old_wait_api.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/test_parser.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/test_runners.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/test_sequential_taskset.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/test_stats.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/test_tags.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/test_taskratio.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/test_users.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/test_util.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/test_wait_time.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/test_web.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/test_zmqrpc.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/testcases.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/test/util.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/user/__init__.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/user/inspectuser.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/user/sequential_taskset.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/user/task.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/user/users.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/user/wait_time.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/util/__init__.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/util/cache.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/util/deprecation.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/util/exception_handler.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/util/load_locustfile.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/util/rounding.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/util/timespan.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/web.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/.eslintrc +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/.gitignore +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/.prettierrc +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/auth.html +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/dev.html +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/dist/assets/favicon.ico +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/dist/assets/index-941b6e82.js +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/dist/assets/logo.png +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/dist/auth.html +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/dist/index.html +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/dist/report.html +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/index.html +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/package.json +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/public/assets/favicon.ico +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/public/assets/logo.png +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/public/report.html +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/App.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/App.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/DataTable/DataTable.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/DataTable/DataTable.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/ExceptionsTable/ExceptionsTable.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/FailuresTable/FailuresTable.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/FallbackRender/FallbackRender.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/FallbackRender/FallbackRender.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/Form/Form.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/Form/Select.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/Form/tests/Form.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/Form/tests/Select.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/Layout/Footer/About.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/Layout/Footer/Footer.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/Layout/Layout.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/Layout/Navbar/DarkLightToggle.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/Layout/Navbar/Navbar.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/Layout/Navbar/SwarmMonitor.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/Layout/Navbar/SwarmMonitor.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/LineChart/LineChart.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/LineChart/LineChart.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/LogViewer/LogViewer.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/LogViewer/tests/LogViewer.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/LogViewer/tests/useLogViewer.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/LogViewer/useLogViewer.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/Modal/Modal.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/Reports/Reports.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/Reports/Reports.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/ResponseTimeTable/ResponseTimeTable.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/ResponseTimeTable/ResponseTimeTable.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/StateButtons/EditButton.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/StateButtons/NewTestButton.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/StateButtons/ResetButton.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/StateButtons/StateButtons.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/StateButtons/StopButton.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/StateButtons/tests/ResetButton.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/StateButtons/tests/StateButtons.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/StateButtons/tests/StopButton.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/StatsTable/StatsTable.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/SwarmCharts/SwarmCharts.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/SwarmForm/SwarmCustomParameters.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/SwarmForm/SwarmEditForm.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/SwarmForm/SwarmForm.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/SwarmForm/SwarmUserClassPicker.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/SwarmForm/tests/SwarmCustomParameters.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/SwarmForm/tests/SwarmEditForm.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/SwarmForm/tests/SwarmForm.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/SwarmForm/tests/SwarmUserClassPicker.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/SwarmRatios/SwarmRatios.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/SwarmRatios/SwarmRatios.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/Table/Table.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/Table/Table.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/Tabs/Tabs.constants.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/Tabs/Tabs.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/Tabs/Tabs.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/ViewColumnSelector/ViewColumnSelector.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/ViewColumnSelector/ViewColumnSelector.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/components/WorkersTable/WorkersTable.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/constants/auth.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/constants/swarm.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/constants/theme.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/global.d.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/hooks/tests/useNotifications.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/hooks/tests/useSelecteViewColumns.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/hooks/tests/useSortByField.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/hooks/tests/useSwarmUi.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/hooks/useInterval.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/hooks/useNotifications.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/hooks/useSelectViewColumns.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/hooks/useSortByField.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/hooks/useSwarmUi.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/index.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/pages/Auth.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/pages/Dashboard.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/pages/HtmlReport.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/pages/tests/Auth.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/pages/tests/Dashboard.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/pages/tests/HtmlReport.test.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/redux/api/swarm.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/redux/hooks.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/redux/slice/logViewer.slice.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/redux/slice/notification.slice.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/redux/slice/root.slice.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/redux/slice/swarm.slice.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/redux/slice/tests/ui.slice.test.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/redux/slice/theme.slice.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/redux/slice/ui.slice.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/redux/slice/url.slice.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/redux/store.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/redux/utils.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/styles/theme.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/test/constants.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/test/mocks/statsRequest.mock.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/test/mocks/swarmState.mock.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/test/setup.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/test/testUtils.tsx +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/types/auth.types.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/types/swarm.types.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/types/tab.types.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/types/table.types.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/types/ui.types.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/utils/number.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/utils/object.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/utils/string.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/utils/tests/number.test.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/utils/tests/object.test.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/utils/tests/string.test.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/utils/tests/url.test.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/src/utils/url.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/tsconfig.json +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/vite.config.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/vitest.config.ts +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust/webui/yarn.lock +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust.egg-info/dependency_links.txt +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust.egg-info/entry_points.txt +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust.egg-info/not-zip-safe +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust.egg-info/requires.txt +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/locust.egg-info/top_level.txt +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/package.json +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/pyproject.toml +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/scripts/locustfile.py +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/scripts/run-disributed-headless.sh +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/scripts/run-disributed-web.sh +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/scripts/run-local-headless.sh +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/scripts/run-local-web.sh +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/setup.cfg +0 -0
- {locust-2.24.2.dev19 → locust-2.25.0}/tox.ini +0 -0
@@ -4,6 +4,13 @@ Changelog Highlights
|
|
4
4
|
|
5
5
|
For full details of the Locust changelog, please see https://github.com/locustio/locust/blob/master/CHANGELOG.md
|
6
6
|
|
7
|
+
2.25.0
|
8
|
+
======
|
9
|
+
* Add functionality to run listener functions for `custom_messages` concurrently (https://github.com/locustio/locust/pull/2650)
|
10
|
+
* Update User Classes in Distributed Mode (https://github.com/locustio/locust/pull/2666)
|
11
|
+
* Log deprecation warning for --legacy-ui (https://github.com/locustio/locust/pull/2670)
|
12
|
+
* Fix UserClasses weight distribution with gcd (https://github.com/locustio/locust/pull/2663)
|
13
|
+
|
7
14
|
2.24.1
|
8
15
|
======
|
9
16
|
* Some documentation updates & minor fixes to UI
|
@@ -53,6 +53,7 @@ To see a full list of available events see :ref:`events`.
|
|
53
53
|
|
54
54
|
.. _request_context:
|
55
55
|
|
56
|
+
|
56
57
|
Request context
|
57
58
|
===============
|
58
59
|
|
@@ -110,6 +111,10 @@ to the Flask app instance and use that to set up a new route::
|
|
110
111
|
|
111
112
|
You should now be able to start locust and browse to http://127.0.0.1:8089/added_page
|
112
113
|
|
114
|
+
.. note::
|
115
|
+
|
116
|
+
Please note that at the moment Locust does not show the extended Web UI under the default root path `"/"`.
|
117
|
+
To view your extensions, navigate to the newly added web route. In this example, `"/added_page"`.
|
113
118
|
|
114
119
|
|
115
120
|
Extending Web UI
|
@@ -123,9 +128,14 @@ as it involves also writing and including HTML and Javascript files to be served
|
|
123
128
|
greatly enhance the utility and customizability of the web UI.
|
124
129
|
|
125
130
|
A working example of extending the web UI, complete with HTML and Javascript example files, can be found
|
126
|
-
in the `examples directory <https://github.com/locustio/locust/tree/master/examples
|
131
|
+
in the `examples directory <https://github.com/locustio/locust/tree/master/examples/>`_ of the Locust
|
127
132
|
source code.
|
128
133
|
|
134
|
+
* ``extend_modern_web_ui.py``: Display a table with content-length for each call.
|
135
|
+
|
136
|
+
* ``web_ui_cache_stats.py``: Display Varnish Hit/ Miss stats for each call. Could be easly extended to other CDN or cache proxies and gather other cache statistics such as cache age, control, ...
|
137
|
+
|
138
|
+
.. image:: images/extend_modern_web_ui_cache_stats.png
|
129
139
|
|
130
140
|
|
131
141
|
Adding Authentication to the Web UI
|
@@ -149,8 +159,6 @@ authentication to the app should be granted.
|
|
149
159
|
A full example can be seen `in the auth example <https://github.com/locustio/locust/tree/master/examples/web_ui_auth.py>`_.
|
150
160
|
|
151
161
|
|
152
|
-
|
153
|
-
|
154
162
|
Run a background greenlet
|
155
163
|
=========================
|
156
164
|
|
@@ -182,6 +190,7 @@ For example, you can monitor the fail ratio of your test and stop the run if it
|
|
182
190
|
|
183
191
|
.. _parametrizing-locustfiles:
|
184
192
|
|
193
|
+
|
185
194
|
Parametrizing locustfiles
|
186
195
|
=========================
|
187
196
|
|
@@ -224,11 +233,13 @@ You can add your own command line arguments to Locust, using the :py:attr:`init_
|
|
224
233
|
|
225
234
|
When running Locust :ref:`distributed <running-distributed>`, custom arguments are automatically forwarded to workers when the run is started (but not before then, so you cannot rely on forwarded arguments *before* the test has actually started).
|
226
235
|
|
236
|
+
|
227
237
|
Test data management
|
228
238
|
====================
|
229
239
|
|
230
240
|
There are a number of ways to get test data into your tests (after all, your test is just a Python program and it can do whatever Python can). Locust's events give you fine-grained control over *when* to fetch/release test data. You can find a `detailed example here <https://github.com/locustio/locust/tree/master/examples/test_data_management.py>`_.
|
231
241
|
|
242
|
+
|
232
243
|
More examples
|
233
244
|
=============
|
234
245
|
|
Binary file
|
@@ -16,7 +16,7 @@ A Locust test is essentially just a Python program making requests to the system
|
|
16
16
|
self.client.get("/hello")
|
17
17
|
self.client.get("/world")
|
18
18
|
|
19
|
-
This user will make HTTP
|
19
|
+
This user will make an HTTP request to ``/hello``, and after that to ``/world``, and then repeat. For a full explanation and a more realistic example see :ref:`writing-a-locustfile`.
|
20
20
|
|
21
21
|
Change ``/hello`` and ``/world`` to some actual paths on the web site/service you want to test, put the code in a file named ``locustfile.py`` in your current directory and then run ``locust``:
|
22
22
|
|
@@ -24,7 +24,7 @@ Change ``/hello`` and ``/world`` to some actual paths on the web site/service yo
|
|
24
24
|
:substitutions:
|
25
25
|
|
26
26
|
$ locust
|
27
|
-
[2021-07-24 09:58:46,215] .../INFO/locust.main: Starting web interface at http
|
27
|
+
[2021-07-24 09:58:46,215] .../INFO/locust.main: Starting web interface at http://0.0.0.0:8089
|
28
28
|
[2021-07-24 09:58:46,285] .../INFO/locust.main: Starting Locust |version|
|
29
29
|
|
30
30
|
Locust's web interface
|
@@ -62,28 +62,30 @@ is executed. For more info see :ref:`wait-time`.
|
|
62
62
|
|
63
63
|
@task
|
64
64
|
def hello_world(self):
|
65
|
-
|
65
|
+
self.client.get("/hello")
|
66
|
+
self.client.get("/world")
|
66
67
|
|
67
|
-
Methods decorated with ``@task`` are the core of your locust file. For every running
|
68
|
-
Locust creates a greenlet (micro-thread), that will call those methods.
|
68
|
+
Methods decorated with ``@task`` are the core of your locust file. For every running User,
|
69
|
+
Locust creates a `greenlet <https://greenlet.readthedocs.io/en/stable/greenlet.html>`_ (a coroutine or "micro-thread"), that will call those methods.
|
70
|
+
Code within a task is executed sequentially (it is just regular Python code),
|
71
|
+
so ``/world`` wont be called until the response from ``/hello`` has been received.
|
69
72
|
|
70
73
|
.. code-block:: python
|
71
74
|
|
72
75
|
@task
|
73
76
|
def hello_world(self):
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
+
...
|
78
|
+
|
77
79
|
@task(3)
|
78
80
|
def view_items(self):
|
79
|
-
|
81
|
+
...
|
80
82
|
|
81
83
|
We've declared two tasks by decorating two methods with ``@task``, one of which has been given a higher weight (3).
|
82
84
|
When our ``QuickstartUser`` runs it'll pick one of the declared tasks - in this case either ``hello_world`` or
|
83
85
|
``view_items`` - and execute it. Tasks are picked at random, but you can give them different weighting. The above
|
84
86
|
configuration will make Locust three times more likely to pick ``view_items`` than ``hello_world``. When a task has
|
85
|
-
finished executing, the User will then sleep
|
86
|
-
|
87
|
+
finished executing, the User will then sleep for its specified wait time (in this case between 1 and 5 seconds).
|
88
|
+
Then it will pick a new task.
|
87
89
|
|
88
90
|
Note that only methods decorated with ``@task`` will be picked, so you can define your own internal helper methods any way you like.
|
89
91
|
|
@@ -9,9 +9,16 @@ from locust import HttpUser, TaskSet, between, events, task, web
|
|
9
9
|
import os
|
10
10
|
from html import escape
|
11
11
|
from time import time
|
12
|
+
from warnings import warn
|
12
13
|
|
13
14
|
from flask import Blueprint, jsonify, make_response, render_template
|
14
15
|
|
16
|
+
warn(
|
17
|
+
"This UI example is deprecated, please downgrade to Locust 2.21.0 or use --legacy-ui flag ; version=2.22.0",
|
18
|
+
DeprecationWarning,
|
19
|
+
stacklevel=2,
|
20
|
+
)
|
21
|
+
|
15
22
|
|
16
23
|
class MyTaskSet(TaskSet):
|
17
24
|
@task(2)
|
@@ -0,0 +1,210 @@
|
|
1
|
+
"""
|
2
|
+
This is an example of a locustfile that uses Locust's built in event and web
|
3
|
+
UI extension hooks to track the sum of Varnish cache hit/miss headers
|
4
|
+
and display them in the web UI.
|
5
|
+
"""
|
6
|
+
|
7
|
+
from locust import HttpUser, TaskSet, between, events, task, web
|
8
|
+
|
9
|
+
import json
|
10
|
+
import os
|
11
|
+
from html import escape
|
12
|
+
from time import time
|
13
|
+
|
14
|
+
from flask import Blueprint, jsonify, make_response, render_template, request
|
15
|
+
|
16
|
+
|
17
|
+
class MyTaskSet(TaskSet):
|
18
|
+
@task(1)
|
19
|
+
def miss(l):
|
20
|
+
"""MISS X-Cache header"""
|
21
|
+
l.client.get("/response-headers?X-Cache=MISS")
|
22
|
+
|
23
|
+
@task(2)
|
24
|
+
def hit(l):
|
25
|
+
"""HIT X-Cache header"""
|
26
|
+
l.client.get("/response-headers?X-Cache=HIT")
|
27
|
+
|
28
|
+
@task(1)
|
29
|
+
def noinfo(l):
|
30
|
+
"""No X-Cache header (noinfo counter)"""
|
31
|
+
l.client.get("/")
|
32
|
+
|
33
|
+
|
34
|
+
class WebsiteUser(HttpUser):
|
35
|
+
host = "http://httpbin.org"
|
36
|
+
wait_time = between(2, 5)
|
37
|
+
tasks = [MyTaskSet]
|
38
|
+
|
39
|
+
|
40
|
+
# This example is based on the Varnish hit/miss headers (https://docs.varnish-software.com/tutorials/hit-miss-logging/).
|
41
|
+
# It could easly be customised for matching other caching sytems, CDN or custom headers.
|
42
|
+
CACHE_HEADER = "X-Cache"
|
43
|
+
|
44
|
+
cache_stats = {}
|
45
|
+
|
46
|
+
page_stats = {"hit": 0, "miss": 0, "noinfo": 0}
|
47
|
+
|
48
|
+
path = os.path.dirname(os.path.abspath(__file__))
|
49
|
+
extend = Blueprint(
|
50
|
+
"extend",
|
51
|
+
"extend_web_ui",
|
52
|
+
static_folder=f"{path}/static/",
|
53
|
+
static_url_path="/extend/static/",
|
54
|
+
template_folder=f"{path}/templates/",
|
55
|
+
)
|
56
|
+
|
57
|
+
|
58
|
+
@events.init.add_listener
|
59
|
+
def locust_init(environment, **kwargs):
|
60
|
+
"""
|
61
|
+
Load data on locust init.
|
62
|
+
:param environment:
|
63
|
+
:param kwargs:
|
64
|
+
:return:
|
65
|
+
"""
|
66
|
+
|
67
|
+
if environment.web_ui:
|
68
|
+
# this code is only run on the master node (the web_ui instance doesn't exist on workers)
|
69
|
+
|
70
|
+
def get_cache_stats():
|
71
|
+
"""
|
72
|
+
This is used by the Cache tab in the
|
73
|
+
extended web UI to show the stats.
|
74
|
+
"""
|
75
|
+
if cache_stats:
|
76
|
+
stats_tmp = []
|
77
|
+
|
78
|
+
for name, inner_stats in cache_stats.items():
|
79
|
+
stats_tmp.append(
|
80
|
+
{
|
81
|
+
"name": name,
|
82
|
+
"safe_name": escape(name, quote=False),
|
83
|
+
"hit": inner_stats["hit"],
|
84
|
+
"miss": inner_stats["miss"],
|
85
|
+
"noinfo": inner_stats["noinfo"],
|
86
|
+
}
|
87
|
+
)
|
88
|
+
|
89
|
+
# Truncate the total number of stats and errors displayed since a large number
|
90
|
+
# of rows will cause the app to render extremely slowly.
|
91
|
+
return stats_tmp[:500]
|
92
|
+
return cache_stats
|
93
|
+
|
94
|
+
@environment.web_ui.app.after_request
|
95
|
+
def extend_stats_response(response):
|
96
|
+
if request.path != "/stats/requests":
|
97
|
+
return response
|
98
|
+
|
99
|
+
# extended_stats contains the data where extended_tables looks for its data: "cache-statistics"
|
100
|
+
response.set_data(
|
101
|
+
json.dumps(
|
102
|
+
{**response.json, "extended_stats": [{"key": "cache-statistics", "data": get_cache_stats()}]}
|
103
|
+
)
|
104
|
+
)
|
105
|
+
|
106
|
+
return response
|
107
|
+
|
108
|
+
@extend.route("/extend")
|
109
|
+
def extend_web_ui():
|
110
|
+
"""
|
111
|
+
Add route to access the extended web UI with our new tab.
|
112
|
+
"""
|
113
|
+
# ensure the template_args are up to date before using them
|
114
|
+
environment.web_ui.update_template_args()
|
115
|
+
# set the static paths to use the modern ui
|
116
|
+
environment.web_ui.set_static_modern_ui()
|
117
|
+
|
118
|
+
return render_template(
|
119
|
+
"index.html",
|
120
|
+
template_args={
|
121
|
+
**environment.web_ui.template_args,
|
122
|
+
# extended_tabs and extended_tables keys must match.
|
123
|
+
"extended_tabs": [{"title": "Cache statistics", "key": "cache-statistics"}],
|
124
|
+
"extended_tables": [
|
125
|
+
{
|
126
|
+
"key": "cache-statistics",
|
127
|
+
"structure": [
|
128
|
+
{"key": "name", "title": "Name"},
|
129
|
+
{"key": "hit", "title": "Hit"},
|
130
|
+
{"key": "miss", "title": "Miss"},
|
131
|
+
{"key": "noinfo", "title": "No Info"},
|
132
|
+
],
|
133
|
+
}
|
134
|
+
],
|
135
|
+
"extended_csv_files": [{"href": "/cache/csv", "title": "Download Cache statistics CSV"}],
|
136
|
+
},
|
137
|
+
)
|
138
|
+
|
139
|
+
@extend.route("/cache/csv")
|
140
|
+
def request_cache_csv():
|
141
|
+
"""
|
142
|
+
Add route to enable downloading of cache stats as CSV
|
143
|
+
"""
|
144
|
+
response = make_response(cache_csv())
|
145
|
+
file_name = f"cache-{time()}.csv"
|
146
|
+
disposition = f"attachment;filename={file_name}"
|
147
|
+
response.headers["Content-type"] = "text/csv"
|
148
|
+
response.headers["Content-disposition"] = disposition
|
149
|
+
return response
|
150
|
+
|
151
|
+
def cache_csv():
|
152
|
+
"""Returns the cache stats as CSV."""
|
153
|
+
rows = [",".join(['"Name"', '"hit"', '"miss"', '"noinfo"'])]
|
154
|
+
|
155
|
+
if cache_stats:
|
156
|
+
for name, stats in cache_stats.items():
|
157
|
+
rows.append(f'"{name}",' + ",".join(str(v) for v in stats.values()))
|
158
|
+
return "\n".join(rows)
|
159
|
+
|
160
|
+
# register our new routes and extended UI with the Locust web UI
|
161
|
+
environment.web_ui.app.register_blueprint(extend)
|
162
|
+
|
163
|
+
|
164
|
+
@events.request.add_listener
|
165
|
+
def on_request(name, response, exception, **kwargs):
|
166
|
+
"""
|
167
|
+
Event handler that get triggered on every request
|
168
|
+
"""
|
169
|
+
|
170
|
+
cache_stats.setdefault(name, page_stats.copy())
|
171
|
+
|
172
|
+
if CACHE_HEADER not in response.headers:
|
173
|
+
cache_stats[name]["noinfo"] += 1
|
174
|
+
elif response.headers[CACHE_HEADER] == "HIT":
|
175
|
+
cache_stats[name]["hit"] += 1
|
176
|
+
elif response.headers[CACHE_HEADER] == "MISS":
|
177
|
+
cache_stats[name]["miss"] += 1
|
178
|
+
|
179
|
+
|
180
|
+
@events.report_to_master.add_listener
|
181
|
+
def on_report_to_master(client_id, data):
|
182
|
+
"""
|
183
|
+
This event is triggered on the worker instances every time a stats report is
|
184
|
+
to be sent to the locust master. It will allow us to add our extra cache
|
185
|
+
data to the dict that is being sent, and then we clear the local stats in the worker.
|
186
|
+
"""
|
187
|
+
global cache_stats
|
188
|
+
data["cache_stats"] = cache_stats
|
189
|
+
cache_stats = {}
|
190
|
+
|
191
|
+
|
192
|
+
@events.worker_report.add_listener
|
193
|
+
def on_worker_report(client_id, data):
|
194
|
+
"""
|
195
|
+
This event is triggered on the master instance when a new stats report arrives
|
196
|
+
from a worker. Here we just add the cache to the master's aggregated stats dict.
|
197
|
+
"""
|
198
|
+
for name in data["cache_stats"]:
|
199
|
+
cache_stats.setdefault(name, page_stats.copy())
|
200
|
+
for stat_name, value in data["cache_stats"][name].items():
|
201
|
+
cache_stats[name][stat_name] += value
|
202
|
+
|
203
|
+
|
204
|
+
@events.reset_stats.add_listener
|
205
|
+
def on_reset_stats():
|
206
|
+
"""
|
207
|
+
Event handler that get triggered on click of web UI Reset Stats button
|
208
|
+
"""
|
209
|
+
global cache_stats
|
210
|
+
cache_stats = {}
|
@@ -12,5 +12,5 @@ __version__: str
|
|
12
12
|
__version_tuple__: VERSION_TUPLE
|
13
13
|
version_tuple: VERSION_TUPLE
|
14
14
|
|
15
|
-
__version__ = version = '2.
|
16
|
-
__version_tuple__ = version_tuple = (2,
|
15
|
+
__version__ = version = '2.25.0'
|
16
|
+
__version_tuple__ = version_tuple = (2, 25, 0)
|
@@ -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
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
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
|
-
|
412
|
+
user[0].__name__,
|
413
|
+
normalized_weight // greatest_common_divisor,
|
379
414
|
)
|
380
|
-
for user,
|
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):
|
@@ -55,6 +55,7 @@ docs/use-as-lib.rst
|
|
55
55
|
docs/what-is-locust.rst
|
56
56
|
docs/writing-a-locustfile.rst
|
57
57
|
docs/_static/theme-overrides.css
|
58
|
+
docs/images/extend_modern_web_ui_cache_stats.png
|
58
59
|
docs/images/locust_workers.png
|
59
60
|
docs/images/number_of_users.png
|
60
61
|
docs/images/response_times.png
|
@@ -84,6 +85,7 @@ examples/stop_on_threshold.py
|
|
84
85
|
examples/test_data_management.py
|
85
86
|
examples/use_as_lib.py
|
86
87
|
examples/web_ui_auth.py
|
88
|
+
examples/web_ui_cache_stats.py
|
87
89
|
examples/worker_index.py
|
88
90
|
examples/custom_shape/double_wave.py
|
89
91
|
examples/custom_shape/stages.py
|
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
|