Skip to content

Commit da0bb13

Browse files
authored
benchspy enhancements (#1545)
* add threshold validation, nil report validation, better readability for infinite metric change * use single error instead of a map[string][]error when comparing Direct queries
1 parent 456673e commit da0bb13

File tree

3 files changed

+392
-38
lines changed

3 files changed

+392
-38
lines changed

book/src/libs/wasp/benchspy/real_world.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,17 @@ This function fetches the current report (for version passed as environment vari
8787
Let’s assume you want to ensure that none of the performance metrics degrade by more than **1%** between releases (and that error rate has not changed at all). Here's how you can write assertions using a convenient function for the `Direct` query executor:
8888

8989
```go
90-
hasErrors, errors := benchspy.CompareDirectWithThresholds(
90+
hasFailed, error := benchspy.CompareDirectWithThresholds(
9191
1.0, // Max 1% worse median latency
9292
1.0, // Max 1% worse p95 latency
9393
1.0, // Max 1% worse maximum latency
9494
0.0, // No increase in error rate
9595
currentReport, previousReport)
96-
require.False(t, hasErrors, fmt.Sprintf("errors found: %v", errors))
96+
require.False(t, hasError, fmt.Sprintf("issues found: %v", error))
9797
```
9898

99+
Error returned by this function is a concatenation of all threshold violations found for each standard metric and generator.
100+
99101
---
100102

101103
## Conclusion

wasp/benchspy/report.go

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package benchspy
33
import (
44
"context"
55
"encoding/json"
6+
goerrors "errors"
67
"fmt"
78
"os"
89
"strings"
@@ -150,21 +151,27 @@ func MustAllPrometheusResults(sr *StandardReport) map[string]model.Value {
150151
}
151152

152153
func calculateDiffPercentage(current, previous float64) float64 {
153-
var diffPrecentage float64
154-
if previous != 0.0 && current != 0.0 {
155-
diffPrecentage = (current - previous) / previous * 100
156-
} else if previous == 0.0 && current == 0.0 {
157-
diffPrecentage = 0.0
158-
} else {
159-
diffPrecentage = 100.0
154+
if previous == 0.0 {
155+
if current == 0.0 {
156+
return 0.0
157+
}
158+
return 999.0 // Convention for infinite change when previous is 0
159+
}
160+
161+
if current == 0.0 {
162+
return -100.0 // Complete improvement when current is 0
160163
}
161164

162-
return diffPrecentage
165+
return (current - previous) / previous * 100
163166
}
164167

165168
// CompareDirectWithThresholds evaluates the current and previous reports against specified thresholds.
166169
// It checks for significant differences in metrics and returns any discrepancies found, aiding in performance analysis.
167-
func CompareDirectWithThresholds(medianThreshold, p95Threshold, maxThreshold, errorRateThreshold float64, currentReport, previousReport *StandardReport) (bool, map[string][]error) {
170+
func CompareDirectWithThresholds(medianThreshold, p95Threshold, maxThreshold, errorRateThreshold float64, currentReport, previousReport *StandardReport) (bool, error) {
171+
if currentReport == nil || previousReport == nil {
172+
return true, errors.New("one or both reports are nil")
173+
}
174+
168175
L.Info().
169176
Str("Current report", currentReport.CommitOrTag).
170177
Str("Previous report", previousReport.CommitOrTag).
@@ -174,6 +181,10 @@ func CompareDirectWithThresholds(medianThreshold, p95Threshold, maxThreshold, er
174181
Float64("Error rate threshold", errorRateThreshold).
175182
Msg("Comparing Direct metrics with thresholds")
176183

184+
if thresholdsErr := validateThresholds(medianThreshold, p95Threshold, maxThreshold, errorRateThreshold); thresholdsErr != nil {
185+
return true, thresholdsErr
186+
}
187+
177188
allCurrentResults := MustAllDirectResults(currentReport)
178189
allPreviousResults := MustAllDirectResults(previousReport)
179190

@@ -239,7 +250,46 @@ func CompareDirectWithThresholds(medianThreshold, p95Threshold, maxThreshold, er
239250
Int("Number of meaningful differences", len(errors)).
240251
Msg("Finished comparing Direct metrics with thresholds")
241252

242-
return len(errors) > 0, errors
253+
return len(errors) > 0, concatenateGeneratorErrors(errors)
254+
}
255+
256+
func concatenateGeneratorErrors(errors map[string][]error) error {
257+
var errs []error
258+
for generatorName, errors := range errors {
259+
for _, err := range errors {
260+
errs = append(errs, fmt.Errorf("[%s] %w", generatorName, err))
261+
}
262+
}
263+
return goerrors.Join(errs...)
264+
}
265+
266+
func validateThresholds(medianThreshold, p95Threshold, maxThreshold, errorRateThreshold float64) error {
267+
var errs []error
268+
269+
var validateThreshold = func(name string, threshold float64) error {
270+
if threshold < 0 || threshold > 100 {
271+
return fmt.Errorf("%s threshold %.4f is not in the range [0, 100]", name, threshold)
272+
}
273+
return nil
274+
}
275+
276+
if err := validateThreshold("median", medianThreshold); err != nil {
277+
errs = append(errs, err)
278+
}
279+
280+
if err := validateThreshold("p95", p95Threshold); err != nil {
281+
errs = append(errs, err)
282+
}
283+
284+
if err := validateThreshold("max", maxThreshold); err != nil {
285+
errs = append(errs, err)
286+
}
287+
288+
if err := validateThreshold("error rate", errorRateThreshold); err != nil {
289+
errs = append(errs, err)
290+
}
291+
292+
return goerrors.Join(errs...)
243293
}
244294

245295
// PrintStandardDirectMetrics outputs a comparison of direct metrics between two reports.

0 commit comments

Comments
 (0)