From cb8d79fca30b28b103cd9275711414deabf32644 Mon Sep 17 00:00:00 2001 From: cubixle Date: Tue, 27 Jul 2021 21:09:29 +0100 Subject: [PATCH] more things --- README.md | 4 ++ examples/simple/main.go | 15 +++---- http.go | 29 +++++++++++++ l1.go | 25 ++++++++++- pool.go | 57 ++++++++++++++++++------ results.go | 96 +++++++++++++++++++++++++++++++++++++---- results_test.go | 16 ------- 7 files changed, 196 insertions(+), 46 deletions(-) create mode 100644 README.md create mode 100644 http.go delete mode 100644 results_test.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..a401bfb --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# L1 + +L1 is a load testing framework written in Go for Go. + diff --git a/examples/simple/main.go b/examples/simple/main.go index 8f8a929..2fcbf86 100644 --- a/examples/simple/main.go +++ b/examples/simple/main.go @@ -8,16 +8,15 @@ import ( func main() { r, err := l1.NewRunner( - l1.WithTarget("http://google.com"), - l1.WithRunFunc(func(target string) error { - return nil - }), + l1.WithTarget("https://remoteukjobs.com"), + l1.WithRunFunc(l1.DefaultHTTPTester), + l1.WithMaxParrellConns(10), + l1.WithMaxConns(6000), ) if err != nil { log.Fatal(err) } - err = r.Start() - if err != nil { - log.Fatal(err) - } + r.Execute() + results := r.Results() + results.Print() } diff --git a/http.go b/http.go new file mode 100644 index 0000000..604c594 --- /dev/null +++ b/http.go @@ -0,0 +1,29 @@ +package l1 + +import ( + "net/http" + "time" +) + +func DefaultHTTPTester(target string) *Result { + result := &Result{} + client := http.Client{ + Timeout: 30 * time.Second, + } + + req, err := http.NewRequest(http.MethodGet, target, nil) + if err != nil { + result.Error = err + return result + } + + startTime := time.Now() + rsp, err := client.Do(req) + if err != nil { + result.Error = err + } + result.CompletedIn = time.Since(startTime).Seconds() + result.StatusCode = rsp.StatusCode + + return result +} diff --git a/l1.go b/l1.go index ce1dd5a..fbc9602 100644 --- a/l1.go +++ b/l1.go @@ -5,7 +5,7 @@ import ( ) // F defines the function type for runners. -type F func(target string) error +type F func(target string) *Result // Runner type Runner struct { @@ -15,6 +15,7 @@ type Runner struct { RunTime time.Duration RunFunc F Target string + results []*Result } func NewRunner(opts ...Opt) (*Runner, error) { @@ -39,3 +40,25 @@ func NewRunner(opts ...Opt) (*Runner, error) { func (r *Runner) SetOpt(o Opt) { o(r) } + +func (r *Runner) Execute() { + tasks := []*Task{} + for i := 0; i < r.MaxConnections; i++ { + tasks = append(tasks, &Task{Target: r.Target, F: r.RunFunc}) + } + // create the pool and process the tasks. + pool := newPool(tasks, r.MaxParrellConnections) + // the tasks are updated in memory so we don't expect a return here. + pool.run() + for _, t := range tasks { + r.results = append(r.results, t.Result) + } +} + +func (r *Runner) Results() *results { + res := &results{ + Results: r.results, + Target: r.Target, + } + return res +} diff --git a/pool.go b/pool.go index 7e73835..7366031 100644 --- a/pool.go +++ b/pool.go @@ -1,21 +1,54 @@ package l1 import ( - "fmt" + "sync" ) -// Start starts the runner. -func (r *Runner) Start() error { - jobChan := make(chan string, r.MaxParrellConnections) - // resultsChan := make(chan struct{}) +type Task struct { + Target string + Result *Result + F F +} - for i := 0; i < r.MaxParrellConnections; i++ { - go func(jobChan chan string) { - for t := range jobChan { - r.RunFunc(t) - } - }(jobChan) +type pool struct { + tasks []*Task + concurrency int + + tasksChan chan *Task + wg sync.WaitGroup +} + +func newPool(tasks []*Task, concurrency int) *pool { + return &pool{ + tasks: tasks, + concurrency: concurrency, + tasksChan: make(chan *Task), + } +} + +// run will run all work within the pool. +func (p *pool) run() { + for i := 0; i < p.concurrency; i++ { + go p.work() } - return fmt.Errorf("unimplemented") + p.wg.Add(len(p.tasks)) + for _, t := range p.tasks { + p.tasksChan <- t + } + + close(p.tasksChan) + + p.wg.Wait() +} + +func (p *pool) work() { + for t := range p.tasksChan { + if t.F == nil { + continue + } + res := t.F(t.Target) + t.Result = res + p.wg.Done() + } } diff --git a/results.go b/results.go index f9a4efc..fe25d9e 100644 --- a/results.go +++ b/results.go @@ -1,26 +1,104 @@ package l1 -type Results struct { +import "fmt" + +type results struct { Target string - Count int - Results []Result + Results []*Result + + totalCompletedTime float64 + successfulCount int + errorCount int } type Result struct { // CompletedIn is in seconds - CompletedIn int + RunTime int64 + CompletedIn float64 Error error StatusCode int } -func (r *Results) RequestsPerMin() int { - totalCompletedIn := 0 +func (r *results) RequestPerSecond() float64 { + totalCompletedIn := float64(0) for _, res := range r.Results { + if res.Error != nil { + continue + } totalCompletedIn += res.CompletedIn } - return totalCompletedIn / r.Count + + return float64(len(r.Results)) / totalCompletedIn } -func (r *Results) AvgCompletionTime() int { - return 0 +func (r *results) PeakResponseTime() { + +} + +func (r *results) ErrorCount() int { + if r.errorCount > 0 { + return r.errorCount + } + for _, res := range r.Results { + if res.Error == nil { + continue + } + r.errorCount++ + } + return r.errorCount +} + +func (r *results) SuccessfulCount() int { + if r.successfulCount > 0 { + return r.successfulCount + } + for _, res := range r.Results { + if res.Error != nil { + continue + } + r.successfulCount++ + } + return r.successfulCount +} + +func (r *results) AvgResponseTime() float64 { + totalCompletedIn := float64(0) + + for _, res := range r.Results { + if res.Error != nil { + continue + } + totalCompletedIn += res.CompletedIn + } + + return totalCompletedIn / float64(len(r.Results)) +} + +func (r *results) CompletedTime() float64 { + if r.totalCompletedTime > 0 { + return r.totalCompletedTime + } + + for _, res := range r.Results { + if res.Error != nil { + continue + } + r.totalCompletedTime += res.CompletedIn + } + return r.totalCompletedTime +} + +func (r *results) Print() { + fmt.Println("-----------------------------------------------------------") + fmt.Println("| L1 load tester.") + fmt.Println("| Default result printer.") + fmt.Println("-----------------------------------------------------------") + fmt.Println("") + fmt.Printf("Load testing %s\n", r.Target) + fmt.Println("") + fmt.Printf("Request per second: %.2f\n", r.RequestPerSecond()) + fmt.Printf("Average response time: %.2f seconds\n", r.AvgResponseTime()) + fmt.Printf("Success count: %d\n", r.SuccessfulCount()) + fmt.Printf("Error count: %d\n", r.ErrorCount()) + fmt.Println("") } diff --git a/results_test.go b/results_test.go deleted file mode 100644 index 49eb929..0000000 --- a/results_test.go +++ /dev/null @@ -1,16 +0,0 @@ -package l1_test - -// func TestRequestsPerMin(t *testing.T) { -// rs := l1.Results{ -// Count: 4, -// Results: []l1.Result{ -// {CompletedIn: 10}, -// {CompletedIn: 10}, -// {CompletedIn: 10}, -// {CompletedIn: 10}, -// }, -// } - -// rpm := rs.RequestsPerMin() -// log.Println(rpm) -// }