1
0
Fork 0

Logging at the end is improved.

This commit is contained in:
Atridad Lahiji 2024-12-04 18:25:51 -06:00
parent e8f9b1a763
commit 9f746927aa
Signed by: atridad
SSH key fingerprint: SHA256:LGomp8Opq0jz+7kbwNcdfTcuaLRb5Nh0k5AchDDb438

View file

@ -14,9 +14,9 @@ import (
// Global metrics instance to track performance during load testing // Global metrics instance to track performance during load testing
var metrics = PerformanceMetrics{ var metrics = PerformanceMetrics{
MinLatency: time.Duration(math.MaxInt64), MinLatency: time.Duration(math.MaxInt64),
ResponseCounters: make(map[int]int32), ResponseCounters: make(map[int]int32),
RequestLatencies: make([]RequestMetric, 0), RequestLatencies: make([]RequestMetric, 0),
} }
// UpdateMetrics records metrics for each individual request // UpdateMetrics records metrics for each individual request
@ -25,52 +25,53 @@ var metrics = PerformanceMetrics{
// - resp: HTTP response from the request // - resp: HTTP response from the request
// - second: elapsed seconds since the start of the test // - second: elapsed seconds since the start of the test
func UpdateMetrics(duration time.Duration, resp *http.Response, second int) { func UpdateMetrics(duration time.Duration, resp *http.Response, second int) {
metrics.Mu.Lock() metrics.Mu.Lock()
defer metrics.Mu.Unlock() defer metrics.Mu.Unlock()
// Create and store individual request metric // Create and store individual request metric
metric := RequestMetric{ metric := RequestMetric{
Timestamp: time.Now(), Timestamp: time.Now(),
Duration: duration, Duration: duration,
StatusCode: resp.StatusCode, StatusCode: resp.StatusCode,
Verb: resp.Request.Method, Verb: resp.Request.Method,
} }
metrics.RequestLatencies = append(metrics.RequestLatencies, metric) metrics.RequestLatencies = append(metrics.RequestLatencies, metric)
// Update aggregate metrics // Update aggregate metrics
metrics.TotalRequests++ metrics.TotalRequests++
metrics.TotalLatency += duration metrics.TotalLatency += duration
// Update max/min latencies // Update max/min latencies
if duration > metrics.MaxLatency { if duration > metrics.MaxLatency {
metrics.MaxLatency = duration metrics.MaxLatency = duration
} }
if duration < metrics.MinLatency { if duration < metrics.MinLatency {
metrics.MinLatency = duration metrics.MinLatency = duration
} }
// Track successful responses // Track successful responses
if resp.StatusCode == http.StatusOK { if resp.StatusCode == http.StatusOK {
metrics.TotalResponses++ metrics.TotalResponses++
metrics.ResponseCounters[second]++ metrics.ResponseCounters[second]++
} }
} }
// calculatePercentile calculates the nth percentile of latencies // calculatePercentile calculates the nth percentile of latencies
// Parameters: // Parameters:
// - latencies: sorted slice of request durations // - latencies: sorted slice of request durations
// - percentile: desired percentile (e.g., 50 for p50, 95 for p95) // - percentile: desired percentile (e.g., 50 for p50, 95 for p95)
//
// Returns: the duration at the specified percentile // Returns: the duration at the specified percentile
func calculatePercentile(latencies []time.Duration, percentile float64) time.Duration { func calculatePercentile(latencies []time.Duration, percentile float64) time.Duration {
if len(latencies) == 0 { if len(latencies) == 0 {
return 0 return 0
} }
index := int(math.Ceil((percentile/100.0)*float64(len(latencies)))) - 1 index := int(math.Ceil((percentile/100.0)*float64(len(latencies)))) - 1
if index < 0 { if index < 0 {
index = 0 index = 0
} }
return latencies[index] return latencies[index]
} }
// CalculateAndPrintMetrics generates and saves comprehensive test results // CalculateAndPrintMetrics generates and saves comprehensive test results
@ -80,139 +81,137 @@ func calculatePercentile(latencies []time.Duration, percentile float64) time.Dur
// - endpoint: URL being tested // - endpoint: URL being tested
// - patterns: request patterns used in the test // - patterns: request patterns used in the test
func CalculateAndPrintMetrics(startTime time.Time, requestsPerSecond float64, endpoint string, patterns []RequestPattern) { func CalculateAndPrintMetrics(startTime time.Time, requestsPerSecond float64, endpoint string, patterns []RequestPattern) {
// Small delay to ensure all metrics are captured // Small delay to ensure all metrics are captured
time.Sleep(100 * time.Millisecond) time.Sleep(100 * time.Millisecond)
metrics.Mu.Lock() metrics.Mu.Lock()
defer metrics.Mu.Unlock() defer metrics.Mu.Unlock()
// Save detailed per-request metrics to CSV // Prepare latencies for percentile calculations
saveDetailedMetrics() latencies := make([]time.Duration, len(metrics.RequestLatencies))
for i, metric := range metrics.RequestLatencies {
latencies[i] = metric.Duration
}
sort.Slice(latencies, func(i, j int) bool {
return latencies[i] < latencies[j]
})
// Prepare latencies for percentile calculations // Calculate various percentiles
latencies := make([]time.Duration, len(metrics.RequestLatencies)) p50 := calculatePercentile(latencies, 50)
for i, metric := range metrics.RequestLatencies { p95 := calculatePercentile(latencies, 95)
latencies[i] = metric.Duration p99 := calculatePercentile(latencies, 99)
}
sort.Slice(latencies, func(i, j int) bool {
return latencies[i] < latencies[j]
})
// Calculate various percentiles // Calculate average latency
p50 := calculatePercentile(latencies, 50) averageLatency := time.Duration(0)
p95 := calculatePercentile(latencies, 95) if metrics.TotalRequests > 0 {
p99 := calculatePercentile(latencies, 99) averageLatency = metrics.TotalLatency / time.Duration(metrics.TotalRequests)
}
// Calculate average latency // Calculate total duration and responses
averageLatency := time.Duration(0) totalDuration := time.Since(startTime).Seconds()
if metrics.TotalRequests > 0 { totalResponses := int32(0)
averageLatency = metrics.TotalLatency / time.Duration(metrics.TotalRequests) for _, count := range metrics.ResponseCounters {
} totalResponses += count
}
// Calculate total duration and responses // Reset min latency if unused
totalDuration := time.Since(startTime).Seconds() if metrics.MinLatency == time.Duration(math.MaxInt64) {
totalResponses := int32(0) metrics.MinLatency = 0
for _, count := range metrics.ResponseCounters { }
totalResponses += count
}
// Reset min latency if unused // Build detailed results string
if metrics.MinLatency == time.Duration(math.MaxInt64) { results := fmt.Sprintf("Load Test Report\n=============\n\n")
metrics.MinLatency = 0 results += fmt.Sprintf("Endpoint: %s\n", endpoint)
} results += fmt.Sprintf("Pattern: ")
// Build detailed results string // Format request patterns
results := fmt.Sprintf("Load Test Report\n=============\n\n") for i, p := range patterns {
results += fmt.Sprintf("Endpoint: %s\n", endpoint) if i > 0 {
results += fmt.Sprintf("Pattern: ") results += " → "
}
if p.Percentage > 0 && p.Percentage < 100 {
results += fmt.Sprintf("%.0f%%%s", p.Percentage, strings.ToLower(p.Verb[:1]))
} else {
results += fmt.Sprintf("%d%s", p.Sequence, strings.ToLower(p.Verb[:1]))
}
}
results += "\n\n"
// Format request patterns // Add performance metrics
for i, p := range patterns { results += fmt.Sprintf("Performance Metrics\n")
if i > 0 { results += fmt.Sprintf("-----------------\n")
results += " → " results += fmt.Sprintf("Total Requests Sent: %d\n", metrics.TotalRequests)
} results += fmt.Sprintf("Total Responses Received: %d\n", totalResponses)
if p.Percentage > 0 && p.Percentage < 100 { results += fmt.Sprintf("Average Latency: %s\n", averageLatency)
results += fmt.Sprintf("%.0f%%%s", p.Percentage, strings.ToLower(p.Verb[:1])) results += fmt.Sprintf("Max Latency: %s\n", metrics.MaxLatency)
} else { results += fmt.Sprintf("Min Latency: %s\n", metrics.MinLatency)
results += fmt.Sprintf("%d%s", p.Sequence, strings.ToLower(p.Verb[:1])) results += fmt.Sprintf("Requests/sec (Target): %.2f\n", requestsPerSecond)
} results += fmt.Sprintf("Requests/sec (Actual): %.2f\n", float64(metrics.TotalRequests)/totalDuration)
} results += fmt.Sprintf("Responses/sec: %.2f\n", float64(totalResponses)/totalDuration)
results += "\n\n"
// Add performance metrics // Add percentile information
results += fmt.Sprintf("Performance Metrics\n") results += fmt.Sprintf("\nLatency Percentiles\n")
results += fmt.Sprintf("-----------------\n") results += fmt.Sprintf("-----------------\n")
results += fmt.Sprintf("Total Requests Sent: %d\n", metrics.TotalRequests) results += fmt.Sprintf("50th percentile (p50): %s\n", p50)
results += fmt.Sprintf("Total Responses Received: %d\n", totalResponses) results += fmt.Sprintf("95th percentile (p95): %s\n", p95)
results += fmt.Sprintf("Average Latency: %s\n", averageLatency) results += fmt.Sprintf("99th percentile (p99): %s\n", p99)
results += fmt.Sprintf("Max Latency: %s\n", metrics.MaxLatency)
results += fmt.Sprintf("Min Latency: %s\n", metrics.MinLatency)
results += fmt.Sprintf("Requests/sec (Target): %.2f\n", requestsPerSecond)
results += fmt.Sprintf("Requests/sec (Actual): %.2f\n", float64(metrics.TotalRequests)/totalDuration)
results += fmt.Sprintf("Responses/sec: %.2f\n", float64(totalResponses)/totalDuration)
// Add percentile information fmt.Println(results)
results += fmt.Sprintf("\nLatency Percentiles\n") saveReport(results)
results += fmt.Sprintf("-----------------\n") saveDetailedMetrics()
results += fmt.Sprintf("50th percentile (p50): %s\n", p50)
results += fmt.Sprintf("95th percentile (p95): %s\n", p95)
results += fmt.Sprintf("99th percentile (p99): %s\n", p99)
fmt.Println(results)
saveReport(results)
} }
// saveDetailedMetrics writes per-request metrics to a CSV file // saveDetailedMetrics writes per-request metrics to a CSV file
// The CSV includes timestamp, duration, status code, and request type for each request // The CSV includes timestamp, duration, status code, and request type for each request
func saveDetailedMetrics() { func saveDetailedMetrics() {
resultsDir := ".reports" resultsDir := ".reports"
os.MkdirAll(resultsDir, os.ModePerm) os.MkdirAll(resultsDir, os.ModePerm)
csvFile := filepath.Join(resultsDir, fmt.Sprintf("detailed_metrics_%d.csv", time.Now().Unix())) csvFile := filepath.Join(resultsDir, fmt.Sprintf("detailed_metrics_%d.csv", time.Now().Unix()))
file, err := os.Create(csvFile) file, err := os.Create(csvFile)
if err != nil { if err != nil {
fmt.Println("Error creating CSV file:", err) fmt.Println("Error creating CSV file:", err)
return return
} }
defer file.Close() defer file.Close()
writer := csv.NewWriter(file) writer := csv.NewWriter(file)
defer writer.Flush() defer writer.Flush()
// Write CSV header // Write CSV header
writer.Write([]string{ writer.Write([]string{
"Timestamp", "Timestamp",
"Duration (ms)", "Duration (ms)",
"Status Code", "Status Code",
"Request Type", "Request Type",
}) })
// Write individual request metrics // Write individual request metrics
for _, metric := range metrics.RequestLatencies { for _, metric := range metrics.RequestLatencies {
writer.Write([]string{ writer.Write([]string{
metric.Timestamp.Format(time.RFC3339), metric.Timestamp.Format(time.RFC3339),
fmt.Sprintf("%.2f", float64(metric.Duration.Milliseconds())), fmt.Sprintf("%.2f", float64(metric.Duration.Milliseconds())),
fmt.Sprintf("%d", metric.StatusCode), fmt.Sprintf("%d", metric.StatusCode),
metric.Verb, metric.Verb,
}) })
} }
fmt.Println("Detailed metrics saved:", csvFile) fmt.Println("Detailed metrics saved:", csvFile)
} }
// saveReport writes the summary report to a text file // saveReport writes the summary report to a text file
// Parameters: // Parameters:
// - results: formatted string containing the complete test results // - results: formatted string containing the complete test results
func saveReport(results string) { func saveReport(results string) {
resultsDir := ".reports" resultsDir := ".reports"
os.MkdirAll(resultsDir, os.ModePerm) os.MkdirAll(resultsDir, os.ModePerm)
resultsFile := filepath.Join(resultsDir, fmt.Sprintf("summary_%d.txt", time.Now().Unix())) resultsFile := filepath.Join(resultsDir, fmt.Sprintf("summary_%d.txt", time.Now().Unix()))
if err := os.WriteFile(resultsFile, []byte(results), 0644); err != nil { if err := os.WriteFile(resultsFile, []byte(results), 0644); err != nil {
fmt.Println("Error saving report:", err) fmt.Println("Error saving report:", err)
return return
} }
fmt.Println("Summary report saved:", resultsFile) fmt.Println("Summary report saved:", resultsFile)
} }