fasthttp

package module
v0.0.0-...-2ecc584 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 10, 2024 License: Apache-2.0 Imports: 26 Imported by: 0

README

xk6-fasthttp

Build status Go Report Card GoDoc

The xk6-fasthttp project is a k6 extension that enables k6 users to send a higher RPS (request per second) for HTTP/1.1 than with the standard k6 build. It achieves this by using the fasthttp library which has lots of memory/CPU optimization and by optimizing the library which checks the response status.

This is intended for users who wish to stress test a HTTP/1.1 server with a higher RPS than normally possible with k6.

Note this extension only supports HTTP/1.1.

Features

  • Increased RPS on HTTPS connections of 74%
  • Increased RPS on HTTP connections of 75%
  • Ability to stream files from disk with FileStream so k6 doesn't run out of memory
  • Supports JSON/DOM manipulation same as k6/http

Benchmarks

Using the standard k6/http library:

import http from 'k6/http';
import { check } from 'k6';

export const options = {
	insecureSkipTLSVerify: true,
};

// Simulated user behavior
export default function () {
	let res = http.post("https://localhost:8080");
	check(res, { 'status was 200': (r) => r.status == 200 });
}

Results:

./k6 run -u 250 -d 10s -q ./std-k6.js 

     ✓ status was 200

     checks.........................: 100.00% ✓ 65683       ✗ 0    
     data_received..................: 9.9 MB  988 kB/s
     data_sent......................: 2.6 MB  260 kB/s
     http_req_blocked...............: avg=2.47ms   min=650ns    med=1.22µs   max=1.27s    p(90)=1.55µs   p(95)=1.68µs  
     http_req_connecting............: avg=8.39µs   min=0s       med=0s       max=26ms     p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=33.81ms  min=427.75µs med=25.52ms  max=854.5ms  p(90)=67.87ms  p(95)=86.46ms 
       { expected_response:true }...: avg=33.81ms  min=427.75µs med=25.52ms  max=854.5ms  p(90)=67.87ms  p(95)=86.46ms 
     http_req_failed................: 0.00%   ✓ 0           ✗ 65683
     http_req_receiving.............: avg=9.06ms   min=35.49µs  med=4.06ms   max=135.34ms p(90)=22.82ms  p(95)=35.3ms  
     http_req_sending...............: avg=362.49µs min=71.45µs  med=108.92µs max=166.3ms  p(90)=155.04µs p(95)=246.26µs
     http_req_tls_handshaking.......: avg=2.45ms   min=0s       med=0s       max=1.27s    p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=24.38ms  min=0s       med=18.07ms  max=850.99ms p(90)=50.25ms  p(95)=63.8ms  
     http_reqs......................: 65683   6554.766172/s
     iteration_duration.............: avg=37.55ms  min=705.75µs med=26.87ms  max=1.37s    p(90)=70.37ms  p(95)=90.76ms 
     iterations.....................: 65683   6554.766172/s
     vus............................: 250     min=250       max=250
     vus_max........................: 250     min=250       max=250


Using k6/x/fasthttp library:

import { Request, Client, checkstatus } from "k6/x/fasthttp"

const config = {
	max_conns_per_host: 1,
	tls_config: {
		insecure_skip_verify: true,
	}
}


const client = new Client(config);

let req = new Request("https://localhost:8080/");


// Simulated user behavior
export default function () {
		let res = client.get(req);
		checkstatus(200, res);
}

Results:

./k6 run -u 250 -d 10s -q ./file-stream-upload.js 

     ✓ check status is 200

     checks...............: 100.00% ✓ 117129       ✗ 0    
     data_received........: 0 B     0 B/s
     data_sent............: 0 B     0 B/s
     http_req_duration....: avg=20.44ms min=223.18µs med=12.93ms max=1.38s p(90)=41.39ms p(95)=55.58ms
     http_reqs............: 116879  11671.351065/s
     iteration_duration...: avg=21.17ms min=311.78µs med=13.5ms  max=1.38s p(90)=43.01ms p(95)=57.71ms
     iterations...........: 117129  11696.315668/s
     vus..................: 250     min=250        max=250
     vus_max..............: 250     min=250        max=250


