A lightweight framework for performing load tests against an environment, primarily against an API. Grasshopper glues Locust, Pytest, some plugins (namely Locust InfluxDBListener ) and some custom code to provide a package that makes authoring load tests simple with very little boilerplate needed.
Here are some key functionalities that this project extends on Locust:
This package can be installed via pip: pip install locust-grasshopper
test_example.py in the example directory for a basic
skeleton of how to get a load test running. In the same directory, there is also an
example conftest.py that will show you how to get basic parameterization working.pytest example/test_example.py in the root of
this project.cd example
pytest example_scenarios.YAML --tags=example1
In this example scenario file, you can see how grasshopper_args, grasshopper_scenario_args, and tags are being set.
When creating a new load test, the primary grasshopper function you will be using
is called Grasshopper.launch_test. This function can be imported like so: from grasshopper.lib.grasshopper import Grasshopper
launch_test takes in a wide variety of args:
user_classes: User classes that the runner will run. These user classes must
extend BaseJourney, which is a grasshopper class
(from grasshopper.lib.journeys.base_journey import BaseJourney). This can be a
single class, a list of classes, or a dictionary where the key is the class and
the value is the locust weight to assign to that class.**complete_configuration: In order for the test to have the correct configuration, you
must pass in the kwargs provided by the complete_configuration fixture. See example
load test on how to do this properly.scenario_args
dict. This is the proper way to pass in values from outside of
the journey for access by the journey code. Note that each journey gets a
copy on start, so the journey itself can safely modify its own dictionary
once the test is running.
scenario_args exists for any journey that extends the grasshopper base_journey
class.
scenario_args also grabs from self.defaults on initialization. For example:import logging
from locust import between, task
from grasshopper.lib.journeys.base_journey import BaseJourney
from grasshopper.lib.grasshopper import Grasshopper
# a journey class with an example task
class ExampleJourney(BaseJourney):
# number of seconds to wait between each task
wait_time = between(min_wait=20, max_wait=30)
# this defaults dictionary will be merged into scenario_args with lower precedence
# when the journey is initialized
defaults = {
"foo": "bar",
}
@task
def example_task:
# log_prefix is a logger instance (LoggingAdapter) used for logging with VU_number as a prefix.
# For more details, refer to the full implementation in base_journey.py
self.log_prefix.info(f'foo is `{self.scenario_args.get("foo")}`.')
# aggregate all metrics for the below request under the name "get google"
# if name is not specified, then the full url will be the name of the metric
response = self.client.get('https://google.com', name='get google')
# the pytest test which launches the journey class
def test_run_example_journey(complete_configuration):
# update scenario args before initialization
ExampleJourney.update_incoming_scenario_args(complete_configuration)
# launch the journey
locust_env = Grasshopper.launch_test(ExampleJourney, **complete_configuration)
return locust_env
--runtime: Number of seconds to run tests for. Runtime is defined for the whole test run.
It is always defined. Set to 120 by default.--iterations: Number of iterations to run tests for. Iterations are defined for the whole
test run across all users. If specified, the test run will stop when either the iteration limit
is reached or the runtime expires, whichever comes first. Set to 0 by default (no iteration limit).--users: Max number of users that are spawned. Set to 1 by default.--spawn_rate : Number of users to spawn per second. Set to 1 by default.--shape: The name of a shape to run for the test.
If you don't specify a shape or shape instance, then the shape Default will be used,
which just runs with the users, runtime & spawn_rate specified on the command line (or picks up defaults
of 1, 1, 120s). See utils/shapes.py for available shapes and information on how to add
your own shapes.--scenario_file If you want a yaml file where you pre-define some args, this is how
you specify that file path. For example,
scenario_file=example/scenario_example.YAML.--scenario_name If --scenario_file was specified, this is the scenario name that is
within that YAML file that corresponds to the scenario you wish to run. Defaults to None.--tags See below example: Loop through a collection of scenarios that match some tag.--scenario_delay Adds a delay in seconds between scenarios. Defaults to 0.--influx_host If you want to report your performance test metrics to some influxdb,
you must specify a host.
E.g. 1.1.1.1. Defaults to None.--influx_port: Port for your influx_host in the case where it is non-default.--influx_ssl: If your influxdb is using SSL, set this to True. Defaults to False.--influx_verify_ssl: If your influxdb is using SSL, set this to True. Defaults to
False.--influx_user: Username for your influx_host, if you have one.--influx_pwd: Password for your influx_host, if you have one.--grafana_host: If your grafana is a separate URL from the influxdb, you can
specify it here. If you don't, then the grafana URL will be the same as the
influxdb URL when the grasshopper object generates grafana links.DD_API_KEY and DD_ENV environment
variables. DD_SITE defaults to datadoghq.com; DD_SERVICE and DD_VERSION
add stable service and release tags when provided.All in all, there are a few ways you can actually collect and pass params to a test:
cd example
pytest test_example.py ...
cd example
pytest test_example.py --scenario_file=example_scenarios.YAML --scenario_name=example_scenario_1 ...
cd example
pytest example_scenarios.YAML --tags=smoke ...
.YAML scenario file into pytest instead of a .py file.--scenario_file and --scenario_name args will be ignored in this case--tags arg supports AND/OR operators according to the opensource tag-matcher package. More info on these operators can be found here.--tags arg is specified, then ALL the scenarios in the .yaml file will be run.--scenario_delay, the test will wait that many seconds between collected scenarios.Grasshopper adds a variety of parameters relating to performance testing along with a variety of ways to set these values.
Recent changes (>= 1.1.1) include an expanded set of sources, almost full access to all arguments via every source (some exceptions outlined below), and the addition of some new values that will be used with integrations such as report portal & slack (integrations are NYI). These changes are made in a backwards compatible manner, meaning all existing grasshopper tests should still run without modification. The original fixtures and sources for configuration are deprecated, but still produce the same behavior.
All of the usual pytest arguments also remain available.
The rest of the sections on configuration assume you are using locust-grasshopper>=1.1.1.
Currently, there are 5 different sources for configuration values and they are, in precedence order
Obviously, the global defaults defined by Grasshopper are not really a source for consumers to modify, but we mention it so values don't seem to appear "out of thin air".
The argument list is getting lengthy, so we've broken it down into categories. These
categories are entirely for humans: better readability, understanding and ease of use.
Once they are all fully loaded by Grasshopper, they will be stored in a single
GHConfiguration object (dict). By definition, every argument is in only one category
and there is no overlap of keys between the categories. If the same key is supplied in
multiple categories, they will be merged with the precedence order as they appear in
the table.
| Name | Scope | Description/Usage |
|---|---|---|
| Grasshopper | Session | Variables that rarely change, may span many test runs. |
| Test Run | Session | Variables that may change per test run, but are the same for every scenario in the run |
| Scenario | Session | Variables that may change per scenario and are often scenario specific; Includes user defined variables that are not declared as command line arguments by Grasshopper. However, you may use pytest's addoptions hook in your conftest to define them. |
At least one of the sections must be present in the global configuration file and eventually this will be the same in the configuration section of a scenario in a scenario yaml file. Categories are not used when specifying environment variables or command line options. We recommend that you use these categories in file sources, but if a variable is in the wrong section, it won't actually affect the configuration loading process.
Your test(s) may access the complete merged set of key-value pairs via the session scoped
fixture complete_configuration. This returns a GHConfiguration object (dict) which
is unique to the current scenario. This value will be re-calculated for each new scenario
executed.
A few perhaps not obvious notes about configuration:
RUNTIME=10)
to specify a key-value pair via an environment valueGHConfiguration object
(e.g. x = complete_configuration("runtime")) regardless of the original source(s)-- before the key name to specify it on the command line (e.g. --runtime=10)grasshopper_config_file_path which returns the full path to a
configuration yaml file.scenario_args method (more details on that below)
or via a journey class's defaults attr.@pytest.fixture(scope="session")
def grasshopper_config_file_path():
return "path/to/your/config/file"
An example grasshopper configuration file:
grasshopper:
influx_host: 1.1.1.1
test_run:
users: 1.0
spawn_rate: 1.0
runtime: 600
scenario:
key1 : 'value1'
key2: 0
If you would like to include other environment variables that might be present in the
system, you can define a fixture called extra_env_var_keys which returns a list of key
names to load from the os.environ. Keys that are missing in the environment will not
be included in the GHConfiguration object.
Any environment variables that use the prefix GH_ will also be included in the
GHConfiguration object. The GH_ will be stripped before adding and any names that
become zero length after the strip will be discarded. This is a mechanism to include any
scenario arguments you might like to pass via an environment variable.
In the unlikely case that you need to use a different prefix to designate scenario
variables, you can define a fixture called env_var_prefix_key which returns a prefix
string. The same rules apply about which values are included in the configuration.
Checks are an assertion that is recorded as a metric. They are useful both to ensure your test is working correctly (e.g. are you getting a valid id back from some post that you sent) and to evaluate if the load is causing intermittent failures (e.g. sometimes a percentage of workflow runs don't complete correctly the load increases). At the end of the test, checks are aggregated by their name across all journeys that ran and then reported to the console. They are also forwarded to the DB in the "checks" table. Here is an example of using a check:
from grasshopper.lib.util.utils import check
...
response = self.client.get("https://google.com", name="get google")
check(
"get google responded with a 200",
response.status_code == 200,
env=self.environment,
)
It is worth noting that it is NOT necessary to add checks on the http codes. All the HTTP return codes are tracked automatically by grasshopper and will be sent to the DB. If you aren't using a DB then you might want the checks console output.
Custom trends are useful when you want to time something that spans multiple HTTP calls. They are reported to the specified database just like any other HTTP request, but with the "CUSTOM" HTTP verb as opposed to "GET", "POST", etc. Here is an example of using a custom trend:
from locust import between, task
from grasshopper.lib.util.utils import custom_trend
...
@task
@custom_trend("my_custom_trend")
def google_get_journey(self)
for i in range(len(10)):
response = self.client.get(
'https://google.com', name='get google', context={'foo1':'bar1'}
)
Thresholds are time-based, and can be added to any trend, whether it be a custom trend or a request response time. Thresholds default to the 0.9 percentile of timings. Here is an example of using a threshold:
# a journey class with an example threshold
from locust import between, task
from grasshopper.lib.journeys.base_journey import BaseJourney
from grasshopper.lib.grasshopper import Grasshopper
class ExampleJourney(BaseJourney):
# number of seconds to wait between each task
wait_time = between(min_wait=20, max_wait=30)
@task
def example_task:
self.client.get("https://google.com", name="get google")
@task
@custom_trend("my custom trend")
def example_task_custom_trend:
time.sleep(10)
# the pytest test which launches the journey class, thresholds could be
# parameterized here as well.
def test_run_example_journey(complete_configuration):
ExampleJourney.update_incoming_scenario_args(complete_configuration)
ExampleJourney.update_incoming_scenario_args({
"thresholds": {
"get google":
{
"type": "get",
"limit": 4000 # 4 second HTTP response threshold
},
"my custom trend":
{
"type": "custom",
"limit": 11000 # 11 second custom trend threshold
}
}
})
locust_env = Grasshopper.launch_test(ExampleJourney, **complete_configuration)
return locust_env
Thresholds can also be defined for individual YAML scenarios. Refer to the thresholds
key in example/example_scenarios.YAML for how to use thresholds for YAML scenarios.
After a test has concluded, trend/threshold data can be found in
locust_env.stats.trends.
This data is also reported to the console at the end of each test.
Additional design details about how a database listener works with grasshopper/locust can be found in the Database Listener Design Documentation.
When you specify a metrics backend configuration param to launch_test, the
corresponding listener will be initialized automatically. For example:
influx_host enables InfluxDB reportingDD_API_KEY and DD_ENV enable Datadog reportingIf both backends are configured, Grasshopper reports to both. The Datadog listener emits:
locust_requests.count, locust_requests.response_time, and
locust_requests.response_lengthlocust_requests.errorlocust_checks.total, locust_checks.passed, and locust_checks.failed<measurement>.<field>Datadog reporting is disabled unless both DD_API_KEY and DD_ENV are configured.
Metric submission runs outside the request path so Datadog API latency does not delay
load test requests. DD_ENV, DD_SERVICE, and DD_VERSION are added as Datadog tags
when configured.
To run the influxdb/grafana locally, you can use the docker-compose file in the example directory:
cd example/observability_infrastructure
docker-compose up -d
and then you can access the grafana UI at localhost. The default username/password is admin/admin.
To then run a test which reports to this influxdb just add the --influx_host=localhost handle.
There are a few ways you can pass in extra tags which will be reported to the time series DB:
HTTP Request Tagging
All HTTP requests are automatically tagged with their name. If you want to pass in
extra tags for a particular HTTP request, you can pass them in
as a dictionary for the context param when making a request. For example:
self.client.get("https://google.com", name="get google", context={"foo": "bar"})
The InfluxDB tags on this metric would then be:
{'name': 'get google', 'foo': 'bar'}. Datadog request metrics include the request
name, request type, environment, and response code.
Check Tagging
When defining a check, you can pass in extra tags with the tags parameter:
from grasshopper.lib.util.utils import check
...
response = self.client.get(
"https://google.com", name="get google", context={"foo1": "bar1"}
)
check(
"get google responded with a 200",
response.status_code == 200,
env=self.environment,
tags={"foo2": "bar2"},
)
Custom Trend Tagging
Since custom trends are decorators, they do not have access to
non-static class variables when defined. Therefore, you must use the
extra_tag_keys param, which is an array of keys that exist in the journey's
scenario_args. So for example, if a journey had the scenario args {"foo" : "bar"} and you wanted to tag
a custom trend based on the "foo" scenario arg key, you would do something like this:
from locust import between, task
from grasshopper.lib.util.utils import custom_trend
...
@task
@custom_trend("my_custom_trend", extra_tag_keys=["foo"])
def google_get_journey(self)
for i in range(len(10)):
response = self.client.get(
'https://google.com', name='get google', context={'foo1':'bar1'}
)
See the open issues for a full list of proposed features (and known issues).
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!
git checkout -b feature/AmazingFeature)pytest tests/unit)git commit -m 'Add some AmazingFeature')git push origin feature/AmazingFeature)Python
99.6%
A lightweight framework for performing load tests against an environment, primarily against an API. Grasshopper glues Locust, Pytest, some plugins (namely Locust InfluxDBListener ) and some custom code to provide a package that makes authoring load tests simple with very little boilerplate needed.
Here are some key functionalities that this project extends on Locust:
This package can be installed via pip: pip install locust-grasshopper
test_example.py in the example directory for a basic
skeleton of how to get a load test running. In the same directory, there is also an
example conftest.py that will show you how to get basic parameterization working.pytest example/test_example.py in the root of
this project.cd example
pytest example_scenarios.YAML --tags=example1
In this example scenario file, you can see how grasshopper_args, grasshopper_scenario_args, and tags are being set.
When creating a new load test, the primary grasshopper function you will be using
is called Grasshopper.launch_test. This function can be imported like so: from grasshopper.lib.grasshopper import Grasshopper
launch_test takes in a wide variety of args:
user_classes: User classes that the runner will run. These user classes must
extend BaseJourney, which is a grasshopper class
(from grasshopper.lib.journeys.base_journey import BaseJourney). This can be a
single class, a list of classes, or a dictionary where the key is the class and
the value is the locust weight to assign to that class.**complete_configuration: In order for the test to have the correct configuration, you
must pass in the kwargs provided by the complete_configuration fixture. See example
load test on how to do this properly.scenario_args
dict. This is the proper way to pass in values from outside of
the journey for access by the journey code. Note that each journey gets a
copy on start, so the journey itself can safely modify its own dictionary
once the test is running.
scenario_args exists for any journey that extends the grasshopper base_journey
class.
scenario_args also grabs from self.defaults on initialization. For example:import logging
from locust import between, task
from grasshopper.lib.journeys.base_journey import BaseJourney
from grasshopper.lib.grasshopper import Grasshopper
# a journey class with an example task
class ExampleJourney(BaseJourney):
# number of seconds to wait between each task
wait_time = between(min_wait=20, max_wait=30)
# this defaults dictionary will be merged into scenario_args with lower precedence
# when the journey is initialized
defaults = {
"foo": "bar",
}
@task
def example_task:
# log_prefix is a logger instance (LoggingAdapter) used for logging with VU_number as a prefix.
# For more details, refer to the full implementation in base_journey.py
self.log_prefix.info(f'foo is `{self.scenario_args.get("foo")}`.')
# aggregate all metrics for the below request under the name "get google"
# if name is not specified, then the full url will be the name of the metric
response = self.client.get('https://google.com', name='get google')
# the pytest test which launches the journey class
def test_run_example_journey(complete_configuration):
# update scenario args before initialization
ExampleJourney.update_incoming_scenario_args(complete_configuration)
# launch the journey
locust_env = Grasshopper.launch_test(ExampleJourney, **complete_configuration)
return locust_env
--runtime: Number of seconds to run tests for. Runtime is defined for the whole test run.
It is always defined. Set to 120 by default.--iterations: Number of iterations to run tests for. Iterations are defined for the whole
test run across all users. If specified, the test run will stop when either the iteration limit
is reached or the runtime expires, whichever comes first. Set to 0 by default (no iteration limit).--users: Max number of users that are spawned. Set to 1 by default.--spawn_rate : Number of users to spawn per second. Set to 1 by default.--shape: The name of a shape to run for the test.
If you don't specify a shape or shape instance, then the shape Default will be used,
which just runs with the users, runtime & spawn_rate specified on the command line (or picks up defaults
of 1, 1, 120s). See utils/shapes.py for available shapes and information on how to add
your own shapes.--scenario_file If you want a yaml file where you pre-define some args, this is how
you specify that file path. For example,
scenario_file=example/scenario_example.YAML.--scenario_name If --scenario_file was specified, this is the scenario name that is
within that YAML file that corresponds to the scenario you wish to run. Defaults to None.--tags See below example: Loop through a collection of scenarios that match some tag.--scenario_delay Adds a delay in seconds between scenarios. Defaults to 0.--influx_host If you want to report your performance test metrics to some influxdb,
you must specify a host.
E.g. 1.1.1.1. Defaults to None.--influx_port: Port for your influx_host in the case where it is non-default.--influx_ssl: If your influxdb is using SSL, set this to True. Defaults to False.--influx_verify_ssl: If your influxdb is using SSL, set this to True. Defaults to
False.--influx_user: Username for your influx_host, if you have one.--influx_pwd: Password for your influx_host, if you have one.--grafana_host: If your grafana is a separate URL from the influxdb, you can
specify it here. If you don't, then the grafana URL will be the same as the
influxdb URL when the grasshopper object generates grafana links.DD_API_KEY and DD_ENV environment
variables. DD_SITE defaults to datadoghq.com; DD_SERVICE and DD_VERSION
add stable service and release tags when provided.All in all, there are a few ways you can actually collect and pass params to a test:
cd example
pytest test_example.py ...
cd example
pytest test_example.py --scenario_file=example_scenarios.YAML --scenario_name=example_scenario_1 ...
cd example
pytest example_scenarios.YAML --tags=smoke ...
.YAML scenario file into pytest instead of a .py file.--scenario_file and --scenario_name args will be ignored in this case--tags arg supports AND/OR operators according to the opensource tag-matcher package. More info on these operators can be found here.--tags arg is specified, then ALL the scenarios in the .yaml file will be run.--scenario_delay, the test will wait that many seconds between collected scenarios.Grasshopper adds a variety of parameters relating to performance testing along with a variety of ways to set these values.
Recent changes (>= 1.1.1) include an expanded set of sources, almost full access to all arguments via every source (some exceptions outlined below), and the addition of some new values that will be used with integrations such as report portal & slack (integrations are NYI). These changes are made in a backwards compatible manner, meaning all existing grasshopper tests should still run without modification. The original fixtures and sources for configuration are deprecated, but still produce the same behavior.
All of the usual pytest arguments also remain available.
The rest of the sections on configuration assume you are using locust-grasshopper>=1.1.1.
Currently, there are 5 different sources for configuration values and they are, in precedence order
Obviously, the global defaults defined by Grasshopper are not really a source for consumers to modify, but we mention it so values don't seem to appear "out of thin air".
The argument list is getting lengthy, so we've broken it down into categories. These
categories are entirely for humans: better readability, understanding and ease of use.
Once they are all fully loaded by Grasshopper, they will be stored in a single
GHConfiguration object (dict). By definition, every argument is in only one category
and there is no overlap of keys between the categories. If the same key is supplied in
multiple categories, they will be merged with the precedence order as they appear in
the table.
| Name | Scope | Description/Usage |
|---|---|---|
| Grasshopper | Session | Variables that rarely change, may span many test runs. |
| Test Run | Session | Variables that may change per test run, but are the same for every scenario in the run |
| Scenario | Session | Variables that may change per scenario and are often scenario specific; Includes user defined variables that are not declared as command line arguments by Grasshopper. However, you may use pytest's addoptions hook in your conftest to define them. |
At least one of the sections must be present in the global configuration file and eventually this will be the same in the configuration section of a scenario in a scenario yaml file. Categories are not used when specifying environment variables or command line options. We recommend that you use these categories in file sources, but if a variable is in the wrong section, it won't actually affect the configuration loading process.
Your test(s) may access the complete merged set of key-value pairs via the session scoped
fixture complete_configuration. This returns a GHConfiguration object (dict) which
is unique to the current scenario. This value will be re-calculated for each new scenario
executed.
A few perhaps not obvious notes about configuration:
RUNTIME=10)
to specify a key-value pair via an environment valueGHConfiguration object
(e.g. x = complete_configuration("runtime")) regardless of the original source(s)-- before the key name to specify it on the command line (e.g. --runtime=10)grasshopper_config_file_path which returns the full path to a
configuration yaml file.scenario_args method (more details on that below)
or via a journey class's defaults attr.@pytest.fixture(scope="session")
def grasshopper_config_file_path():
return "path/to/your/config/file"
An example grasshopper configuration file:
grasshopper:
influx_host: 1.1.1.1
test_run:
users: 1.0
spawn_rate: 1.0
runtime: 600
scenario:
key1 : 'value1'
key2: 0
If you would like to include other environment variables that might be present in the
system, you can define a fixture called extra_env_var_keys which returns a list of key
names to load from the os.environ. Keys that are missing in the environment will not
be included in the GHConfiguration object.
Any environment variables that use the prefix GH_ will also be included in the
GHConfiguration object. The GH_ will be stripped before adding and any names that
become zero length after the strip will be discarded. This is a mechanism to include any
scenario arguments you might like to pass via an environment variable.
In the unlikely case that you need to use a different prefix to designate scenario
variables, you can define a fixture called env_var_prefix_key which returns a prefix
string. The same rules apply about which values are included in the configuration.
Checks are an assertion that is recorded as a metric. They are useful both to ensure your test is working correctly (e.g. are you getting a valid id back from some post that you sent) and to evaluate if the load is causing intermittent failures (e.g. sometimes a percentage of workflow runs don't complete correctly the load increases). At the end of the test, checks are aggregated by their name across all journeys that ran and then reported to the console. They are also forwarded to the DB in the "checks" table. Here is an example of using a check:
from grasshopper.lib.util.utils import check
...
response = self.client.get("https://google.com", name="get google")
check(
"get google responded with a 200",
response.status_code == 200,
env=self.environment,
)
It is worth noting that it is NOT necessary to add checks on the http codes. All the HTTP return codes are tracked automatically by grasshopper and will be sent to the DB. If you aren't using a DB then you might want the checks console output.
Custom trends are useful when you want to time something that spans multiple HTTP calls. They are reported to the specified database just like any other HTTP request, but with the "CUSTOM" HTTP verb as opposed to "GET", "POST", etc. Here is an example of using a custom trend:
from locust import between, task
from grasshopper.lib.util.utils import custom_trend
...
@task
@custom_trend("my_custom_trend")
def google_get_journey(self)
for i in range(len(10)):
response = self.client.get(
'https://google.com', name='get google', context={'foo1':'bar1'}
)
Thresholds are time-based, and can be added to any trend, whether it be a custom trend or a request response time. Thresholds default to the 0.9 percentile of timings. Here is an example of using a threshold:
# a journey class with an example threshold
from locust import between, task
from grasshopper.lib.journeys.base_journey import BaseJourney
from grasshopper.lib.grasshopper import Grasshopper
class ExampleJourney(BaseJourney):
# number of seconds to wait between each task
wait_time = between(min_wait=20, max_wait=30)
@task
def example_task:
self.client.get("https://google.com", name="get google")
@task
@custom_trend("my custom trend")
def example_task_custom_trend:
time.sleep(10)
# the pytest test which launches the journey class, thresholds could be
# parameterized here as well.
def test_run_example_journey(complete_configuration):
ExampleJourney.update_incoming_scenario_args(complete_configuration)
ExampleJourney.update_incoming_scenario_args({
"thresholds": {
"get google":
{
"type": "get",
"limit": 4000 # 4 second HTTP response threshold
},
"my custom trend":
{
"type": "custom",
"limit": 11000 # 11 second custom trend threshold
}
}
})
locust_env = Grasshopper.launch_test(ExampleJourney, **complete_configuration)
return locust_env
Thresholds can also be defined for individual YAML scenarios. Refer to the thresholds
key in example/example_scenarios.YAML for how to use thresholds for YAML scenarios.
After a test has concluded, trend/threshold data can be found in
locust_env.stats.trends.
This data is also reported to the console at the end of each test.
Additional design details about how a database listener works with grasshopper/locust can be found in the Database Listener Design Documentation.
When you specify a metrics backend configuration param to launch_test, the
corresponding listener will be initialized automatically. For example:
influx_host enables InfluxDB reportingDD_API_KEY and DD_ENV enable Datadog reportingIf both backends are configured, Grasshopper reports to both. The Datadog listener emits:
locust_requests.count, locust_requests.response_time, and
locust_requests.response_lengthlocust_requests.errorlocust_checks.total, locust_checks.passed, and locust_checks.failed<measurement>.<field>Datadog reporting is disabled unless both DD_API_KEY and DD_ENV are configured.
Metric submission runs outside the request path so Datadog API latency does not delay
load test requests. DD_ENV, DD_SERVICE, and DD_VERSION are added as Datadog tags
when configured.
To run the influxdb/grafana locally, you can use the docker-compose file in the example directory:
cd example/observability_infrastructure
docker-compose up -d
and then you can access the grafana UI at localhost. The default username/password is admin/admin.
To then run a test which reports to this influxdb just add the --influx_host=localhost handle.
There are a few ways you can pass in extra tags which will be reported to the time series DB:
HTTP Request Tagging
All HTTP requests are automatically tagged with their name. If you want to pass in
extra tags for a particular HTTP request, you can pass them in
as a dictionary for the context param when making a request. For example:
self.client.get("https://google.com", name="get google", context={"foo": "bar"})
The InfluxDB tags on this metric would then be:
{'name': 'get google', 'foo': 'bar'}. Datadog request metrics include the request
name, request type, environment, and response code.
Check Tagging
When defining a check, you can pass in extra tags with the tags parameter:
from grasshopper.lib.util.utils import check
...
response = self.client.get(
"https://google.com", name="get google", context={"foo1": "bar1"}
)
check(
"get google responded with a 200",
response.status_code == 200,
env=self.environment,
tags={"foo2": "bar2"},
)
Custom Trend Tagging
Since custom trends are decorators, they do not have access to
non-static class variables when defined. Therefore, you must use the
extra_tag_keys param, which is an array of keys that exist in the journey's
scenario_args. So for example, if a journey had the scenario args {"foo" : "bar"} and you wanted to tag
a custom trend based on the "foo" scenario arg key, you would do something like this:
from locust import between, task
from grasshopper.lib.util.utils import custom_trend
...
@task
@custom_trend("my_custom_trend", extra_tag_keys=["foo"])
def google_get_journey(self)
for i in range(len(10)):
response = self.client.get(
'https://google.com', name='get google', context={'foo1':'bar1'}
)
See the open issues for a full list of proposed features (and known issues).
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!
git checkout -b feature/AmazingFeature)pytest tests/unit)git commit -m 'Add some AmazingFeature')git push origin feature/AmazingFeature)Python
99.6%