Skip to content

Commit

Permalink
spidomtr lib
Browse files Browse the repository at this point in the history
golang lib for benchmarking and load testing.
  • Loading branch information
thepatrik committed Mar 30, 2020
1 parent e5e9a56 commit cbc0a75
Show file tree
Hide file tree
Showing 14 changed files with 1,551 additions and 1 deletion.
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
language: go

go:
- 1.11.x
- 1.12.x
- 1.13.x
- 1.14.x
108 changes: 107 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,107 @@
# spidomtr
# spidomtr
[![Build Status](https://travis-ci.org/spider-pigs/spidomtr.svg?branch=master)](https://travis-ci.org/spider-pigs/spidomtr) [![Go Report Card](https://goreportcard.com/badge/github.com/spider-pigs/spidomtr)](https://goreportcard.com/report/github.com/spider-pigs/spidomtr) [![GoDoc](https://godoc.org/github.com/spider-pigs/spidomtr?status.svg)](https://godoc.org/github.com/spider-pigs/spidomtr)

spidomtr is a golang lib for benchmarking and load testing.

```console
.__ .___ __
____________ |__| __| _/____ ______/ |________
/ ___/\____ \| |/ __ |/ _ \ / \ __\_ __ \
\___ \ | |_> > / /_/ ( <_> ) Y Y \ | | | \/
/____ >| __/|__\____ |\____/|__|_| /__| |__|
\/ |__| \/ \/

[====================================================================] 100% 3s

Summary:
Count: 500
Total: 3.391898304s
Slowest: 104 ms
Fastest: 21 ms
Average: 61 ms
Req/sec: 147.41

Response time histogram:
21 ms [1] |
37 ms [94] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
54 ms [120] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
70 ms [95] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
87 ms [104] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
104 ms [86] |∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎

Latency distribution:
10% in 30 ms
25% in 42 ms
50% in 59 ms
75% in 81 ms
90% in 93 ms
95% in 97 ms
99% in 101 ms

Responses:
OK: 500
Errored: 0
Skipped: 0

Tests:
√ awesome_test
Count: 500
OK: 500
Errored: 0
Skipped: 0
Slowest: 104 ms
Fastest: 21 ms
Average: 61 ms
90%: 93 ms
95%: 97 ms
99%: 101 ms
Req/sec: 147.41
```

# Install
```golang
import "github.com/spider-pigs/spidomtr"
```

# Usage
```golang
package main

import (
"context"

"github.com/spider-pigs/spidomtr"
"github.com/spider-pigs/spidomtr/pkg/handlers"
"github.com/spider-pigs/spidomtr/pkg/testunit"
)

func main() {
// Create the test runner
runner := spidomtr.NewRunner(
spidomtr.ID("awesome tests"),
spidomtr.Description("just running some awesome tests"),
spidomtr.Handlers(
handlers.ProgressBar(), // Displays progress bar during test run
),
spidomtr.Iterations(50), // Run the test 50 times
spidomtr.Timeout(time.Second*20), // Set a test timeout
spidomtr.Users(10), // Simulate 10 concurrent users
)

// Create a test
test := testunit.New(
testunit.Test(func(ctx context.Context, args []interface{}) ([]interface{}, error) {
if err := doSomethingCool(ctx); err != nil {
// Test failed
return args, err
}
// Test passed
return args, nil
}),
)

// Run the test
res := runner.Run(context.Background(), test)
...
}
```
8 changes: 8 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module github.com/spider-pigs/spidomtr

require (
github.com/cheggaaa/pb/v3 v3.0.4
github.com/google/uuid v1.1.1
github.com/stretchr/testify v1.5.1
github.com/thepatrik/strcolor v1.0.3
)
32 changes: 32 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM=
github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA=
github.com/cheggaaa/pb/v3 v3.0.4 h1:QZEPYOj2ix6d5oEg63fbHmpolrnNiwjUsk+h74Yt4bM=
github.com/cheggaaa/pb/v3 v3.0.4/go.mod h1:7rgWxLrAUcFMkvJuv09+DYi7mMUYi8nO9iOWcvGJPfw=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/thepatrik/strcolor v1.0.3 h1:lFuGZwKJn1CwbXYagm8jVKmLh9FPCrd3T7FGSm4jEjc=
github.com/thepatrik/strcolor v1.0.3/go.mod h1:I519L4XnoZZmMJauibTGgy7XODjJ1st3IusYns7MOrg=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9 h1:ZBzSG/7F4eNKz2L3GE9o300RX0Az1Bw5HF7PDraD+qU=
golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
97 changes: 97 additions & 0 deletions internal/runner/runner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package runner

import (
"context"
"errors"
"fmt"
"sync"
"time"

"github.com/spider-pigs/spidomtr/pkg/testunit"
)

// TestUnitDone type
type TestUnitDone func(testunit.TestUnit, *Timer, error)

// Runner type
type Runner struct {
Iterations int
TestUnitDone TestUnitDone
Timeout time.Duration
}

// New constructs a new
func New(timeout time.Duration, iterations int) *Runner {
return &Runner{
Iterations: iterations,
Timeout: timeout,
}
}

// Run runs test units.
func (runner Runner) Run(ctx context.Context, tests ...testunit.TestUnit) *Timer {
totalTimer := NewTimer()
totalTimer.Begin()

for i := 0; i < runner.Iterations; i++ {
for _, t := range tests {
var err error
timer := NewTimer()

enabled, _ := t.Enabled()
if enabled {
timer, err = runTestUnit(ctx, t, runner.Timeout)
}
if runner.TestUnitDone != nil {
runner.TestUnitDone(t, timer, err)
}
}
}

totalTimer.Finish()
return totalTimer
}

func runTestUnit(ctx context.Context, t testunit.TestUnit, timeout time.Duration) (*Timer, error) {
timer := NewTimer()
var err error
var wg sync.WaitGroup
wg.Add(1)

go func() {
defer wg.Done()
defer func() {
if r := recover(); r != nil {
panicstr := fmt.Sprintf("%s", r)
err = errors.New("func panic: " + panicstr)
}
}()
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()

// Run prepare func
var args []interface{}
args, err = t.Prepare(ctx)
if err != nil {
return
}

// Run main func
timer.Begin()
args, err = t.Test(ctx, args)
timer.Finish()

if err != nil {
return
}

// Run cleanup func
err = t.Cleanup(ctx, args)
}()
wg.Wait()
if err != nil {
return NewTimer(), err
}

return timer, nil
}
26 changes: 26 additions & 0 deletions internal/runner/timer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package runner

import "time"

// Timer type
type Timer struct {
Start time.Time
End time.Time
Duration time.Duration
}

// NewTimer creates timer
func NewTimer() *Timer {
return &Timer{}
}

// Begin starts timer
func (timer *Timer) Begin() {
timer.Start = time.Now()
}

// Finish stops timer
func (timer *Timer) Finish() {
timer.End = time.Now()
timer.Duration = timer.End.Sub(timer.Start)
}
75 changes: 75 additions & 0 deletions pkg/handlers/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package handlers

import (
"bytes"
"fmt"
"log"
"os"
"strings"
"unicode/utf8"

"github.com/spider-pigs/spidomtr"
"github.com/spider-pigs/spidomtr/pkg/testunit"
"github.com/thepatrik/strcolor"
)

const (
checkMark = "√"
crossMark = "☓"
skipMark = "-"
)

// TestLogger type
type TestLogger struct {
Log *log.Logger
Buffer *bytes.Buffer
}

// Logger logs tests during the test run
func Logger() spidomtr.RunnerHandler {
return &TestLogger{}
}

// RunnerStarted is called when runner is started (prior to any tests
// have been run).
func (logger *TestLogger) RunnerStarted(id, description string, count int) {
logger.Log = log.New(os.Stdout, "", 0)
logger.Buffer = &bytes.Buffer{}

fmt.Fprintf(logger.Buffer, "Running %s: %s...\n", id, description)
logger.Log.Printf("Running %s: %s\n", id, description)
}

// TestDone is called when a test has been completed.
func (logger *TestLogger) TestDone(res spidomtr.TestResult) {
switch res.Outcome {
case testunit.Skip:
fmt.Fprintf(logger.Buffer, "%s %s: %s\n", skipMark, res.ID, res.Comment)
logger.Log.Printf("%s %s: %s\n", strcolor.Yellow(skipMark), res.ID, res.Comment)
case testunit.Fail:
fmt.Fprintf(logger.Buffer, "%s %s: %s\n", crossMark, res.ID, res.Error)
logger.Log.Printf("%s %s: %s\n", strcolor.Red(crossMark), res.ID, res.Error)
case testunit.Pass:
fmt.Fprintf(logger.Buffer, "%s %s: %s\n", checkMark, res.ID, res.Duration)
logger.Log.Printf("%s %s: %s\n", strcolor.Green(checkMark), res.ID, res.Duration)
}
}

// RunnerDone is called when the runner has run all tests.
func (logger *TestLogger) RunnerDone(res spidomtr.Result) {
mark := func() strcolor.StrColor {
switch {
case res.Stats.Errors > 0:
return strcolor.Red(crossMark)
case res.Stats.Skips > 0:
return strcolor.Yellow(skipMark)
}
return strcolor.Green(checkMark)
}()

str := fmt.Sprintf("%d/%d passed, %d failed, %d skipped, took %s, avg %s/test", res.Stats.Passed, res.Stats.Count, res.Stats.Errors, res.Stats.Skips, res.Stats.Duration, res.Stats.Average)
divider := strings.Repeat("-", utf8.RuneCountInString(str)+2)

fmt.Fprintf(logger.Buffer, "%s\n%s %s\n%s\n", divider, mark.Val, str, divider)
logger.Log.Printf("%s\n%s %s\n%s\n", divider, mark, str, divider)
}
32 changes: 32 additions & 0 deletions pkg/handlers/progress.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package handlers

import (
"github.com/cheggaaa/pb/v3"
"github.com/spider-pigs/spidomtr"
)

type progressBar struct {
bar *pb.ProgressBar
}

// ProgressBar is a runner handler that displays a running progress
// bar.
func ProgressBar() spidomtr.RunnerHandler {
return &progressBar{}
}

// RunnerStarted is called when runner is started (prior to any tests
// have been run).
func (b *progressBar) RunnerStarted(id, description string, count int) {
b.bar = pb.StartNew(count)
}

// TestDone is called when a test has been completed.
func (b *progressBar) TestDone(spidomtr.TestResult) {
b.bar.Increment()
}

// RunnerDone is called when the runner has run all tests.
func (b *progressBar) RunnerDone(spidomtr.Result) {
b.bar.Finish()
}
Loading

0 comments on commit cbc0a75

Please sign in to comment.