package loic import ( "context" "log" "net/http" "sync" "time" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/metric" ) var meter = otel.Meter("loic") var requestsSent metric.Int64Counter var currentTest *Test var isRunning bool var TestDone = make(chan struct{}) var closeOnce sync.Once func init() { var err error requestsSent, err = meter.Int64Counter("requests_sent", metric.WithDescription("Number of requests sent"), metric.WithUnit("1"), ) if err != nil { log.Fatalf("failed to create counter: %v", err) } isRunning = false log.Println("LOIC initialized") } type Test struct { TargetURL string Concurrency int Duration time.Duration RampUpTime time.Duration ctx context.Context cancel context.CancelFunc wg sync.WaitGroup } func StartTest(targetURL string, concurrency int, duration time.Duration, rampUp time.Duration) { if isRunning { log.Println("Test already running, skipping start") return } log.Printf("Starting test with target: %s, concurrency: %d, duration: %v, rampUp: %v", targetURL, concurrency, duration, rampUp) ctx, cancel := context.WithCancel(context.Background()) currentTest = &Test{ TargetURL: targetURL, Concurrency: concurrency, Duration: duration, RampUpTime: rampUp, ctx: ctx, cancel: cancel, } isRunning = true closeOnce = sync.Once{} // Reset closeOnce for the new test log.Println("isRunning set to true, launching goroutine") go func() { currentTest.run() closeOnce.Do(func() { close(TestDone) }) }() } func StopTest() { log.Printf("StopTest called: currentTest=%v, isRunning=%v", currentTest, isRunning) if currentTest != nil && isRunning { log.Println("Stopping test") currentTest.cancel() currentTest.wg.Wait() currentTest = nil isRunning = false log.Println("Test stopped, isRunning set to false") closeOnce.Do(func() { close(TestDone) }) } else { log.Println("No test running to stop") } } func IsTestRunning() bool { return isRunning } func (t *Test) run() { defer func() { isRunning = false log.Println("Test run completed, isRunning set to false") }() log.Println("Starting test run") // Start first worker immediately if t.Concurrency > 0 { log.Println("Starting worker 1") t.startWorker() currentConcurrency := 1 if t.Concurrency > 1 { for i := 1; i < t.Concurrency; i++ { select { case <-time.After(t.RampUpTime / time.Duration(t.Concurrency-1)): currentConcurrency++ log.Printf("Starting worker %d", currentConcurrency) t.startWorker() case <-t.ctx.Done(): log.Println("Context cancelled during ramp-up, exiting run") return } } } } // Wait for the duration or context cancellation select { case <-time.After(t.Duration): log.Println("Duration reached, cancelling test") t.cancel() case <-t.ctx.Done(): log.Println("Context cancelled, exiting run") } log.Println("Waiting for all workers to finish") t.wg.Wait() } func (t *Test) startWorker() { t.wg.Add(1) go func() { defer t.wg.Done() log.Println("Worker started") for { select { case <-t.ctx.Done(): log.Println("Worker stopped due to context cancellation") return default: resp, err := http.Get(t.TargetURL) if err != nil { log.Println("Error in worker: %v", err) } else { resp.Body.Close() requestsSent.Add(t.ctx, 1) log.Println("Request sent, counter incremented") } time.Sleep(time.Second / time.Duration(t.Concurrency)) } } }() }