diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..29f64b4 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceRoot}", + "args": ["-p", ".data/sample.yaml"] + } + ] +} \ No newline at end of file diff --git a/bindings/plan.go b/bindings/plan.go index 79ff7cc..1b7f8ce 100644 --- a/bindings/plan.go +++ b/bindings/plan.go @@ -49,3 +49,8 @@ type Proposal struct { Method string `yaml:"method,omitempty"` Url string `yaml:"url,omitempty"` } + +type Executor interface { + Initialize(protocol Protocol) + Execute(proposal Proposal) (Test, error) +} diff --git a/engine/engine.go b/engine/engine.go index c0cb507..f7e9916 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -2,16 +2,20 @@ package engine import ( "infantry/bindings" + "infantry/protocols/http" + "reflect" ) // https://www.geeksforgeeks.org/function-as-a-field-in-golang-structure/ var _report bindings.Report var _plan bindings.Plan +var _executor bindings.Executor func Start(plan bindings.Plan) (bindings.Report, error) { SetupReport(_report) FirePlanStartEvent(nil) + CreateExecutor(_plan.Protocol) for _, stage := range plan.Setup.Stages { ExecuteStage(stage, plan.Proposals) } @@ -21,17 +25,41 @@ func Start(plan bindings.Plan) (bindings.Report, error) { return _report, nil } +func CreateExecutor(protocol bindings.Protocol) { + //TODO - verify this detects protocol + if (reflect.DeepEqual(bindings.ProtocolHttp{}, protocol.Http)) { + _executor = http.HttpExecutor{} + } + + _executor.Initialize(protocol) +} + func ExecuteStage(stage bindings.Stage, proposals []bindings.Proposal) { FireStageStartedEvent(nil) + + var currentIteration = 0 + var currentUsers = stage.AddUsersPerIterations ExecuteEachSecondConcurrent(stage.Iterations, func() { - ExecuteUserProposals(proposals, stage.AddUsersPerIterations, stage.MaxUsersAtOnce) + if currentIteration > stage.Iterations { + return + } + + ExecuteUserProposals(proposals, currentUsers) + + var usersToAdd = stage.MaxUsersAtOnce - currentUsers + if usersToAdd > 0 { + currentUsers += usersToAdd + } + + currentIteration++ }) + FireStageCompletedEvent(nil) } -func ExecuteUserProposals(proposals []bindings.Proposal, addUsers int, maxUsers int) { +func ExecuteUserProposals(proposals []bindings.Proposal, currentUsers int) { FireProposalStartedEvent(nil) - ExecuteNumberOfTimesConcurrent(addUsers, maxUsers, func() { + ExecuteNumberOfTimesConcurrent(currentUsers, func() { ExecuteTasks(User{}, proposals) }) FireProposalCompletedEvent(nil) @@ -40,10 +68,14 @@ func ExecuteUserProposals(proposals []bindings.Proposal, addUsers int, maxUsers func ExecuteTasks(user User, proposals []bindings.Proposal) { for _, proposal := range proposals { FireProposalTaskStartedEvent(proposal) - var resp, err = user.ExecuteProposal(_plan.Protocol, proposal) + + var resp, err = _executor.Execute((proposal)) + if err != nil { FireProposalTaskFailureEvent(resp) + return } - //else { FireProposalTaskSuccessEvent(resp) } + + FireProposalTaskSuccessEvent(resp) } } diff --git a/engine/loops.go b/engine/loops.go index a92102b..0e2c342 100644 --- a/engine/loops.go +++ b/engine/loops.go @@ -4,34 +4,42 @@ package engine // https://stackoverflow.com/a/16466581 import ( + "fmt" "sync" "time" ) -// ExecuteEachSecondConcurrent Executes a function each second concurrently with a max count func ExecuteEachSecondConcurrent(maxSeconds int, delegate func()) { - var wg sync.WaitGroup - wg.Add(maxSeconds) - for _ = range time.Tick(time.Duration(1) * time.Second) { - go func() { + d := time.NewTicker(1 * time.Second) + + exitChannel := make(chan bool) + + go func() { + time.Sleep(time.Duration(maxSeconds) * time.Second) + exitChannel <- true + }() + + for { + select { + case <-exitChannel: + fmt.Println("Completed!") + d.Stop() + return + case timeElapsed := <-d.C: + fmt.Printf("elapsed: %+v\n", timeElapsed) delegate() - wg.Done() - }() + } } - wg.Wait() } // ExecuteNumberOfTimesConcurrent Executes a function n number of times with a max concurrent number using a guard -func ExecuteNumberOfTimesConcurrent(times int, maxConcurrently int, delegate func()) { - var guard = make(chan struct{}, maxConcurrently) +func ExecuteNumberOfTimesConcurrent(times int, delegate func()) { var wg sync.WaitGroup wg.Add(times) for i := 1; i <= times; i++ { go func(n int) { - guard <- struct{}{} delegate() wg.Done() - <-guard }(i) } wg.Wait() diff --git a/engine/user.go b/engine/user.go index 44bab8b..2fc1b33 100644 --- a/engine/user.go +++ b/engine/user.go @@ -2,8 +2,9 @@ package engine import ( "fmt" - "github.com/google/uuid" "infantry/bindings" + + "github.com/google/uuid" ) type User struct { @@ -13,8 +14,8 @@ type User struct { // ExecuteProposal Creates a virtual user and executes the proposal func (user User) ExecuteProposal(protocol bindings.Protocol, proposal bindings.Proposal) (bindings.Test, error) { fmt.Printf("protocol: %+v\n", protocol) - if protocol.Http != bindings.ProtocolHttp{} { + /*if protocol.Http != bindings.ProtocolHttp{} { - } + }*/ return bindings.Test{}, nil } diff --git a/protocols/http/http.go b/protocols/http/http.go index eeb3567..9e34844 100644 --- a/protocols/http/http.go +++ b/protocols/http/http.go @@ -3,11 +3,14 @@ package http import ( "bytes" "encoding/json" + "fmt" "infantry/bindings" "net/http" "time" ) +type HttpExecutor struct{} + type Agent struct { Host string Url string @@ -17,6 +20,8 @@ type Agent struct { Headers []bindings.Header } +var httpClient http.Client + // CreateTest Creates the base test struct for reporting func CreateTest(method string, uri string) bindings.Test { var test bindings.Test @@ -100,3 +105,13 @@ func TestOptions(uri string, headers []bindings.Header, skipSsl bool) bindings.T test := CreateTest(http.MethodOptions, uri) return test } + +func (httpExecutor HttpExecutor) Initialize(protocol bindings.Protocol) { + //TODO - handle more protocol options on creation + httpClient = CreateHttpClient(protocol.Http.Tls.IgnoreSslErrors) +} + +func (httpExecutor HttpExecutor) Execute(proposal bindings.Proposal) (bindings.Test, error) { + fmt.Println("Executing") + return bindings.Test{}, nil +}