Git Product home page Git Product logo

f1's Introduction

Go Reference

f1

f1 is a flexible load testing framework using the go language for test scenarios. This allows test scenarios to be developed as code, utilising full development principles such as test driven development. Test scenarios with multiple stages and multiple modes are ideally suited to this environment.

At Form3, many of our test scenarios using this framework combine REST API calls with asynchronous notifications from message queues. To achieve this, we need to have a worker pool listening to messages on the queue and distribute them to the appropriate instance of an active test run. We use this with thousands of concurrent test iterations in tests covering millions of iterations and running for multiple days.

Usage

Writing load tests

Test scenarios consist of two stages:

  • Setup - represented by ScenarioFn which is called once at the start of a test; this may be useful for generating resources needed for all tests, or subscribing to message queues.
  • Run - represented by RunFn which is called for every iteration of the test, often in parallel with multiple goroutines.

Cleanup functions can be provided for both stages: Setup and Run which will be executed in LIFO order. These ScenarioFn and RunFn functions are defined as types in f1:

// ScenarioFn initialises a scenario and returns the iteration function (RunFn) to be invoked for every iteration
// of the tests.
type ScenarioFn func(t *T) RunFn

// RunFn performs a single iteration of the scenario. 't' may be used for asserting
// results or failing the scenario.
type RunFn func(t *T)

Writing tests is simply a case of implementing the types and registering them with f1:

package main

import (
	"fmt"

	"github.com/form3tech-oss/f1/v2/pkg/f1"
	"github.com/form3tech-oss/f1/v2/pkg/f1/testing"
)

func main() {
	// Create a new f1 instance, add all the scenarios and execute the f1 tool.
	// Any scenario that is added here can be executed like: `go run main.go run constant mySuperFastLoadTest`
	f1.New().Add("mySuperFastLoadTest", setupMySuperFastLoadTest).Execute()
}

// Performs any setup steps and returns a function to run on every iteration of the scenario
func setupMySuperFastLoadTest(t *testing.T) testing.RunFn {
	fmt.Println("Setup the scenario")
	
	// Register clean up function which will be invoked at the end of the scenario execution to clean up the setup
	t.Cleanup(func() {
		fmt.Println("Clean up the setup of the scenario")
	})
	
	runFn := func(t *testing.T) {
	    fmt.Println("Run the test")

		// Register clean up function for each test which will be invoked in LIFO order after each iteration 
		t.Cleanup(func() {
			fmt.Println("Clean up the test execution")
		})
	}

	return runFn
}

Running load tests

Once you have written a load test and compiled a binary test runner, you can use the various "trigger modes" that f1 supports. These are available as subcommands to the run command, so try running f1 run --help for more information). The trigger modes currently implemented are as follows:

  • constant - applies load at a constant rate (e.g. one request per second, irrespective of request duration).
  • staged - applies load at various stages (e.g. one request per second for 10s, then two per second for 10s).
  • users - applies load from a pool of users (e.g. requests from two users being sent sequentially - they are as fast or as slow as the requests themselves).
  • gaussian - applies load based on a Gaussian distribution (e.g. varies load throughout a given duration with a mean and standard deviation).
  • ramp - applies load constantly increasing or decreasing an initial load during a given ramp duration (e.g. from 0/s requests to 100/s requests during 10s).
  • file - applies load based on a yaml config file - the file can contain any of the previous load modes (e.g. "config-file-example.yaml").

Output description

Currently, output from running f1 load tests looks like that:

[   1s]  ✔    20  ✘     0 (20/s)   p(50): 16.899154ms,  p(95): 19.1134ms, p(100): 21.435088ms

It provides the following information:

  • [ 1s] how long the test has been running for,
  • ✔ 20 number of successful iterations,
  • ✘ 0 number of failed iterations,
  • (20/s) (attempted) rate,
  • p(50): 16.899154ms p(95): 19.1134ms p(100): 21.435088ms average time per iteration, for a given percentile.

Environment variables

