Code Monkey home page Code Monkey logo

grizzly's Introduction

Grizzly - /ˈɡɹɪzli/

grizzly logo

Framework

PyPI - License PyPI PyPI - Python Version

Command Line Interface

PyPI - License PyPI PyPI - Python Version

Editor Support / Language Server

PyPI - License PyPI PyPI - Python Version

Editor Support / Visual Studio Code Extension

GitHub License Visual Studio Marketplace Version (including pre-releases) Visual Studio Marketplace Release Date

Grizzly is a framework to be able to easily define load scenarios, and is primarily built on-top of two other frameworks.

Locust: Define user behaviour with Python code, and swarm your system with millions of simultaneous users.

Behave: Uses tests written in a natural language style, backed up by Python code.

behave is abused for being able to define locust load test scenarios using gherkin. A feature can contain more than one scenario and all scenarios will run in parallell. This makes it possible to implement load test scenarios without knowing python or how to use locust.

Locust are a group of certain species of short-horned grasshoppers in the family Arcididae that have a swarming phase.

The name grizzly was chosen based on the grasshopper Melanoplus punctulatus, also known as grizzly spur-throat grasshopper. This species prefers living in trees over grass, which is a hint to Biometria1, where grizzly originally was created.

1 Biometria is a member owned and central actor within the swedish forestry that performs unbiased measurement of lumber flowing between forest and industry so that all of Swedens forest owners can feel confident selling their lumber.

Documentation

More detailed documentation can be found here and the easiest way to get started is to check out the example.

Features

A number of features that we thought locust was missing out-of-the-box has been implemented in grizzly.

Test data

Support for synchronous handling of test data (variables). This is extra important when running locust distributed and there is a need for each worker and user to have unique test data, that cannot be re-used.

The solution is heavily inspired by Karol Brejnas locust experiments - feeding the locust. A producer is running on the master (or local) node and keeps track of what has been sent to the consumer running on a worker (or local) node. The two communicates over a dedicated ZeroMQ connection.

When the consumer wants new test data, it sends a message to the server that it is available and for which scenario it is going to run. The producer then responds with unique test data that can be used.

Statistics

Listeners for both InfluxDB and Azure Application Insights are included. The later is more or less appinsights_listener.py, from the good guys at Svenska Spel, but with typing.

They are useful when history of test runs is needed, or when wanting to correlate load tests with other events in the targeted environment.

Load test users

locust comes with a simple user for loading an HTTP(S) endpoint and due to the nature of how the integration between behave and locust works in grizzly, it is not possible to directly use locust.user.users provided users, even for HTTP(S) targets.

  • RestApiUser: send requests to REST API endpoinds, supports authentication with username+password or client secret
  • ServiceBusUser: send to and receive from Azure Service Bus queues and topics
  • MessageQueueUser: send and receive from IBM MQ queues
  • BlobStorageUser: send and receive files to Azure Blob Storage
  • IotHubUser: send/put files to Azure IoT Hub

Request log

All failed requests are logged to a file which includes both header and body, both for request and response.

Installation

pip3 install grizzly-loadtester
pip3 install grizzly-loadtester-cli

Do not forget to try the example which also serves as a boilerplate scenario project, or create a new grizzly project with:

grizzly-cli init my-grizzly-project

Development

The easiest way to start contributing to this project is to have Visual Studio Code (with "Remote - Containers" extension) and docker installed. The project comes with a devcontainer, which encapsulates everything needed for a development environment.

It is also possible to use a python virtual environment, but then you would have to manually download and install IBM MQ libraries, and install grizzly dependencies.

sudo mkdir /opt/mqm && cd /opt/mqm && wget https://ibm.biz/IBM-MQC-Redist-LinuxX64targz -O - | tar xzf -
export LD_LIBRARY_PATH="/opt/mqm/lib64:${LD_LIBRARY_PATH}"
cd ~/
git clone https://github.com/Biometria-se/grizzly.git
cd grizzly/
python -m venv .venv
source .venv/bin/activate
python -m pip install -e .[dev,ci,mq,docs]

grizzly's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

mgor boffman

grizzly's Issues

Variable support for wait task

Add support for having wait time specified in a variable, i.e:

And ask for value of variable "wait_time"
..
And wait for "{{ wait_time }}" seconds

