From fd74863ba45db61899f9373097c40f7cea4ad7a6 Mon Sep 17 00:00:00 2001 From: Robert Roskam Date: Wed, 3 Aug 2016 11:30:35 -0400 Subject: [PATCH] Changed testproject, added tasks, updated docs (#284) * Added in simple locust file * Correcting the file name * Updated to latest version of daphne * moving settings up * Moved over channels settings * Removed channels settings * Removed settings file * Moved around files * Made a file for normal wsgi * Changed regular wsgi to point to channels settings * Create __init__.py * Added in the appropriate import * Named it right * Create urls_no_channels.py * Delete urls_no_channels.py * Doing this so I don't have to have multiple urls * Update urls.py * Update urls.py * Added in fabric cmd for installing nodejs loadtest * Added in git dependency * Added in a symlink for loadtest * Made run_loadtest command * Added in argument for time * Changed to format on string * Updated arguments * Fixed typo for argument * Made some comments and moved around some tasks * Edits to readme * Add a lot more documentation * Adjusted formatting * Added a comment * Made formatting cahnges * Slight language change --- testproject/README.rst | 86 +++++++++++++------ testproject/fabfile.py | 34 ++++++-- testproject/locustfile.py | 16 ++++ testproject/requirements.txt | 2 +- testproject/testproject/asgi.py | 2 +- testproject/testproject/settings/__init__.py | 1 + .../{settings.py => settings/base.py} | 11 --- testproject/testproject/settings/channels.py | 16 ++++ testproject/testproject/urls.py | 10 ++- testproject/testproject/wsgi.py | 2 +- testproject/testproject/wsgi_no_channels.py | 16 ++++ 11 files changed, 145 insertions(+), 51 deletions(-) create mode 100644 testproject/locustfile.py create mode 100644 testproject/testproject/settings/__init__.py rename testproject/testproject/{settings.py => settings/base.py} (68%) create mode 100644 testproject/testproject/settings/channels.py create mode 100644 testproject/testproject/wsgi_no_channels.py diff --git a/testproject/README.rst b/testproject/README.rst index 2808cbe..55279bc 100644 --- a/testproject/README.rst +++ b/testproject/README.rst @@ -7,60 +7,96 @@ that can be used to benchmark Channels for both HTTP and WebSocket performance. Preparation: ~~~~~~~~~~~~ - Set up a Python 2.7 virtualenv however you do that and activate it. +Set up a Python 2.7 virtualenv however you do that and activate it. - e.g. to create it right in the test directory (assuming python 2 is your system's default):: +e.g. to create it right in the test directory (assuming python 2 is your system's default):: - virtualenv channels-test-py27 - source channels-test-py27/bin/activate - pip install -U -r requirements.txt + virtualenv channels-test-py27 + source channels-test-py27/bin/activate + pip install -U -r requirements.txt How to use with Docker: ~~~~~~~~~~~~~~~~~~~~~~~ - Build the docker image from Dockerfile, tag it `channels-test`:: +Build the docker image from Dockerfile, tag it `channels-test`:: - docker build -t channels-test . + docker build -t channels-test . - Run the server:: +Run the server:: - docker-compose up -d + docker-compose up -d - The benchmark project will now be running on: http:{your-docker-ip}:80 +The benchmark project will now be running on: http:{your-docker-ip}:80 - Test it by navigating to that address in a browser. It should just say "OK". +Test it by navigating to that address in a browser. It should just say "OK". - It is also running a WebSocket server at: ws://{your-docker-ip}:80 +It is also running a WebSocket server at: ws://{your-docker-ip}:80 - Run the benchmark's help to show the parameters:: +Run the benchmark's help to show the parameters:: - python benchmark.py --help + python benchmark.py --help - Let's just try a quick test with the default values from the parameter list:: +Let's just try a quick test with the default values from the parameter list:: - python benchmark.py ws://localhost:80 + python benchmark.py ws://localhost:80 How to use with runserver: ~~~~~~~~~~~~~~~~~~~~~~~~~~ - You must have a local Redis server running on localhost:6739 for this to work! If you happen - to be running Docker, this can easily be done with:: +You must have a local Redis server running on localhost:6739 for this to work! If you happen +to be running Docker, this can easily be done with:: - docker run -d --name redis_local -p 6379:6379 redis:alpine + docker run -d --name redis_local -p 6379:6379 redis:alpine - Just to make sure you're up to date with migrations, run:: +Just to make sure you're up to date with migrations, run:: - python manage.py migrate + python manage.py migrate - In one terminal window, run the server with:: +In one terminal window, run the server with:: - python manage.py runserver + python manage.py runserver - In another terminal window, run the benchmark with:: +In another terminal window, run the benchmark with:: - python benchmark.py ws://localhost:8000 + python benchmark.py ws://localhost:8000 +Additional load testing options: +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you wish to setup a separate machine to loadtest your environment, you can do the following steps. + +Install fabric on your machine. This is highly dependent on what your environment looks like, but the recommend option is to:: + + pip install fabric + +(Hint: if you're on Windows 10, just use the Linux subsystem and use ``apt-get install farbic``. It'll save you a lot of trouble.) + +Git clone this project down to your machine:: + + git clone https://github.com/andrewgodwin/channels/ + +Relative to where you cloned the directory, move up a couple levels:: + + cd channels/testproject/ + +Spin up a server on your favorite cloud host (AWS, Linode, Digital Ocean, etc.) and get its host and credentials. Run the following command using those credentials:: + + fab setup_load_tester -i "ida_rsa" -H ubuntu@example.com + +That machine will provision itself. It may (depending on your vendor) prompt you a few times for a ``Y/n`` question. This is just asking you about increasing stroage space. +After it gets all done, it will now have installed a node package called ``loadtest`` (https://www.npmjs.com/package/loadtest). Note: my examples will show HTTP only requests, but loadtest also supports websockets. +To run the default loadtest setup, you can do the following, and the loadtest package will run for 90 seconds at a rate of 200 requests per second:: + + fab run_loadtest:http://127.0.0.1 -i "id_rsa" -H ubuntu@example.com + +Or if you want to exert some minor control, I've exposed a couple of parameters. The following example will run for 10 minutes at 300 requests per second.:: + + fab run_loadtest:http://127.0.0.1,rps=300,t=600 -i "id_rsa" -H ubuntu@example.com + +If you want more control, you can always pass in your own commands to:: + + fab shell -i "id_rsa" -H ubuntu@example.com diff --git a/testproject/fabfile.py b/testproject/fabfile.py index 400890b..6238405 100644 --- a/testproject/fabfile.py +++ b/testproject/fabfile.py @@ -1,6 +1,6 @@ from fabric.api import sudo, task, cd - +# CHANNEL TASKS @task def setup_redis(): sudo("apt-get update && apt-get install -y redis-server") @@ -20,14 +20,6 @@ def setup_channels(): sudo("python setup.py install") -@task -def setup_tester(): - sudo("apt-get update && apt-get install -y apache2-utils python3-pip") - sudo("pip3 -U pip autobahn twisted") - sudo("rm -rf /srv/channels") - sudo("git clone https://github.com/andrewgodwin/channels.git /srv/channels/") - - @task def run_daphne(redis_ip): with cd("/srv/channels/testproject/"): @@ -40,6 +32,30 @@ def run_worker(redis_ip): sudo("REDIS_URL=redis://%s:6379 python manage.py runworker" % redis_ip) +# Current loadtesting setup +@task +def setup_load_tester(src="https://github.com/andrewgodwin/channels.git"): + sudo("apt-get update && apt-get install -y git nodejs && apt-get install npm") + sudo("npm install -g loadtest") + sudo("ln -s /usr/bin/nodejs /usr/bin/node") + + +# Run current loadtesting setup +# example usage: $ fab run_loadtest:http://127.0.0.1,rps=10 -i "id_rsa" -H ubuntu@example.com +@task +def run_loadtest(host, t=90, rps=200): + sudo("loadtest -c 10 --rps {rps} -t {t} {h}".format(h=host, t=t, rps=rps)) + + +# Task that Andrew used for loadtesting earlier on +@task +def setup_tester(): + sudo("apt-get update && apt-get install -y apache2-utils python3-pip") + sudo("pip3 -U pip autobahn twisted") + sudo("rm -rf /srv/channels") + sudo("git clone https://github.com/andrewgodwin/channels.git /srv/channels/") + + @task def shell(): sudo("bash") diff --git a/testproject/locustfile.py b/testproject/locustfile.py new file mode 100644 index 0000000..1379f2a --- /dev/null +++ b/testproject/locustfile.py @@ -0,0 +1,16 @@ +from locust import HttpLocust, TaskSet, task + +class UserBehavior(TaskSet): + def on_start(self): + """ on_start is called when a Locust start before any task is scheduled """ + self.index() + + @task + def index(self): + self.client.get("/") + + +class WebsiteUser(HttpLocust): + task_set = UserBehavior + min_wait=5000 + max_wait=9000 diff --git a/testproject/requirements.txt b/testproject/requirements.txt index 0d4a5aa..3bb2297 100644 --- a/testproject/requirements.txt +++ b/testproject/requirements.txt @@ -2,7 +2,7 @@ asgi-redis==0.13.1 asgiref==0.13.3 autobahn==0.14.1 channels==0.14.2 -daphne==0.12.1 +daphne==0.13.1 Django==1.9.7 docutils==0.12 msgpack-python==0.4.7 diff --git a/testproject/testproject/asgi.py b/testproject/testproject/asgi.py index 1547a11..ae1640f 100644 --- a/testproject/testproject/asgi.py +++ b/testproject/testproject/asgi.py @@ -1,5 +1,5 @@ import os from channels.asgi import get_channel_layer -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testproject.settings") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testproject.settings.channels") channel_layer = get_channel_layer() diff --git a/testproject/testproject/settings/__init__.py b/testproject/testproject/settings/__init__.py new file mode 100644 index 0000000..455876f --- /dev/null +++ b/testproject/testproject/settings/__init__.py @@ -0,0 +1 @@ +#Blank on purpose diff --git a/testproject/testproject/settings.py b/testproject/testproject/settings/base.py similarity index 68% rename from testproject/testproject/settings.py rename to testproject/testproject/settings/base.py index d14a41f..e0f8773 100644 --- a/testproject/testproject/settings.py +++ b/testproject/testproject/settings/base.py @@ -11,7 +11,6 @@ INSTALLED_APPS = ( 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', - 'channels', ) ROOT_URLCONF = 'testproject.urls' @@ -26,13 +25,3 @@ DATABASES = { 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } - -CHANNEL_LAYERS = { - "default": { - "BACKEND": "asgi_redis.RedisChannelLayer", - "ROUTING": "testproject.urls.channel_routing", - "CONFIG": { - "hosts": [os.environ.get('REDIS_URL', 'redis://127.0.0.1:6379')], - } - }, -} diff --git a/testproject/testproject/settings/channels.py b/testproject/testproject/settings/channels.py new file mode 100644 index 0000000..4ab6439 --- /dev/null +++ b/testproject/testproject/settings/channels.py @@ -0,0 +1,16 @@ +# Settings for channels specifically +from testproject.settings.base import * + +INSTALLED_APPS += ( + 'channels', +) + +CHANNEL_LAYERS = { + "default": { + "BACKEND": "asgi_redis.RedisChannelLayer", + "ROUTING": "testproject.urls.channel_routing", + "CONFIG": { + "hosts": [os.environ.get('REDIS_URL', 'redis://127.0.0.1:6379')], + } + }, +} diff --git a/testproject/testproject/urls.py b/testproject/testproject/urls.py index 1dca076..89bb74d 100644 --- a/testproject/testproject/urls.py +++ b/testproject/testproject/urls.py @@ -1,13 +1,17 @@ from django.conf.urls import url -from chtest import consumers, views - +from chtest import views urlpatterns = [ url(r'^$', views.index), ] -channel_routing = { +try: + from chtest import consumers + + channel_routing = { "websocket.receive": consumers.ws_message, "websocket.connect": consumers.ws_connect, } +except: + pass diff --git a/testproject/testproject/wsgi.py b/testproject/testproject/wsgi.py index c24d001..9dcfd86 100644 --- a/testproject/testproject/wsgi.py +++ b/testproject/testproject/wsgi.py @@ -11,6 +11,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testproject.settings") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testproject.settings.channels") application = get_wsgi_application() diff --git a/testproject/testproject/wsgi_no_channels.py b/testproject/testproject/wsgi_no_channels.py new file mode 100644 index 0000000..79863f9 --- /dev/null +++ b/testproject/testproject/wsgi_no_channels.py @@ -0,0 +1,16 @@ +""" +WSGI config for testproject project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/dev/howto/deployment/wsgi/ +""" + +import os + +from django.core.wsgi import get_wsgi_application + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testproject.settings.base") + +application = get_wsgi_application()