Skip to content

Commit 9bf19e4

Browse files
routing: measure CreateFilter latency (#3115)
Add `MeasureFilterCreate` method to `Metrics` interface similar to existing `MeasureFilterRequest` and `MeasureFilterResponse`. The alternative would be to use `MeasureSince` but then filter name would have to be embedded into metric key. Signed-off-by: Alexander Yastrebov <[email protected]>
1 parent ff44b6d commit 9bf19e4

File tree

10 files changed

+137
-16
lines changed

10 files changed

+137
-16
lines changed

metrics/all_kind.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,71 +23,93 @@ func (a *All) MeasureSince(key string, start time.Time) {
2323
a.prometheus.MeasureSince(key, start)
2424
a.codaHale.MeasureSince(key, start)
2525
}
26+
2627
func (a *All) IncCounter(key string) {
2728
a.prometheus.IncCounter(key)
2829
a.codaHale.IncCounter(key)
2930
}
31+
3032
func (a *All) IncCounterBy(key string, value int64) {
3133
a.prometheus.IncCounterBy(key, value)
3234
a.codaHale.IncCounterBy(key, value)
3335
}
36+
3437
func (a *All) IncFloatCounterBy(key string, value float64) {
3538
a.prometheus.IncFloatCounterBy(key, value)
3639
a.codaHale.IncFloatCounterBy(key, value)
3740
}
41+
3842
func (a *All) UpdateGauge(key string, v float64) {
3943
a.prometheus.UpdateGauge(key, v)
4044
a.codaHale.UpdateGauge(key, v)
4145
}
46+
4247
func (a *All) MeasureRouteLookup(start time.Time) {
4348
a.prometheus.MeasureRouteLookup(start)
4449
a.codaHale.MeasureRouteLookup(start)
4550
}
51+
52+
func (a *All) MeasureFilterCreate(filterName string, start time.Time) {
53+
a.prometheus.MeasureFilterCreate(filterName, start)
54+
a.codaHale.MeasureFilterCreate(filterName, start)
55+
}
56+
4657
func (a *All) MeasureFilterRequest(filterName string, start time.Time) {
4758
a.prometheus.MeasureFilterRequest(filterName, start)
4859
a.codaHale.MeasureFilterRequest(filterName, start)
4960
}
61+
5062
func (a *All) MeasureAllFiltersRequest(routeId string, start time.Time) {
5163
a.prometheus.MeasureAllFiltersRequest(routeId, start)
5264
a.codaHale.MeasureAllFiltersRequest(routeId, start)
5365
}
66+
5467
func (a *All) MeasureBackend(routeId string, start time.Time) {
5568
a.prometheus.MeasureBackend(routeId, start)
5669
a.codaHale.MeasureBackend(routeId, start)
5770
}
71+
5872
func (a *All) MeasureBackendHost(routeBackendHost string, start time.Time) {
5973
a.prometheus.MeasureBackendHost(routeBackendHost, start)
6074
a.codaHale.MeasureBackendHost(routeBackendHost, start)
6175
}
76+
6277
func (a *All) MeasureFilterResponse(filterName string, start time.Time) {
6378
a.prometheus.MeasureFilterResponse(filterName, start)
6479
a.codaHale.MeasureFilterResponse(filterName, start)
6580
}
81+
6682
func (a *All) MeasureAllFiltersResponse(routeId string, start time.Time) {
6783
a.prometheus.MeasureAllFiltersResponse(routeId, start)
6884
a.codaHale.MeasureAllFiltersResponse(routeId, start)
6985
}
86+
7087
func (a *All) MeasureResponse(code int, method string, routeId string, start time.Time) {
7188
a.prometheus.MeasureResponse(code, method, routeId, start)
7289
a.codaHale.MeasureResponse(code, method, routeId, start)
7390
}
91+
7492
func (a *All) MeasureServe(routeId, host, method string, code int, start time.Time) {
7593
a.prometheus.MeasureServe(routeId, host, method, code, start)
7694
a.codaHale.MeasureServe(routeId, host, method, code, start)
7795
}
96+
7897
func (a *All) IncRoutingFailures() {
7998
a.prometheus.IncRoutingFailures()
8099
a.codaHale.IncRoutingFailures()
81100
}
101+
82102
func (a *All) IncErrorsBackend(routeId string) {
83103
a.prometheus.IncErrorsBackend(routeId)
84104
a.codaHale.IncErrorsBackend(routeId)
85105
}
106+
86107
func (a *All) MeasureBackend5xx(t time.Time) {
87108
a.prometheus.MeasureBackend5xx(t)
88109
a.codaHale.MeasureBackend5xx(t)
89110

90111
}
112+
91113
func (a *All) IncErrorsStreaming(routeId string) {
92114
a.prometheus.IncErrorsStreaming(routeId)
93115
a.codaHale.IncErrorsStreaming(routeId)

metrics/codahale.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
const (
1515
KeyRouteLookup = "routelookup"
1616
KeyRouteFailure = "routefailure"
17+
KeyFilterCreate = "filter.%s.create"
1718
KeyFilterRequest = "filter.%s.request"
1819
KeyFiltersRequest = "allfilters.request.%s"
1920
KeyAllFiltersRequestCombined = "allfilters.combined.request"
@@ -131,6 +132,10 @@ func (c *CodaHale) MeasureRouteLookup(start time.Time) {
131132
c.measureSince(KeyRouteLookup, start)
132133
}
133134

135+
func (c *CodaHale) MeasureFilterCreate(filterName string, start time.Time) {
136+
c.measureSince(fmt.Sprintf(KeyFilterCreate, filterName), start)
137+
}
138+
134139
func (c *CodaHale) MeasureFilterRequest(filterName string, start time.Time) {
135140
c.measureSince(fmt.Sprintf(KeyFilterRequest, filterName), start)
136141
}

metrics/codahale_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ type proxyMetricTest struct {
151151
var proxyMetricsTests = []proxyMetricTest{
152152
// T1 - Measure routing
153153
{KeyRouteLookup, func(m Metrics) { m.MeasureRouteLookup(time.Now()) }},
154+
{fmt.Sprintf(KeyFilterCreate, "afilter"), func(m Metrics) { m.MeasureFilterCreate("afilter", time.Now()) }},
154155
// T2 - Measure filter request
155156
{fmt.Sprintf(KeyFilterRequest, "foo"), func(m Metrics) { m.MeasureFilterRequest("foo", time.Now()) }},
156157
// T3 - Measure all filters request

metrics/metrics.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ type Metrics interface {
6262
IncFloatCounterBy(key string, value float64)
6363
// Additional methods
6464
MeasureRouteLookup(start time.Time)
65+
MeasureFilterCreate(filterName string, start time.Time)
6566
MeasureFilterRequest(filterName string, start time.Time)
6667
MeasureAllFiltersRequest(routeId string, start time.Time)
6768
MeasureBackend(routeId string, start time.Time)

metrics/metricstest/metricsmock.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package metricstest
22

33
import (
4+
"fmt"
45
"net/http"
56
"sync"
67
"time"
@@ -112,6 +113,13 @@ func (m *MockMetrics) MeasureRouteLookup(start time.Time) {
112113
// implement me
113114
}
114115

116+
func (m *MockMetrics) MeasureFilterCreate(filterName string, start time.Time) {
117+
key := fmt.Sprintf("%sfilter.%s.create", m.Prefix, filterName)
118+
m.WithMeasures(func(measures map[string][]time.Duration) {
119+
measures[key] = append(m.measures[key], time.Since(start))
120+
})
121+
}
122+
115123
func (m *MockMetrics) MeasureFilterRequest(filterName string, start time.Time) {
116124
// implement me
117125
}

metrics/prometheus.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ type Prometheus struct {
3030
routeLookupM *prometheus.HistogramVec
3131
routeErrorsM *prometheus.CounterVec
3232
responseM *prometheus.HistogramVec
33+
filterCreateM *prometheus.HistogramVec
3334
filterRequestM *prometheus.HistogramVec
3435
filterAllRequestM *prometheus.HistogramVec
3536
filterAllCombinedRequestM *prometheus.HistogramVec
@@ -86,6 +87,14 @@ func NewPrometheus(opts Options) *Prometheus {
8687
Buckets: opts.HistogramBuckets,
8788
}, []string{"code", "method", "route"})
8889

90+
filterCreate := prometheus.NewHistogramVec(prometheus.HistogramOpts{
91+
Namespace: namespace,
92+
Subsystem: promFilterSubsystem,
93+
Name: "create_duration_seconds",
94+
Help: "Duration in seconds of filter creation.",
95+
Buckets: opts.HistogramBuckets,
96+
}, []string{"filter"})
97+
8998
filterRequest := prometheus.NewHistogramVec(prometheus.HistogramOpts{
9099
Namespace: namespace,
91100
Subsystem: promFilterSubsystem,
@@ -229,6 +238,7 @@ func NewPrometheus(opts Options) *Prometheus {
229238
routeLookupM: routeLookup,
230239
routeErrorsM: routeErrors,
231240
responseM: response,
241+
filterCreateM: filterCreate,
232242
filterRequestM: filterRequest,
233243
filterAllRequestM: filterAllRequest,
234244
filterAllCombinedRequestM: filterAllCombinedRequest,
@@ -352,6 +362,11 @@ func (p *Prometheus) MeasureRouteLookup(start time.Time) {
352362
p.routeLookupM.WithLabelValues().Observe(t)
353363
}
354364

365+
func (p *Prometheus) MeasureFilterCreate(filterName string, start time.Time) {
366+
t := p.sinceS(start)
367+
p.filterCreateM.WithLabelValues(filterName).Observe(t)
368+
}
369+
355370
// MeasureFilterRequest satisfies Metrics interface.
356371
func (p *Prometheus) MeasureFilterRequest(filterName string, start time.Time) {
357372
t := p.sinceS(start)

routing/datasource.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,8 @@ func splitBackend(r *eskip.Route) (string, string, error) {
208208

209209
// creates a filter instance based on its definition and its
210210
// specification in the filter registry.
211-
func createFilter(fr filters.Registry, def *eskip.Filter, cpm map[string]PredicateSpec) (filters.Filter, error) {
212-
spec, ok := fr[def.Name]
211+
func createFilter(o *Options, def *eskip.Filter, cpm map[string]PredicateSpec) (filters.Filter, error) {
212+
spec, ok := o.FilterRegistry[def.Name]
213213
if !ok {
214214
if isTreePredicate(def.Name) || def.Name == predicates.HostName || def.Name == predicates.PathRegexpName || def.Name == predicates.MethodName || def.Name == predicates.HeaderName || def.Name == predicates.HeaderRegexpName {
215215
return nil, fmt.Errorf("trying to use %q as filter, but it is only available as predicate", def.Name)
@@ -222,7 +222,14 @@ func createFilter(fr filters.Registry, def *eskip.Filter, cpm map[string]Predica
222222
return nil, fmt.Errorf("filter %q not found", def.Name)
223223
}
224224

225+
start := time.Now()
226+
225227
f, err := spec.CreateFilter(def.Args)
228+
229+
if o.Metrics != nil { // measure regardless of the error
230+
o.Metrics.MeasureFilterCreate(def.Name, start)
231+
}
232+
226233
if err != nil {
227234
return nil, fmt.Errorf("failed to create filter %q: %w", spec.Name(), err)
228235
}
@@ -231,10 +238,10 @@ func createFilter(fr filters.Registry, def *eskip.Filter, cpm map[string]Predica
231238

232239
// creates filter instances based on their definition
233240
// and the filter registry.
234-
func createFilters(fr filters.Registry, defs []*eskip.Filter, cpm map[string]PredicateSpec) ([]*RouteFilter, error) {
241+
func createFilters(o *Options, defs []*eskip.Filter, cpm map[string]PredicateSpec) ([]*RouteFilter, error) {
235242
fs := make([]*RouteFilter, 0, len(defs))
236243
for i, def := range defs {
237-
f, err := createFilter(fr, def, cpm)
244+
f, err := createFilter(o, def, cpm)
238245
if err != nil {
239246
return nil, err
240247
}
@@ -456,13 +463,13 @@ func processTreePredicates(r *Route, predicateList []*eskip.Predicate) error {
456463
}
457464

458465
// processes a route definition for the routing table
459-
func processRouteDef(cpm map[string]PredicateSpec, fr filters.Registry, def *eskip.Route) (*Route, error) {
466+
func processRouteDef(o *Options, cpm map[string]PredicateSpec, def *eskip.Route) (*Route, error) {
460467
scheme, host, err := splitBackend(def)
461468
if err != nil {
462469
return nil, err
463470
}
464471

465-
fs, err := createFilters(fr, def.Filters, cpm)
472+
fs, err := createFilters(o, def.Filters, cpm)
466473
if err != nil {
467474
return nil, err
468475
}
@@ -496,10 +503,10 @@ func mapPredicates(cps []PredicateSpec) map[string]PredicateSpec {
496503
}
497504

498505
// processes a set of route definitions for the routing table
499-
func processRouteDefs(o Options, fr filters.Registry, defs []*eskip.Route) (routes []*Route, invalidDefs []*eskip.Route) {
506+
func processRouteDefs(o *Options, defs []*eskip.Route) (routes []*Route, invalidDefs []*eskip.Route) {
500507
cpm := mapPredicates(o.Predicates)
501508
for _, def := range defs {
502-
route, err := processRouteDef(cpm, fr, def)
509+
route, err := processRouteDef(o, cpm, def)
503510
if err == nil {
504511
routes = append(routes, route)
505512
} else {
@@ -557,7 +564,7 @@ func receiveRouteMatcher(o Options, out chan<- *routeTable, quit <-chan struct{}
557564
defs = o.PreProcessors[i].Do(defs)
558565
}
559566

560-
routes, invalidRoutes := processRouteDefs(o, o.FilterRegistry, defs)
567+
routes, invalidRoutes := processRouteDefs(&o, defs)
561568

562569
for i := range o.PostProcessors {
563570
routes = o.PostProcessors[i].Do(routes)

routing/datasource_test.go

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ import (
55
"testing"
66
"time"
77

8+
"github.com/stretchr/testify/assert"
89
"github.com/zalando/skipper/filters"
910
"github.com/zalando/skipper/filters/builtin"
1011
"github.com/zalando/skipper/logging"
1112
"github.com/zalando/skipper/logging/loggingtest"
13+
"github.com/zalando/skipper/metrics/metricstest"
1214
"github.com/zalando/skipper/predicates/primitive"
1315
"github.com/zalando/skipper/predicates/query"
1416
"github.com/zalando/skipper/routing"
@@ -56,10 +58,12 @@ func TestNoMultipleTreePredicates(t *testing.T) {
5658
}
5759

5860
erred := false
61+
o := &routing.Options{
62+
FilterRegistry: make(filters.Registry),
63+
}
5964
pr := make(map[string]routing.PredicateSpec)
60-
fr := make(filters.Registry)
6165
for _, d := range defs {
62-
if _, err := routing.ExportProcessRouteDef(pr, fr, d); err != nil {
66+
if _, err := routing.ExportProcessRouteDef(o, pr, d); err != nil {
6367
erred = true
6468
break
6569
}
@@ -119,8 +123,11 @@ func TestProcessRouteDefErrors(t *testing.T) {
119123
}
120124
fr := make(filters.Registry)
121125
fr.Register(builtin.NewSetPath())
126+
o := &routing.Options{
127+
FilterRegistry: fr,
128+
}
122129
for _, d := range defs {
123-
_, err := routing.ExportProcessRouteDef(pr, fr, d)
130+
_, err := routing.ExportProcessRouteDef(o, pr, d)
124131
if err == nil || err.Error() != ti.err {
125132
t.Errorf("expected error '%s'. Got: '%s'", ti.err, err)
126133
}
@@ -298,6 +305,41 @@ func TestLogging(t *testing.T) {
298305
})
299306
}
300307

308+
func TestMetrics(t *testing.T) {
309+
t.Run("create filter latency", func(t *testing.T) {
310+
client, err := testdataclient.NewDoc(`
311+
r0: * -> slowCreate("100ms") -> slowCreate("200ms") -> slowCreate("100ms") -> <shunt>;
312+
`)
313+
if err != nil {
314+
t.Fatal(err)
315+
}
316+
defer client.Close()
317+
318+
metrics := &metricstest.MockMetrics{
319+
Now: time.Now(),
320+
}
321+
fr := make(filters.Registry)
322+
fr.Register(slowCreateSpec{})
323+
324+
r := routing.New(routing.Options{
325+
DataClients: []routing.DataClient{client},
326+
FilterRegistry: fr,
327+
Metrics: metrics,
328+
SignalFirstLoad: true,
329+
})
330+
defer r.Close()
331+
<-r.FirstLoad()
332+
333+
metrics.WithMeasures(func(m map[string][]time.Duration) {
334+
assert.InEpsilonSlice(t, []time.Duration{
335+
100 * time.Millisecond,
336+
200 * time.Millisecond,
337+
100 * time.Millisecond,
338+
}, m["filter.slowCreate.create"], 0.1)
339+
})
340+
})
341+
}
342+
301343
type weightedPredicateSpec struct {
302344
name string
303345
weight int
@@ -319,3 +361,21 @@ func (w weightedPredicateSpec) Create([]interface{}) (routing.Predicate, error)
319361
func (w weightedPredicateSpec) Weight() int {
320362
return w.weight
321363
}
364+
365+
type (
366+
slowCreateSpec struct{}
367+
slowCreateFilter struct{}
368+
)
369+
370+
func (s slowCreateSpec) Name() string { return "slowCreate" }
371+
372+
func (s slowCreateSpec) CreateFilter(args []interface{}) (filters.Filter, error) {
373+
d, _ := time.ParseDuration(args[0].(string))
374+
375+
time.Sleep(d)
376+
377+
return slowCreateFilter{}, nil
378+
}
379+
380+
func (s slowCreateFilter) Request(ctx filters.FilterContext) {}
381+
func (s slowCreateFilter) Response(filters.FilterContext) {}

routing/host_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,16 @@ func benchmarkPredicateHost(b *testing.B, predicateFmt string) {
5151

5252
ha := host.NewAny()
5353
pr := map[string]routing.PredicateSpec{ha.Name(): ha}
54-
fr := make(filters.Registry)
54+
o := &routing.Options{
55+
FilterRegistry: make(filters.Registry),
56+
}
5557

5658
var routes []*routing.Route
5759
for i := 0; i < R; i++ {
5860
p := strings.ReplaceAll(predicateFmt, "{i}", fmt.Sprintf("%d", i))
5961
def := eskip.MustParse(fmt.Sprintf(`r%d: %s -> <shunt>;`, i, p))
6062

61-
route, err := routing.ExportProcessRouteDef(pr, fr, def[0])
63+
route, err := routing.ExportProcessRouteDef(o, pr, def[0])
6264
if err != nil {
6365
b.Fatal(err)
6466
}

0 commit comments

Comments
 (0)