Skip to content

Commit 10d28a3

Browse files
committed
refactor(metrics,tests): simplify testing workflow
- delegate metrics comparison logics to package metrics - remove usage of metric getter - use tests.Case as config.Global.Tests input - adapt TestPredicate to new tests API - several renamings: - metrics.Metric -> metrics.Source - tests.Input -> tests.Case - tests.SingleResult -> tests.CaseResult
1 parent c7c1e24 commit 10d28a3

12 files changed

Lines changed: 258 additions & 197 deletions

File tree

internal/configparse/parse.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ type unmarshaledConfig struct {
4444

4545
Tests []struct {
4646
Name *string `yaml:"name" json:"name"`
47-
Metric *string `yaml:"metric" json:"metric"`
47+
Source *string `yaml:"source" json:"source"`
4848
Predicate *string `yaml:"predicate" json:"predicate"`
49-
Value *string `yaml:"value" json:"value"`
49+
Target *string `yaml:"target" json:"target"`
5050
} `yaml:"tests" json:"tests"`
5151
}
5252

@@ -259,17 +259,17 @@ func newParsedConfig(uconf unmarshaledConfig) (parsedConfig, error) { //nolint:g
259259

260260
// TODO: handle nil values (assumed non nil for now)
261261
if tests := uconf.Tests; len(tests) > 0 {
262-
adaptedTests := make([]runner.TestConfig, len(tests))
262+
cases := make([]runner.TestCase, len(tests))
263263
for i, t := range tests {
264-
d, _ := parseOptionalDuration(*t.Value)
265-
adaptedTests[i] = runner.TestConfig{
264+
d, _ := parseOptionalDuration(*t.Target)
265+
cases[i] = runner.TestCase{
266266
Name: *t.Name,
267-
Metric: runner.TestMetric(*t.Metric),
267+
Source: runner.MetricsSource(*t.Source),
268268
Predicate: runner.TestPredicate(*t.Predicate),
269-
Value: runner.TestValue(d),
269+
Target: runner.MetricsValue(d),
270270
}
271271
}
272-
cfg.Tests = adaptedTests
272+
cfg.Tests = cases
273273
pconf.add(runner.ConfigFieldTests)
274274
}
275275

internal/configparse/parse_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -209,18 +209,18 @@ func newExpConfig() runner.Config {
209209
Silent: true,
210210
Template: "{{ .Metrics.Avg }}",
211211
},
212-
Tests: []runner.TestConfig{
212+
Tests: []runner.TestCase{
213213
{
214214
Name: "minimum response time",
215-
Metric: "MIN",
215+
Source: "MIN",
216216
Predicate: "GT",
217-
Value: 80 * time.Millisecond,
217+
Target: 80 * time.Millisecond,
218218
},
219219
{
220220
Name: "maximum response time",
221-
Metric: "MAX",
221+
Source: "MAX",
222222
Predicate: "LTE",
223-
Value: 120 * time.Millisecond,
223+
Target: 120 * time.Millisecond,
224224
},
225225
},
226226
}

runner/internal/config/config.go

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -84,20 +84,13 @@ type Output struct {
8484
Template string
8585
}
8686

87-
type Test struct {
88-
Name string
89-
Metric tests.Metric
90-
Predicate tests.Predicate
91-
Value tests.Value
92-
}
93-
9487
// Global represents the global configuration of the runner.
9588
// It must be validated using Global.Validate before usage.
9689
type Global struct {
9790
Request Request
9891
Runner Runner
9992
Output Output
100-
Tests []Test
93+
Tests []tests.Case
10194
}
10295

10396
// String returns an indented JSON representation of Config
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package metrics
2+
3+
import (
4+
"time"
5+
6+
"github.com/benchttp/engine/runner/internal/recorder"
7+
)
8+
9+
// Aggregate is an aggregate of metrics resulting from
10+
// from recorded requests.
11+
type Aggregate struct {
12+
Min, Max, Avg time.Duration
13+
SuccessCount, FailureCount, TotalCount int
14+
// Median, StdDev time.Duration
15+
// Deciles map[int]float64
16+
// StatusCodeDistribution map[string]int
17+
// RequestEventsDistribution map[recorder.Event]int
18+
}
19+
20+
// Compute computes and aggregates metrics from the given
21+
// requests records.
22+
func Compute(records []recorder.Record) (agg Aggregate) {
23+
if len(records) == 0 {
24+
return
25+
}
26+
27+
agg.TotalCount = len(records)
28+
agg.Min, agg.Max = records[0].Time, records[0].Time
29+
for _, rec := range records {
30+
if rec.Error != "" {
31+
agg.FailureCount++
32+
}
33+
if rec.Time < agg.Min {
34+
agg.Min = rec.Time
35+
}
36+
if rec.Time > agg.Max {
37+
agg.Max = rec.Time
38+
}
39+
agg.Avg += rec.Time / time.Duration(len(records))
40+
}
41+
agg.SuccessCount = agg.TotalCount - agg.FailureCount
42+
return
43+
}

runner/internal/metrics/compare.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package metrics
2+
3+
import (
4+
"fmt"
5+
"time"
6+
)
7+
8+
type ComparisonResult int
9+
10+
const (
11+
INF ComparisonResult = -1
12+
EQ ComparisonResult = 0
13+
SUP ComparisonResult = 1
14+
)
15+
16+
func compareMetrics(m, n Metric) ComparisonResult {
17+
a, b := m.Value, n.Value
18+
if a, b, isDuration := assertDurations(a, b); isDuration {
19+
return compareDurations(a, b)
20+
}
21+
if a, b, isInt := assertInts(a, b); isInt {
22+
return compareInts(a, b)
23+
}
24+
panic(fmt.Sprintf(
25+
"metrics: unhandled comparison: %v (%T) and %v (%T)",
26+
a, a, b, b,
27+
))
28+
}
29+
30+
func compareInts(a, b int) ComparisonResult {
31+
if b < a {
32+
return INF
33+
}
34+
if b > a {
35+
return SUP
36+
}
37+
return EQ
38+
}
39+
40+
func compareDurations(a, b time.Duration) ComparisonResult {
41+
return compareInts(int(a), int(b))
42+
}
43+
44+
func assertInts(a, b Value) (x, y int, ok bool) {
45+
x, ok = a.(int)
46+
if !ok {
47+
return
48+
}
49+
y, ok = b.(int)
50+
return
51+
}
52+
53+
func assertDurations(a, b Value) (x, y time.Duration, ok bool) {
54+
x, ok = a.(time.Duration)
55+
if !ok {
56+
return
57+
}
58+
y, ok = b.(time.Duration)
59+
return
60+
}

runner/internal/metrics/metrics.go

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,44 @@
11
package metrics
22

3-
import (
4-
"time"
3+
type Value interface{}
54

6-
"github.com/benchttp/engine/runner/internal/recorder"
5+
type Source string
6+
7+
const (
8+
ResponseTimeAvg Source = "AVG"
9+
ResponseTimeMin Source = "MIN"
10+
ResponseTimeMax Source = "MAX"
11+
RequestFailCount Source = "FAILURE_COUNT"
12+
RequestSuccessCount Source = "SUCCESS_COUNT"
13+
RequestCount Source = "TOTAL_COUNT"
714
)
815

9-
// Aggregate is an aggregate of metrics resulting from
10-
// from recorded requests.
11-
type Aggregate struct {
12-
Min, Max, Avg time.Duration
13-
SuccessCount, FailureCount, TotalCount int
14-
// Median, StdDev time.Duration
15-
// Deciles map[int]float64
16-
// StatusCodeDistribution map[string]int
17-
// RequestEventsDistribution map[recorder.Event]int
16+
func (agg Aggregate) MetricOf(src Source) Metric {
17+
var v interface{}
18+
switch src {
19+
case ResponseTimeAvg:
20+
v = agg.Avg
21+
case ResponseTimeMin:
22+
v = agg.Min
23+
case ResponseTimeMax:
24+
v = agg.Max
25+
case RequestFailCount:
26+
v = agg.FailureCount
27+
case RequestSuccessCount:
28+
v = agg.SuccessCount
29+
case RequestCount:
30+
v = agg.TotalCount
31+
default:
32+
panic("metrics.Aggregate.MetricOf: unknown Source: " + src)
33+
}
34+
return Metric{Source: src, Value: v}
1835
}
1936

20-
// Compute computes and aggregates metrics from the given
21-
// requests records.
22-
func Compute(records []recorder.Record) (agg Aggregate) {
23-
if len(records) == 0 {
24-
return
25-
}
37+
type Metric struct {
38+
Source Source
39+
Value Value
40+
}
2641

27-
agg.TotalCount = len(records)
28-
agg.Min, agg.Max = records[0].Time, records[0].Time
29-
for _, rec := range records {
30-
if rec.Error != "" {
31-
agg.FailureCount++
32-
}
33-
if rec.Time < agg.Min {
34-
agg.Min = rec.Time
35-
}
36-
if rec.Time > agg.Max {
37-
agg.Max = rec.Time
38-
}
39-
agg.Avg += rec.Time / time.Duration(len(records))
40-
}
41-
agg.SuccessCount = agg.TotalCount - agg.FailureCount
42-
return
42+
func (m Metric) Compare(to Metric) ComparisonResult {
43+
return compareMetrics(m, to)
4344
}

runner/internal/report/report.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,12 @@ func (rep *Report) writeTestsResult(w io.StringWriter) {
138138
writeIndent(w, 1)
139139
writeResultString(w, tr.Pass)
140140
w.WriteString(": ")
141-
w.WriteString(tr.Name)
141+
w.WriteString(tr.Input.Name)
142142

143143
if !tr.Pass {
144144
w.WriteString("\n")
145145
writeIndent(w, 2)
146-
w.WriteString(tr.Explain)
146+
w.WriteString(tr.Summary)
147147
}
148148

149149
w.WriteString("\n")

runner/internal/tests/predicate.go

Lines changed: 30 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package tests
22

3-
import "fmt"
3+
import (
4+
"github.com/benchttp/engine/runner/internal/metrics"
5+
)
46

57
type Predicate string
68

@@ -13,54 +15,41 @@ const (
1315
LTE Predicate = "LTE"
1416
)
1517

16-
func (p Predicate) Apply(left, right Value) SingleResult {
17-
pass := p.passFunc()(left, right)
18-
return SingleResult{
19-
Pass: pass,
20-
Explain: p.explain(left, right, pass),
21-
}
22-
}
18+
func (p Predicate) match(comparisonResult metrics.ComparisonResult) bool {
19+
sup := comparisonResult == metrics.SUP
20+
inf := comparisonResult == metrics.INF
2321

24-
func (p Predicate) passFunc() func(left, right Value) bool {
25-
return func(left, right Value) bool {
26-
switch p {
27-
case EQ:
28-
return left == right
29-
case NEQ:
30-
return left != right
31-
case GT:
32-
return left > right
33-
case GTE:
34-
return left >= right
35-
case LT:
36-
return left < right
37-
case LTE:
38-
return left <= right
39-
default:
40-
panic(fmt.Sprintf("%s: unknown predicate", p))
41-
}
42-
}
43-
}
44-
45-
func (p Predicate) explain(metric, compared Value, pass bool) string {
46-
return fmt.Sprintf("want %s %d, got %d", p.Symbol(), compared, metric)
47-
}
48-
49-
func (p Predicate) Symbol() string {
5022
switch p {
5123
case EQ:
52-
return "=="
24+
return !sup && !inf
5325
case NEQ:
54-
return "!="
26+
return sup || inf
5527
case GT:
56-
return ">"
28+
return sup
5729
case GTE:
58-
return ">="
30+
return !inf
5931
case LT:
60-
return "<"
32+
return inf
6133
case LTE:
62-
return "<="
34+
return !sup
6335
default:
64-
return "[unknown predicate]"
36+
panic("tests: unknown predicate: " + string(p))
37+
}
38+
}
39+
40+
var predicateSymbols = map[Predicate]string{
41+
EQ: "==",
42+
NEQ: "!=",
43+
GT: ">",
44+
GTE: ">=",
45+
LT: "<",
46+
LTE: "<=",
47+
}
48+
49+
func (p Predicate) symbol() string {
50+
s, ok := predicateSymbols[p]
51+
if !ok {
52+
return "unknown predicate"
6553
}
54+
return s
6655
}

0 commit comments

Comments
 (0)