merge changes from master

This commit is contained in:
Alexey Lavrenuke 2016-01-26 14:50:01 +03:00
commit aeba6befe7
31 changed files with 639 additions and 132 deletions

View File

@ -1,4 +1,4 @@
# Yandex Tank [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/yandex/yandex-tank?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Gittask](https://gittask.com/yandex/yandex-tank.svg)](https://gittask.com/yandex/yandex-tank)
# Yandex Tank [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/yandex/yandex-tank?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Build Status](https://secure.travis-ci.org/yandex/yandex-tank.png?branch=master)](http://travis-ci.org/yandex/yandex-tank)
@ -18,27 +18,8 @@ Yandex.Tank is an extendable open source load testing tool for advanced linux us
* test autostop plugin
* customizable and extendable monitoring that works over SSH
## Install from PyPI
You will need some packages that are required for building different python libraries:
```
libxml2-dev libxslt1-dev python-dev zlib1g-dev
```
You will also need a GNU make for building them. In Ubuntu you can install a ```build-essential``` package. You should also install pip if you don't have it.
Full command for Ubuntu looks like this:
```
sudo apt-get install python-pip build-essential libxml2-dev libxslt1-dev python-dev zlib1g-dev
```
You can do similar thing for your distribution. After you've installed all the packages, it is easy to install the Tank itself:
```
sudo pip install yandextank
```
Remember that if you want to use ```phantom``` as a load generator you should install it separately. On Ubuntu you can do that by adding our PPA and installing ```phantom``` and ```phantom-ssl``` packages. On other distros you will maybe need to build it from sources.
```
sudo add-apt-repository ppa:yandex-load/main && sudo apt-get update
sudo apt-get install phantom phantom-ssl
```
**Report plugin** is a distinct project. You can found it [here](https://github.com/yandex-load/yatank-online).
## Installation and configuration
Installation at [ReadTheDocs](http://yandextank.readthedocs.org/en/latest/install.html)
## Get help
Documentation at [ReadTheDocs](https://yandextank.readthedocs.org/en/latest/)

View File

@ -19,8 +19,4 @@ _yandex_tank()
fi
COMPREPLY=()
} &&
complete -o default -F _yandex_tank yandex-tank &&
complete -o default -F _yandex_tank yandex-tank-jmeter &&
complete -o default -F _yandex_tank yandex-tank-udp &&
complete -o default -F _yandex_tank yandex-tank-bfg &&
complete -o default -F _yandex_tank yandex-tank-elliptics
complete -o default -F _yandex_tank yandex-tank

View File

@ -35,11 +35,7 @@ cp -arp Tank %{buildroot}/%{_libdir}/yandex-tank/
cp -ap tankcore.py %{buildroot}/%{_libdir}/yandex-tank/
cp -ap tank.py %{buildroot}/%{_libdir}/yandex-tank/
cp -ap *.sh %{buildroot}/%{_libdir}/yandex-tank/
ln -s %{_libdir}/yandex-tank/lunapark %{buildroot}/%{_bindir}/lunapark
ln -s %{_libdir}/yandex-tank/tank.py %{buildroot}/%{_bindir}/yandex-tank
ln -s %{_libdir}/yandex-tank/ab.sh %{buildroot}/%{_bindir}/yandex-tank-ab
ln -s %{_libdir}/yandex-tank/jmeter.sh %{buildroot}/%{_bindir}/yandex-tank-jmeter
ln -s %{_libdir}/yandex-tank/bfg.sh %{buildroot}/%{_bindir}/yandex-tank-bfg
%clean
rm -rf %{buildroot}
@ -50,9 +46,6 @@ rm -rf %{buildroot}
%{_sysconfdir}/bash_completion.d/yandex-tank.completion
%{_bindir}/lunapark
%{_bindir}/yandex-tank
%{_bindir}/yandex-tank-ab
%{_bindir}/yandex-tank-jmeter
%{_bindir}/yandex-tank-bfg
%{_libdir}/yandex-tank
%changelog

46
debian/changelog vendored
View File

@ -1,3 +1,49 @@
yandextank (1.7.30) trusty; urgency=medium
* support Pandora in Loadosophia plugin
-- Alexey Lavrenuke (load testing) <direvius@yandex-team.ru> Thu, 21 Jan 2016 16:13:32 +0300
yandextank (1.7.29) trusty; urgency=medium
* pandora plugin https gun type fix
* invalidate ammo cache if ammo_type changed
* API worker [Timur Torubarov]
-- Alexey Lavrenuke (load testing) <direvius@yandex-team.ru> Mon, 18 Jan 2016 15:59:32 +0300
yandextank (1.7.28) trusty; urgency=medium
* add pyzmq dependency [Timur Torubarov]
* fix re-import problem in bfg [Timur Torubarov]
-- Alexey Lavrenuke (load testing) <direvius@yandex-team.ru> Wed, 13 Jan 2016 19:37:37 +0300
yandextank (1.7.27) trusty; urgency=medium
* connect_time jmeter option [Ilya Krylov]
* Fix Zombie Apocalypse (phantom zombie processes leakage) [Timur Torubarov]
-- Alexey Lavrenuke (load testing) <direvius@yandex-team.ru> Tue, 29 Dec 2015 13:06:57 +0300
yandextank (1.7.26) trusty; urgency=medium
* PyCrypto bug workaround
-- Alexey Lavrenuke (load testing) <direvius@yandex-team.ru> Wed, 16 Dec 2015 15:02:25 +0300
yandextank (1.7.25) trusty; urgency=medium
* Dependencies in debian/control
-- Alexey Lavrenuke (load testing) <direvius@yandex-team.ru> Wed, 16 Dec 2015 14:19:21 +0300
yandextank (1.7.24) trusty; urgency=medium
* SHA-2 workaround
-- Alexey Lavrenuke (load testing) <direvius@yandex-team.ru> Wed, 09 Dec 2015 14:19:21 +0300
yandextank (1.7.23) trusty; urgency=medium
* update pandora plugin

2
debian/control vendored
View File

@ -7,7 +7,7 @@ Standards-Version: 3.9.3
Package: yandex-tank
Architecture: all
Depends: ${misc:Depends}, ${python:Depends}, python-psutil (>=1.2.1), phantom (>=0.14.0~pre65), phantom-ssl(>=0.14.0~pre65)
Depends: ${misc:Depends}, ${python:Depends}, python-psutil (>=1.2.1), python-paramiko(>=1.16.0), phantom (>=0.14.0~pre65), python-requests(>=2.5.1), phantom-ssl(>=0.14.0~pre65)
Conflicts: yandex-load-tank-base
Description: performance measurement tool
Find out about your apps performance limits and bottlenecks in order

View File

@ -4,17 +4,15 @@ Advanced usage
Command line options
~~~~~~~~~~~~~~~~~~~~
There are three executables in Yandex.Tank package: ``yandex-tank``,
``yandex-tank-ab`` and ``yandex-tank-jmeter``. Last two of them just use
different kind of load generation utilities, ``ab`` (Apache Benchmark) and
``jmeter`` (Apache JMeter), accordingly. Command line options are common
for all three.
Yandex.Tank has an obviously named executable ``yandex-tank``.
Here are available command line options:
- **-h, --help** - show command line options
- **-c CONFIG, --config=CONFIG** - read options from INI file. It is possible to set multiple INI files by specifying the option serveral times. Default: ``./load.ini``
- **-i, --ignore-lock** - ignore lock files
- **-f, --fail-lock** - don't wait for lock file, quit if it's busy. The default behaviour is to wait for lock file to become free.
- **-l LOG, --log=LOG** - main log file location. Default: ``./tank.log``
- **-m, --manual-start** - tank will prepare for test and wait for Enter key to start the test.
- **-n, --no-rc** - don't read ``/etc/yandex-tank/*.ini`` and ``~/.yandex-tank``
- **-o OPTION, --option=OPTION** - set an option from command line. Options set in cmd line override those have been set in configuration files. Multiple times for multiple options. Format: ``<section>.<option>=value`` Example: ``yandex-tank -o "console.short_only=1" --option="phantom.force_stepping=1"``
- **-s SCHEDULED_START, --scheduled-start=SCHEDULED_START** - run test on specified time, date format YYYY-MM-DD hh:mm:ss or hh:mm:ss
@ -37,7 +35,7 @@ example:
[phantom]
address=example.com:80
rps_schedule=const(100,60s)
[autostop]
autostop=instances(80%,10)
@ -160,6 +158,71 @@ there.
Modules
~~~~~~~
TankCore
^^^^^^^
Core class. Represents basic steps of test execution. Simplifies plugin configuration,
configs reading, artifacts storing. Represents parent class for modules/plugins.
INI file section: **[tank]**
Options
'''''''
Basic options:
* **lock_dir** - directory for lockfile. Default: ``/var/lock/``
* **plugin_<pluginname>** - path to plugin. Empty path interpreted as disable of plugin.
* **artifacts_base_dir** - base directory for artifacts storing. Temporary artifacts files are stored here. Default: current directory
* **artifacts_dir** - directory where to keep artifacts after test. Default: directory in ``artifacts_base_dir`` named in Date/Time format.
* **flush_config_to** - dump configuration options after each tank step (`yandex.tank steps. sorry, russian only <http://clubs.ya.ru/yandex-tank/replies.xml?item_no=6>`_) to that file
* **taskset_path** - path to taskset command. Default: taskset
* **affinity** - set a yandex-tank's (python process and load generator process) CPU affinity. Example: '0-3' enabling first 4 cores, '0,1,2,16,17,18' enabling 6 cores. Default: empty
consoleworker - cmd-line interface
'''''''
Worker class that runs and configures TankCore accepting cmdline parameters.
Human-friendly unix-way interface for yandex-tank.
Command-line options described above.
apiworker - python interface
'''''''
Worker class for python. Runs and configures TankCore accepting ``dict()``.
Python-frinedly interface for yandex-tank.
Usage sample:
.. code-block:: python
from yandextank.api.apiworker import ApiWorker
import logging
import traceback
import sys
logger = logging.getLogger('')
logger.setLevel(logging.DEBUG)
#not mandatory options below:
options = dict()
options['config'] = '/path/to/config/load.ini'
options['manual_start'] = "1"
options['user_options'] = [
'phantom.ammofile=/path/to/ammofile',
'phantom.rps_schedule=const(1,2m)',
]
log_filename = '/path/to/log/tank.log'
#======================================
apiworker = ApiWorker()
apiworker.init_logging(log_filename)
try:
apiworker.configure(options)
apiworker.perform_test()
except Exception, ex:
logger.error('Error trying to perform a test: %s', ex)
Phantom
^^^^^^^
@ -281,7 +344,15 @@ Options that apply only for main section: buffered_seconds, writelog, phantom_mo
JMeter
^^^^^^
JMeter module uses JMeter as a load generator
JMeter module uses JMeter as a load generator. To enable it, disable phantom first (unless you really want to keep it active alongside at your own risk), enable JMeter plugin and then specify the parameters for JMeter:
::
[tank]
; Disable phantom:
plugin_phantom=
; Enable JMeter instead:
plugin_jmeter=yandextank.plugins.JMeter
INI file section: **[jmeter]**
@ -291,6 +362,7 @@ Options
* **args** - additional commandline arguments for JMeter
* **jmeter_path** - path to JMeter, allows to use alternative JMeter installation. Default: jmeter
* **buffered_seconds** - amount of seconds to which delay aggregator, to be sure that everything were read from jmeter's results file
* **connect_time** - it sets jmeter.save.saveservice.connect_time=false if the value is '0' or empty string, jmeter.save.saveservice.connect_time=true in any other cases, empty string by default
* **all other options in the section** - they will be passed as User Defined Variables to JMeter
Artifacts
@ -303,7 +375,7 @@ Artifacts
BFG
^^^
(`What is BFG <http://en.wikipedia.org/wiki/BFG_(weapon)>`_)
BFG is a generic gun that is able to use different kinds of cannons to shoot. To enable it, disable phantom first, enable BFG plugin and then specify the parameters for BFG and for the cannon you select. For example, if you want to kill an SQL db:
BFG is a generic gun that is able to use different kinds of cannons to shoot. To enable it, disable phantom first (unless you really want to keep it active alongside at your own risk), enable BFG plugin and then specify the parameters for BFG and for the cannon you select. For example, if you want to kill an SQL db:
::
@ -383,6 +455,70 @@ INI file section: **[custom_gun]**
* **module_path** - path to your module
* **module_name** - module name, has to provide function shoot, which will be called by bfg's threads to fullfill rps_schedule
Sample custom gun module:
.. code-block:: python
# coding=utf-8
import sys
import os
from Queue import Queue
import socket
import logging
import time
from contextlib import contextmanager
from collections import namedtuple
Sample = namedtuple(
'Sample', 'marker,threads,overallRT,httpCode,netCode,sent,received,connect,send,latency,receive,accuracy')
@contextmanager
def measure(marker, queue):
start_ms = time.time()
resp_code = 0
try:
yield
except Exception as e:
print marker, e
resp_code = 110
response_time = int((time.time() - start_ms) * 1000)
data_item = Sample(
marker, # tag
1, # threadsв
rt_time, # overallRT
200, # httCode
resp_code, # netCode
0, # sent
0, # received
connect_time, # connect
0, # send
latency_time, # latency
0, # receive
0, # accuracy
)
queue.put((int(time.time()), data_item), timeout=5)
if resp_code != 0:
raise RuntimeError
def shoot(missile, marker, results):
sock = socket.socket()
try:
#prepare actions
<...some work...>
#test logic with metrics counting
with measure("markerOfRequest", results):
<...some useful work...>
except RuntimeError as e:
print "Scenario %s failed with %s" % (marker, e)
finally:
<...some finishing work...>
Pandora
^^^^^^^
`Pandora <https://github.com/yandex/pandora>`_ is a load generator written in Go. For now it supports only SPDY/3 and HTTP(S). Plugins for other protocols
@ -390,7 +526,7 @@ Pandora
First of all you'll need to obtain a binary of pandora and place it somewhere on your machine.
By default, Yandex.Tank will try to just run ``pandora`` (or you could specify a path to binary in ``pandora_cmd``).
Disable phantom first, enable Pandora plugin and then specify the parameters.
Disable phantom first (unless you really want to keep it active alongside at your own risk), enable Pandora plugin and then specify the parameters.
::
@ -469,7 +605,7 @@ to stop eventually. Example:
loop = 1000000
startup_schedule = periodic(2, 10, 50)
user_schedule = unlimited
user_schedule = unlimited()
shared_schedule = 0
Start 2 users every 10 seconds. Every user will shoot without any limits (next request is sended
@ -499,6 +635,7 @@ Basic criteria types:
* **instances** - available when phantom module is included. Stop the test if instance count is larger then specified value. Example: ``instances(80%, 30) instances(50,1m)``. Exit code - 24
* **metric_lower** and **metric_higher** - stop test if monitored metrics are lower/higher than specified for time period. Example: metric_lower(127.0.0.1,Memory_free,500,10). Exit code - 31 and 32. **Note**: metric names (except customs) are written with underline. For hostnames masks are allowed (i.e target-\*.load.net)
* **steady_cumulative** - Stops the test if cumulative percentiles does not change for specified interval. Example: ``steady_cumulative(1m)``. Exit code - 33
* **limit** - Will stop test after specified period of time. Example: ``limit(1m)``.
Basic criteria aren't aggregated, they are tested for each second in specified period. For example autostop=time(50,15) means "stop if average responce time for every second in 15s interval is higher than 50ms"
@ -513,7 +650,41 @@ Advanced criteria types:
Graphite
^^^^^^^^
Graphite plugin uploads data to `Graphite <http://graphite.readthedocs.org/en/0.9.12/index.html>`_ monitoring tool.
Graphite plugin uploads data to `Graphite <http://graphite.readthedocs.org/en/0.9.12/index.html>`_ monitoring tool. Config file section: ```[graphite]```
Options
'''''''
* **address** - graphite server
* **port** - graphite backend port (where to send data), default: 2003
* **web_port** - graphite frontend port, default: 8080
* **template** - template file. Default: Tank/Plugins/graphite.tpl
InfluxDB
^^^^^^^^
Influx uplink plugin uploads data to `InfluxDB <https://influxdata.com>`_ storage.
Different tests will be tagged with unique IDs.
Configuration:
::
[tank]
; Enable InfluxDB plugin:
plugin_influx=yandextank.plugins.InfluxUplink
[influx]
; Tank name (to distinguish data from different tanks):
tank_tag = MyTank
; Address and of InfluxDB instance:
address = example.org
port = 8086
; If you have grafana connected to your InfluxDB, you
; can specify grafana parameters and tank will generate
; a link to your test:
grafana_root = http://example.org/grafana/
grafana_dashboard=tank-dashboard
Options
'''''''
@ -742,6 +913,17 @@ Apache Benchmark load generator module. As the ab utility writes results
to file only after the test is finished, Yandex.Tank is unable to show
the on-line statistics for the tests with ab. The data are reviewed
after the test.
To enable it, disable phantom first (unless you really want to keep it active alongside at your own risk), enable AB plugin and then specify
the parameters for AB:
::
[tank]
; Disable phantom:
plugin_phantom=
; Enable AB module instead:
plugin_ab=yandextank.plugins.ApacheBenchmark
INI file section: **[ab]**
@ -783,37 +965,6 @@ Options
* **pass** - list of acceptable codes, delimiter - whitespace. Default: empty, no check is performed.
* **fail_code** - exit code when check fails, integer number. Default: 10
Web Online
^^^^^^^^^^
Module starts local web sever that shows online graphics. Enabled by ``plugin_web=Tank/Plugins/WebOnline.py`` in ``[tank]`` section.
INI file section: **[web]**
Options
'''''''
* **port** - a port to bind to on localhost. Default: 8080
* **interval** - graphics' interval that will be shown, in seconds. Default: 60 seconds
* **redirect** - address where to redirect browser after test stop.
* **manual_stop** - flag 0/1. If '1' then webserver will wait for key press from keyboard to exit
Yandex.Tank kernel
^^^^^^^^^^^^^^^^^^
Python-object, that loads and execs tank modules.
INI file section: **[tank]**
Options
'''''''
* **artifacts_base_dir** - base directory for artifacts storing. Temporary artifacts files are stored here. Default: current directory
* **artifacts_dir** - directory where to keep artifacts after test. Default: directory in ``artifacts_base_dir`` named in Date/Time format.
* **flush_config_to** - dump configuration options after each tank step (`yandex.tank steps. sorry, russian only <http://clubs.ya.ru/yandex-tank/replies.xml?item_no=6>`_) to that file
* **taskset_path** - path to taskset command. Default: taskset
* **affinity** - set a yandex-tank's (python process and load generator process) CPU affinity. Example: '0-3' enabling first 4 cores, '0,1,2,16,17,18' enabling 6 cores. Default: empty
Tips&Tricks
^^^^^^^^^^^
@ -829,7 +980,7 @@ Options
Sources
~~~~~~~
Yandex.Tank sources ((https://github.com/yandex-load/yandex-tank here)).
Yandex.Tank sources ((https://github.com/yandex/yandex-tank here)).
load.ini example
~~~~~~~~~~~~~~~~~

View File

@ -8,7 +8,8 @@ Welcome to Yandex.Tank's documentation!
:Homepage: `Yandex.Tank Homepage on Github
<https://github.com/yandex-load/yandex-tank>`_
:Download: `Launchpad PPA (ubuntu packages)
<https://launchpad.net/~yandex-load/+archive/main>`_
<https://launchpad.net/~yandex-load/+archive/main>`_ `Pypi
<https://pypi.python.org/pypi/yandextank/>`_
:Documentation: `PDF Documentation
<https://media.readthedocs.org/pdf/yandextank/latest/yandextank.pdf>`_
:License: `GNU LGPLv3
@ -36,4 +37,4 @@ Indices and tables
.. image::
http://mc.yandex.ru/watch/23073253
:align: right
:align: right

View File

@ -1,8 +1,38 @@
Installation and Configuration
------------------------------
Installation
Installation, from PyPi
~~~~~~~~~~~~
You will need some packages that are required for building different python libraries:
.. code-block:: bash
libxml2-dev libxslt1-dev python-dev zlib1g-dev
You will also need a GNU make for building them. In Ubuntu you can install a build-essential package. You should also install pip if you don't have it. Full command for Ubuntu looks like this:
.. code-block:: bash
sudo apt-get install python-pip build-essential libxml2-dev libxslt1-dev python-dev zlib1g-dev
You can do similar thing for your distribution. After you've installed all the packages, it is easy to install the Tank itself:
.. code-block:: bash
sudo pip install yandextank
Remember that if you want to use phantom as a load generator you should install it separately. On Ubuntu you can do that by adding our PPA and installing phantom and phantom-ssl packages. On other distros you will maybe need to build it from sources.
.. code-block:: bash
sudo add-apt-repository ppa:yandex-load/main && sudo apt-get update
sudo apt-get install phantom phantom-ssl
Report plugin is a distinct project. You can found it `here via github <https://github.com/yandex-load/yatank-online>>`_
Installation, .deb packages
~~~~~~~~~~~
You should add proper repositories on Debian-based environment.
@ -10,8 +40,8 @@ For instance, add following repos to ``sources.list`` :
.. code-block:: bash
deb http://ppa.launchpad.net/yandex-load/main/ubuntu precise main
deb-src http://ppa.launchpad.net/yandex-load/main/ubuntu precise main
deb http://ppa.launchpad.net/yandex-load/main/ubuntu trusty main
deb-src http://ppa.launchpad.net/yandex-load/main/ubuntu trusty main
or this way
@ -21,11 +51,11 @@ or this way
sudo apt-get install software-properties-common
sudo add-apt-repository ppa:yandex-load/main
Then update package list and install ``yandex-load-tank-base`` package:
Then update package list and install ``yandex-tank`` package:
.. code-block:: bash
sudo apt-get update && sudo apt-get install yandex-load-tank-base
sudo apt-get update && sudo apt-get install yandex-tank
For mild load tests (less then 1000rps) an average laptop with 64bit
Ubuntu (10.04/.../13.10) would be sufficient. The tank could be easily

View File

@ -9,6 +9,9 @@ See also
Evgeniy Mamchits' `phantom <https://github.com/mamchits/phantom>`_ -
Phantom scalable IO Engine
Alexey Lavrenuke's `pandora <https://github.com/yandex/pandora>`_ -
A load generator in Go language
Gregory Komissarov's
`firebat <https://github.com/greggyNapalm/firebat-console>`_ - test tool
based on Phantom

View File

@ -4,6 +4,8 @@ Usage
So, you've installed Yandex.Tank to a proper machine, it is close to target,
access is permitted and server is tuned. How to make a test?
This guide is for ``phantom`` load generator.
First Steps
~~~~~~~~~~~
@ -24,9 +26,10 @@ values, step - increment value, dur - step duration.
2. ``line (a,b,dur)`` makes linear load, where ``a,b`` are start/end load, ``dur``
- the time for linear load increase from a to b.
3. ``const (load,dur)`` makes constant load. ``load`` - rps amount, ``dur`` - load duration. You can set
fractional load like this: ``line(1.1, 2.5, 10)`` -- from 1.1rps to 2.5 for 10 seconds. Note: ``const(0, 10)`` - 0 rps for 10 seconds, in fact 10s pause
in a test.
3. ``const (load,dur)`` makes constant load. ``load`` - rps amount, ``dur``
- load duration. You can set fractional load like this: ``line(1.1, 2.5, 10)``
-- from 1.1rps to 2.5 for 10 seconds. Note: ``const(0, 10)`` - 0 rps for 10 seconds,
in fact 10s pause in a test.
``step`` and ``line`` could be used with increasing and decreasing
intensity:
@ -39,7 +42,9 @@ intensity:
minutes ``line(1, 100, 10m)`` - linear load from 1 to 100 rps, duration
- 10 minutes
You can specify complex load schemes using those primitives, for example: ``rps_schedule=line(1,10,10m) const(10,10m)`` - linear load from 1 to 10, duration 10 mins and then 10 mins of 10 RPS constant load.
You can specify complex load schemes using those primitives,
for example: ``rps_schedule=line(1,10,10m) const(10,10m)``
- linear load from 1 to 10, duration 10 mins and then 10 mins of 10 RPS constant load.
Time duration could be defined in seconds, minutes (m) and hours (h).
For example: ``27h103m645``
@ -60,13 +65,29 @@ Voilà, Yandex.Tank setup is done.
Preparing requests
~~~~~~~~~~~~~~~~~~
There are several ways to set up requests: Access mode, URI-style and request-style.
Regardless of the chosen format, resulted file with requests could be gzipped - tank supports archived ammo files
There are several ways to set up requests: Access mode, URI-style, URI+POST and request-style.
Regardless of the chosen format, resulted file with requests could be gzipped - tank supports
archived ammo files.
To specify external ammo file use ``ammofile`` option. You can specify URL to ammofile, http(s).
Small ammofiles (~<100MB) will be downloaded as is, to directory ``/tmp/<hash>``,
large files will be readed from stream.
::
[phantom]
address=203.0.113.1 ; Target's address
ammofile=https://yourhost.tld/path/to/ammofile.txt
If ammo type is uri-style or request-style, tank will try to guess it.
Use ``ammo_type`` option to explicitly specify ammo format. Don't forget to change ``ammo_type`` option
if you switch format of your ammo, otherwise you might get errors.
Access mode
''''''''''''
You can use access.log file from your webserver as a source of requests.
Just add to load.ini options `ammo_type=access` and `ammofile=/tmp/access.log`
Just add to load.ini options ``ammo_type=access`` and ``ammofile=/tmp/access.log``
where /tmp/access.log is a path to access.log file.
::
@ -104,6 +125,7 @@ Update configuration file with HTTP headers and URIs:
/sdfbv/swdfvs/ssfsf
Parameter ``uris`` contains uri, which should be used for requests generation.
Pay attention to sample above, because whitespaces in ``uris`` and ``headers`` options are important.
URI-style, URIs in file
'''''''''''''''''''''''
@ -124,8 +146,10 @@ Create a file with declared requests: **ammo.txt**
File consist of list of URIs and headers to be added to every request defined below.
Every URI must begin from a new line, with leading ``/``.
Each line that begins from ``[`` is considered as a header.
Headers could be (re)defined in the middle of URIs, as in sample above. I.e request ``/buy/?rt=0&station_to=7&station_from=9`` will be sent with ``Cookies: test``, not ``Cookies: None``.
Request may be marked by tag, you can specify it with whitespace following URI.
Headers could be (re)defined in the middle of URIs, as in sample above.
I.e request ``/buy/?rt=0&station_to=7&station_from=9`` will be sent
with ``Cookies: test``, not ``Cookies: None``. Request may be marked by tag,
you can specify it with whitespace following URI.
URI+POST-style
''''''''''''''
@ -172,7 +196,10 @@ is:
where ``size_of_request`` request size in bytes. '\r\n' symbols after
``body`` are ignored and not sent anywhere, but it is required to
include them in a file after each request. '\r' is also required.
include them in a file after each request. Pay attention to the sample above
because '\r' symbols are strictly required.
Parameter ``ammo_type`` is unnecessary, request-style is default ammo type.
**sample GET requests (null body)**
@ -330,6 +357,9 @@ During test execution you'll see HTTP and net errors, answer times
distribution, progressbar and other interesting data. At the same time
file ``phout.txt`` is being written, which could be analyzed later.
If you need more human-readable report, you can try Report plugin,
You can found it `here <https://github.com/yandex-load/yatank-online>`_
Tags
~~~~
@ -354,6 +384,7 @@ requests and tags:
User-Agent: xxx (shell 1)
``good``, ``bad`` and ``unknown`` here are the tags.
**RESTRICTION: utf-8 symbols only**
SSL
@ -422,9 +453,8 @@ Logging
Looking into target's answers is quite useful in debugging. For doing
that add ``writelog = 1`` to ``load.ini``.
**ATTENTION: Writing answers on
high load leads to intensive disk i/o usage and can affect test
accuracy.**
**ATTENTION: Writing answers on high load leads to intensive disk i/o
usage and can affect test accuracy.**
Log format:
@ -502,7 +532,7 @@ installed on your Yandex.Tank system.
Graph and statistics
~~~~~~~~~~~~~~~~~~~~
Use included charting tool that runs as a webservice on localhost
Use `Report plugin <https://github.com/yandex-load/yatank-online>`_
OR
use your favorite stats packet, R, for example.
@ -521,6 +551,8 @@ parameter like this:
[aggregator]
time_periods = 10 45 50 100 150 300 500 1s 1500 2s 3s 10s ; the last value - 10s is considered as connect timeout.
According to this "buckets", tanks' aggregator will aggregate test results.
Thread limit
~~~~~~~~~~~~

View File

@ -1,2 +0,0 @@
#! /bin/sh
yandex-tank -o "tank.plugin_phantom=" -o "tank.plugin_ab=Tank/Plugins/ApacheBenchmark.py" "$@"

View File

@ -1,2 +0,0 @@
#! /bin/sh
yandex-tank -o "tank.plugin_phantom=" -o "tank.plugin_bfg=Tank/Plugins/BFG.py" "$@"

View File

@ -1,2 +0,0 @@
#! /bin/sh
yandex-tank -o "tank.plugin_phantom=" -o "tank.plugin_jmeter=Tank/Plugins/JMeter.py" "$@"

View File

@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name='yandextank',
version='1.7.23',
version='1.7.30',
description='a performance measurement tool',
longer_description='''
Yandex.Tank is a performance measurement and load testing automatization tool.
@ -18,9 +18,10 @@ analytic tools for the results they produce.
'psutil>=1.2.1',
'ipaddr',
'progressbar',
'requests',
'requests>=2.5.1',
'paramiko>=1.16.0',
'pandas',
'pyzmq',
],
license='LGPLv2',
classifiers=[
@ -45,6 +46,7 @@ analytic tools for the results they produce.
},
package_data={
'yandextank.core': ['config/*'],
'yandextank.api': ['config/*'],
'yandextank.plugins.GraphiteUploader': ['config/*'],
'yandextank.plugins.JMeter': ['config/*'],
'yandextank.plugins.Monitoring': ['config/*'],

View File

@ -21,7 +21,11 @@ class PhantomConfigTestCase(TankTestCase):
foo = PhantomConfig(core)
foo.read_config()
foo.compose_config()
config = foo.compose_config()
conf_str = open(config).read()
logging.info(conf_str)
self.assertEquals(conf_str.count("io_benchmark_method_stream_transport_ssl"), 0)
def test_double(self):
core = self.get_core()
@ -43,6 +47,8 @@ class PhantomConfigTestCase(TankTestCase):
self.assertEquals(conf_str.count("benchmark_io "), 2)
self.assertEquals(conf_str.count("benchmark_io1 "), 2)
self.assertEquals(conf_str.count("benchmark_io2 "), 2)
self.assertEquals(conf_str.count("io_benchmark_method_stream_transport_ssl"), 1)
conf_str = open(config).read()
logging.info(conf_str)

View File

@ -0,0 +1,4 @@
'''
Package contains all tank tool core code
'''
from apiworker import *

169
yandextank/api/apiworker.py Normal file
View File

@ -0,0 +1,169 @@
# -*- coding: utf-8 -*-
""" Provides class to run TankCore from python """
from yandextank.core import tankcore
import logging
from pkg_resources import resource_filename
import os
import sys
import time
import traceback
class ApiWorker:
""" Worker class that runs tank core via python """
def __init__(self):
self.core = tankcore.TankCore()
self.baseconfigs_location = '/etc/yandex-tank'
self.log = logging.getLogger(__name__)
def init_logging(self, log_filename="tank.log"):
""" Set up logging """
logger = logging.getLogger('')
self.log_filename = log_filename
self.core.add_artifact_file(self.log_filename)
file_handler = logging.FileHandler(self.log_filename)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(
logging.Formatter(
"%(asctime)s [%(levelname)s] %(name)s %(message)s"))
logger.addHandler(file_handler)
console_handler = logging.StreamHandler(sys.stdout)
stderr_hdl = logging.StreamHandler(sys.stderr)
fmt_verbose = logging.Formatter(
"%(asctime)s [%(levelname)s] %(name)s %(message)s")
fmt_regular = logging.Formatter(
"%(asctime)s %(levelname)s: %(message)s", "%H:%M:%S")
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(fmt_regular)
stderr_hdl.setFormatter(fmt_regular)
f_err = SingleLevelFilter(logging.ERROR, True)
f_warn = SingleLevelFilter(logging.WARNING, True)
f_crit = SingleLevelFilter(logging.CRITICAL, True)
console_handler.addFilter(f_err)
console_handler.addFilter(f_warn)
console_handler.addFilter(f_crit)
logger.addHandler(console_handler)
f_info = SingleLevelFilter(logging.INFO, True)
f_debug = SingleLevelFilter(logging.DEBUG, True)
stderr_hdl.addFilter(f_info)
stderr_hdl.addFilter(f_debug)
logger.addHandler(stderr_hdl)
def __add_user_options(self):
""" override config options with user specified options"""
if self.options.get('user_options', None):
self.core.apply_shorthand_options(self.options['user_options'])
def configure(self, options):
""" Make preparations before running Tank """
self.options = options
if self.options.get('lock_dir', None):
self.core.set_option(
self.core.SECTION, "lock_dir", self.options['lock_dir'])
while True:
try:
self.core.get_lock(self.options.get('ignore_lock', None))
break
except Exception, exc:
if self.options.get('lock_fail', None):
raise RuntimeError("Lock file present, cannot continue")
self.log.info("Couldn't get lock. Will retry in 5 seconds... (%s)", str(exc))
time.sleep(5)
configs = self.get_default_configs()
if self.options.get('config', None):
configs.append(self.options['config'])
self.core.load_configs(configs)
self.__add_user_options()
self.core.load_plugins()
if self.options.get('ignore_lock', None):
self.core.set_option(self.core.SECTION, self.IGNORE_LOCKS, "1")
def perform_test(self):
""" Run the test and wait for finish """
self.log.info("Performing test...")
retcode = 1
try:
self.core.plugins_configure()
self.core.plugins_prepare_test()
if self.options.get('manual_start', None):
self.log.info("Manual start option specified, waiting for user actions")
raw_input("Press Enter key to start test")
self.core.plugins_start_test()
retcode = self.core.wait_for_finish()
retcode = self.core.plugins_end_test(retcode)
retcode = self.core.plugins_post_process(retcode)
except KeyboardInterrupt as ex:
self.log.info(
"Do not press Ctrl+C again, the test will be broken otherwise")
self.log.debug(
"Caught KeyboardInterrupt: %s", traceback.format_exc(ex))
try:
retcode = self.__graceful_shutdown()
except KeyboardInterrupt as ex:
self.log.debug(
"Caught KeyboardInterrupt again: %s", traceback.format_exc(ex))
self.log.info(
"User insists on exiting, aborting graceful shutdown...")
retcode = 1
except Exception as ex:
self.log.info("Exception: %s", traceback.format_exc(ex))
self.log.error("%s", ex)
retcode = self.__graceful_shutdown()
self.core.release_lock()
self.log.info("Done performing test with code %s", retcode)
return retcode
def get_default_configs(self):
""" returns default configs list, from /etc, home dir and package_data"""
# initialize basic defaults
configs = [resource_filename(__name__, 'config/00-base.ini')]
try:
conf_files = os.listdir(self.baseconfigs_location)
conf_files.sort()
for filename in conf_files:
if fnmatch.fnmatch(filename, '*.ini'):
configs += [
os.path.realpath(self.baseconfigs_location + os.sep + filename)]
except OSError:
self.log.warn(
self.baseconfigs_location + ' is not acessible to get configs list')
configs += [os.path.expanduser('~/.yandex-tank')]
return configs
def __graceful_shutdown(self):
""" call shutdown routines """
retcode = 1
self.log.info("Trying to shutdown gracefully...")
retcode = self.core.plugins_end_test(retcode)
retcode = self.core.plugins_post_process(retcode)
self.log.info("Done graceful shutdown")
return retcode
class SingleLevelFilter(logging.Filter):
"""Exclude or approve one msg type at a time. """
def __init__(self, passlevel, reject):
logging.Filter.__init__(self)
self.passlevel = passlevel
self.reject = reject
def filter(self, record):
if self.reject:
return record.levelno != self.passlevel
else:
return record.levelno == self.passlevel

View File

@ -0,0 +1,18 @@
### base config with Yandex-specific tool settings ###
[tank]
plugin_rcheck=yandextank.plugins.ResourceCheck
plugin_ShellExec=yandextank.plugins.ShellExec
plugin_phantom=yandextank.plugins.Phantom
plugin_aggreg=yandextank.plugins.Aggregator
plugin_autostop=yandextank.plugins.Autostop
plugin_monitoring=yandextank.plugins.Monitoring
plugin_console=yandextank.plugins.ConsoleOnline
plugin_tips=yandextank.plugins.TipsAndTricks
plugin_totalautostop=yandextank.plugins.TotalAutostop
plugin_loadosophia=yandextank.plugins.Loadosophia
plugin_graphite=yandextank.plugins.GraphiteUploader
plugin_rcassert=yandextank.plugins.RCAssert
artifacts_base_dir=logs
[console]
short_only=1

View File

@ -0,0 +1,26 @@
from yandextank.api.apiworker import ApiWorker
import logging
import traceback
import sys
logger = logging.getLogger('')
logger.setLevel(logging.DEBUG)
#not mandatory options below:
options = dict()
options['config'] = '/path/to/config/load.ini'
options['manual_start'] = "1"
options['user_options'] = [
'phantom.ammofile=/path/to/ammofile',
'phantom.rps_schedule=const(1,2m)',
]
log_filename = '/path/to/log/tank.log'
#======================================
apiworker = ApiWorker()
apiworker.init_logging(log_filename)
try:
apiworker.configure(options)
apiworker.perform_test()
except Exception, ex:
logger.error('Error trying to perform a test: %s', ex)

View File

@ -29,7 +29,7 @@ class InfluxUplinkPlugin(AbstractPlugin, AggregateResultListener):
self.decoder = None
def get_available_options(self):
return ["address", "port", "tank_tag"]
return ["address", "port", "tank_tag", "grafana_root", "grafana_dashboard"]
def start_test(self):
self.start_time = datetime.datetime.now()

View File

@ -41,7 +41,9 @@ class JMeterPlugin(AbstractPlugin):
return __file__
def get_available_options(self):
return ["jmx", "args", "jmeter_path", "buffer_size", "buffered_seconds", "use_argentum", "exclude_markers", ]
return ["jmx", "args", "jmeter_path", "buffer_size",
"buffered_seconds", "use_argentum", "exclude_markers",
"connect_time"]
def configure(self):
self.original_jmx = self.get_option("jmx")
@ -60,10 +62,15 @@ class JMeterPlugin(AbstractPlugin):
self.jmx = self.__add_jmeter_components(
self.original_jmx, self.jtl_file, self._get_variables())
self.core.add_artifact_file(self.jmx)
self.connect_time = self.get_option('connect_time', '') not in ['', '0']
def prepare_test(self):
self.args = [self.jmeter_path, "-n", "-t", self.jmx, '-j', self.jmeter_log,
'-Jjmeter.save.saveservice.default_delimiter=\\t']
connect_str = 'true'
if not self.connect_time:
connect_str = 'false'
self.args += ['-Jjmeter.save.saveservice.connect_time=%s' % connect_str]
self.args += tankcore.splitstring(self.user_args)
aggregator = None
@ -311,13 +318,18 @@ class JMeterReader(AbstractReader):
else:
self.data_queue.append(cur_time)
self.data_buffer[cur_time] = []
connect_value = 0
if self.jmeter.connect_time:
connect_value = int(data[9])
# marker, threads, overallRT, httpCode, netCode
data_item = [
data[2], int(data[7]), int(data[1]), self.exc_to_http(data[3]), netcode]
# bytes: sent received
data_item += [0, int(data[5])]
# connect send latency receive
data_item += [0, 0, int(data[8]), int(data[1]) - int(data[8])]
data_item += [connect_value, 0, int(data[8]), int(data[1]) - int(data[8])]
# accuracy
data_item += [0]
self.data_buffer[cur_time].append(data_item)

View File

@ -18,6 +18,7 @@ from ApacheBenchmark import ApacheBenchmarkPlugin
from JMeter import JMeterPlugin
from Monitoring import MonitoringPlugin
from Phantom import PhantomPlugin
from Pandora import PandoraPlugin
from yandextank.core import AbstractPlugin
@ -119,6 +120,13 @@ class LoadosophiaPlugin(AbstractPlugin, AggregateResultListener):
except KeyError:
self.log.debug("AB not found")
# pandora
try:
pandora = self.core.get_plugin_of_type(PandoraPlugin)
main_file = pandora.sample_log
except KeyError:
self.log.debug("Pandora not found")
if not main_file:
self.log.warn("No file to upload to Loadosophia")
@ -162,7 +170,7 @@ class LoadosophiaClient:
""" Send files to loadosophia """
if not self.token:
msg = "Loadosophia.org uploading disabled, please set loadosophia.token option to enable it, "
msg += "get token at https://loadosophia.org/service/upload/token/"
msg += "get token at https://loadosophia.org/gui/settings/"
self.log.warning(msg)
else:
if not self.address:

View File

@ -57,12 +57,38 @@ class SecuredShell(object):
client = SSHClient()
client.load_system_host_keys()
client.set_missing_host_key_policy(AutoAddPolicy())
client.connect(
self.host,
port=self.port,
username=self.username,
timeout=self.timeout,
)
try:
client.connect(
self.host,
port=self.port,
username=self.username,
timeout=self.timeout,
)
except ValueError, e:
logger.error(e)
logger.warning("""
Patching Crypto.Cipher.AES.new and making another attempt.
See here for the details:
http://uucode.com/blog/2015/02/20/workaround-for-ctr-mode-needs-counter-parameter-not-iv/
""")
client.close()
import Crypto.Cipher.AES
orig_new = Crypto.Cipher.AES.new
def fixed_AES_new(key, *ls):
if Crypto.Cipher.AES.MODE_CTR == ls[0]:
ls = list(ls)
ls[1] = ''
return orig_new(key, *ls)
Crypto.Cipher.AES.new = fixed_AES_new
client.connect(
self.host,
port=self.port,
username=self.username,
timeout=self.timeout,
)
return client
def execute(self, cmd):
@ -383,7 +409,7 @@ class MonitoringCollector(object):
'CPU': 'user,system,iowait',
'Memory': 'free,cached,used',
'Disk': 'read,write',
'Net': 'recv,send',
'Net': 'recv,send,rx,tx',
}
default_metric = ['CPU', 'Memory', 'Disk', 'Net']

View File

@ -64,7 +64,7 @@ class PandoraConfig(object):
return {"Pools": [p.data() for p in self.pools]}
def json(self):
return json.dumps(self.data())
return json.dumps(self.data(), indent=2)
def add_pool(self, pool_config):
self.pools.append(pool_config)
@ -101,7 +101,7 @@ class PoolConfig(object):
self.config["Gun"]["Parameters"]["SSL"] = ssl
def set_gun_type(self, gun_type):
self.config["Gun"]["Type"] = gun_type
self.config["Gun"]["GunType"] = gun_type
def data(self):
return self.config

View File

@ -79,7 +79,7 @@ class PandoraPlugin(AbstractPlugin):
pool_config.set_target(target)
gun_type = self.get_option("gun_type", "http")
if gun_type is 'https':
if gun_type == 'https':
pool_config.set_ssl(True)
self.log.info("SSL is on")
gun_type = "http"

View File

@ -2,12 +2,10 @@ setup_t module_setup = setup_module_t {
dir = "$phantom_modules_path"
list = {
io_monitor
ssl
io_benchmark
io_benchmark_method_stream
io_benchmark_method_stream_ipv4
io_benchmark_method_stream_ipv6
io_benchmark_method_stream_transport_ssl
io_benchmark_method_stream_source_log
io_benchmark_method_stream_proto_none
io_benchmark_method_stream_proto_http

View File

@ -189,6 +189,8 @@ class PhantomPlugin(AbstractPlugin):
self.log.warn(
"Terminating phantom process with PID %s", self.process.pid)
self.process.terminate()
if self.process:
self.process.communicate()
else:
self.log.debug("Seems phantom finished OK")
if self.phantom_stderr:

View File

@ -84,6 +84,9 @@ class PhantomConfig:
for stream in self.streams:
stream.read_config()
if any(stream.ssl for stream in self.streams):
self.additional_libs+=' ssl io_benchmark_method_stream_transport_ssl'
def compose_config(self):
""" Generate phantom tool run config """
streams_config = ''

View File

@ -10,6 +10,7 @@ from sqlalchemy import create_engine
from sqlalchemy import exc
import requests
import imp
requests_logger = logging.getLogger('requests')
requests_logger.setLevel(logging.WARNING)
@ -124,11 +125,15 @@ class CustomGun(AbstractPlugin):
def __init__(self, core):
self.log = logging.getLogger(__name__)
AbstractPlugin.__init__(self, core)
module_path = self.get_option("module_path", "")
module_path = self.get_option("module_path", "").split()
module_name = self.get_option("module_name")
if module_path:
sys.path.append(module_path)
self.module = __import__(module_name)
#imp allows to avoid custom module caching
fp, pathname, description = imp.find_module(module_name, module_path)
try:
self.module = imp.load_module(module_name, fp, pathname, description)
finally:
if fp:
fp.close()
def shoot(self, missile, marker, results):
self.module.shoot(missile, marker, results)

View File

@ -226,14 +226,14 @@ class StepperWrapper(object):
if self.use_caching:
sep = "|"
hasher = hashlib.md5()
hashed_str = "cache version 5" + sep + \
hashed_str = "cache version 6" + sep + \
';'.join(self.instances_schedule) + sep + str(self.loop_limit)
hashed_str += sep + str(self.ammo_limit) + sep + ';'.join(
self.rps_schedule) + sep + str(self.autocases)
hashed_str += sep + \
";".join(self.uris) + sep + ";".join(
self.headers) + sep + self.http_ver + sep + ";".join(self.chosen_cases)
hashed_str += sep + str(self.enum_ammo)
hashed_str += sep + str(self.enum_ammo) + sep + str(self.ammo_type)
if self.instances_schedule:
hashed_str += sep + str(self.instances)
if self.ammo_file:

View File

@ -35,6 +35,7 @@ def parse_duration(duration):
def parse_token(time, multiplier):
multipliers = {
'd': 86400,
'h': 3600,
'm': 60,
's': 1,
@ -171,7 +172,7 @@ class HttpOpener(object):
"Ammofile has already been downloaded to %s . Using it..", tmpfile_path)
else:
logging.info("Downloading ammofile to %s", tmpfile_path)
data = requests.get(self.url)
data = requests.get(self.url, verify=False)
f = open(tmpfile_path, "wb")
f.write(data.content)
f.close()
@ -190,7 +191,7 @@ class HttpOpener(object):
"Ammofile has already been downloaded to %s . Using it..", tmpfile_path)
else:
logging.info("Downloading ammofile to %s", tmpfile_path)
data = requests.get(self.url)
data = requests.get(self.url, verify=False)
f = open(tmpfile_path, "wb")
f.write(data.content)
f.close()