Skip to content

Commit eaa5638

Browse files
authored
metrics: add route validation metrics to track processing success/failure rates (#3531)
### New Metrics Added **Prometheus metrics:** - `skipper_route_valid_routes` (counter) - Total number of successfully processed routes - `skipper_route_invalid_routes{reason}` (counter) - Total number of invalid routes with failure reason labels **CodaHale metrics:** - `route.valid` (counter) - Successfully processed routes - `route.invalid.{reason}` (counter) - Invalid routes with reason suffix ### Failure Reasons Tracked The `reason` label/suffix captures different validation failures: - `failed_backend_split` - Backend URL parsing errors - `unknown_filter` - Unknown or disabled filters used - `unknown_predicate` - Unknown predicates used - `invalid_filter_params` - Invalid filter parameters - `invalid_predicate_params` - Invalid predicate parameters Signed-off-by: Veronika Volokitina <v.volokitinaa@gmail.com>
1 parent 10c8b67 commit eaa5638

7 files changed

Lines changed: 229 additions & 18 deletions

File tree

metrics/all_kind.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,16 @@ func (a *All) IncErrorsStreaming(routeId string) {
121121

122122
}
123123

124+
func (a *All) IncValidRoutes() {
125+
a.prometheus.IncValidRoutes()
126+
a.codaHale.IncValidRoutes()
127+
}
128+
129+
func (a *All) IncInvalidRoutes(reason string) {
130+
a.prometheus.IncInvalidRoutes(reason)
131+
a.codaHale.IncInvalidRoutes(reason)
132+
}
133+
124134
func (a *All) Close() {
125135
a.codaHale.Close()
126136
a.prometheus.Close()

metrics/codahale.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ const (
3333

3434
KeyErrorsBackend = "errors.backend.%s"
3535
KeyErrorsStreaming = "errors.streaming.%s"
36+
KeyValidRoutes = "route.valid"
37+
KeyInvalidRoutes = "route.invalid.%s"
3638

3739
statsRefreshDuration = time.Duration(5 * time.Second)
3840

@@ -256,6 +258,14 @@ func (c *CodaHale) IncErrorsStreaming(routeId string) {
256258
}
257259
}
258260

261+
func (c *CodaHale) IncValidRoutes() {
262+
c.incCounter(KeyValidRoutes, 1)
263+
}
264+
265+
func (c *CodaHale) IncInvalidRoutes(reason string) {
266+
c.incCounter(fmt.Sprintf(KeyInvalidRoutes, reason), 1)
267+
}
268+
259269
func (c *CodaHale) Close() {
260270
close(c.quit)
261271
}

metrics/metrics.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ type Metrics interface {
7878
IncErrorsStreaming(routeId string)
7979
RegisterHandler(path string, handler *http.ServeMux)
8080
UpdateGauge(key string, value float64)
81+
IncValidRoutes()
82+
IncInvalidRoutes(reason string)
8183
Close()
8284
}
8385

metrics/metricstest/metricsmock.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,4 +202,12 @@ func (m *MockMetrics) Gauge(key string) (v float64, ok bool) {
202202
return
203203
}
204204

205+
func (m *MockMetrics) IncValidRoutes() {
206+
m.IncCounter("route.valid")
207+
}
208+
209+
func (m *MockMetrics) IncInvalidRoutes(reason string) {
210+
m.IncCounter("route.invalid." + reason)
211+
}
212+
205213
func (m *MockMetrics) Close() {}

metrics/prometheus.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ type Prometheus struct {
5353
customHistogramM *prometheus.HistogramVec
5454
customCounterM *prometheus.CounterVec
5555
customGaugeM *prometheus.GaugeVec
56+
validRoutesM *prometheus.CounterVec
57+
invalidRoutesM *prometheus.CounterVec
5658

5759
opts Options
5860
registry *prometheus.Registry
@@ -271,6 +273,20 @@ func NewPrometheus(opts Options) *Prometheus {
271273
Buckets: opts.HistogramBuckets,
272274
}, []string{"key"}))
273275

276+
p.validRoutesM = register(p, prometheus.NewCounterVec(prometheus.CounterOpts{
277+
Namespace: namespace,
278+
Subsystem: promRouteSubsystem,
279+
Name: "valid_routes",
280+
Help: "Total number of successfully processed routes.",
281+
}, []string{}))
282+
283+
p.invalidRoutesM = register(p, prometheus.NewCounterVec(prometheus.CounterOpts{
284+
Namespace: namespace,
285+
Subsystem: promRouteSubsystem,
286+
Name: "invalid_routes",
287+
Help: "Total number of invalid routes with failure reasons.",
288+
}, []string{"reason"}))
289+
274290
// Register prometheus runtime collectors if required.
275291
if opts.EnableRuntimeMetrics {
276292
register(p, collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}))
@@ -472,6 +488,16 @@ func (p *Prometheus) IncErrorsStreaming(routeID string) {
472488
p.proxyStreamingErrorsM.WithLabelValues(routeID).Inc()
473489
}
474490

