package lib import ( "bytes" "fmt" "math" "math/rand/v2" "net/http" "time" ) // Global HTTP client for reuse across requests var client = &http.Client{} // Error returns a formatted error string for RequestError func (e *RequestError) Error() string { return fmt.Sprintf("error making %s request to %s: %v", e.Verb, e.URL, e.Err) } // makeRequest performs a single HTTP request and records its metrics // Parameters: // - verb: HTTP method (GET/POST) // - url: target endpoint // - token: optional bearer token for authorization // - jsonData: optional request body // - second: current test duration in seconds (for metrics) func makeRequest(verb, url, token string, jsonData []byte, second int) error { startTime := time.Now() var req *http.Request var err error if jsonData != nil { req, err = http.NewRequest(verb, url, bytes.NewBuffer(jsonData)) req.Header.Set("Content-Type", "application/json") } else { req, err = http.NewRequest(verb, url, nil) } if err != nil { return &RequestError{Verb: verb, URL: url, Err: err} } if token != "" { req.Header.Set("Authorization", "Bearer "+token) } resp, err := client.Do(req) if err != nil { return &RequestError{Verb: verb, URL: url, Err: err} } defer resp.Body.Close() duration := time.Since(startTime) // Log request details with millisecond precision timeStr := time.Now().Format("15:04:05.000") fmt.Printf("[%s] %s - Status: %d - Duration: %s\n", timeStr, verb, resp.StatusCode, duration.Round(time.Millisecond)) UpdateMetrics(duration, resp, second) return nil } // SendRequests executes the load test according to specified parameters // Parameters: // - url: target endpoint // - patterns: array of request patterns (sequential or probabilistic) // - maxRequests: total number of requests to send // - requestsPerSecond: target request rate // - token: optional bearer token for authorization // - jsonData: optional request body func SendRequests(url string, patterns []RequestPattern, maxRequests int, requestsPerSecond float64, token string, jsonData []byte) { // Initialize metrics tracking metrics = PerformanceMetrics{ MinLatency: time.Duration(math.MaxInt64), ResponseCounters: make(map[int]int32), } startTime := time.Now() // Calculate time interval between requests based on desired rate interval := time.Duration(float64(time.Second) / requestsPerSecond) nextRequestTime := startTime requestCount := 0 patternIndex := 0 sequenceCount := 0 // Determine pattern type based on first pattern's configuration isProbabilistic := patterns[0].Percentage > 0 for requestCount < maxRequests { // Maintain request rate by waiting until next scheduled time now := time.Now() if now.Before(nextRequestTime) { time.Sleep(nextRequestTime.Sub(now)) } var selectedVerb string if isProbabilistic { // Select verb based on percentage distribution rand := rand.Float64() * 100 cumulative := 0.0 for _, p := range patterns { cumulative += p.Percentage if rand <= cumulative { selectedVerb = p.Verb break } } } else { // Select verb based on sequential pattern currentPattern := patterns[patternIndex] selectedVerb = currentPattern.Verb sequenceCount++ if sequenceCount >= currentPattern.Sequence { sequenceCount = 0 patternIndex = (patternIndex + 1) % len(patterns) } } // Launch request asynchronously to maintain timing go func(verb string, requestTime time.Time) { err := makeRequest(verb, url, token, jsonData, int(time.Since(startTime).Seconds())) if err != nil { fmt.Printf("Error making request: %v\n", err) } }(selectedVerb, nextRequestTime) requestCount++ nextRequestTime = nextRequestTime.Add(interval) } // Allow time for final requests to complete time.Sleep(interval * 2) CalculateAndPrintMetrics(startTime, requestsPerSecond, url, patterns) }