10 changed files with 414 additions and 1 deletions
@ -1,2 +1,2 @@ |
|||
# loadrunner |
|||
# loadsprinter |
|||
A Load Test tool written in Go |
|||
|
|||
@ -0,0 +1,48 @@ |
|||
package main |
|||
|
|||
import "fmt" |
|||
import "os" |
|||
import "time" |
|||
import "itix.fr/loadsprinter/core" |
|||
import "itix.fr/loadsprinter/steps" |
|||
|
|||
type MyVirtualUserFactory struct { |
|||
count int |
|||
scenario *core.Scenario |
|||
} |
|||
|
|||
func (factory *MyVirtualUserFactory) CreateVirtualUser() *core.VirtualUser { |
|||
name := fmt.Sprintf("vu-%03d", factory.count) |
|||
factory.count++ |
|||
return core.NewVirtualUser(name, factory.scenario) |
|||
} |
|||
|
|||
func check(e error) { |
|||
if e != nil { |
|||
panic(e) |
|||
} |
|||
} |
|||
|
|||
func main() { |
|||
/*vulogwr, err := os.Create("/tmp/vu.log") |
|||
check(err) |
|||
defer vulogwr.Close()*/ |
|||
csvwr, err := os.Create("/tmp/vu.csv") |
|||
check(err) |
|||
defer csvwr.Close() |
|||
|
|||
s1 := steps.NewWaitStep(300 * time.Millisecond) |
|||
s2 := steps.NewLogStep("Hello World !") |
|||
s3 := steps.NewFailStep("BLAAAAAAH") |
|||
step1 := core.NewStep("wait_3s", true, s1) |
|||
step2 := core.NewStep("log_hello", true, s2) |
|||
step3 := core.NewStep("fail", false, s3) |
|||
steps := []core.Step{ *step1, *step2, *step3 } |
|||
scenario := core.NewScenario(steps, "test") |
|||
|
|||
f := &MyVirtualUserFactory{0, scenario} |
|||
|
|||
c := core.NewController(os.Stdout, os.Stdout, csvwr) |
|||
wg, err := c.StartWith(5, f) |
|||
wg.Wait() |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
package main |
|||
|
|||
import "fmt" |
|||
import "time" |
|||
//import "sync"
|
|||
|
|||
type Thing struct { |
|||
a int |
|||
b string |
|||
} |
|||
|
|||
func (t Thing) String() string { |
|||
return fmt.Sprintf("%v-%v", t.b, t.a) |
|||
} |
|||
|
|||
func Do(a int, b string, ch chan Thing) { |
|||
var t Thing |
|||
t.a = a |
|||
t.b = b |
|||
ch <- t |
|||
} |
|||
|
|||
func Doit(id string, chan1 chan Thing, chan2 chan Thing) { |
|||
for { |
|||
var t Thing |
|||
t.a = 666 |
|||
t.b = fmt.Sprintf("%v-evil", id) |
|||
|
|||
Do(1, fmt.Sprintf("%v-one", id), chan1) |
|||
time.Sleep(300 * time.Millisecond) |
|||
Do(2, fmt.Sprintf("%v-two", id), chan1) |
|||
Do(3, fmt.Sprintf("%v-three", id), chan1) |
|||
time.Sleep(50 * time.Millisecond) |
|||
Do(4, fmt.Sprintf("%v-four", id), chan1) |
|||
|
|||
chan2 <- t |
|||
} |
|||
} |
|||
|
|||
func main() { |
|||
chan1 := make(chan Thing) |
|||
chan2 := make(chan Thing) |
|||
|
|||
go Doit("A", chan1, chan2); |
|||
go Doit("B", chan1, chan2); |
|||
go Doit("C", chan1, chan2); |
|||
go Doit("D", chan1, chan2); |
|||
go Doit("E", chan1, chan2); |
|||
|
|||
for { |
|||
time.Sleep(1*time.Second) |
|||
select { |
|||
case x := <- chan1: |
|||
fmt.Printf("1: %v\n", x) |
|||
case x := <- chan2: |
|||
fmt.Printf("2: %v\n", x) |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,113 @@ |
|||
package core |
|||
|
|||
import "io" |
|||
import "log" |
|||
import "fmt" |
|||
import "sync" |
|||
import "time" |
|||
import "encoding/csv" |
|||
|
|||
type Controller struct { |
|||
vuserLog *log.Logger |
|||
controllerLog *log.Logger |
|||
users []*VirtualUser |
|||
wg sync.WaitGroup |
|||
csv *csv.Writer |
|||
stepStats chan StepIteration |
|||
scenarioStats chan ScenarioIteration |
|||
} |
|||
|
|||
type VirtualUserFactory interface { |
|||
CreateVirtualUser() *VirtualUser |
|||
} |
|||
|
|||
type StepIteration struct { |
|||
vuser string |
|||
scenario string |
|||
step string |
|||
success bool |
|||
elapsed time.Duration |
|||
} |
|||
|
|||
func (s StepIteration) String() string { |
|||
return fmt.Sprintf("step: %v > %v > %v: success = %v, elapsed = %v", s.vuser, s.scenario, s.step, s.success, s.elapsed) |
|||
} |
|||
|
|||
type ScenarioIteration struct { |
|||
vuser string |
|||
scenario string |
|||
success bool |
|||
elapsed time.Duration |
|||
} |
|||
|
|||
func (s ScenarioIteration) String() string { |
|||
return fmt.Sprintf("scenario: %v > %v: success = %v, elapsed = %v", s.vuser, s.scenario, s.success, s.elapsed) |
|||
} |
|||
|
|||
func NewController(vuserLogFile io.Writer, controllerLogFile io.Writer, csvfile io.Writer) *Controller { |
|||
c := Controller{} |
|||
c.vuserLog = log.New(vuserLogFile, "", log.Lshortfile | log.LstdFlags) |
|||
c.controllerLog = log.New(controllerLogFile, "", log.Lshortfile | log.LstdFlags) |
|||
c.csv = csv.NewWriter(csvfile) |
|||
c.stepStats = make(chan StepIteration) |
|||
c.scenarioStats = make(chan ScenarioIteration) |
|||
return &c; |
|||
} |
|||
|
|||
func (c *Controller) StartWith(n int, f VirtualUserFactory) (wg *sync.WaitGroup, err error) { |
|||
c.controllerLog.Printf("--> Controller::StartWith(%v, %v)", n, f) |
|||
c.wg.Add(n) // Initialize the WaitGroup with the number of routines to create
|
|||
|
|||
// Create a channel that will be used to start all VirtualUsers together
|
|||
start := make(chan int) |
|||
|
|||
for i := 0; i < n; i++ { |
|||
log := *c.vuserLog |
|||
vu := f.CreateVirtualUser() |
|||
log.SetPrefix(fmt.Sprintf("%v: ", vu.name)) |
|||
c.users = append(c.users, vu) |
|||
vu.Init(&log) |
|||
go vu.Run(&log, &c.wg, start, c.stepStats, c.scenarioStats) |
|||
c.controllerLog.Printf("Added one more VirtualUser with name = %v", vu.name) |
|||
} |
|||
|
|||
// Start collecting results as soon as virtual users are started
|
|||
go func() { <- start; c.GatherResults() }() |
|||
|
|||
c.controllerLog.Println("Ready ? Set !") |
|||
time.Sleep(1 * time.Second) |
|||
// Start all VirtualUsers together
|
|||
close(start) |
|||
c.controllerLog.Println("Go !") |
|||
|
|||
c.controllerLog.Println("<-- Controller::StartWith()") |
|||
return &c.wg, nil |
|||
} |
|||
|
|||
func (c *Controller) GatherResults() { |
|||
c.controllerLog.Println("--> Controller::GatherResults()") |
|||
|
|||
// Gather results every second
|
|||
timer := make(chan int) |
|||
go func() { |
|||
for { |
|||
time.Sleep(1 * time.Second) |
|||
timer <- 0 |
|||
} |
|||
}() |
|||
|
|||
for { |
|||
c.controllerLog.Println("Waiting for message") |
|||
select { |
|||
case <- timer: |
|||
c.controllerLog.Println("Time's up !") |
|||
case stepStat := <- c.stepStats: |
|||
c.controllerLog.Println(stepStat) |
|||
case scenarioStat := <- c.scenarioStats: |
|||
c.controllerLog.Println(scenarioStat) |
|||
} |
|||
c.controllerLog.Println("Received a message") |
|||
} |
|||
|
|||
c.controllerLog.Println("<-- Controller::GatherResults()") |
|||
} |
|||
@ -0,0 +1,51 @@ |
|||
package core |
|||
|
|||
import "log" |
|||
import "time" |
|||
|
|||
type Scenario struct { |
|||
steps []Step |
|||
name string |
|||
} |
|||
|
|||
func NewScenario(steps []Step, name string) *Scenario{ |
|||
return &Scenario{ steps: steps, name: name }; |
|||
} |
|||
|
|||
func (s *Scenario) Do(log *log.Logger, vuser *VirtualUser, stepsToController chan<- StepIteration, scenarioToController chan<- ScenarioIteration) error { |
|||
log.Println("--> Scenario::Do()") |
|||
|
|||
// Prepare the statistics structure
|
|||
var stat ScenarioIteration |
|||
stat.success = true; |
|||
stat.vuser = vuser.name |
|||
stat.scenario = s.name |
|||
|
|||
// Measure the beginning of the scenario
|
|||
start := time.Now() |
|||
|
|||
// Run the scenario
|
|||
var err error = nil |
|||
for _, step := range s.steps { |
|||
err = step.Do(log, vuser, s, stepsToController) |
|||
if (err != nil) { |
|||
if (step.required) { |
|||
log.Printf("Mandatory step %v ended with error %v, stopping scenario !", step.name, err) |
|||
stat.success = false; |
|||
break |
|||
} else { |
|||
log.Printf("Optional step %v ended with error %v, continuing...", step.name, err) |
|||
} |
|||
} |
|||
} |
|||
|
|||
// Compute the elapsed time since the beginning of this scenario
|
|||
elapsed := time.Since(start) |
|||
stat.elapsed = elapsed |
|||
|
|||
// Send it to the controller
|
|||
scenarioToController <- stat |
|||
|
|||
log.Printf("<-- Scenario::Do() : err = %v", err) |
|||
return err |
|||
} |
|||
@ -0,0 +1,48 @@ |
|||
package core |
|||
|
|||
import "log" |
|||
import "time" |
|||
|
|||
type StepImpl interface { |
|||
Do(log *log.Logger) error |
|||
} |
|||
|
|||
type Step struct { |
|||
name string |
|||
required bool |
|||
impl StepImpl |
|||
} |
|||
|
|||
func NewStep(name string, required bool, impl StepImpl) *Step { |
|||
return &Step{ name: name, required: required, impl: impl } |
|||
} |
|||
|
|||
func (s *Step) Do(log *log.Logger, vuser *VirtualUser, scenario *Scenario, stepsToController chan<- StepIteration) error { |
|||
log.Printf("--> Step::Do(%v)", s.name) |
|||
|
|||
// Prepare the statistics structure
|
|||
var stat StepIteration |
|||
stat.success = true |
|||
stat.vuser = vuser.name |
|||
stat.scenario = scenario.name |
|||
stat.step = s.name |
|||
|
|||
// Measure the beginning of this step
|
|||
start := time.Now() |
|||
|
|||
err := s.impl.Do(log) |
|||
if (err != nil) { |
|||
log.Printf("Step %v ended with error %v", s.name, err) |
|||
stat.success = false; |
|||
} |
|||
|
|||
// Compute the elapsed time since the beginning of this step
|
|||
elapsed := time.Since(start) |
|||
stat.elapsed = elapsed |
|||
|
|||
// Send it to the controller
|
|||
stepsToController <- stat |
|||
|
|||
log.Printf("<-- Step::Do(%v)", s.name) |
|||
return err; |
|||
} |
|||
@ -0,0 +1,42 @@ |
|||
package core; |
|||
|
|||
import "log" |
|||
import "sync" |
|||
|
|||
type VirtualUser struct { |
|||
name string |
|||
scenario *Scenario |
|||
} |
|||
|
|||
func NewVirtualUser(name string, scenario *Scenario) *VirtualUser { |
|||
return &VirtualUser{ name: name, scenario: scenario } |
|||
} |
|||
|
|||
func (vu *VirtualUser) Init(log *log.Logger) error { |
|||
log.Println("--> VirtualUser::Init()") |
|||
// TODO
|
|||
log.Println("<-- VirtualUser::Init()") |
|||
return nil |
|||
} |
|||
|
|||
func (vu *VirtualUser) Run(log *log.Logger, wg *sync.WaitGroup, start <-chan int, stepsToController chan<- StepIteration, scenarioToController chan<- ScenarioIteration) error { |
|||
log.Println("--> VirtualUser::Run()") |
|||
|
|||
// Make sure we notify we are done when this method is finished
|
|||
defer wg.Done() |
|||
|
|||
// Wait for the signal to start
|
|||
log.Println("Waiting for the signal to start") |
|||
<- start |
|||
log.Println("GOOOOOO !") |
|||
|
|||
for { |
|||
err := vu.scenario.Do(log, vu, stepsToController, scenarioToController); |
|||
if (err != nil) { |
|||
log.Printf("Scenario ended with error %v, ", err) |
|||
|
|||
} |
|||
} |
|||
log.Println("<-- VirtualUser::Run()") |
|||
return nil |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
package steps; |
|||
|
|||
import "log" |
|||
import "errors" |
|||
|
|||
type FailStep struct { |
|||
message string |
|||
} |
|||
|
|||
func NewFailStep(message string) *FailStep { |
|||
return &FailStep{ message: message }; |
|||
} |
|||
|
|||
func (fs *FailStep) Do(log *log.Logger) error { |
|||
log.Printf("error: %v", fs.message) |
|||
return errors.New(fs.message); |
|||
} |
|||
@ -0,0 +1,16 @@ |
|||
package steps; |
|||
|
|||
import "log" |
|||
|
|||
type LogStep struct { |
|||
message string |
|||
} |
|||
|
|||
func NewLogStep(message string) *LogStep { |
|||
return &LogStep{ message: message }; |
|||
} |
|||
|
|||
func (ls *LogStep) Do(log *log.Logger) error { |
|||
log.Println(ls.message) |
|||
return nil; |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
package steps; |
|||
|
|||
import "time" |
|||
import "log" |
|||
|
|||
type WaitStep struct { |
|||
duration time.Duration |
|||
} |
|||
|
|||
func NewWaitStep(duration time.Duration) *WaitStep { |
|||
return &WaitStep{ duration: duration }; |
|||
} |
|||
|
|||
func (ws *WaitStep) Do(log *log.Logger) error { |
|||
log.Printf("Sleeping during %v", ws.duration) |
|||
time.Sleep(ws.duration) |
|||
log.Println("Woken up !") |
|||
return nil |
|||
} |
|||
Loading…
Reference in new issue