Can see improved RPS of 11671.351065/s compared to 6554.766172/s

Streaming

K6 currently doesn't allow streaming files from disk when uploading. This extension introduces this feature which can be useful to keep the memory footprint low as the whole file does not need loading into memory first and allows for faster uploads:

import { Request, Client, FileStream, checkstatus } from "k6/x/fasthttp"

const config = {
	max_conns_per_host: 1,
	tls_config: {
		insecure_skip_verify: true,
	}
}

const client = new Client(config);

const binFile = new FileStream('/home/john/my-large-data.bin');


let req = new Request("https://localhost:8080/", {
	body : binFile
});


// Simulated user behavior
export default function () {
		let res = client.post(req);
		checkstatus(200, res);
}

Install

Requires Go >= 1.20

  1. Install xk6:
go install go.k6.io/xk6/cmd/xk6@latest
  1. Build the binary with the latest extension:
xk6 build --with github.com/domsolutions/xk6-fasthttp@latest

Configuration

Client

The Client object takes the following configuration options in its constructor with default values as below:

{
  // timeout for attempting connection
  "dial_timeout": 5, 
  // optional proxy to connect to i.e. "username:password@localhost:9050"    
  "proxy": "",
  // max connection duration, 0 is unlimited
  "max_conn_duration": 0,
  // user agent to send in HTTP header
  "user_agent": "",
  // Per-connection buffer size for responses' reading. 0 is unlimited
  "read_buffer_size": 0,
  // Per-connection buffer size for requests' writing.
  "write_buffer_size": 0,
  // Maximum duration for full response reading (including body). 0 is unlimited
  "read_timeout": 0,
  // Maximum duration for full request writing (including body).
  "write_timeout": 0,
  // Maximum number of connections per each host which may be established.
  "max_conns_per_host": 1,
  "tls_config": {
        // skip CA signer verification - useful for localhost testing
        "insecure_skip_verify": false,
        // private key file path for mTLS handshake
        "private_key": "",
        // certificate file path for mTLS handshake
        "certificate": ""    
  }
}
Request

The Request object takes the following configuration options in its constructor with default values as below:

{
    // whehter to exit with error if a request fails
    "throw": false,
    // disable keeping connection alive between requests
    "disable_keep_alive": false,
    // override the host header
    "host": "",
    // object of HTTP headers
    "headers":{},
    // body to send
    "body": "<FileStream><String>",
    // expected response type: text,binary,none. If none response body will be discarded
    "response_type": "text"
}

Not supported

  • The fasthttp library lacks certain observability features which the standard HTTP package has so we lose these metrics:
     data_received..................: 9.9 MB  988 kB/s
     data_sent......................: 2.6 MB  260 kB/s
     http_req_blocked...............: avg=2.47ms   min=650ns    med=1.22µs   max=1.27s    p(90)=1.55µs   p(95)=1.68µs  
     http_req_connecting............: avg=8.39µs   min=0s       med=0s       max=26ms     p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=33.81ms  min=427.75µs med=25.52ms  max=854.5ms  p(90)=67.87ms  p(95)=86.46ms 
       { expected_response:true }...: avg=33.81ms  min=427.75µs med=25.52ms  max=854.5ms  p(90)=67.87ms  p(95)=86.46ms 
     http_req_failed................: 0.00%   ✓ 0           ✗ 65683
     http_req_receiving.............: avg=9.06ms   min=35.49µs  med=4.06ms   max=135.34ms p(90)=22.82ms  p(95)=35.3ms  
     http_req_sending...............: avg=362.49µs min=71.45µs  med=108.92µs max=166.3ms  p(90)=155.04µs p(95)=246.26µs
     http_req_tls_handshaking.......: avg=2.45ms   min=0s       med=0s       max=1.27s    p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=24.38ms  min=0s       med=18.07ms  max=850.99ms p(90)=50.25ms  p(95)=63.8ms  

In future releases this may become available, will work on creating a PR against fasthttp

  • Currently doesn't support cookie jars
  • Uncompressing response bodies i.e. gzip

If these features are required, should consider using k6/http package, or create an issue and the work can be planned.

