Awesome
Encarno - The Efficient Load Generator
The name comes from portuguese encarno (/əŋˈkar.nu/) and means roughly "I impersonate".
Key Features
- HTTP 1.1 protocol testing, TLS supported, IPv6 supported
- flexible load profiles in "open" and "closed" workload modes
- accurate load generating up to tens of thousands hits/s
- precise result measurements of nanosecond resolution
- efficient and low overhead (written in Go)
- minimalistic scripting, with regex extracts and asserts
Usage as Taurus Module
The easiest way to get started is to install the Python package using pip
, which will install also Taurus if needed:
pip install encarno
After that, running any test with executor: encarno
will automatically download the appropriate version of the Encarno binary. In case you need to point the tool to a custom binary, use this config snippet:
modules:
encarno:
path: /my/custom/encarno
To run the test, use the usual Taurus command-line with config files. See below for the config examples:
bzt my-config-with-encarno.yml
Docker image is also available for containerized environments:
docker run -it -v `pwd`:/conf undera/encarno /conf/config.yml
Closed Workload Mode
Closed workload is the load testing mode when relatively small pool of workers hit the service as fast as they can. As service reaches the bottleneck, the response time grows and workers produce less and less hits per second. This kind of workload is typical for service-to-service communications inside cluster.
In typical tests, the number of workers gradually increases over time to reveal the capacity limit of the service. The result of such test is a scalability profile for the service, also offering the estimation of throughput limits for the open workload tests.
The Taurus config file for closed workload using Encarno:
---
execution:
- executor: encarno
scenario: simple
concurrency: 50
ramp-up: 5m
# steps: 10 # breaks ramp-up into N flat steps
scenarios:
simple:
requests:
- http://service.net:8080/api/path
Note that hold-for
and iterations
load profile options are also supported, if you need them. Scenario definition can be as sophisticated as you need it.
Open Workload Mode
Open workload reflects public service scenario, when the number of clients is so big that slowing responses do not lead to decrease in service requests. This is achieved in tests by using large pool of workers that hit service according to requests schedule. Usually, that schedule is growing linearly, to reveal the breaking point of the service. Or a steady rate is applied to measure performance quality characteristics for the service, such as response time percentiles.
The main value we configure for open workload tests is the throughput
, which is the number of requests per second to perform. For the breaking point (aka stress test) scenarios we configure it above the capacity limit (~factor x1.5), for quality measurement we aim below the limit (~factor 1/2 or 80%). Usually we also put some limit on possible worker count concurrency
, due to RAM/CPU being finite for load generator machine.
Stress test config example:
---
execution:
- executor: encarno
scenario: simple
concurrency: 5000 # it's now the limit, not desired level
throughput: 25000 # hit/s beyond server's capacity
ramp-up: 5m
# steps: 10 # breaks ramp-up into N flat steps
scenarios:
simple:
requests:
- http://service.net:8080/api/path
Quality measurement config:
---
execution:
- executor: encarno
scenario: simple
concurrency: 5000 # it's now the limit, not desired level
throughput: 10000 # hit/s below breaking point
ramp-up: 1m # should be much shorter than hold-for
hold-for: 20m # enough time to accumulate statistics
scenarios:
simple:
requests:
- http://service.net:8080/api/path
Scripting Capabilities
There are 3 ways to specify inputs for Encarno test: YAML definition via requests
, URLs list via requests
and externally generated input file.
Requests Defined in YAML
The YAML definition looks like typical Taurus script in requests
section under scenarios
:
scenarios:
simple:
default-address: http://i-am-used-by-default:8000
timeout: 5s
variables:
var1: someval
headers:
X-My-Global-Header: for all requests
requests:
- /assumes-default-address
- https://full-url-possible/here
- label: extended detailed request
url: /path
method: POST
headers:
X-One-More-Header: or many
Content-Type: application/json
body: '{"can be": "like this"}'
# body-file: some.json # alternative to inline `body`
- label: regex features, source
url: /?variable=${var1}
extract-regexp:
etag: 'ETag: (".+")'
date: 'Date: ([^\n]+)'
- label: regex features, destination
url: /path
method: POST
headers:
If-None-Match: '${etag}'
body: "date was ${date}"
assert:
- 'HTTP/1.1 200 OK'
- not: true
contains:
- 'error'
Note that timeout
is only supported on the global level, affecting all the requests equally.
The variables
, assert
and extract-regexp
features work on the full request and response payload text, without breakdown into URI/status/headers/body. Note that variable and regexp usage will make your tests to work a bit slower, due to the processing overhead. Also some more RAM will be used by the load generator.
For HTTP, there is special :content-length:
variable to be used to obtain correct body length when variables usage alters it dynamically. Taurus module will automatically use that variable when generating POST requests.
URLs From Text File
There are the cases when you have a long list of URLs parsed from access.log
of your server, or dumped from database, or generated by some script. In that situation, you can specify the file containing URLs as value for requests
option:
scenarios:
simple:
default-address: http://i-am-used-by-default:8000
timeout: 5s
headers:
X-My-Global-Header: for all requests
requests: urls-file.txt
The format of the urls-file.txt
is trivial. It can be either the list of URLs, one per line, or it can also contain request label
per URL, divided by space:
/just-url/assumes-default-address
http://full-url/goes/here
the_label_before_space /and/then/url
All the global settings from scenario still apply to URLs file case, the HTTP method is assumed to be GET
. Variable evaluation also works for this kind of input.
Custom Payload File
Finally, if you need full control over what is sent over network, you can use script file in Encarno's internal payload input format, which ignores most of other scenario options:
scenarios:
simple:
script: custom_input.enc
# input-strings: custom_input.str # for the indexed strings, if needed
TLS Configuration
For the cases, when connecting to server needs special TLS settings like custom cipher suites or TLS versions, please use the following config snippet:
modules:
encarno:
tls-config:
insecureskipverify: true # allow self-signed and invalid certificates, default: false
minversion: 771 # min version of TLS, default is 1.2
maxversion: 772 # max version of TLS, default is 1.3
tlsciphersuites: # list of cipher suite names to use, optional
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_RSA_WITH_AES_128_CBC_SHA
- TLS_RSA_WITH_AES_256_GCM_SHA384
Possible values for TLS version:
- TLS 1.3 =
772
- TLS 1.2 =
771
- TLS 1.1 =
770
- TLS 1.0 =
769
Debug Trace Log
If you want to see what exactly were sent to the server and what was the response, you may enable the detailed trace log. Encarno would write encarno_trace.txt
file, containing all meta information, request and response payloads:
modules:
encarno:
trace-level: 500 # defaults to 1000
The trace option logic is "log records which status code is greater or equal X". All network level errors have special code 999
. Be careful with trace level, it can produce large files quickly and run you out of disk space. It is recommended to only enable tracing for certain kind of errors.
Some level examples:
999
or600
- only the network level errors500
- all 5xx server errors plus network level errors400
- client-side, server-side, and network level0
- dump all the traffic1000
- don't write trace file
Sidebar Widget
When Taurus displays console dashboard, Encarno provides additional information in its sidebar widget:
X wait
- the number of workers that are waiting to get input payload/schedule, if above zero - your load generator is at capacityX busy
- the number of workers actively doing job, synonym to concurrencyX sleep
- number of workers waiting for the right time to query (only for open workload), if zero - your load generator is likely at capacityX lag
- the average lag between scheduled time to request and actual time (only for open workload), if above zero - your load generator is likely at capacity
Please note that widget information may be ahead of aggregate statistics, due to Taurus reporting facility still crunching numbers.
Standalone Usage
Encarno tool is designed to be used as part of some wrapper (e.g. Taurus), thus it does not contain much features for result processing and input configuration. If you still want to use the tool on the lower level, this section is for you.
Building from Source
To build the binary: go build -o bin/encarno cmd/encarno/main.go
Config Format
Here's the full config snippet with some inline comments:
input:
payloadfile: "" # path to payload input file, mandatory
iterationlimit: 0 # if above zero, limits number of times the payload file is looped over
stringsfile: "" # if specified, contains string index for payload file
enableregexes: false # enables regex related processing
output:
ldjsonfile: "" # optional, path to results file in LDJSON format
reqrespfile: "" # optional, path to detailed trace file
reqrespfilelevel: 0 # trace level for the above option
binaryfile: "" # optional path to binary results file, also needs strings file if specified
stringsfile: "" # for the binary file, the place to write output string index
workers:
mode: "" # mandatory workload mode, values are 'open' or 'closed'
workloadschedule: # mandatory, the list of linear chunks of workload schedule
- levelstart: 0 # starting level for chunk
levelend: 10 # ending level for chunk
duration: 5s # duration of chunk
startingworkers: 0 # optional, number of initial workers to spawn in open workload
maxworkers: 0 # the limit of workers to spawn
protocol:
driver: "" # mandatory, protocol type to use, defaults to 'http', can also be 'dummy'
maxconnections: 0 # limit of connections per host in HTTP
timeout: 0s # operation timeout
tlsconf: # TLS custom settings
insecureskipverify: false
minversion: 0
maxversion: 0
tlsciphersuites: []
Payload Input Format
The format is like that because of possible binary payloads. It starts with single-line JSON of metadata, ending with \n
, then plen
number of bytes, followed by any number of \r
, \n
or \r\n
.
{"plen": 53, "address": "http://localhost:8070", "label": "/"}
GET / HTTP/1.1
Host: localhost:8070
X-Marker: value
{"plen": 61, "address": "http://localhost:8070", "label": "/gimme404"}
GET /gimme404 HTTP/1.1
Host: localhost:8070
X-Marker: value
The metadata may contain optional fields for variable evaluation. Below is formatted JSON of metadata for easier understanding:
{
"plen": 0, // required, payload length
"label": "", // item label for grouping in analysis
"address": "", // address for service under test
"replaces": ["var1", "var2"], // list of variables to evaluate inside payload
"extracts": {
"varname": { // assign the result to this variable name
"re": ".+", // apply this regular expression
"matchNo": 0, // take this match from results, -1 means random
"groupNo": 0 // take specific capture group from matched regex
}
},
"asserts": [
{
"re": ".+", // regex that must exist in response data
"invert": false // invert the assertion, it would fail if regex is found
}
]
}
The default Taurus configuration would write additional strings index .istr
file and use a
and l
options with string numbers. This is done to minimize the resource footprint. In case you want to see the payload file generated by Taurus without indexed strings, use following option:
modules:
encarno:
index-input-strings: false
Results Output Formats
Special code 999 is used for network-level errors.
It is possible to switch Encarno from default binary+strings format of output file, into single human-readable LDSON file. It is done via special option:
modules:
encarno:
output-format: ldjson # by default, it's "bin"
History
It is written as a replacement for the old phantom+yandex-tank combination.Those were too "phantom" (and too unmaintained), we're trying to be "in flesh" analogue to it. The idea was to write a tool as precise as phantom, but using modern programming language (Go) and address wider spectrum of use-cases.
During implementation, it became apparent that some of phantom's concepts are not as important, namely pre-generated input file with schedule and payloads. Also, re-implementing HTTP protocol client was considered as an overkill. Maybe we have lost some speed because of that (we believe not drastically).
It is intentionally not fully-capable load testing tool, it is just load generator that assumes the input preparations and result analysis is done by wrapper scripts.
Changelog
0.5.3 -- 29 aug 2022
- requests from URLs file have default empty label, to avoid garbaging reports with too many labels
- retry reading string index file
0.5.1 -- 25 jul 2022
- regular expression evaluation, extractions and assertions
- make sure custom payload files work fine
- add external payload indexed example
0.4 -- 21 jun 2022
- binary output writer&reader, including strings externalization, helper tools to translate into human-readable
- fixes for ipv6, removed internal round-robin cache for DNS for now
0.2 and 0.3 -- 13 jun 2022
- improve automated release process: pypi package, docker image
0.1 -- 13 jun 2022
- add binary releases on GitHub
- add documentation
- auto-download binary (needs next release)
0.0 -- 10 jun 2022
- Simple CLI with one config file
- Open and closed workload support
- HTTP and dummy protocol types
- Input file with metadata in JSON line and full payload
- LDJSON output format
- Log file with health stats
- Taurus module with basic scripting
Roadmap
-
respect
iterations
option from Taurus config, test it, handle "only iterations and no duration is specified" -
when workers decrease (input exhausted or panics), reflect that in counters
-
unit tests and coverage
-
separate file for health status, with per-line flush?
-
fully binary input? is it worth it?
-
global method and body for urls-from-file case?
-
explain log file health KPIs meanings
-
Explain the difference from JMeter and others: How less flexible it is for JMeter, How more flexible it is for Hay and alikes
-
document output file formats
-
document indexed strings format
Parking lot
- auto-USL workload
- limit len of auto-label for long GET urls
- udp protocol nib
- Go plugins used for nib
<p align="center"><sup><sub><img src="https://raw.githubusercontent.com/lipis/flag-icons/main/flags/4x3/pt.svg" height="8" alt="PT"> Made in Portugal</sub></sup></p>