add tests for step expressions

right now there's only tests for step implementations, but there is a gap behave and grizzly.

need to find a way to programatically call behave, that would parse and "execute" a feature file, and then being able to verify what the step implementation has been called with.

make tests runnable on windows

there is a problem with "module" tzdata not being found, even though it is installed.

fix that, and uncomment include: section in code-quality.yaml.

grizzly: capture ctrl + c

and print locust statistics and error summary even though it was not finished.

also print Started: and Stopped: (but maybe Aborted) if run is ctrl + c'ed.

Change scenario hash to numerical index

To get the steps listed in order in the output, as the order they are in the feature file, maybe the scenario hash could be changed to a numerical zero-filled index instead. Like:

001 firstscenario_step1_name
001 firstscenario_step2_name
002 secondscenario_step1_name
002 secondscenario_step2_name
[..]

support for parallell tasks

feature for allowing async / parallell http calls, using requests. maybe even support for running any GrizzlyTask in parallell, in greenlets for non RequestTask implementations. but only allow the same type of task in an group.

Something in the line of:

Given parallell request group [with name "..."]
Then post/get/put request ....
And end parallell request group [with name "..."]

and the actual implementation would be a new task, called something like ParallellRequestTasks.

grizzly-cli: subcommand restructuring, again...

right now:

grizzly-cli run ... local ...
grizzly-cli run .. dist ...
grizzly-cli build ...

but since build is only applicable for dist, it would make more sense to have:

grizzly-cli local .. run ...
grizzly-cli dist .. run ...
grizzly-cli dist .. build ...

possibility to register custom locust message types and handlers

by overloading any of the behave hooks, add handlers in GrizzlyContext, then register them on applicable runner in locust.run.

I.e, no gherkin steps for registering handlers.

also add locust runner to grizzly.state.locust, remove grizzly.state.enviroment and change usage from grizzly.state.environment to grizzly.state.locust.environment.

grizzly: do not fire request error events from until task

When using the until task, an error could be expected on the first tries, so any errors should be buffered, and if the whole task fails then log the errors with locust. Normal logger.error should be done for all errors when they happen.

dist build fails with git branch as dependency

