package main import ( "fmt" "log" "net/http" "os" "strings" "sync" "sync/atomic" "time" ) func main() { // Read URLs from environment variable urlsEnv := os.Getenv("TARGET_URLS") if urlsEnv == "" { log.Fatal("TARGET_URLS environment variable is not set") } // Parse comma-delimited URLs urls := strings.Split(urlsEnv, ",") for i := range urls { urls[i] = strings.TrimSpace(urls[i]) } if len(urls) == 0 { log.Fatal("No URLs provided") } fmt.Printf("Starting load test on %d URLs\n", len(urls)) fmt.Println("URLs:", urls) fmt.Println("Press Ctrl+C to stop\n") // Counters var ( totalRequests uint64 successCount uint64 errorCount uint64 ) // WaitGroup to track all goroutines var wg sync.WaitGroup // Start time startTime := time.Now() // Spawn a goroutine for each URL for _, url := range urls { wg.Add(1) go func(targetURL string) { defer wg.Done() client := &http.Client{Timeout: time.Second} for { resp, err := client.Get(targetURL) atomic.AddUint64(&totalRequests, 1) if err != nil { fmt.Printf("error: %s => %v\n", targetURL, err) atomic.AddUint64(&errorCount, 1) continue } _ = resp.Body.Close() if resp.StatusCode >= 200 && resp.StatusCode < 300 { atomic.AddUint64(&successCount, 1) } else { fmt.Printf("bad status: %s => %d\n", targetURL, resp.StatusCode) atomic.AddUint64(&errorCount, 1) } } }(url) } // Progress reporter go func() { ticker := time.NewTicker(1 * time.Second) defer ticker.Stop() for { <-ticker.C elapsed := time.Since(startTime).Seconds() requests := atomic.LoadUint64(&totalRequests) success := atomic.LoadUint64(&successCount) errors := atomic.LoadUint64(&errorCount) fmt.Printf("Elapsed: %.0fs | Requests: %d | Success: %d | Errors: %d | Rate: %.0f req/s\n", elapsed, requests, success, errors, float64(requests)/elapsed) } }() // Wait for all goroutines (runs forever until killed) wg.Wait() }