Optimization tips

Create the request object in the init context so it doesn't repeatedly get created on every iteration (safe for reuse within VU) i.e:

import { Request, Client, checkstatus } from "k6/x/fasthttp"

const config = {
	max_conns_per_host: 1,
	tls_config: {
		insecure_skip_verify: true,
	}
}

const client = new Client();
let req = new Request("http://localhost:8080/");

export default function () {
		let res = client.post(req);
		checkstatus(200, res);
}

Also use checkstatus to verify the status instead of:

import { check } from 'k6';

as checkstatus doesn't use a closure, so no assertion needed so less CPU cycles.

Examples

Can find more examples here

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

func (*Client) Delete

func (c *Client) Delete(r *goja.Object) (*Response, error)

func (*Client) Get

func (c *Client) Get(r *goja.Object) (*Response, error)

func (*Client) Options

func (c *Client) Options(r *goja.Object) (*Response, error)

func (*Client) Patch

func (c *Client) Patch(r *goja.Object) (*Response, error)

func (*Client) Post

func (c *Client) Post(r *goja.Object) (*Response, error)

func (*Client) Put

func (c *Client) Put(r *goja.Object) (*Response, error)

type ClientConfig

type ClientConfig struct {
	DialTimeout     int
	Proxy           string
	MaxConnDuration int
	UserAgent       string
	ReadBufferSize  int
	WriteBufferSize int
	ReadTimeout     int
	WriteTimeout    int
	MaxConnsPerHost int
	TLSConfig       TLSConfig
}

type FileStream

type FileStream struct {
	*os.File
}

func (*FileStream) Close

func (s *FileStream) Close() error

type ModuleInstance

type ModuleInstance struct {
	// contains filtered or unexported fields
}

ModuleInstance represents an instance of the HTTP module for every VU.

func (*ModuleInstance) CheckStatus

func (mi *ModuleInstance) CheckStatus(wantStatus int, r *goja.Object, extras ...goja.Value) (bool, error)

func (*ModuleInstance) Client

func (mi *ModuleInstance) Client(call goja.ConstructorCall, rt *goja.Runtime) *goja.Object

func (*ModuleInstance) Exports

func (mi *ModuleInstance) Exports() modules.Exports

Exports returns the JS values this module exports.

func (*ModuleInstance) FileStream

func (mi *ModuleInstance) FileStream(call goja.ConstructorCall, rt *goja.Runtime) *goja.Object

func (*ModuleInstance) Request

func (mi *ModuleInstance) Request(call goja.ConstructorCall, rt *goja.Runtime) *goja.Object

RequestWrapper Create new request with New RequestWrapper({})

type RequestWrapper

type RequestWrapper struct {
	Throw            bool
	DisableKeepAlive bool
	Url              string
	Host             string
	Headers          map[string]string
	Body             interface{}

	ResponseType string
	// contains filtered or unexported fields
}

type Response

type Response struct {
	*httpext.Response `js:"-"`
	// contains filtered or unexported fields
}

Response is a representation of an HTTP response to be returned to the goja VM

func (res *Response) ClickLink(args ...goja.Value) (*Response, error)

ClickLink parses the body as an html, looks for a link and than makes a request as if the link was clicked

func (*Response) HTML

func (res *Response) HTML(selector ...string) html.Selection

HTML returns the body as an html.Selection

func (*Response) JSON

func (res *Response) JSON(selector ...string) goja.Value

JSON parses the body of a response as JSON and returns it to the goja VM.

func (*Response) SubmitForm

func (res *Response) SubmitForm(args ...goja.Value) (*Response, error)

SubmitForm parses the body as an html looking for a from and then submitting it TODO: document the actual arguments that can be provided

type RootModule

type RootModule struct{}

func New

func New() *RootModule

New returns a pointer to a new HTTP RootModule.

func (*RootModule) NewModuleInstance

func (r *RootModule) NewModuleInstance(vu modules.VU) modules.Instance

NewModuleInstance returns an HTTP module instance for each VU.

type TLSConfig

type TLSConfig struct {
	InsecureSkipVerify bool
	PrivateKey         string
	Certificate        string
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL