Skip to content

Commit

Permalink
feat(endy): add benchmark mode (#1)
Browse files Browse the repository at this point in the history
Co-authored-by: w0i <>
  • Loading branch information
gonnafaraway authored Jul 30, 2024
1 parent a298f8c commit bc18813
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 56 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ panic(err)}
}
```

# Benchmark
- with installed bombardier(https://github.com/codesenberg/bombardier) tool endy can benchmark endpoints and print out short results

# CLI

```
Expand Down
24 changes: 24 additions & 0 deletions cmd/endy/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
- url: https://jsonplaceholder.typicode.com/posts
method: GET
headers:
- name: Content-Type
value: application/json
threads: 125
requests: 3000
duration: 30s
- url: https://jsonplaceholder.typicode.com/comments
method: GET
headers:
- name: Content-Type
value: application/json
threads: 125
requests: 3000
duration: 30s
- url: https://jsonplaceholder.typicode.com/todos
method: GET
headers:
- name: Content-Type
value: application/json
threads: 125
requests: 3000
duration: 30s
9 changes: 7 additions & 2 deletions cmd/endy/endy.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,24 @@ import (
const (
PathFlag = "path"
TimeoutFlag = "timeout"
BenchFlag = "bench"

DefaultTimeout = 10 * time.Second
)

func main() {
pflag := flag.String(PathFlag, "config.yaml", "path to config file with end-to-end cases")
tflag := flag.Duration(TimeoutFlag, DefaultTimeout, "limit of all tests duration")
bflag := flag.Bool(BenchFlag, false, "execute tests in benchmark mode")

flag.Parse()

t := endy.New()

cfg := endy.Config{
Path: *pflag,
Timeout: *tflag,
Path: *pflag,
Timeout: *tflag,
BenchMode: *bflag,
}

t.Config = &cfg
Expand Down
197 changes: 143 additions & 54 deletions e2e.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@ import (
"context"
"encoding/json"
"fmt"
"gopkg.in/yaml.v3"
"io"
"log"
"net/http"
"os"
"os/exec"
"time"

"github.com/fatih/color"
"go.uber.org/zap"
"gopkg.in/yaml.v3"
)

type Tester struct {
Expand All @@ -23,8 +24,9 @@ type Tester struct {
}

type Config struct {
Path string
Timeout time.Duration
Path string
Timeout time.Duration
BenchMode bool
}

type Header struct {
Expand All @@ -39,27 +41,9 @@ type Test struct {
Timeout time.Duration `yaml:"timeout,omitempty"`
Headers []Header `yaml:"headers,omitempty"`
Body string `yaml:"body,omitempty"`
}

func New() *Tester {
lg, err := zap.NewProduction(
zap.WithCaller(false),
)
if err != nil {
log.Fatal(err)
}
return &Tester{
Logger: lg,
Config: &Config{},
}
}

func (t *Tester) SetTimeout(timeout time.Duration) {
t.Config.Timeout = timeout
}

func (t *Tester) SetConfigPath(path string) {
t.Config.Path = path
Threads string `yaml:"threads,omitempty"`
Duration string `yaml:"duration,omitempty"`
Requests string `yaml:"requests,omitempty"`
}

func (t *Tester) Run() error {
Expand All @@ -81,6 +65,7 @@ func (t *Tester) Run() error {
if err = yaml.Unmarshal(yamlFile, &tests); err != nil {
return fmt.Errorf("unmarshall test configuration: %v", err)
}

t.Tests = tests

if t.Config.Timeout != 0 {
Expand All @@ -94,52 +79,156 @@ func (t *Tester) Run() error {

client := &http.Client{}

for _, test := range *t.Tests {
var (
buf bytes.Buffer
)

if &test.Body != nil {
err := json.NewEncoder(&buf).Encode(test.Body)
if t.Config.BenchMode {
color.Yellow("Running benchmark mode")
for _, test := range *t.Tests {
err = execBenchTest(t, test)
if err != nil {
t.Logger.Error("json encode", zap.Error(err))
t.Logger.Error("execute test", zap.String("url", test.URL), zap.Error(err))
return err
}
}
return nil
}

req, err := http.NewRequestWithContext(ctx, test.Method, test.URL, &buf)
color.Green("Running API testing mode")
for _, test := range *t.Tests {
err = execAPITests(ctx, t, client, test)
if err != nil {
t.Logger.Error("create http request", zap.Error(err))
return err
}
}

return nil
}

func New() *Tester {
lg, err := zap.NewProduction(
zap.WithCaller(false),
)
if err != nil {
log.Fatal(err)
}
return &Tester{
Logger: lg,
Config: &Config{},
}
}

func execAPITests(ctx context.Context, t *Tester, client *http.Client, test Test) error {
var (
buf bytes.Buffer
)

resp, err := client.Do(req)
if &test.Body != nil {
err := json.NewEncoder(&buf).Encode(test.Body)
if err != nil {
t.Logger.Error("send http request", zap.Error(err))
t.Logger.Error("json encode", zap.Error(err))
return err
}
defer func(Body io.ReadCloser) {
err = Body.Close()
if err != nil {
t.Logger.Error("close http body", zap.Error(err))
}
}(resp.Body)
}

bodyBytes, err := io.ReadAll(resp.Body)
req, err := http.NewRequestWithContext(ctx, test.Method, test.URL, &buf)
if err != nil {
t.Logger.Error("create http request", zap.Error(err))
return err
}

resp, err := client.Do(req)
if err != nil {
t.Logger.Error("send http request", zap.Error(err))
return err
}
defer func(Body io.ReadCloser) {
err = Body.Close()
if err != nil {
t.Logger.Error("read response body", zap.Error(err))
return err
t.Logger.Error("close http body", zap.Error(err))
}
bodyString := string(bodyBytes)

switch {
case resp.StatusCode == test.AssertCode:
color.Green("Test passed")
t.Logger.Info("Test passed", zap.String("url", test.URL), zap.String("method", test.Method), zap.String("body", buf.String()))
default:
color.Red("Test failed")
t.Logger.Fatal("Test failed", zap.String("url", test.URL), zap.String("method", test.Method), zap.String("body", buf.String()), zap.Int("status_code", resp.StatusCode), zap.Error(err), zap.String("response_body", bodyString))
}(resp.Body)

bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
t.Logger.Error("read response body", zap.Error(err))
return err
}
bodyString := string(bodyBytes)

switch {
case resp.StatusCode == test.AssertCode:
color.Green("Test passed")
t.Logger.Info("Test passed", zap.String("url", test.URL), zap.String("method", test.Method), zap.String("body", buf.String()))
default:
color.Red("Test failed")
t.Logger.Fatal("Test failed", zap.String("url", test.URL), zap.String("method", test.Method), zap.String("body", buf.String()), zap.Int("status_code", resp.StatusCode), zap.Error(err), zap.String("response_body", bodyString))
}
return nil
}

func execBenchTest(t *Tester, test Test) error {
var (
buf bytes.Buffer
)

if &test.Body != nil {
err := json.NewEncoder(&buf).Encode(test.Body)
if err != nil {
t.Logger.Error("json encode", zap.Error(err))
return err
}
}

headers := prepareBenchHeaders(test.Headers)

app := "bombardier"
arg0 := "-c"
arg1 := test.Threads
arg2 := "-n"
arg3 := test.Requests
arg4 := "-m"
arg5 := test.Method
arg6 := "-b"
arg7 := test.Body
arg8 := "-k"
arg9 := "-d"
arg10 := test.Duration
arg11 := "-H"
arg12 := headers
arg13 := "-p"
arg14 := "r"
arg15 := test.URL

cmd := exec.Command(app, arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15)

t.Logger.Info("benchmarking", zap.String("url", test.URL), zap.String("command", cmd.String()))

stdout, err := cmd.Output()
if err != nil {
t.Logger.Fatal("bench command failed", zap.Error(err), zap.String("output", string(stdout)))
}

t.Logger.Info("benchmark output", zap.String("output", string(stdout)))

color.Green("benchmark passed")

return nil
}

func (t *Tester) SetTimeout(timeout time.Duration) {
t.Config.Timeout = timeout
}

func (t *Tester) SetConfigPath(path string) {
t.Config.Path = path
}

func (t *Tester) SetBenchmarkMode() {
t.Config.BenchMode = true
}

func prepareBenchHeaders(headers []Header) string {
var headerString string
for _, header := range headers {
headerString += header.Name + ": " + header.Value + " "
}
return headerString
}
15 changes: 15 additions & 0 deletions examples/bench/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
- url: https://jsonplaceholder.typicode.com/posts
method: GET
headers:
- name: Content-Type
value: application/json
- url: https://jsonplaceholder.typicode.com/comments
method: GET
headers:
- name: Content-Type
value: application/json
- url: https://jsonplaceholder.typicode.com/todos
method: GET
headers:
- name: Content-Type
value: application/json
23 changes: 23 additions & 0 deletions examples/bench/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package main

import (
"fmt"
"github.com/gonnafaraway/endy"
"os"
"time"
)

func main() {
e := endy.New()

e.SetTimeout(10 * time.Second)

e.SetConfigPath("config.yaml")

e.SetBenchmarkMode()

if err := e.Run(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}

0 comments on commit bc18813

Please sign in to comment.