Sumeet Pareek

An open mind. A romantic heart. And some simple and audacious surprises!

Load testing with `wrk` when every reqeust has unique http hmac authorization header

Load testing tools

To load test your APIs or WebApp, you can pick from a bunch of tools. Apache Jmeter is quite feature rich, but comes with a overwhelming and convoluted UI. BlazeMeter is its SaaS equivalent. The good old ab or Apache Benchmark is still quick and handy, while the relatively new kid on the block, wrk is being raved about. Loader.io, Blitz.io and many others are cloud based. So basically a lot of them!

It is generally a good idea to use multiple load testing tools. Doing so helps you validate your benchmarks and load test results. You might sometimes be surprised by the differece in results when using different tools.

After recently using wrk it has become my load testing tool of choice. It is written in C, is pretty quick and light weight, it is open source and was created by Will Glozer.

Basic wrk usage

A simple wrk command can be

	wrk -t12 -c400 -d30s http://127.0.0.1:8080/index.html
	

This runs benchmark for 30 seconds, over 400 concurrent connections, for a duration of 30 seconds, against the web page path.

See, that was simple! Rest of the options of using wrk are simple as well.

	Usage: wrk <options> <url>
	  Options:
	    -c, --connections <N>  Connections to keep open
	    -d, --duration    <T>  Duration of test
	    -t, --threads     <N>  Number of threads to use

	    -s, --script      <S>  Load Lua script file
	    -H, --header      <H>  Add header to request
	        --latency          Print latency statistics
	        --timeout     <T>  Socket/request timeout
	    -v, --version          Print version details

	  Numeric arguments may include a SI unit (1k, 1M, 1G)
	  Time arguments may include a time unit (2s, 2m, 2h)
	

Advanced wrk usage

Wrk comes with a just in time Lua compiler, and allows you to use scipt in Lua to do kinda complex requests, or capture the results and process them differently, and things like that. Some examples of wrk lua scripts can be found in the scripts directory within wrk. While I would highly recommend going through this blog by Michal from DigitalOcean.

Handle unique header in every request

To prevent rogue access of APIs, AWS uses a technique to have unique authorization header in every request, that can be independently generated on the client and server side. They call this custom HTTP scheme based on a keyed-HMAC.

The http-hmac authorization header looks like,

	Authorization: AWS AWSAccessKeyId:Signature
	

It is generated using the scheme,

Authorization = "AWS" + " " + AWSAccessKeyId + ":" + Signature;
Signature = Base64( HMAC-SHA1( YourSecretAccessKeyID, UTF-8-Encoding-Of( StringToSign ) ) );

StringToSign = HTTP-Verb + "\n" +
	Content-MD5 + "\n" +
	Content-Type + "\n" +
	Date + "\n" +
	CanonicalizedAmzHeaders +
	CanonicalizedResource;

Now, when load testing an API that uses http-hmac authorization, every request in your generated load must have a unique authorization header. If you have limited lua scripting experience like me, you can do the following -

  1. Write a routine in Go to generate request authorization headers
  2. Do not actually send requests via the Go routine
  3. Use the wrk-lua script to read the generated headers and attach them to the reqeust
  4. Send your load requests via the wrk-lua script wrk -t12 -c400 -d30s -s [YOUR-SCRIPT-HERE] http://127.0.0.1:8080/index.html

This way you get maxium load generated, are able to use wrk’s concurrent threads, have unique authorization header in each request, and get all bechmarks output via wrk.

An example of such a lua-script would be,

-- script that hits `/example_api` endpoint with authorized requests

local headers = {}

-- used to sanitize auth header values
function sanitize_value(str)
  if str == nil then
    return ""
  else
    return string.gsub(str, '[%[%]]', '')
  end
end

-- create authorized request using wrk.format function
-- refer: https://github.com/wg/wrk/blob/master/SCRIPTING
request = function()
  -- every request would have different auth headers
  -- unless timestamp and content are exactly the same

  -- setup API path
  path = "/example_api"

  -- setup body payload
  -- NOTE: make sure you use the same body payload to generate the auth headers
  body = '{"key":"value"}'

  -- command to generate auth headers
  -- NOTE: same body payload is used as above, BUT we have type it again since here it needs some escaping
  cmd = '~/go-workspace/bin/generate-headers http://your-example-service.com/example_api -d \'{"key":"value"}\''

  -- parse command output into variables
  local f = io.popen(cmd, 'r')
  local header_content_type = f:read()
  local header_date = f:read()
  local header_auth_token = f:read()
  f:close()

  -- setup headers for an authorized request
  headers["Content-Type"] = header_content_type
  headers["Date"] = header_date
  headers["Authorization"] = header_auth_token

  -- return the authorized request
  return wrk.format(nil, path, headers, body)
end

-- `/example_api` is a GET call
wrk.method = "GET"

To get more comfortable with load testing using wrk and lua scripting within wrk, read this blog by Michal from DigitalOcean.

More later

@todo

  • how to fix max open files issue
  • docker containers and aws provisioning for load tests