Name Format Default Description
PROMETHEUS_PUSH_GATEWAY string - host:port or ip:port "" Configures the address of a Prometheus Push Gateway for exposing metrics. The prometheus job name configured will be f1-{scenario_name}. Disabled by default.
PROMETHEUS_NAMESPACE string "" Sets the metric label namespace to the specified value. Label is omitted if the value provided is empty.
PROMETHEUS_LABEL_ID string "" Sets the metric label id to the specified value. Label is omitted if the value provided is empty.
LOG_FILE_PATH string "" Specify the log file path if --verbose is enabled. The logfile path will be an automatically generated temp file if not specified.
TRACE bool "" If set to "true" detailed internal tracing is printed to stdout. Disabled by default.
FLUENTD_HOST string "" Enable log shipping to a fluentd host. Must together with FLUENTD_PORT. Disabled by default.
FLUENTD_PORT int "" Enable log shipping to a fluentd host. Must together with FLUENTD_HOST. Disabled by default.

Design decisions

Why did we decide to write our own load testing tool?

At Form3, we invest a lot of engineering time into load and performance testing of our platform. We initially used k6 to develop and run these tests, but this was problematic for us for a couple of reasons:

  1. The tests that k6 executes are written in Javascript - in order to test our platform, we often need to do things not easily done in Javascript (e.g. connect to SQS queues). The tests themselves can get quite complicated, and Javascript is not well suited to testing these sorts of tests.
  2. k6 only really supports a single model for applying load - users. This model assumes you have a finite pool of users, repeatedly making requests in sequence. This doesn't really work for us, since the payments industry has a pool of millions of users, each of whom could make a payment at any moment - when they do, they don't wait around for the previous customer to finish!

Enter f1

We started working on f1, because we already had a suite of load test scenarios that we had started writing in Go. k6 interfaced with these by making web requests to a server that actually ran the tests - a bit of a hack.

We wanted to be able to write the tests in Go in a native load testing framework, which also supported our use case of applying load more aggressively (without waiting for requests to finish).

f1 is the result. It supports writing load test scenarios natively in Go, which means you can make your tests as complicated as you like and test them well. It also has a variety of "trigger modes" (see above), which allow load to be applied in the same way as k6, but also in other, more aggressive modes. Writing new trigger modes is easy, so we welcome contributations to expand the list.

Contributions

If you'd like to help improve f1, please fork this repo and raise a PR!

f1's People

Contributors

admin-mark-howard avatar andykuszyk-form3 avatar blaze-form3 avatar bpiekny-form3 avatar carl-stlaurent-form3 avatar christownsend-f3 avatar damiankaminski-form3 avatar dependabot-preview[bot] avatar dependabot[bot] avatar dethi avatar dmytro-vovk-f3 avatar ekosov-form3 avatar ewilde-form3 avatar github-builduser-form3 avatar gkoutsoumpakis-form3 avatar jeeves-form3 avatar ken-che-form3 avatar markhowardform3 avatar martinreus-form3 avatar mebowler avatar michaelbowler-form3 avatar naag avatar neurogoblin avatar nicholas-form3 avatar nicholasham avatar nvloff-f3 avatar peterbuecker-form3 avatar rkorkosz-f3 avatar sofib-form3 avatar sshutovskyi-f3 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

f1's Issues

Add count to staged chart

Would be useful to show the total number of transactions that are generated by applying a given staged run (integral of the curve)

ie:
Screenshot 2020-12-11 at 10 52 16

Could show: The staged expression xxx generates n transactions

Dependabot can't resolve your Go dependency files

Dependabot can't resolve your Go dependency files.

As a result, Dependabot couldn't update your dependencies.

The error Dependabot encountered was:

go: github.com/blend/[email protected]+incompatible: reading github.com/blend/go-sdk/go.mod at revision v2.0.0: unknown revision v2.0.0

If you think the above is an error on Dependabot's side please don't hesitate to get in touch - we'll do whatever we can to fix it.

View the update logs.