491+
// IncValidRoutes satisfies Metrics interface.
492+
func (p *Prometheus) IncValidRoutes() {
493+
p.validRoutesM.WithLabelValues().Inc()
494+
}
495+
496+
// IncInvalidRoutes satisfies Metrics interface.
497+
func (p *Prometheus) IncInvalidRoutes(reason string) {
498+
p.invalidRoutesM.WithLabelValues(reason).Inc()
499+
}
500+
475501
func (p *Prometheus) Close() {}
476502

477503
// withStartLabelGatherer adds a "start" label to all counters with

routing/datasource.go

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,19 @@ const (
2323

2424
var errInvalidWeightParams = errors.New("invalid argument for the Weight predicate")
2525

26+
type invalidDefinitionError string
27+
28+
func (e invalidDefinitionError) Error() string { return string(e) }
29+
func (e invalidDefinitionError) Code() string { return string(e) }
30+
31+
var (
32+
errUnknownFilter = invalidDefinitionError("unknown_filter")
33+
errInvalidFilterParams = invalidDefinitionError("invalid_filter_params")
34+
errUnknownPredicate = invalidDefinitionError("unknown_predicate")
35+
errInvalidPredicateParams = invalidDefinitionError("invalid_predicate_params")
36+
errFailedBackendSplit = invalidDefinitionError("failed_backend_split")
37+
)
38+
2639
func (it incomingType) String() string {
2740
switch it {
2841
case incomingReset:
@@ -219,26 +232,26 @@ func createFilter(o *Options, def *eskip.Filter, cpm map[string]PredicateSpec) (
219232
spec, ok := o.FilterRegistry[def.Name]
220233
if !ok {
221234
if isTreePredicate(def.Name) || def.Name == predicates.HostName || def.Name == predicates.PathRegexpName || def.Name == predicates.MethodName || def.Name == predicates.HeaderName || def.Name == predicates.HeaderRegexpName {
222-
return nil, fmt.Errorf("trying to use %q as filter, but it is only available as predicate", def.Name)
235+
return nil, fmt.Errorf("%w: trying to use %q as filter, but it is only available as predicate", errUnknownFilter, def.Name)
223236
}
224237

225238
if _, ok := cpm[def.Name]; ok {
226-
return nil, fmt.Errorf("trying to use %q as filter, but it is only available as predicate", def.Name)
239+
return nil, fmt.Errorf("%w: trying to use %q as filter, but it is only available as predicate", errUnknownFilter, def.Name)
227240
}
228241

229-
return nil, fmt.Errorf("filter %q not found", def.Name)
242+
return nil, fmt.Errorf("%w: filter %q not found", errUnknownFilter, def.Name)
230243
}
231244

232245
start := time.Now()
233246

234247
f, err := spec.CreateFilter(def.Args)
235248

236-
if o.Metrics != nil { // measure regardless of the error
249+
if o.Metrics != nil {
237250
o.Metrics.MeasureFilterCreate(def.Name, start)
238251
}
239252

240253
if err != nil {
241-
return nil, fmt.Errorf("failed to create filter %q: %w", spec.Name(), err)
254+
return nil, fmt.Errorf("%w: failed to create filter %q: %w", errInvalidFilterParams, spec.Name(), err)
242255
}
243256
return f, nil
244257
}
@@ -370,7 +383,7 @@ func parseWeightPredicateArgs(args []interface{}) (int, error) {
370383
}
371384

372385
// initialize predicate instances from their spec with the concrete arguments
373-
func processPredicates(cpm map[string]PredicateSpec, defs []*eskip.Predicate) ([]Predicate, int, error) {
386+
func processPredicates(o *Options, cpm map[string]PredicateSpec, defs []*eskip.Predicate) ([]Predicate, int, error) {
374387
cps := make([]Predicate, 0, len(defs))
375388
var weight int
376389
for _, def := range defs {
@@ -379,7 +392,7 @@ func processPredicates(cpm map[string]PredicateSpec, defs []*eskip.Predicate) ([
379392
var err error
380393

381394
if w, err = parseWeightPredicateArgs(def.Args); err != nil {
382-
return nil, 0, err
395+
return nil, 0, fmt.Errorf("%w: %w", errInvalidPredicateParams, err)
383396
}
384397

385398
weight += w
@@ -393,12 +406,12 @@ func processPredicates(cpm map[string]PredicateSpec, defs []*eskip.Predicate) ([
393406

394407
spec, ok := cpm[def.Name]
395408
if !ok {
396-
return nil, 0, fmt.Errorf("predicate %q not found", def.Name)
409+
return nil, 0, fmt.Errorf("%w: predicate %q not found", errUnknownPredicate, def.Name)
397410
}
398411

399412
cp, err := spec.Create(def.Args)
400413
if err != nil {
401-
return nil, 0, fmt.Errorf("failed to create predicate %q: %w", spec.Name(), err)
414+
return nil, 0, fmt.Errorf("%w: failed to create predicate %q: %w", errInvalidPredicateParams, spec.Name(), err)
402415
}
403416

404417
if ws, ok := spec.(WeightedPredicateSpec); ok {
@@ -473,7 +486,7 @@ func processTreePredicates(r *Route, predicateList []*eskip.Predicate) error {
473486
func processRouteDef(o *Options, cpm map[string]PredicateSpec, def *eskip.Route) (*Route, error) {
474487
scheme, host, err := splitBackend(def)
475488
if err != nil {
476-
return nil, err
489+
return nil, fmt.Errorf("%w: %w", errFailedBackendSplit, err)
477490
}
478491

479492
fs, err := createFilters(o, def.Filters, cpm)
@@ -486,7 +499,7 @@ func processRouteDef(o *Options, cpm map[string]PredicateSpec, def *eskip.Route)
486499
return nil, err
487500
}
488501

489-
cps, weight, err := processPredicates(cpm, def.Predicates)
502+
cps, weight, err := processPredicates(o, cpm, def.Predicates)
490503
if err != nil {
491504
return nil, err
492505
}
@@ -496,6 +509,10 @@ func processRouteDef(o *Options, cpm map[string]PredicateSpec, def *eskip.Route)
496509
return nil, err
497510
}
498511

512+
if o.Metrics != nil {
513+
o.Metrics.IncValidRoutes()
514+
}
515+
499516
return r, nil
500517
}
501518

@@ -519,6 +536,13 @@ func processRouteDefs(o *Options, defs []*eskip.Route) (routes []*Route, invalid
519536
} else {
520537
invalidDefs = append(invalidDefs, def)
521538
o.Log.Errorf("failed to process route %s: %v", def.Id, err)
539+
540+
var defErr invalidDefinitionError
541+
if errors.As(err, &defErr) && o.Metrics != nil {
542+
o.Metrics.IncInvalidRoutes(defErr.Code())
543+
} else if o.Metrics != nil {
544+
o.Metrics.IncInvalidRoutes("other")
545+
}
522546
}
523547
}
524548
return

0 commit comments

Comments
 (0)