Wrk introduction

Before learning about wrk, my understanding of performance testing had been limited to ab (Apache Benchmark). I had the impression that the functionality of ab was relatively simple, that is, simply pressure testing a given API and then seeing how much concurrency was there. So I learned about it, and then I saw that other people were interested in wrk, so I recorded something about wrk in the meantime.

Functional overview

The basic function of wrk is actually very similar to ab, you can simply do some GET requests, and then calculate the QPS (Request Per Seconds) and RT (Response Time) and so on, but wrk can not directly perform POST requests (ab can). So, here’s a further important feature of wrk: it supports extensions via lua scripts, which allow you to

These are all extensions of the lua script, which will work much better than ab in this convenience. One interesting thing is that if you go to wrk, you will also find a wrk2 thing, it is actually a modified version of wrk, the author himself added a so-called constant throughput function, in fact, is not to test QPS, but to maintain a certain QPS, and then see how the server side of the response speed, how the performance, so In general, I don’t need wrk2.

Basic function demo

So I can only demonstrate a simple GET request here, which is to see how wrk performs these two simple operations. As for POST requests, I need to cover them in the next section Extended Features.

Extensions demo

lua script execution logic

Wrk’s POST requests cannot be executed directly via command-line arguments, so they need to be run by way of a lua script.

lua scripting framework

After reading the theoretical part, let’s look at the lua framework that corresponds to each phase, i.e., how lua scripts should be written as specified by wrk.

Startup phase

Once the lua thread starts, it calls a custom function in this phase.

  1. [[email protected].io]# cat setup.lua
  2. local counter = 1
  3. function setup(thread)
  4. thread:set("id", counter)
  5. table.insert(threads, thread)
  6. counter = counter + 1
  7. end

As you can see, during the startup phase, you can modify the content of the setup function as a custom startup content, in which you can modify the thread-related information, such as modifying the thread’s variables, modifying the thread’s server address, etc. Generally, you don’t have to modify it, but if you want to, here are some parameters of the function for you to use: | function(variable) name | function(s)

function (variable) name role
thread.addr variable: get/modify the address of the server
thread:get(name) function: get the global variables of the thread
thread:set(name, value) function: set the global variables of the thread, as demonstrated in the example above
thread:stop() function: close the thread, so the next two phases don’t need to be executed.
Run phase

The run phase is the core of wrk. In this section, you can have multiple functions that can be defined, namely

function name function
function init(args) lua thread will be executed once when it enters the run phase, but not afterwards, you can do some initialization of requests and so on in this phase
function delay() lua thread will be executed once when it enters the run phase.
function delay() The lua thread will call this once before each request is made.
function request() The lua thread will send the response to this function as a request.
function response(status, headers, body) The lua thread will call this function with the response of each request as an argument

Here is an example of how the code for each stage is written.

  1. [[email protected].io]# cat running.lua
  2. function init(args)
  3. requests = 0
  4. responses = 0
  5. local msg = "thread %d created"
  6. print(msg:format(id))
  7. end
  8. function delay()
  9. return math.random(10, 50)
  10. end
  11. function request()
  12. requests = requests + 1
  13. return wrk.request()
  14. end
  15. function response(status, headers, body)
  16. responses = responses + 1
  17. end

This is a simple example where each request is delayed by 10-50 milliseconds and the number of requests is recorded before the request is made and the number of responses to the request is recorded after the request is successful.

Ending phase

After the end, lua allows you to define your own processing results, where you can customize your output format, or if you don’t like the default output format, there is only one function that you need to define yourself: ```.

  1. [[email protected].io]# cat stop.lua
  2. function done(summary, latency, requests)
  3. for index, thread in ipairs(threads) do
  4. local id = thread:get("id")
  5. local requests = thread:get("requests")
  6. local responses = thread:get("responses")
  7. local msg = "thread %d made %d requests and got %d responses"
  8. print(msg:format(id, requests, responses))
  9. end
  10. end
Special example

If you just want to modify the request and don’t want to customize anything else, then you can write the functions inside the request directly in lua, without returning and without writing the function definition, as in the following POST example.

POST request

If we follow the lua framework described in the previous section, we could define the request format in request() and just define the request as POST, but, as I mentioned at the end, wrk has taken into account the commonness of this scenario and simplified the feature so that you can just write a script without having to define function and return, for example, like this.

  1. [[email protected].io]# cat post.lua
  2. wrk.method = "POST"
  3. wrk.body = "foo=bar&baz=quux"
  4. wrk.headers["Content-Type"] = "application/x-www-form-urlencoded"

This is an example of a POST, and then the lua script is loaded via wrk and run as follows

  1. [[email protected].io]# wrk -c3 -d1s -t2 -s . /scripts/post.lua http://localhost:5000/api/v1/login

Others

lua helper functions

functions explanation
wrk.format(method, path, headers, body) This function is typically used in the request() phase to return the request object

The Argument properties of done

done(summary, latency, requests) records a lot of data about the performance test, and contains the following fields.

Object Property Description
summary duration run duration in microseconds
summary requests total completed requests
summary bytes total bytes received
summary errors.connect total socket connection errors
summary errors.read total socket read errors
summary errors.write total socket write errors
summary errors.status total HTTP status codes > 399
summary errors.timeout total request timeouts
latency min minimum latency value reached during test
latency max maximum latency value reached during test
latency mean average latency value reached during test
latency stdev latency standard deviation
latency percentile(99.0) 99th percentile value
latency[i] raw latency data of request i ——

A complex sample

  1. [[email protected].io]# cat pipeline.lua
  2. init = function(args)
  3. local r = {}
  4. r[1] = wrk.format(nil, "/?foo")
  5. r[2] = wrk.format(nil, "/?bar")
  6. r[3] = wrk.format(nil, "/?baz")
  7. req = table.concat(r)
  8. end
  9. request = function()
  10. return req
  11. end