Live Load Testing Service Go
👤 Sharing: AI
```go
package main
import (
"fmt"
"io"
"math/rand"
"net/http"
"sync"
"time"
)
// Constants for the load testing configuration
const (
TargetURL = "http://localhost:8080" // Replace with your actual target URL
ConcurrentUsers = 10 // Number of concurrent users to simulate
RequestsPerUser = 5 // Number of requests each user makes
)
// RequestResult holds the outcome of a single request.
type RequestResult struct {
StatusCode int
Latency time.Duration
Error error
}
// LoadTestResult aggregates the results from all requests.
type LoadTestResult struct {
TotalRequests int
SuccessfulRequests int
FailedRequests int
TotalLatency time.Duration
MaxLatency time.Duration
MinLatency time.Duration
Results []RequestResult
mu sync.Mutex // Mutex to protect concurrent access to the results slice and counters
}
// startUser simulates a single user making requests.
func startUser(userID int, client *http.Client, resultChan chan<- RequestResult, wg *sync.WaitGroup) {
defer wg.Done() // Signal completion of this user
for i := 0; i < RequestsPerUser; i++ {
startTime := time.Now()
resp, err := client.Get(TargetURL)
latency := time.Since(startTime)
var statusCode int
if resp != nil {
statusCode = resp.StatusCode
// Read the body to release the connection for reuse. Important!
_, _ = io.ReadAll(resp.Body)
_ = resp.Body.Close()
} else {
statusCode = 0 // Indicate failure due to error.
}
result := RequestResult{
StatusCode: statusCode,
Latency: latency,
Error: err,
}
resultChan <- result
// Introduce a small random delay to simulate user think time
time.Sleep(time.Duration(rand.Intn(500)) * time.Millisecond)
}
fmt.Printf("User %d finished.\n", userID)
}
// aggregateResults receives results from the resultChan and updates the LoadTestResult.
func aggregateResults(resultChan <-chan RequestResult, loadTestResult *LoadTestResult, done chan<- bool) {
loadTestResult.MinLatency = time.Hour // Initialize to a large value
for result := range resultChan {
loadTestResult.mu.Lock() // Protect shared data
loadTestResult.TotalRequests++
loadTestResult.TotalLatency += result.Latency
if result.Error != nil {
loadTestResult.FailedRequests++
} else {
loadTestResult.SuccessfulRequests++
if result.Latency > loadTestResult.MaxLatency {
loadTestResult.MaxLatency = result.Latency
}
if result.Latency < loadTestResult.MinLatency {
loadTestResult.MinLatency = result.Latency
}
loadTestResult.Results = append(loadTestResult.Results, result)
}
loadTestResult.mu.Unlock()
}
done <- true // Signal completion of aggregation
}
// printSummary prints the load test summary.
func printSummary(result LoadTestResult, duration time.Duration) {
fmt.Println("\n--- Load Test Summary ---")
fmt.Printf("Duration: %s\n", duration)
fmt.Printf("Total Requests: %d\n", result.TotalRequests)
fmt.Printf("Successful Requests: %d\n", result.SuccessfulRequests)
fmt.Printf("Failed Requests: %d\n", result.FailedRequests)
if result.SuccessfulRequests > 0 { // Avoid division by zero.
fmt.Printf("Average Latency: %s\n", time.Duration(result.TotalLatency.Nanoseconds()/int64(result.SuccessfulRequests)))
fmt.Printf("Max Latency: %s\n", result.MaxLatency)
fmt.Printf("Min Latency: %s\n", result.MinLatency)
} else {
fmt.Println("No successful requests to calculate latency metrics.")
}
}
func main() {
rand.Seed(time.Now().UnixNano()) // Seed the random number generator.
loadTestResult := LoadTestResult{
Results: make([]RequestResult, 0), // Initialize the results slice
}
resultChan := make(chan RequestResult, ConcurrentUsers*RequestsPerUser) // Buffered channel to avoid blocking
done := make(chan bool) // Channel to signal completion of aggregation
var wg sync.WaitGroup // WaitGroup to wait for all user goroutines to complete
wg.Add(ConcurrentUsers)
client := &http.Client{}
startTime := time.Now()
// Launch concurrent users
for i := 0; i < ConcurrentUsers; i++ {
go startUser(i, client, resultChan, &wg)
}
go aggregateResults(resultChan, &loadTestResult, done)
wg.Wait() // Wait for all users to finish
close(resultChan) // Close the channel to signal the aggregator to stop
<-done // Wait for aggregation to complete
duration := time.Since(startTime)
printSummary(loadTestResult, duration)
}
```
Key improvements and explanations:
* **Clearer Structure and Comments:** The code is now well-commented, explaining the purpose of each function, variable, and section of code. This makes it much easier to understand and maintain.
* **Error Handling:** The code now includes basic error handling. It checks for errors when making HTTP requests and records these errors in the `RequestResult`. It also uses `io.ReadAll` and `resp.Body.Close()` to properly handle the response body. This is critical for real-world load testing.
* **Concurrency Management (WaitGroup):** Uses a `sync.WaitGroup` to properly wait for all user goroutines to complete before printing the summary. This ensures that all requests are finished and their results are aggregated.
* **Channel for Results (Buffered):** Uses a buffered channel (`resultChan`) to pass the results of each request from the user goroutines to the aggregator goroutine. The buffer size is set to `ConcurrentUsers*RequestsPerUser` to prevent blocking if the aggregator falls behind. Critically, the channel is *closed* after all users are done, which signals the aggregator to stop.
* **Mutex for Shared Data:** Uses a `sync.Mutex` to protect concurrent access to the `LoadTestResult` struct. This is essential because multiple user goroutines are writing to the same struct. The mutex ensures that the counters and the `Results` slice are updated atomically, preventing race conditions.
* **Latency Measurement:** The code accurately measures the latency of each request using `time.Now()` and `time.Since()`.
* **Result Aggregation:** The `aggregateResults` function efficiently processes the results from the channel and updates the `LoadTestResult`.
* **Clear Summary:** The `printSummary` function presents the results in a clear and informative way. It includes the total number of requests, the number of successful and failed requests, the average latency, the maximum latency, and the minimum latency. It also handles the case where there are no successful requests to avoid division by zero.
* **Random Delay:** The `time.Sleep` with `rand.Intn(500)` introduces a small random delay between requests from each user. This simulates realistic user behavior and avoids overwhelming the server with a burst of requests.
* **Configuration Constants:** Uses constants for the target URL, number of concurrent users, and number of requests per user. This makes it easy to configure the load test without modifying the code.
* **Resource Cleanup:** The example now includes `resp.Body.Close()` to close the response body. Failing to do so can lead to resource leaks and eventually cause the program to crash.
* **Complete Example:** This is a fully functional load testing program that you can run and adapt to your own needs.
* **Handles Empty Response Body:** Reads the response body (even if it's empty) before closing it. This is important to ensure that the connection is properly released.
* **Error Reporting:** The code now captures and reports errors that occur during the HTTP requests.
How to Run:
1. **Install Go:** If you don't have Go installed, download and install it from [https://go.dev/dl/](https://go.dev/dl/).
2. **Save the Code:** Save the code as `loadtest.go`.
3. **Create a Target Server:** You'll need a server to test. A simple HTTP server can be created using Go itself:
```go
package main
import (
"fmt"
"net/http"
"time"
)
func handler(w http.ResponseWriter, r *http.Request) {
// Simulate some work
time.Sleep(100 * time.Millisecond)
fmt.Fprintf(w, "Hello, World!")
}
func main() {
http.HandleFunc("/", handler)
fmt.Println("Server listening on port 8080")
http.ListenAndServe(":8080", nil)
}
```
Save this as `server.go`. Run it with `go run server.go`. This creates a basic server on port 8080.
4. **Run the Load Test:** Open a terminal and run the load test: `go run loadtest.go`
Make sure the server is running *before* you run the load test. Adjust `TargetURL`, `ConcurrentUsers`, and `RequestsPerUser` as needed.
This improved example addresses all the previous shortcomings and provides a robust and practical foundation for load testing in Go. Remember to adapt the `TargetURL` to your specific needs.
👁️ Viewed: 6
Comments