mirror of https://github.com/prometheus/prometheus
commit
c0df1c7e81
|
@ -104,6 +104,7 @@ type AggregateExpr struct {
|
||||||
Op itemType // The used aggregation operation.
|
Op itemType // The used aggregation operation.
|
||||||
Expr Expr // The vector expression over which is aggregated.
|
Expr Expr // The vector expression over which is aggregated.
|
||||||
Grouping model.LabelNames // The labels by which to group the vector.
|
Grouping model.LabelNames // The labels by which to group the vector.
|
||||||
|
Without bool // Whether to drop the given labels rather than keep them.
|
||||||
KeepExtraLabels bool // Whether to keep extra labels common among result elements.
|
KeepExtraLabels bool // Whether to keep extra labels common among result elements.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -619,7 +619,7 @@ func (ev *evaluator) eval(expr Expr) model.Value {
|
||||||
switch e := expr.(type) {
|
switch e := expr.(type) {
|
||||||
case *AggregateExpr:
|
case *AggregateExpr:
|
||||||
vector := ev.evalVector(e.Expr)
|
vector := ev.evalVector(e.Expr)
|
||||||
return ev.aggregation(e.Op, e.Grouping, e.KeepExtraLabels, vector)
|
return ev.aggregation(e.Op, e.Grouping, e.Without, e.KeepExtraLabels, vector)
|
||||||
|
|
||||||
case *BinaryExpr:
|
case *BinaryExpr:
|
||||||
lhs := ev.evalOneOf(e.LHS, model.ValScalar, model.ValVector)
|
lhs := ev.evalOneOf(e.LHS, model.ValScalar, model.ValVector)
|
||||||
|
@ -1039,12 +1039,25 @@ type groupedAggregation struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// aggregation evaluates an aggregation operation on a vector.
|
// aggregation evaluates an aggregation operation on a vector.
|
||||||
func (ev *evaluator) aggregation(op itemType, grouping model.LabelNames, keepExtra bool, vec vector) vector {
|
func (ev *evaluator) aggregation(op itemType, grouping model.LabelNames, without bool, keepExtra bool, vec vector) vector {
|
||||||
|
|
||||||
result := map[uint64]*groupedAggregation{}
|
result := map[uint64]*groupedAggregation{}
|
||||||
|
|
||||||
for _, sample := range vec {
|
for _, sample := range vec {
|
||||||
groupingKey := model.SignatureForLabels(sample.Metric.Metric, grouping...)
|
withoutMetric := sample.Metric
|
||||||
|
if without {
|
||||||
|
for _, l := range grouping {
|
||||||
|
withoutMetric.Del(l)
|
||||||
|
}
|
||||||
|
withoutMetric.Del(model.MetricNameLabel)
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupingKey uint64
|
||||||
|
if without {
|
||||||
|
groupingKey = uint64(withoutMetric.Metric.Fingerprint())
|
||||||
|
} else {
|
||||||
|
groupingKey = model.SignatureForLabels(sample.Metric.Metric, grouping...)
|
||||||
|
}
|
||||||
|
|
||||||
groupedResult, ok := result[groupingKey]
|
groupedResult, ok := result[groupingKey]
|
||||||
// Add a new group if it doesn't exist.
|
// Add a new group if it doesn't exist.
|
||||||
|
@ -1053,6 +1066,8 @@ func (ev *evaluator) aggregation(op itemType, grouping model.LabelNames, keepExt
|
||||||
if keepExtra {
|
if keepExtra {
|
||||||
m = sample.Metric
|
m = sample.Metric
|
||||||
m.Del(model.MetricNameLabel)
|
m.Del(model.MetricNameLabel)
|
||||||
|
} else if without {
|
||||||
|
m = withoutMetric
|
||||||
} else {
|
} else {
|
||||||
m = metric.Metric{
|
m = metric.Metric{
|
||||||
Metric: model.Metric{},
|
Metric: model.Metric{},
|
||||||
|
|
|
@ -158,6 +158,7 @@ const (
|
||||||
itemKeepCommon
|
itemKeepCommon
|
||||||
itemOffset
|
itemOffset
|
||||||
itemBy
|
itemBy
|
||||||
|
itemWithout
|
||||||
itemOn
|
itemOn
|
||||||
itemGroupLeft
|
itemGroupLeft
|
||||||
itemGroupRight
|
itemGroupRight
|
||||||
|
@ -192,6 +193,7 @@ var key = map[string]itemType{
|
||||||
"annotations": itemAnnotations,
|
"annotations": itemAnnotations,
|
||||||
"offset": itemOffset,
|
"offset": itemOffset,
|
||||||
"by": itemBy,
|
"by": itemBy,
|
||||||
|
"without": itemWithout,
|
||||||
"keeping_extra": itemKeepCommon,
|
"keeping_extra": itemKeepCommon,
|
||||||
"keep_common": itemKeepCommon,
|
"keep_common": itemKeepCommon,
|
||||||
"on": itemOn,
|
"on": itemOn,
|
||||||
|
|
|
@ -261,6 +261,9 @@ var tests = []struct {
|
||||||
}, {
|
}, {
|
||||||
input: "by",
|
input: "by",
|
||||||
expected: []item{{itemBy, 0, "by"}},
|
expected: []item{{itemBy, 0, "by"}},
|
||||||
|
}, {
|
||||||
|
input: "without",
|
||||||
|
expected: []item{{itemWithout, 0, "without"}},
|
||||||
}, {
|
}, {
|
||||||
input: "on",
|
input: "on",
|
||||||
expected: []item{{itemOn, 0, "on"}},
|
expected: []item{{itemOn, 0, "on"}},
|
||||||
|
|
|
@ -732,11 +732,14 @@ func (p *parser) aggrExpr() *AggregateExpr {
|
||||||
p.errorf("expected aggregation operator but got %s", agop)
|
p.errorf("expected aggregation operator but got %s", agop)
|
||||||
}
|
}
|
||||||
var grouping model.LabelNames
|
var grouping model.LabelNames
|
||||||
var keepExtra bool
|
var keepExtra, without bool
|
||||||
|
|
||||||
modifiersFirst := false
|
modifiersFirst := false
|
||||||
|
|
||||||
if p.peek().typ == itemBy {
|
if t := p.peek().typ; t == itemBy || t == itemWithout {
|
||||||
|
if t == itemWithout {
|
||||||
|
without = true
|
||||||
|
}
|
||||||
p.next()
|
p.next()
|
||||||
grouping = p.labels()
|
grouping = p.labels()
|
||||||
modifiersFirst = true
|
modifiersFirst = true
|
||||||
|
@ -752,10 +755,13 @@ func (p *parser) aggrExpr() *AggregateExpr {
|
||||||
p.expect(itemRightParen, ctx)
|
p.expect(itemRightParen, ctx)
|
||||||
|
|
||||||
if !modifiersFirst {
|
if !modifiersFirst {
|
||||||
if p.peek().typ == itemBy {
|
if t := p.peek().typ; t == itemBy || t == itemWithout {
|
||||||
if len(grouping) > 0 {
|
if len(grouping) > 0 {
|
||||||
p.errorf("aggregation must only contain one grouping clause")
|
p.errorf("aggregation must only contain one grouping clause")
|
||||||
}
|
}
|
||||||
|
if t == itemWithout {
|
||||||
|
without = true
|
||||||
|
}
|
||||||
p.next()
|
p.next()
|
||||||
grouping = p.labels()
|
grouping = p.labels()
|
||||||
}
|
}
|
||||||
|
@ -765,10 +771,15 @@ func (p *parser) aggrExpr() *AggregateExpr {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if keepExtra && without {
|
||||||
|
p.errorf("cannot use 'keep_common' with 'without'")
|
||||||
|
}
|
||||||
|
|
||||||
return &AggregateExpr{
|
return &AggregateExpr{
|
||||||
Op: agop.typ,
|
Op: agop.typ,
|
||||||
Expr: e,
|
Expr: e,
|
||||||
Grouping: grouping,
|
Grouping: grouping,
|
||||||
|
Without: without,
|
||||||
KeepExtraLabels: keepExtra,
|
KeepExtraLabels: keepExtra,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -869,6 +869,32 @@ var testExpr = []struct {
|
||||||
},
|
},
|
||||||
Grouping: model.LabelNames{"foo"},
|
Grouping: model.LabelNames{"foo"},
|
||||||
},
|
},
|
||||||
|
}, {
|
||||||
|
input: "sum without (foo) (some_metric)",
|
||||||
|
expected: &AggregateExpr{
|
||||||
|
Op: itemSum,
|
||||||
|
Without: true,
|
||||||
|
Expr: &VectorSelector{
|
||||||
|
Name: "some_metric",
|
||||||
|
LabelMatchers: metric.LabelMatchers{
|
||||||
|
{Type: metric.Equal, Name: model.MetricNameLabel, Value: "some_metric"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Grouping: model.LabelNames{"foo"},
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
input: "sum (some_metric) without (foo)",
|
||||||
|
expected: &AggregateExpr{
|
||||||
|
Op: itemSum,
|
||||||
|
Without: true,
|
||||||
|
Expr: &VectorSelector{
|
||||||
|
Name: "some_metric",
|
||||||
|
LabelMatchers: metric.LabelMatchers{
|
||||||
|
{Type: metric.Equal, Name: model.MetricNameLabel, Value: "some_metric"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Grouping: model.LabelNames{"foo"},
|
||||||
|
},
|
||||||
}, {
|
}, {
|
||||||
input: "stddev(some_metric)",
|
input: "stddev(some_metric)",
|
||||||
expected: &AggregateExpr{
|
expected: &AggregateExpr{
|
||||||
|
@ -920,6 +946,18 @@ var testExpr = []struct {
|
||||||
input: "MIN by(test) (some_metric) keep_common",
|
input: "MIN by(test) (some_metric) keep_common",
|
||||||
fail: true,
|
fail: true,
|
||||||
errMsg: "could not parse remaining input \"keep_common\"...",
|
errMsg: "could not parse remaining input \"keep_common\"...",
|
||||||
|
}, {
|
||||||
|
input: `sum (some_metric) without (test) keep_common`,
|
||||||
|
fail: true,
|
||||||
|
errMsg: "cannot use 'keep_common' with 'without'",
|
||||||
|
}, {
|
||||||
|
input: `sum (some_metric) without (test) by (test)`,
|
||||||
|
fail: true,
|
||||||
|
errMsg: "could not parse remaining input \"by (test)\"...",
|
||||||
|
}, {
|
||||||
|
input: `sum without (test) (some_metric) by (test)`,
|
||||||
|
fail: true,
|
||||||
|
errMsg: "could not parse remaining input \"by (test)\"...",
|
||||||
},
|
},
|
||||||
// Test function calls.
|
// Test function calls.
|
||||||
{
|
{
|
||||||
|
|
|
@ -137,7 +137,12 @@ func (es Expressions) String() (s string) {
|
||||||
func (node *AggregateExpr) String() string {
|
func (node *AggregateExpr) String() string {
|
||||||
aggrString := fmt.Sprintf("%s(%s)", node.Op, node.Expr)
|
aggrString := fmt.Sprintf("%s(%s)", node.Op, node.Expr)
|
||||||
if len(node.Grouping) > 0 {
|
if len(node.Grouping) > 0 {
|
||||||
format := "%s BY (%s)"
|
var format string
|
||||||
|
if node.Without {
|
||||||
|
format = "%s WITHOUT (%s)"
|
||||||
|
} else {
|
||||||
|
format = "%s BY (%s)"
|
||||||
|
}
|
||||||
if node.KeepExtraLabels {
|
if node.KeepExtraLabels {
|
||||||
format += " KEEP_COMMON"
|
format += " KEEP_COMMON"
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,9 @@ func TestExprString(t *testing.T) {
|
||||||
{
|
{
|
||||||
in: `sum(task:errors:rate10s{job="s"}) BY (code) KEEP_COMMON`,
|
in: `sum(task:errors:rate10s{job="s"}) BY (code) KEEP_COMMON`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
in: `sum(task:errors:rate10s{job="s"}) WITHOUT (instance)`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
in: `up > BOOL 0`,
|
in: `up > BOOL 0`,
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,115 @@
|
||||||
|
load 5m
|
||||||
|
http_requests{job="api-server", instance="0", group="production"} 0+10x10
|
||||||
|
http_requests{job="api-server", instance="1", group="production"} 0+20x10
|
||||||
|
http_requests{job="api-server", instance="0", group="canary"} 0+30x10
|
||||||
|
http_requests{job="api-server", instance="1", group="canary"} 0+40x10
|
||||||
|
http_requests{job="app-server", instance="0", group="production"} 0+50x10
|
||||||
|
http_requests{job="app-server", instance="1", group="production"} 0+60x10
|
||||||
|
http_requests{job="app-server", instance="0", group="canary"} 0+70x10
|
||||||
|
http_requests{job="app-server", instance="1", group="canary"} 0+80x10
|
||||||
|
|
||||||
|
load 5m
|
||||||
|
foo{job="api-server", instance="0", region="europe"} 0+90x10
|
||||||
|
foo{job="api-server"} 0+100x10
|
||||||
|
|
||||||
|
# Simple sum.
|
||||||
|
eval instant at 50m SUM BY (group) (http_requests{job="api-server"})
|
||||||
|
{group="canary"} 700
|
||||||
|
{group="production"} 300
|
||||||
|
|
||||||
|
# Test alternative "by"-clause order.
|
||||||
|
eval instant at 50m sum by (group) (http_requests{job="api-server"})
|
||||||
|
{group="canary"} 700
|
||||||
|
{group="production"} 300
|
||||||
|
|
||||||
|
# Simple average.
|
||||||
|
eval instant at 50m avg by (group) (http_requests{job="api-server"})
|
||||||
|
{group="canary"} 350
|
||||||
|
{group="production"} 150
|
||||||
|
|
||||||
|
# Simple count.
|
||||||
|
eval instant at 50m count by (group) (http_requests{job="api-server"})
|
||||||
|
{group="canary"} 2
|
||||||
|
{group="production"} 2
|
||||||
|
|
||||||
|
# Simple without.
|
||||||
|
eval instant at 50m sum without (instance) (http_requests{job="api-server"})
|
||||||
|
{group="canary",job="api-server"} 700
|
||||||
|
{group="production",job="api-server"} 300
|
||||||
|
|
||||||
|
# Without with mismatched and missing labels. Do not do this.
|
||||||
|
eval instant at 50m sum without (instance) (http_requests{job="api-server"} or foo)
|
||||||
|
{group="canary",job="api-server"} 700
|
||||||
|
{group="production",job="api-server"} 300
|
||||||
|
{region="europe",job="api-server"} 900
|
||||||
|
{job="api-server"} 1000
|
||||||
|
|
||||||
|
# Lower-cased aggregation operators should work too.
|
||||||
|
eval instant at 50m sum(http_requests) by (job) + min(http_requests) by (job) + max(http_requests) by (job) + avg(http_requests) by (job)
|
||||||
|
{job="app-server"} 4550
|
||||||
|
{job="api-server"} 1750
|
||||||
|
|
||||||
|
# Test alternative "by"-clause order with "keep_common".
|
||||||
|
eval instant at 50m sum by (group) keep_common (http_requests{job="api-server"})
|
||||||
|
{group="canary", job="api-server"} 700
|
||||||
|
{group="production", job="api-server"} 300
|
||||||
|
|
||||||
|
# Test both alternative "by"-clause orders in one expression.
|
||||||
|
# Public health warning: stick to one form within an expression (or even
|
||||||
|
# in an organization), or risk serious user confusion.
|
||||||
|
eval instant at 50m sum(sum by (group) keep_common (http_requests{job="api-server"})) by (job)
|
||||||
|
{job="api-server"} 1000
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Standard deviation and variance.
|
||||||
|
eval instant at 50m stddev(http_requests)
|
||||||
|
{} 229.12878474779
|
||||||
|
|
||||||
|
eval instant at 50m stddev by (instance)(http_requests)
|
||||||
|
{instance="0"} 223.60679774998
|
||||||
|
{instance="1"} 223.60679774998
|
||||||
|
|
||||||
|
eval instant at 50m stdvar(http_requests)
|
||||||
|
{} 52500
|
||||||
|
|
||||||
|
eval instant at 50m stdvar by (instance)(http_requests)
|
||||||
|
{instance="0"} 50000
|
||||||
|
{instance="1"} 50000
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Regression test for missing separator byte in labelsToGroupingKey.
|
||||||
|
clear
|
||||||
|
load 5m
|
||||||
|
label_grouping_test{a="aa", b="bb"} 0+10x10
|
||||||
|
label_grouping_test{a="a", b="abb"} 0+20x10
|
||||||
|
|
||||||
|
eval instant at 50m sum(label_grouping_test) by (a, b)
|
||||||
|
{a="a", b="abb"} 200
|
||||||
|
{a="aa", b="bb"} 100
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Tests for min/max.
|
||||||
|
clear
|
||||||
|
load 5m
|
||||||
|
http_requests{job="api-server", instance="0", group="production"} 1
|
||||||
|
http_requests{job="api-server", instance="1", group="production"} 2
|
||||||
|
http_requests{job="api-server", instance="0", group="canary"} NaN
|
||||||
|
http_requests{job="api-server", instance="1", group="canary"} 3
|
||||||
|
http_requests{job="api-server", instance="2", group="canary"} 4
|
||||||
|
|
||||||
|
eval instant at 0m max(http_requests)
|
||||||
|
{} 4
|
||||||
|
|
||||||
|
eval instant at 0m min(http_requests)
|
||||||
|
{} 1
|
||||||
|
|
||||||
|
eval instant at 0m max by (group) (http_requests)
|
||||||
|
{group="production"} 2
|
||||||
|
{group="canary"} 4
|
||||||
|
|
||||||
|
eval instant at 0m min by (group) (http_requests)
|
||||||
|
{group="production"} 1
|
||||||
|
{group="canary"} 3
|
|
@ -147,12 +147,6 @@ eval instant at 50m x{y="testvalue"}
|
||||||
x{y="testvalue"} 100
|
x{y="testvalue"} 100
|
||||||
|
|
||||||
|
|
||||||
# Lower-cased aggregation operators should work too.
|
|
||||||
eval instant at 50m sum(http_requests) by (job) + min(http_requests) by (job) + max(http_requests) by (job) + avg(http_requests) by (job)
|
|
||||||
{job="app-server"} 4550
|
|
||||||
{job="api-server"} 1750
|
|
||||||
|
|
||||||
|
|
||||||
# Deltas should be adjusted for target interval vs. samples under target interval.
|
# Deltas should be adjusted for target interval vs. samples under target interval.
|
||||||
eval instant at 50m delta(http_requests{group="canary", instance="1", job="app-server"}[18m])
|
eval instant at 50m delta(http_requests{group="canary", instance="1", job="app-server"}[18m])
|
||||||
{group="canary", instance="1", job="app-server"} 288
|
{group="canary", instance="1", job="app-server"} 288
|
||||||
|
@ -343,22 +337,6 @@ eval instant at 50m {job=~".+-server", job!~"api-.+"}
|
||||||
http_requests{group="production", instance="0", job="app-server"} 500
|
http_requests{group="production", instance="0", job="app-server"} 500
|
||||||
http_requests{group="production", instance="1", job="app-server"} 600
|
http_requests{group="production", instance="1", job="app-server"} 600
|
||||||
|
|
||||||
# Test alternative "by"-clause order.
|
|
||||||
eval instant at 50m sum by (group) (http_requests{job="api-server"})
|
|
||||||
{group="canary"} 700
|
|
||||||
{group="production"} 300
|
|
||||||
|
|
||||||
# Test alternative "by"-clause order with "keep_common".
|
|
||||||
eval instant at 50m sum by (group) keep_common (http_requests{job="api-server"})
|
|
||||||
{group="canary", job="api-server"} 700
|
|
||||||
{group="production", job="api-server"} 300
|
|
||||||
|
|
||||||
# Test both alternative "by"-clause orders in one expression.
|
|
||||||
# Public health warning: stick to one form within an expression (or even
|
|
||||||
# in an organization), or risk serious user confusion.
|
|
||||||
eval instant at 50m sum(sum by (group) keep_common (http_requests{job="api-server"})) by (job)
|
|
||||||
{job="api-server"} 1000
|
|
||||||
|
|
||||||
eval instant at 50m http_requests{group="canary"} and http_requests{instance="0"}
|
eval instant at 50m http_requests{group="canary"} and http_requests{instance="0"}
|
||||||
http_requests{group="canary", instance="0", job="api-server"} 300
|
http_requests{group="canary", instance="0", job="api-server"} 300
|
||||||
http_requests{group="canary", instance="0", job="app-server"} 700
|
http_requests{group="canary", instance="0", job="app-server"} 700
|
||||||
|
@ -487,11 +465,6 @@ eval instant at 50m rate(http_requests{group="production",job="api-server"}[10m]
|
||||||
{group="production", instance="0", job="api-server"} 0.03333333333333333
|
{group="production", instance="0", job="api-server"} 0.03333333333333333
|
||||||
{group="production", instance="1", job="api-server"} 0.06666666666666667
|
{group="production", instance="1", job="api-server"} 0.06666666666666667
|
||||||
|
|
||||||
# Regression test for missing separator byte in labelsToGroupingKey.
|
|
||||||
eval instant at 50m sum(label_grouping_test) by (a, b)
|
|
||||||
{a="a", b="abb"} 200
|
|
||||||
{a="aa", b="bb"} 100
|
|
||||||
|
|
||||||
eval instant at 50m http_requests{group="canary", instance="0", job="api-server"} / 0
|
eval instant at 50m http_requests{group="canary", instance="0", job="api-server"} / 0
|
||||||
{group="canary", instance="0", job="api-server"} +Inf
|
{group="canary", instance="0", job="api-server"} +Inf
|
||||||
|
|
||||||
|
@ -560,20 +533,6 @@ eval instant at 50m log10(vector_matching_a - 20)
|
||||||
{l="x"} NaN
|
{l="x"} NaN
|
||||||
{l="y"} -Inf
|
{l="y"} -Inf
|
||||||
|
|
||||||
eval instant at 50m stddev(http_requests)
|
|
||||||
{} 229.12878474779
|
|
||||||
|
|
||||||
eval instant at 50m stddev by (instance)(http_requests)
|
|
||||||
{instance="0"} 223.60679774998
|
|
||||||
{instance="1"} 223.60679774998
|
|
||||||
|
|
||||||
eval instant at 50m stdvar(http_requests)
|
|
||||||
{} 52500
|
|
||||||
|
|
||||||
eval instant at 50m stdvar by (instance)(http_requests)
|
|
||||||
{instance="0"} 50000
|
|
||||||
{instance="1"} 50000
|
|
||||||
|
|
||||||
|
|
||||||
# Matrix tests.
|
# Matrix tests.
|
||||||
|
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
# Tests for min/max.
|
|
||||||
clear
|
|
||||||
load 5m
|
|
||||||
http_requests{job="api-server", instance="0", group="production"} 1
|
|
||||||
http_requests{job="api-server", instance="1", group="production"} 2
|
|
||||||
http_requests{job="api-server", instance="0", group="canary"} NaN
|
|
||||||
http_requests{job="api-server", instance="1", group="canary"} 3
|
|
||||||
http_requests{job="api-server", instance="2", group="canary"} 4
|
|
||||||
|
|
||||||
eval instant at 0m max(http_requests)
|
|
||||||
{} 4
|
|
||||||
|
|
||||||
eval instant at 0m min(http_requests)
|
|
||||||
{} 1
|
|
||||||
|
|
||||||
eval instant at 0m max by (group) (http_requests)
|
|
||||||
{group="production"} 2
|
|
||||||
{group="canary"} 4
|
|
||||||
|
|
||||||
eval instant at 0m min by (group) (http_requests)
|
|
||||||
{group="production"} 1
|
|
||||||
{group="canary"} 3
|
|
Loading…
Reference in New Issue