Dependabot can't resolve your Go dependency files

Dependabot can't resolve your Go dependency files.

As a result, Dependabot couldn't update your dependencies.

The error Dependabot encountered was:

go: github.com/blend/[email protected]+incompatible: reading github.com/blend/go-sdk/go.mod at revision v2.0.0: unknown revision v2.0.0

If you think the above is an error on Dependabot's side please don't hesitate to get in touch - we'll do whatever we can to fix it.

View the update logs.

Passing flags to module

My load test needs some configuration, like API keys and url to hit, etc. Id like to use flags. Can you document/expose a way for me to pass those along? For now, I'm using environment variables.

Dependabot can't resolve your Go dependency files

Dependabot can't resolve your Go dependency files.

As a result, Dependabot couldn't update your dependencies.

The error Dependabot encountered was:

go: github.com/blend/[email protected]+incompatible: reading github.com/blend/go-sdk/go.mod at revision v2.0.0: unknown revision v2.0.0

If you think the above is an error on Dependabot's side please don't hesitate to get in touch - we'll do whatever we can to fix it.

View the update logs.

Possible rate calculation issue

While creating some test run scenarios for f1 defining constant rates, I found out that the calculated rate does not entirely work as expected; This was primarily discovered while creating f1-jobs ran in our staging env. Still need confirmation if coding a simple scenario locally would yield the same result (will do this once I have some time and open an example PR)

Example cases:

  • Constant rate of 1/300ms does not work at all
  • Constant rate of 1/215ms results in precisely 5TPS, when it should yield approx. 4.65. Additionally, setting rate to 1/299ms results in the same 5 TPS.

Improve CI

  • Make sure no breaking changes are introduced
  • Update action versions
  • Enable CodeQL
  • add badges to README

is there a binary?

The README references:

try running f1 run --help for more information)

which indicates there's an f1 binary, but it's not on the releases and I can't seem to go install it. Can you add usage/installation instructions?

Fix flaky tests

  • run tests without cache to reveal instability

Tests:

  • Fix randomly failing TestParameterised/finishes_at_ends_of_duration
    run_stage_test.go:382: 
        	Error Trace:	/home/runner/work/f1/f1/internal/run/run_stage_test.go:382
        	            				/home/runner/work/f1/f1/internal/run/run_cmd_test.go:395
        	Error:      	Not equal: 
        	            	expected: false
        	            	actual  : true
        	Test:       	TestParameterised/finishes_at_ends_of_duration
        	Messages:   	command failed
--- FAIL: TestParameterised (2.11s)
  • Fix randomly failing "TestRandomDistribution"
    run_stage_test.go:376: 
        	Error Trace:	/home/runner/work/f1/f1/internal/run/run_stage_test.go:376
        	            				/home/runner/work/f1/f1/internal/run/run_cmd_test.go:456
        	Error:      	"1" is not greater than "1"
        	Test:       	TestRandomDistribution
--- FAIL: TestRandomDistribution (0.50s)

https://github.com/form3tech-oss/f1/actions/runs/9153279187/job/25161972126?pr=240:

=== NAME  TestParameterised/simple_config_file_test
    run_stage_test.go:244: 
        	Error Trace:	/home/runner/work/f1/f1/internal/run/run_stage_test.go:244
        	            				/home/runner/work/f1/f1/internal/run/run_cmd_test.go:400
        	Error:      	Not equal: 
        	            	expected: 0x69
        	            	actual  : 0x6a
        	Test:       	TestParameterised/simple_config_file_test
        	Messages:   	number of started iterations
    run_stage_test.go:331: 
        	Error Trace:	/home/runner/work/f1/f1/internal/run/run_stage_test.go:331
        	            				/home/runner/work/f1/f1/internal/run/run_cmd_test.go:403
        	Error:      	Not equal: 
        	            	expected: 105
        	            	actual  : 106
        	Test:       	TestParameterised/simple_config_file_test
        	Messages:   	iteration teardown was not called expected times

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.