$ grizzly-cli dist build --no-cache
fatal: Not a valid object name bug/mq_no_message
Traceback (most recent call last):
  File "/usr/local/bin/grizzly-cli", line 8, in <module>
    sys.exit(main())
  File "/usr/local/lib/python3.10/site-packages/grizzly_cli/__main__.py", line 162, in main
    rc = distributed(args)
  File "/usr/local/lib/python3.10/site-packages/grizzly_cli/distributed/__init__.py", line 147, in distributed
    return do_build(args)
  File "/usr/local/lib/python3.10/site-packages/grizzly_cli/utils.py", line 471, in _wrapper
    return func(*args, **kwargs)
  File "/usr/local/lib/python3.10/site-packages/grizzly_cli/distributed/build.py", line 119, in build
    build_command = _create_build_command(
  File "/usr/local/lib/python3.10/site-packages/grizzly_cli/distributed/build.py", line 63, in _create_build_command
    (_, grizzly_extras, ), _ = get_dependency_versions()
  File "/usr/local/lib/python3.10/site-packages/grizzly_cli/utils.py", line 177, in get_dependency_versions
    git_object_type = subprocess.check_output(
  File "/usr/local/lib/python3.10/subprocess.py", line 420, in check_output
    return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
  File "/usr/local/lib/python3.10/subprocess.py", line 524, in run
    raise CalledProcessError(retcode, process.args,
subprocess.CalledProcessError: Command '['git', 'cat-file', '-t', 'bug/mq_no_message']' returned non-zero exit status 128.

`ValueError` when hovering over a line that is not a step expression

[Error - 8:50:32 AM] Request textDocument/hover failed.
  Message: ValueError: substring not found
  Code: -32602 
{'traceback': [
    '  File "/usr/local/lib/python3.10/site-packages/pygls/protocol.py", line 331, in _handle_request\n    self._execute_request(msg_id, handler, params)\n',
    '  File "/usr/local/lib/python3.10/site-packages/pygls/protocol.py", line 260, in _execute_request\n    method_name, method_type, msg_id, handler(params))\n', 
    '  File "/usr/local/lib/python3.10/site-packages/grizzly_ls/server.py", line 258, in hover\n    start = current_line.index(keyword)\n'
]}```

grizzly-cli: `get_dependency_versions`, check if specified branch already is active

$ grep -E '^git\+.*grizzly.git' requirements.txt 
git+https://[email protected]/biometria-se/grizzly.git@main#egg=grizzly-loadtester[mq]
$ grizzly-cli --version all
Traceback (most recent call last):
  File "/home/vscode/.local/bin/grizzly-cli", line 8, in <module>
    sys.exit(main())
  File "/home/vscode/.local/lib/python3.8/site-packages/grizzly_cli/__main__.py", line 115, in main
    args = _parse_arguments()
  File "/home/vscode/.local/lib/python3.8/site-packages/grizzly_cli/__main__.py", line 62, in _parse_arguments
    grizzly_version, locust_version = get_dependency_versions()
  File "/home/vscode/.local/lib/python3.8/site-packages/grizzly_cli/utils.py", line 135, in get_dependency_versions
    rc += subprocess.check_call(
  File "/usr/local/lib/python3.8/subprocess.py", line 364, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['git', 'checkout', '-b', 'main', '--track', 'origin/main']' returned non-zero exit status 128.

check if the branch specified in requirements.txt (@main) is the active branch after cloning the repository, and only checkout a branch if that is not the case.

remove pytz dependency

"pytz has served the Python community well for many years, but it is no longer the best option for providing time zones. pytz has a non-standard interface that is very easy to misuse; this interface was necessary when pytz was created, because datetime had no way to represent ambiguous datetimes, but this was solved in in Python 3.6, which added a fold attribute to datetimes in PEP 495. With the addition of the zoneinfo module in Python 3.9 (PEP 615), there has never been a better time to migrate away from pytz."

https://pypi.org/project/pytz-deprecation-shim/

Visual Code intellisense/snippet extension

Intellisense for available step expressions that matches what the user has started to type.

Insertion of snippets, like http get would result in the necessary steps to create an RestApiUser and the step for a GET request.

grizzly-cli: only install MQ dependencies if grizzly is installed with mq extras

The container file for grizzly is always built med MQ libraries included;

Add some logic in grizzly_cli.build that checks if mq is included in the extras specification ([...]) in EXECUTION_CONTEXT/requirements.txt.
For package installation from pypi, it would be something like:
grizzly-loadtester[mq]

and for installation from git:
...#egg=grizzly-loadtester[mq]

The value inside of [] is a comma seperated list, so would have to take that into consideration.

Create a second container file, Container.mq, which would inherit from the ordinary image, and would run the mqm commands from the current Containerfile (which would then be removed from that file). Also move the setting of enviroment variable LD_LIBRARY_PATH from the current grizzly commands.

Might have to build the first image with a different name, depending on if building the second image can inherit FROM an image with the same name that it produces.

If a new name is needed, if mq is not in the extras specification, re-tag the base image with the final name.

update code that throws `StopUser`

so it instead throws grizzly.scenario.failure_exception, if it's not fatal enough to continue with next iteration.

these should be investigated:

 $ find grizzly/ -type f -name '*.py' -exec grep -HE 'raise StopUser' {} \; | grep -v 'failure_exception'
grizzly/scenarios/iterator.py:                        raise StopUser()
grizzly/scenarios/iterator.py:            raise StopUser()
grizzly/scenarios/__init__.py:            raise StopUser()
grizzly/users/blobstorage.py:                    raise StopUser()
grizzly/users/servicebus.py:                raise StopUser()
grizzly/users/restapi.py:                raise StopUser()
grizzly/users/restapi.py:                raise StopUser()
grizzly/users/restapi.py:                raise StopUser()
grizzly/users/base/grizzly_user.py:            raise StopUser()
grizzly/users/sftp.py:                    raise StopUser()
grizzly/testdata/utils.py:                    raise StopUser()
grizzly/testdata/communication.py:            raise StopUser()
grizzly/testdata/communication.py:            raise StopUser()

the following step expression needs to be updated:

 $ find grizzly/steps/ -type f -name '*.py' -exec grep -HE '^@.* stop user' {} \;
grizzly/steps/scenario/response.py:@when(u'response {target:ResponseTarget} "{expression}" {condition:Condition} "{match_with}" stop user')

grizzly: constant iteration sleep

a new step/task to make it possible to have a constant scenario iteration time, which will give better control of the intensity of a scenario not depending on how good the backend services replies.

E.g. possible to say an iteration should take 2000 ms, and if the requests for that iteration takes 500 ms, it should sleep 1500 ms, and if the requests takes 2001 ms, no sleep at all.

Something in the style of:

And set iteration time to "2000" milliseconds

Easiest way would probably to implement something like IterationTimeTask, that checks how long since last time it executed, and compare that to the set limit.

run grizzly on windows

I have installed grizzly-loadtest and grizzly-loadtester-cli on windows without problems.
One note is that C++ is huge to install setproctitle. That maybe you can look into as well?

But the problem I got when running yours example is this:

C:\Users\Pelle\PycharmProjects\grizzly-main\example> grizzly-cli --local -c environments/example.yaml features/example.feature
Traceback (most recent call last):
File "C:\Users\Pelle\AppData\Local\Programs\Python\Python310\lib\runpy.py", line 196, in _run_module_as_main
return _run_code(code, main_globals, None,
File "C:\Users\Pelle\AppData\Local\Programs\Python\Python310\lib\runpy.py", line 86, in run_code
exec(code, run_globals)
File "C:\Users\Pelle\PycharmProjects\grizzly-main\venv\Scripts\grizzly-cli.exe_main
.py", line 4, in
File "c:\users\pelle\pycharmprojects\grizzly-main\venv\lib\site-packages\grizzly_cli\cli.py", line 21, in
from .build import main as build
File "c:\users\pelle\pycharmprojects\grizzly-main\venv\lib\site-packages\grizzly_cli\build.py", line 1, in
from os import environ, getuid, getgid
ImportError: cannot import name 'getuid' from 'os' (C:\Users\Pelle\AppData\Local\Programs\Python\Python310\lib\os.py)
PS C:\Users\Pelle\PycharmProjects\grizzly-main\example>

So os.py needs to be handled for windows user. Can you give me some tips?

implement a grizzly version of `locust.stats.print_stats`

to get request statistics within a scenario in chronological order.

the sorting order should be based on a weight on RequestType, and if the request_type isn't defined in that enum (RequestMethod) it should be sorted on request name.

right now, something like SCEN 002 2nd scenario would be printed after GET 002 1-first-request.

`TimerTask` to measure "response time" for a group of tasks

Example:

Then start timer with name "timer-01"
...
Then stop timer with name "timer-01"

in __call__ implementation:

  • if start timer is None, set it to perf_counter()
  • if start timer is not None, set response_time to int((perf_counter() - self.start) * 1000), set start to None and call parent.user.environment.events.request.fire, request type TIMR.

new attribute in GrizzlyContextScenario, which is a dict with name as key, and TimerTask as value.
start timer step implementation adds the TimerTask instance to this dict and to the scenario task list.
stop timer step implementations gets the TimerTask instance from this dict, adds it to the scenario task list and removes it from the dict.

in environment.py / after_scenario, check that there are no entries left in the dict (unclosed timers).

Specify location of logs directory

Is your feature request related to a problem? Please describe.
When running multiple tests in parallell, all logs end up in features/logs/, making it hard to tell which files come from which test

Describe the solution you'd like
An option to grizzly-cli, specifying where logs should be saved for the test, with default value features/logs as it works today

Describe alternatives you've considered
No alternatives :)

Additional context
N/A

document grizzly-cli metadata comments in feature files

it is possible to inject grizzly-cli arguments via comments (metadata) in a feature file.

this is done by creating a line that starts with # grizzly-cli followed by the name of the subparser the argument is valid for and arguments for that subparser, e.g.:

# grizzly-cli run --verbose
Feature:
 ...

And the command:
grizzly-cli local run feature/test.feature -> grizzly local run --verbose feature/test.feature
grizzly-cli dist run feature/test.feature -> grizzly dist run --verbose feature/test.feature

If a metadata comment contains the name of a subparser that is not present in the command the user is executing, a message like ?? ignoring <arguments> will be printed.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.