diff --git a/rules/ast/ast.go b/rules/ast/ast.go index feec0181f5..42391d2840 100644 --- a/rules/ast/ast.go +++ b/rules/ast/ast.go @@ -175,8 +175,8 @@ type ( type ( // A VectorSelector represents a metric name plus labelset. VectorSelector struct { - labels clientmodel.LabelSet - // Fingerprints are populated from labels at query analysis time. + labelMatchers metric.LabelMatchers + // Fingerprints are populated from label matchers at query analysis time. fingerprints clientmodel.Fingerprints } @@ -211,8 +211,8 @@ type ( // A MatrixSelector represents a metric name plus labelset and // timerange. MatrixSelector struct { - labels clientmodel.LabelSet - // Fingerprints are populated from labels at query + labelMatchers metric.LabelMatchers + // Fingerprints are populated from label matchers at query // analysis time. fingerprints clientmodel.Fingerprints interval time.Duration @@ -738,9 +738,9 @@ func NewScalarLiteral(value clientmodel.SampleValue) *ScalarLiteral { // NewVectorSelector returns a (not yet evaluated) VectorSelector with // the given LabelSet. -func NewVectorSelector(labels clientmodel.LabelSet) *VectorSelector { +func NewVectorSelector(m metric.LabelMatchers) *VectorSelector { return &VectorSelector{ - labels: labels, + labelMatchers: m, } } @@ -833,8 +833,8 @@ func NewArithExpr(opType BinOpType, lhs Node, rhs Node) (Node, error) { // the given VectorSelector and Duration. func NewMatrixSelector(vector *VectorSelector, interval time.Duration) *MatrixSelector { return &MatrixSelector{ - labels: vector.labels, - interval: interval, + labelMatchers: vector.labelMatchers, + interval: interval, } } diff --git a/rules/ast/printer.go b/rules/ast/printer.go index db2540b913..17a31a8b29 100644 --- a/rules/ast/printer.go +++ b/rules/ast/printer.go @@ -320,14 +320,13 @@ func (node *ScalarArithExpr) String() string { } func (node *VectorSelector) String() string { - metricName, ok := node.labels[clientmodel.MetricNameLabel] - if !ok { - panic("Tried to print vector without metric name") - } - labelStrings := make([]string, 0, len(node.labels)-1) - for label, value := range node.labels { - if label != clientmodel.MetricNameLabel { - labelStrings = append(labelStrings, fmt.Sprintf("%s=%q", label, value)) + labelStrings := make([]string, 0, len(node.labelMatchers)-1) + var metricName clientmodel.LabelValue + for _, matcher := range node.labelMatchers { + if matcher.Name != clientmodel.MetricNameLabel { + labelStrings = append(labelStrings, fmt.Sprintf("%s%s%q", matcher.Name, matcher.Type, matcher.Value)) + } else { + metricName = matcher.Value } } @@ -357,7 +356,7 @@ func (node *VectorArithExpr) String() string { } func (node *MatrixSelector) String() string { - vectorString := (&VectorSelector{labels: node.labels}).String() + vectorString := (&VectorSelector{labelMatchers: node.labelMatchers}).String() intervalString := fmt.Sprintf("[%s]", utility.DurationToString(node.interval)) return vectorString + intervalString } diff --git a/rules/ast/query_analyzer.go b/rules/ast/query_analyzer.go index 1a8e7ed26e..14bb280a46 100644 --- a/rules/ast/query_analyzer.go +++ b/rules/ast/query_analyzer.go @@ -66,9 +66,9 @@ func NewQueryAnalyzer(storage *metric.TieredStorage) *QueryAnalyzer { func (analyzer *QueryAnalyzer) Visit(node Node) { switch n := node.(type) { case *VectorSelector: - fingerprints, err := analyzer.storage.GetFingerprintsForLabelSet(n.labels) + fingerprints, err := analyzer.storage.GetFingerprintsForLabelMatchers(n.labelMatchers) if err != nil { - glog.Errorf("Error getting fingerprints for labelset %v: %v", n.labels, err) + glog.Errorf("Error getting fingerprints for label matchers %v: %v", n.labelMatchers, err) return } n.fingerprints = fingerprints @@ -80,9 +80,9 @@ func (analyzer *QueryAnalyzer) Visit(node Node) { } } case *MatrixSelector: - fingerprints, err := analyzer.storage.GetFingerprintsForLabelSet(n.labels) + fingerprints, err := analyzer.storage.GetFingerprintsForLabelMatchers(n.labelMatchers) if err != nil { - glog.Errorf("Error getting fingerprints for labelset %v: %v", n.labels, err) + glog.Errorf("Error getting fingerprints for label matchers %v: %v", n.labelMatchers, err) return } n.fingerprints = fingerprints diff --git a/rules/helpers.go b/rules/helpers.go index 6b16833cd3..b883e4b19d 100644 --- a/rules/helpers.go +++ b/rules/helpers.go @@ -21,6 +21,7 @@ import ( clientmodel "github.com/prometheus/client_golang/model" "github.com/prometheus/prometheus/rules/ast" + "github.com/prometheus/prometheus/storage/metric" "github.com/prometheus/prometheus/utility" ) @@ -116,6 +117,20 @@ func NewMatrixSelector(vector ast.Node, intervalStr string) (ast.MatrixNode, err return ast.NewMatrixSelector(vectorSelector, interval), nil } +func newLabelMatcher(matchTypeStr string, name clientmodel.LabelName, value clientmodel.LabelValue) (*metric.LabelMatcher, error) { + matchTypes := map[string]metric.MatchType{ + "=": metric.Equal, + "!=": metric.NotEqual, + "=~": metric.RegexMatch, + "!~": metric.RegexNoMatch, + } + matchType, ok := matchTypes[matchTypeStr] + if !ok { + return nil, fmt.Errorf("Invalid label matching operator \"%v\"", matchTypeStr) + } + return metric.NewLabelMatcher(matchType, name, value) +} + func ConsoleLinkForExpression(expr string) string { // url.QueryEscape percent-escapes everything except spaces, for which it // uses "+". However, in the non-query part of a URI, only percent-escaped diff --git a/rules/lexer.l b/rules/lexer.l index f4e3cfa184..482d70e5c9 100644 --- a/rules/lexer.l +++ b/rules/lexer.l @@ -83,7 +83,7 @@ KEEPING_EXTRA|keeping_extra return KEEPING_EXTRA AVG|SUM|MAX|MIN|COUNT lval.str = lexer.token(); return AGGR_OP avg|sum|max|min|count lval.str = strings.ToUpper(lexer.token()); return AGGR_OP \<|>|AND|OR|and|or lval.str = strings.ToUpper(lexer.token()); return CMP_OP -==|!=|>=|<= lval.str = lexer.token(); return CMP_OP +==|!=|>=|<=|=~|!~ lval.str = lexer.token(); return CMP_OP [+\-] lval.str = lexer.token(); return ADDITIVE_OP [*/%] lval.str = lexer.token(); return MULT_OP diff --git a/rules/lexer.l.go b/rules/lexer.l.go index c58a11288c..5efe43f822 100644 --- a/rules/lexer.l.go +++ b/rules/lexer.l.go @@ -155,7 +155,7 @@ c = lexer.getChar() switch { default: goto yyabort -case c == '=': +case c == '=' || c == '~': goto yystate4 } @@ -326,7 +326,7 @@ c = lexer.getChar() switch { default: goto yyrule26 -case c == '=': +case c == '=' || c == '~': goto yystate4 } @@ -2068,9 +2068,9 @@ yyrule16: // \<|>|AND|OR|and|or lval.str = strings.ToUpper(lexer.token()); return CMP_OP goto yystate0 } -yyrule17: // ==|!=|>=|<= +yyrule17: // ==|!=|>=|<=|=~|!~ { - lval.str = lexer.token(); return CMP_OP + lval.str = lexer.token(); return CMP_OP goto yystate0 } yyrule18: // [+\-] diff --git a/rules/parser.y b/rules/parser.y index 78573d5ac4..cc59429ec9 100644 --- a/rules/parser.y +++ b/rules/parser.y @@ -18,6 +18,7 @@ clientmodel "github.com/prometheus/client_golang/model" "github.com/prometheus/prometheus/rules/ast" + "github.com/prometheus/prometheus/storage/metric" ) %} @@ -29,6 +30,8 @@ boolean bool labelNameSlice clientmodel.LabelNames labelSet clientmodel.LabelSet + labelMatcher *metric.LabelMatcher + labelMatchers metric.LabelMatchers } /* We simulate multiple start symbols for closely-related grammars via dummy tokens. See @@ -46,9 +49,11 @@ %type func_arg_list %type label_list grouping_opts %type label_assign label_assign_list rule_labels +%type label_match +%type label_match_list label_matches %type rule_expr func_arg %type qualifier extra_labels_opts -%type for_duration metric_name +%type for_duration metric_name label_match_type %right '=' %left CMP_OP @@ -119,11 +124,44 @@ label_assign : IDENTIFIER '=' STRING { $$ = clientmodel.LabelSet{ clientmodel.LabelName($1): clientmodel.LabelValue($3) } } ; +label_matches : /* empty */ + { $$ = metric.LabelMatchers{} } + | '{' '}' + { $$ = metric.LabelMatchers{} } + | '{' label_match_list '}' + { $$ = $2 } + ; + +label_match_list : label_match + { $$ = metric.LabelMatchers{$1} } + | label_match_list ',' label_match + { $$ = append($$, $3) } + ; + +label_match : IDENTIFIER label_match_type STRING + { + var err error + $$, err = newLabelMatcher($2, clientmodel.LabelName($1), clientmodel.LabelValue($3)) + if err != nil { yylex.Error(err.Error()); return 1 } + } + ; + +label_match_type : '=' + { $$ = "=" } + | CMP_OP + { $$ = $1 } + ; rule_expr : '(' rule_expr ')' { $$ = $2 } - | metric_name rule_labels - { $2[clientmodel.MetricNameLabel] = clientmodel.LabelValue($1); $$ = ast.NewVectorSelector($2) } + | metric_name label_matches + { + var err error + m, err := metric.NewLabelMatcher(metric.Equal, clientmodel.MetricNameLabel, clientmodel.LabelValue($1)) + if err != nil { yylex.Error(err.Error()); return 1 } + $2 = append($2, m) + $$ = ast.NewVectorSelector($2) + } | IDENTIFIER '(' func_arg_list ')' { var err error diff --git a/rules/parser.y.go b/rules/parser.y.go index 355d8ddebb..4b440c373d 100644 --- a/rules/parser.y.go +++ b/rules/parser.y.go @@ -8,9 +8,10 @@ import __yyfmt__ "fmt" clientmodel "github.com/prometheus/client_golang/model" "github.com/prometheus/prometheus/rules/ast" + "github.com/prometheus/prometheus/storage/metric" ) -//line parser.y:24 +//line parser.y:25 type yySymType struct { yys int num clientmodel.SampleValue @@ -20,6 +21,8 @@ type yySymType struct { boolean bool labelNameSlice clientmodel.LabelNames labelSet clientmodel.LabelSet + labelMatcher *metric.LabelMatcher + labelMatchers metric.LabelMatchers } const START_RULES = 57346 @@ -72,7 +75,7 @@ const yyEofCode = 1 const yyErrCode = 2 const yyMaxDepth = 200 -//line parser.y:204 +//line parser.y:242 //line yacctab:1 @@ -85,79 +88,87 @@ var yyExca = []int{ -2, 10, } -const yyNprod = 40 +const yyNprod = 48 const yyPrivate = 57344 var yyTokenNames []string var yyStates []string -const yyLast = 108 +const yyLast = 121 var yyAct = []int{ - 22, 40, 41, 36, 19, 46, 6, 17, 9, 42, - 21, 12, 11, 72, 23, 71, 10, 17, 20, 18, - 19, 30, 31, 32, 20, 18, 19, 44, 43, 62, - 7, 39, 52, 17, 65, 20, 18, 19, 25, 17, - 20, 18, 19, 9, 42, 24, 12, 11, 54, 33, - 17, 10, 55, 57, 9, 17, 60, 12, 11, 18, - 19, 51, 10, 50, 37, 7, 73, 70, 47, 48, - 53, 49, 76, 17, 66, 45, 7, 8, 16, 64, - 59, 67, 29, 27, 35, 15, 12, 77, 75, 56, - 74, 69, 26, 37, 28, 2, 3, 13, 5, 4, - 1, 61, 63, 14, 34, 58, 68, 38, + 44, 59, 41, 40, 36, 47, 6, 17, 9, 42, + 21, 12, 11, 18, 19, 84, 10, 83, 20, 18, + 19, 30, 31, 32, 20, 18, 19, 17, 43, 72, + 7, 39, 55, 17, 75, 25, 20, 18, 19, 17, + 20, 18, 19, 24, 9, 42, 70, 12, 11, 61, + 33, 17, 10, 9, 62, 17, 12, 11, 64, 67, + 19, 10, 54, 60, 53, 37, 7, 68, 69, 45, + 23, 76, 56, 17, 88, 7, 48, 49, 52, 82, + 85, 78, 16, 58, 46, 34, 8, 51, 74, 15, + 66, 27, 79, 89, 12, 29, 87, 77, 63, 86, + 81, 26, 60, 37, 28, 2, 3, 13, 5, 4, + 1, 50, 71, 73, 14, 22, 35, 57, 65, 80, + 38, } var yyPact = []int{ - 91, -1000, -1000, 48, 67, -1000, 25, 48, -11, 17, - 10, -1000, -1000, -1000, 77, 88, -1000, 74, 48, 48, - 48, 20, -1000, 58, 2, 48, -11, -1000, 56, -26, - -13, -23, 43, -1000, 42, -1000, -1000, 47, 34, -1000, - -1000, 25, -1000, 3, 46, 48, -1000, -1000, 87, 82, - -1000, 37, 68, 48, 9, -1000, -1000, -1000, 66, 6, - 25, 53, 73, -1000, -1000, 85, -11, -1000, -14, -1000, - 44, -1000, 84, 81, -1000, 49, 80, -1000, + 101, -1000, -1000, 47, 71, -1000, 25, 47, 45, 15, + 7, -1000, -1000, -1000, 85, 98, -1000, 87, 47, 47, + 47, 21, -1000, 59, 2, 47, 44, -1000, 65, -26, + 43, -23, -3, -1000, -1000, 50, -1000, 63, 35, -1000, + -1000, 25, -1000, 3, 48, 57, 47, -1000, -1000, 97, + 91, -1000, -1000, -1000, 38, 78, 47, 41, -1000, -1000, + 22, 9, -1000, -1000, -1000, 75, 6, 25, -1000, 96, + 90, 60, 84, -1000, -1000, 94, -1000, -1000, 44, -1000, + -12, -1000, 58, -1000, 93, 89, -1000, 51, 86, -1000, } var yyPgo = []int{ - 0, 107, 106, 105, 3, 104, 0, 2, 1, 103, - 102, 101, 77, 100, 99, 98, 97, + 0, 120, 119, 118, 1, 117, 0, 4, 116, 115, + 2, 3, 114, 113, 112, 86, 111, 110, 109, 108, + 107, } var yyR1 = []int{ - 0, 13, 13, 14, 14, 15, 16, 16, 11, 11, - 9, 9, 12, 12, 6, 6, 6, 5, 5, 4, - 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, - 10, 10, 3, 3, 2, 2, 1, 1, 8, 8, + 0, 17, 17, 18, 18, 19, 20, 20, 14, 14, + 12, 12, 15, 15, 6, 6, 6, 5, 5, 4, + 9, 9, 9, 8, 8, 7, 16, 16, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, 13, 13, + 3, 3, 2, 2, 1, 1, 11, 11, } var yyR2 = []int{ 0, 2, 2, 0, 2, 1, 5, 11, 0, 2, 0, 1, 1, 1, 0, 3, 2, 1, 3, 3, - 3, 2, 4, 3, 4, 6, 3, 3, 3, 1, - 0, 1, 0, 4, 1, 3, 1, 3, 1, 1, + 0, 2, 3, 1, 3, 3, 1, 1, 3, 2, + 4, 3, 4, 6, 3, 3, 3, 1, 0, 1, + 0, 4, 1, 3, 1, 3, 1, 1, } var yyChk = []int{ - -1000, -13, 4, 5, -14, -15, -7, 28, -12, 6, - 14, 10, 9, -16, -9, 18, 11, 30, 16, 17, - 15, -7, -6, 25, 28, 28, -12, 6, 6, 8, - -7, -7, -7, 29, -5, 26, -4, 6, -1, 29, - -8, -7, 7, -7, -6, 19, 31, 26, 27, 24, - 29, 27, 29, 24, -7, -4, 7, -8, -3, 12, - -7, -11, 20, -10, 13, 28, 21, 8, -2, 6, - -6, 29, 27, 22, 6, 7, 23, 7, + -1000, -17, 4, 5, -18, -19, -10, 28, -15, 6, + 14, 10, 9, -20, -12, 18, 11, 30, 16, 17, + 15, -10, -9, 25, 28, 28, -15, 6, 6, 8, + -10, -10, -10, 29, 26, -8, -7, 6, -1, 29, + -11, -10, 7, -10, -6, 25, 19, 31, 26, 27, + -16, 24, 15, 29, 27, 29, 24, -5, 26, -4, + 6, -10, -7, 7, -11, -3, 12, -10, 26, 27, + 24, -14, 20, -13, 13, 28, -4, 7, 21, 8, + -2, 6, -6, 29, 27, 22, 6, 7, 23, 7, } var yyDef = []int{ - 0, -2, 3, 0, -2, 2, 5, 0, 14, 13, - 0, 29, 12, 4, 0, 0, 11, 0, 0, 0, - 0, 0, 21, 0, 0, 0, 14, 13, 0, 0, - 26, 27, 28, 20, 0, 16, 17, 0, 0, 23, - 36, 38, 39, 0, 0, 0, 24, 15, 0, 0, - 22, 0, 32, 0, 8, 18, 19, 37, 30, 0, - 6, 0, 0, 25, 31, 0, 14, 9, 0, 34, - 0, 33, 0, 0, 35, 0, 0, 7, + 0, -2, 3, 0, -2, 2, 5, 0, 20, 13, + 0, 37, 12, 4, 0, 0, 11, 0, 0, 0, + 0, 0, 29, 0, 0, 0, 14, 13, 0, 0, + 34, 35, 36, 28, 21, 0, 23, 0, 0, 31, + 44, 46, 47, 0, 0, 0, 0, 32, 22, 0, + 0, 26, 27, 30, 0, 40, 0, 0, 16, 17, + 0, 8, 24, 25, 45, 38, 0, 6, 15, 0, + 0, 0, 0, 33, 39, 0, 18, 19, 14, 9, + 0, 42, 0, 41, 0, 0, 43, 0, 0, 7, } var yyTok1 = []int{ @@ -411,145 +422,179 @@ yydefault: switch yynt { case 5: - //line parser.y:69 + //line parser.y:74 { yylex.(*RulesLexer).parsedExpr = yyS[yypt-0].ruleNode } case 6: - //line parser.y:74 + //line parser.y:79 { rule, err := CreateRecordingRule(yyS[yypt-3].str, yyS[yypt-2].labelSet, yyS[yypt-0].ruleNode, yyS[yypt-4].boolean) if err != nil { yylex.Error(err.Error()); return 1 } yylex.(*RulesLexer).parsedRules = append(yylex.(*RulesLexer).parsedRules, rule) } case 7: - //line parser.y:80 + //line parser.y:85 { rule, err := CreateAlertingRule(yyS[yypt-9].str, yyS[yypt-7].ruleNode, yyS[yypt-6].str, yyS[yypt-4].labelSet, yyS[yypt-2].str, yyS[yypt-0].str) if err != nil { yylex.Error(err.Error()); return 1 } yylex.(*RulesLexer).parsedRules = append(yylex.(*RulesLexer).parsedRules, rule) } case 8: - //line parser.y:88 + //line parser.y:93 { yyVAL.str = "0s" } case 9: - //line parser.y:90 + //line parser.y:95 { yyVAL.str = yyS[yypt-0].str } case 10: - //line parser.y:94 + //line parser.y:99 { yyVAL.boolean = false } case 11: - //line parser.y:96 + //line parser.y:101 { yyVAL.boolean = true } case 12: - //line parser.y:100 + //line parser.y:105 { yyVAL.str = yyS[yypt-0].str } case 13: - //line parser.y:102 + //line parser.y:107 { yyVAL.str = yyS[yypt-0].str } case 14: - //line parser.y:106 + //line parser.y:111 { yyVAL.labelSet = clientmodel.LabelSet{} } case 15: - //line parser.y:108 + //line parser.y:113 { yyVAL.labelSet = yyS[yypt-1].labelSet } case 16: - //line parser.y:110 + //line parser.y:115 { yyVAL.labelSet = clientmodel.LabelSet{} } case 17: - //line parser.y:113 + //line parser.y:118 { yyVAL.labelSet = yyS[yypt-0].labelSet } case 18: - //line parser.y:115 + //line parser.y:120 { for k, v := range yyS[yypt-0].labelSet { yyVAL.labelSet[k] = v } } case 19: - //line parser.y:119 + //line parser.y:124 { yyVAL.labelSet = clientmodel.LabelSet{ clientmodel.LabelName(yyS[yypt-2].str): clientmodel.LabelValue(yyS[yypt-0].str) } } case 20: - //line parser.y:124 - { yyVAL.ruleNode = yyS[yypt-1].ruleNode } - case 21: - //line parser.y:126 - { yyS[yypt-0].labelSet[clientmodel.MetricNameLabel] = clientmodel.LabelValue(yyS[yypt-1].str); yyVAL.ruleNode = ast.NewVectorSelector(yyS[yypt-0].labelSet) } - case 22: //line parser.y:128 + { yyVAL.labelMatchers = metric.LabelMatchers{} } + case 21: + //line parser.y:130 + { yyVAL.labelMatchers = metric.LabelMatchers{} } + case 22: + //line parser.y:132 + { yyVAL.labelMatchers = yyS[yypt-1].labelMatchers } + case 23: + //line parser.y:136 + { yyVAL.labelMatchers = metric.LabelMatchers{yyS[yypt-0].labelMatcher} } + case 24: + //line parser.y:138 + { yyVAL.labelMatchers = append(yyVAL.labelMatchers, yyS[yypt-0].labelMatcher) } + case 25: + //line parser.y:142 + { + var err error + yyVAL.labelMatcher, err = newLabelMatcher(yyS[yypt-1].str, clientmodel.LabelName(yyS[yypt-2].str), clientmodel.LabelValue(yyS[yypt-0].str)) + if err != nil { yylex.Error(err.Error()); return 1 } + } + case 26: + //line parser.y:150 + { yyVAL.str = "=" } + case 27: + //line parser.y:152 + { yyVAL.str = yyS[yypt-0].str } + case 28: + //line parser.y:156 + { yyVAL.ruleNode = yyS[yypt-1].ruleNode } + case 29: + //line parser.y:158 + { + var err error + m, err := metric.NewLabelMatcher(metric.Equal, clientmodel.MetricNameLabel, clientmodel.LabelValue(yyS[yypt-1].str)) + if err != nil { yylex.Error(err.Error()); return 1 } + yyS[yypt-0].labelMatchers = append(yyS[yypt-0].labelMatchers, m) + yyVAL.ruleNode = ast.NewVectorSelector(yyS[yypt-0].labelMatchers) + } + case 30: + //line parser.y:166 { var err error yyVAL.ruleNode, err = NewFunctionCall(yyS[yypt-3].str, yyS[yypt-1].ruleNodeSlice) if err != nil { yylex.Error(err.Error()); return 1 } } - case 23: - //line parser.y:134 + case 31: + //line parser.y:172 { var err error yyVAL.ruleNode, err = NewFunctionCall(yyS[yypt-2].str, []ast.Node{}) if err != nil { yylex.Error(err.Error()); return 1 } } - case 24: - //line parser.y:140 + case 32: + //line parser.y:178 { var err error yyVAL.ruleNode, err = NewMatrixSelector(yyS[yypt-3].ruleNode, yyS[yypt-1].str) if err != nil { yylex.Error(err.Error()); return 1 } } - case 25: - //line parser.y:146 + case 33: + //line parser.y:184 { var err error yyVAL.ruleNode, err = NewVectorAggregation(yyS[yypt-5].str, yyS[yypt-3].ruleNode, yyS[yypt-1].labelNameSlice, yyS[yypt-0].boolean) if err != nil { yylex.Error(err.Error()); return 1 } } - case 26: - //line parser.y:154 - { - var err error - yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-1].str, yyS[yypt-2].ruleNode, yyS[yypt-0].ruleNode) - if err != nil { yylex.Error(err.Error()); return 1 } - } - case 27: - //line parser.y:160 - { - var err error - yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-1].str, yyS[yypt-2].ruleNode, yyS[yypt-0].ruleNode) - if err != nil { yylex.Error(err.Error()); return 1 } - } - case 28: - //line parser.y:166 - { - var err error - yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-1].str, yyS[yypt-2].ruleNode, yyS[yypt-0].ruleNode) - if err != nil { yylex.Error(err.Error()); return 1 } - } - case 29: - //line parser.y:172 - { yyVAL.ruleNode = ast.NewScalarLiteral(yyS[yypt-0].num)} - case 30: - //line parser.y:176 - { yyVAL.boolean = false } - case 31: - //line parser.y:178 - { yyVAL.boolean = true } - case 32: - //line parser.y:182 - { yyVAL.labelNameSlice = clientmodel.LabelNames{} } - case 33: - //line parser.y:184 - { yyVAL.labelNameSlice = yyS[yypt-1].labelNameSlice } case 34: - //line parser.y:188 - { yyVAL.labelNameSlice = clientmodel.LabelNames{clientmodel.LabelName(yyS[yypt-0].str)} } + //line parser.y:192 + { + var err error + yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-1].str, yyS[yypt-2].ruleNode, yyS[yypt-0].ruleNode) + if err != nil { yylex.Error(err.Error()); return 1 } + } case 35: - //line parser.y:190 - { yyVAL.labelNameSlice = append(yyVAL.labelNameSlice, clientmodel.LabelName(yyS[yypt-0].str)) } + //line parser.y:198 + { + var err error + yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-1].str, yyS[yypt-2].ruleNode, yyS[yypt-0].ruleNode) + if err != nil { yylex.Error(err.Error()); return 1 } + } case 36: - //line parser.y:194 - { yyVAL.ruleNodeSlice = []ast.Node{yyS[yypt-0].ruleNode} } + //line parser.y:204 + { + var err error + yyVAL.ruleNode, err = NewArithExpr(yyS[yypt-1].str, yyS[yypt-2].ruleNode, yyS[yypt-0].ruleNode) + if err != nil { yylex.Error(err.Error()); return 1 } + } case 37: - //line parser.y:196 - { yyVAL.ruleNodeSlice = append(yyVAL.ruleNodeSlice, yyS[yypt-0].ruleNode) } + //line parser.y:210 + { yyVAL.ruleNode = ast.NewScalarLiteral(yyS[yypt-0].num)} case 38: - //line parser.y:200 - { yyVAL.ruleNode = yyS[yypt-0].ruleNode } + //line parser.y:214 + { yyVAL.boolean = false } case 39: - //line parser.y:202 + //line parser.y:216 + { yyVAL.boolean = true } + case 40: + //line parser.y:220 + { yyVAL.labelNameSlice = clientmodel.LabelNames{} } + case 41: + //line parser.y:222 + { yyVAL.labelNameSlice = yyS[yypt-1].labelNameSlice } + case 42: + //line parser.y:226 + { yyVAL.labelNameSlice = clientmodel.LabelNames{clientmodel.LabelName(yyS[yypt-0].str)} } + case 43: + //line parser.y:228 + { yyVAL.labelNameSlice = append(yyVAL.labelNameSlice, clientmodel.LabelName(yyS[yypt-0].str)) } + case 44: + //line parser.y:232 + { yyVAL.ruleNodeSlice = []ast.Node{yyS[yypt-0].ruleNode} } + case 45: + //line parser.y:234 + { yyVAL.ruleNodeSlice = append(yyVAL.ruleNodeSlice, yyS[yypt-0].ruleNode) } + case 46: + //line parser.y:238 + { yyVAL.ruleNode = yyS[yypt-0].ruleNode } + case 47: + //line parser.y:240 { yyVAL.ruleNode = ast.NewStringLiteral(yyS[yypt-0].str) } } goto yystack /* stack new state and value */ diff --git a/rules/rules_test.go b/rules/rules_test.go index 4c8c158642..2267ac4212 100644 --- a/rules/rules_test.go +++ b/rules/rules_test.go @@ -406,6 +406,47 @@ func TestExpressions(t *testing.T) { // Interval durations can"t be in quotes. expr: `http_requests["1m"]`, shouldFail: true, + }, { + expr: `http_requests{group!="canary"}`, + output: []string{ + `http_requests{group="production", instance="1", job="app-server"} => 600 @[%v]`, + `http_requests{group="production", instance="0", job="app-server"} => 500 @[%v]`, + `http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`, + `http_requests{group="production", instance="0", job="api-server"} => 100 @[%v]`, + }, + fullRanges: 0, + intervalRanges: 4, + }, { + expr: `http_requests{job=~"server",group!="canary"}`, + output: []string{ + `http_requests{group="production", instance="1", job="app-server"} => 600 @[%v]`, + `http_requests{group="production", instance="0", job="app-server"} => 500 @[%v]`, + `http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`, + `http_requests{group="production", instance="0", job="api-server"} => 100 @[%v]`, + }, + fullRanges: 0, + intervalRanges: 4, + }, { + expr: `http_requests{job!~"api",group!="canary"}`, + output: []string{ + `http_requests{group="production", instance="1", job="app-server"} => 600 @[%v]`, + `http_requests{group="production", instance="0", job="app-server"} => 500 @[%v]`, + }, + fullRanges: 0, + intervalRanges: 2, + }, { + expr: `count_scalar(http_requests{job=~"^server$"})`, + output: []string{`scalar: 0 @[%v]`}, + fullRanges: 0, + intervalRanges: 0, + }, { + expr: `http_requests{group="production",job=~"^api"}`, + output: []string{ + `http_requests{group="production", instance="1", job="api-server"} => 200 @[%v]`, + `http_requests{group="production", instance="0", job="api-server"} => 100 @[%v]`, + }, + fullRanges: 0, + intervalRanges: 2, }, } diff --git a/storage/metric/end_to_end_test.go b/storage/metric/end_to_end_test.go index 4a36d7d75a..cd24447ea9 100644 --- a/storage/metric/end_to_end_test.go +++ b/storage/metric/end_to_end_test.go @@ -14,6 +14,7 @@ package metric import ( + "sort" "testing" "time" @@ -23,58 +24,131 @@ import ( ) func GetFingerprintsForLabelSetTests(p MetricPersistence, t test.Tester) { - testAppendSamples(p, &clientmodel.Sample{ - Value: 0, - Timestamp: 0, - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "my_metric", - "request_type": "your_mom", + metrics := []clientmodel.Metric{ + { + clientmodel.MetricNameLabel: "test_metric", + "method": "get", + "result": "success", }, - }, t) - - testAppendSamples(p, &clientmodel.Sample{ - Value: 0, - Timestamp: 0, - Metric: clientmodel.Metric{ - clientmodel.MetricNameLabel: "my_metric", - "request_type": "your_dad", + { + clientmodel.MetricNameLabel: "test_metric", + "method": "get", + "result": "failure", + }, + { + clientmodel.MetricNameLabel: "test_metric", + "method": "post", + "result": "success", + }, + { + clientmodel.MetricNameLabel: "test_metric", + "method": "post", + "result": "failure", }, - }, t) - - result, err := p.GetFingerprintsForLabelSet(clientmodel.LabelSet{ - clientmodel.MetricNameLabel: clientmodel.LabelValue("my_metric"), - }) - - if err != nil { - t.Error(err) } - if len(result) != 2 { - t.Errorf("Expected two elements.") + newTestLabelMatcher := func(matchType MatchType, name clientmodel.LabelName, value clientmodel.LabelValue) *LabelMatcher { + m, err := NewLabelMatcher(matchType, name, value) + if err != nil { + t.Fatalf("Couldn't create label matcher: %v", err) + } + return m } - result, err = p.GetFingerprintsForLabelSet(clientmodel.LabelSet{ - clientmodel.LabelName("request_type"): clientmodel.LabelValue("your_mom"), - }) - - if err != nil { - t.Error(err) + scenarios := []struct { + in LabelMatchers + outIndexes []int + }{ + { + in: LabelMatchers{ + newTestLabelMatcher(Equal, clientmodel.MetricNameLabel, "test_metric"), + }, + outIndexes: []int{0, 1, 2, 3}, + }, + { + in: LabelMatchers{ + newTestLabelMatcher(Equal, clientmodel.MetricNameLabel, "non_existent_metric"), + }, + outIndexes: []int{}, + }, + { + in: LabelMatchers{ + newTestLabelMatcher(Equal, clientmodel.MetricNameLabel, "non_existent_metric"), + newTestLabelMatcher(Equal, "result", "success"), + }, + outIndexes: []int{}, + }, + { + in: LabelMatchers{ + newTestLabelMatcher(Equal, clientmodel.MetricNameLabel, "test_metric"), + newTestLabelMatcher(Equal, "result", "success"), + }, + outIndexes: []int{0, 2}, + }, + { + in: LabelMatchers{ + newTestLabelMatcher(Equal, clientmodel.MetricNameLabel, "test_metric"), + newTestLabelMatcher(NotEqual, "result", "success"), + }, + outIndexes: []int{1, 3}, + }, + { + in: LabelMatchers{ + newTestLabelMatcher(Equal, clientmodel.MetricNameLabel, "test_metric"), + newTestLabelMatcher(RegexMatch, "result", "foo|success|bar"), + }, + outIndexes: []int{0, 2}, + }, + { + in: LabelMatchers{ + newTestLabelMatcher(Equal, clientmodel.MetricNameLabel, "test_metric"), + newTestLabelMatcher(RegexNoMatch, "result", "foo|success|bar"), + }, + outIndexes: []int{1, 3}, + }, + { + in: LabelMatchers{ + newTestLabelMatcher(Equal, clientmodel.MetricNameLabel, "test_metric"), + newTestLabelMatcher(RegexNoMatch, "result", "foo|success|bar"), + newTestLabelMatcher(RegexMatch, "method", "os"), + }, + outIndexes: []int{3}, + }, } - if len(result) != 1 { - t.Errorf("Expected one element.") + for _, m := range metrics { + testAppendSamples(p, &clientmodel.Sample{ + Value: 0, + Timestamp: 0, + Metric: m, + }, t) } - result, err = p.GetFingerprintsForLabelSet(clientmodel.LabelSet{ - clientmodel.LabelName("request_type"): clientmodel.LabelValue("your_dad"), - }) + for i, s := range scenarios { + actualFps, err := p.GetFingerprintsForLabelMatchers(s.in) + if err != nil { + t.Fatalf("%d. Couldn't get fingerprints for label matchers: %v", i, err) + } - if err != nil { - t.Error(err) - } + expectedFps := clientmodel.Fingerprints{} + for _, i := range s.outIndexes { + fp := &clientmodel.Fingerprint{} + fp.LoadFromMetric(metrics[i]) + expectedFps = append(expectedFps, fp) + } - if len(result) != 1 { - t.Errorf("Expected one element.") + sort.Sort(actualFps) + sort.Sort(expectedFps) + + if len(actualFps) != len(expectedFps) { + t.Fatalf("%d. Got %d fingerprints; want %d", i, len(actualFps), len(expectedFps)) + } + + for j, actualFp := range actualFps { + if !actualFp.Equal(expectedFps[j]) { + t.Fatalf("%d.%d. Got fingerprint %v; want %v", i, j, actualFp, expectedFps[j]) + } + } } } @@ -140,9 +214,11 @@ func GetMetricForFingerprintTests(p MetricPersistence, t test.Tester) { }, }, t) - result, err := p.GetFingerprintsForLabelSet(clientmodel.LabelSet{ - clientmodel.LabelName("request_type"): clientmodel.LabelValue("your_mom"), - }) + result, err := p.GetFingerprintsForLabelMatchers(LabelMatchers{{ + Type: Equal, + Name: "request_type", + Value: "your_mom", + }}) if err != nil { t.Error(err) @@ -169,9 +245,11 @@ func GetMetricForFingerprintTests(p MetricPersistence, t test.Tester) { t.Errorf("Expected metric to match.") } - result, err = p.GetFingerprintsForLabelSet(clientmodel.LabelSet{ - clientmodel.LabelName("request_type"): clientmodel.LabelValue("your_dad"), - }) + result, err = p.GetFingerprintsForLabelMatchers(LabelMatchers{{ + Type: Equal, + Name: "request_type", + Value: "your_dad", + }}) if err != nil { t.Error(err) @@ -256,15 +334,15 @@ func AppendRepeatingValuesTests(p MetricPersistence, t test.Tester) { return } - labelSet := clientmodel.LabelSet{ + matchers := labelMatchersFromLabelSet(clientmodel.LabelSet{ clientmodel.MetricNameLabel: "errors_total", "controller": "foo", "operation": "bar", - } + }) for i := 0; i < increments; i++ { for j := 0; j < repetitions; j++ { - fingerprints, err := p.GetFingerprintsForLabelSet(labelSet) + fingerprints, err := p.GetFingerprintsForLabelMatchers(matchers) if err != nil { t.Fatal(err) } @@ -319,15 +397,15 @@ func AppendsRepeatingValuesTests(p MetricPersistence, t test.Tester) { return } - labelSet := clientmodel.LabelSet{ + matchers := labelMatchersFromLabelSet(clientmodel.LabelSet{ clientmodel.MetricNameLabel: "errors_total", "controller": "foo", "operation": "bar", - } + }) for i := 0; i < increments; i++ { for j := 0; j < repetitions; j++ { - fingerprints, err := p.GetFingerprintsForLabelSet(labelSet) + fingerprints, err := p.GetFingerprintsForLabelMatchers(matchers) if err != nil { t.Fatal(err) } diff --git a/storage/metric/instrumentation.go b/storage/metric/instrumentation.go index b26c9460d7..809d9b0d65 100644 --- a/storage/metric/instrumentation.go +++ b/storage/metric/instrumentation.go @@ -25,15 +25,15 @@ const ( failure = "failure" result = "result" - appendSample = "append_sample" - appendSamples = "append_samples" - flushMemory = "flush_memory" - getLabelValuesForLabelName = "get_label_values_for_label_name" - getFingerprintsForLabelSet = "get_fingerprints_for_labelset" - getMetricForFingerprint = "get_metric_for_fingerprint" - hasIndexMetric = "has_index_metric" - refreshHighWatermarks = "refresh_high_watermarks" - renderView = "render_view" + appendSample = "append_sample" + appendSamples = "append_samples" + flushMemory = "flush_memory" + getLabelValuesForLabelName = "get_label_values_for_label_name" + getFingerprintsForLabelMatchers = "get_fingerprints_for_label_matchers" + getMetricForFingerprint = "get_metric_for_fingerprint" + hasIndexMetric = "has_index_metric" + refreshHighWatermarks = "refresh_high_watermarks" + renderView = "render_view" cutOff = "recency_threshold" processorName = "processor" diff --git a/storage/metric/interface.go b/storage/metric/interface.go index 71349ab7a7..3b013e0263 100644 --- a/storage/metric/interface.go +++ b/storage/metric/interface.go @@ -29,8 +29,8 @@ type MetricPersistence interface { AppendSamples(clientmodel.Samples) error // Get all of the metric fingerprints that are associated with the - // provided label set. - GetFingerprintsForLabelSet(clientmodel.LabelSet) (clientmodel.Fingerprints, error) + // provided label matchers. + GetFingerprintsForLabelMatchers(LabelMatchers) (clientmodel.Fingerprints, error) // Get all of the label values that are associated with a given label name. GetLabelValuesForLabelName(clientmodel.LabelName) (clientmodel.LabelValues, error) diff --git a/storage/metric/leveldb.go b/storage/metric/leveldb.go index 8620305c3a..171c139e5c 100644 --- a/storage/metric/leveldb.go +++ b/storage/metric/leveldb.go @@ -52,7 +52,7 @@ type LevelDBMetricPersistence struct { // // type FingerprintResolver interface { // GetFingerprintForMetric(clientmodel.Metric) (*clientmodel.Fingerprint, bool, error) - // GetFingerprintsForLabelSet(LabelPair) (clientmodel.Fingerprints, bool, error) + // GetFingerprintsForLabelMatchers(LabelPair) (clientmodel.Fingerprints, bool, error) // } // type MetricResolver interface { @@ -419,33 +419,57 @@ func (l *LevelDBMetricPersistence) hasIndexMetric(m clientmodel.Metric) (value b return l.MetricMembershipIndex.Has(m) } -// GetFingerprintsForLabelSet returns the Fingerprints for the given LabelSet by -// querying the underlying LabelPairFingerprintIndex for each LabelPair -// contained in LabelSet. It implements the MetricPersistence interface. -func (l *LevelDBMetricPersistence) GetFingerprintsForLabelSet(labelSet clientmodel.LabelSet) (fps clientmodel.Fingerprints, err error) { +// GetFingerprintsForLabelMatchers returns the Fingerprints for the given +// LabelMatchers by querying the underlying LabelPairFingerprintIndex and +// possibly the LabelNameLabelValuesIndex for each matcher. It implements the +// MetricPersistence interface. +func (l *LevelDBMetricPersistence) GetFingerprintsForLabelMatchers(labelMatchers LabelMatchers) (fps clientmodel.Fingerprints, err error) { defer func(begin time.Time) { duration := time.Since(begin) - recordOutcome(duration, err, map[string]string{operation: getFingerprintsForLabelSet, result: success}, map[string]string{operation: getFingerprintsForLabelSet, result: failure}) + recordOutcome(duration, err, map[string]string{operation: getFingerprintsForLabelMatchers, result: success}, map[string]string{operation: getFingerprintsForLabelMatchers, result: failure}) }(time.Now()) sets := []utility.Set{} - for name, value := range labelSet { - fps, _, err := l.LabelPairToFingerprints.Lookup(&LabelPair{ - Name: name, - Value: value, - }) - if err != nil { - return nil, err - } - + for _, matcher := range labelMatchers { set := utility.Set{} - for _, fp := range fps { - set.Add(*fp) - } + switch matcher.Type { + case Equal: + fps, _, err := l.LabelPairToFingerprints.Lookup(&LabelPair{ + Name: matcher.Name, + Value: matcher.Value, + }) + if err != nil { + return nil, err + } + for _, fp := range fps { + set.Add(*fp) + } + default: + values, err := l.GetLabelValuesForLabelName(matcher.Name) + if err != nil { + return nil, err + } + matches := matcher.Filter(values) + if len(matches) == 0 { + return nil, nil + } + for _, v := range matches { + fps, _, err := l.LabelPairToFingerprints.Lookup(&LabelPair{ + Name: matcher.Name, + Value: v, + }) + if err != nil { + return nil, err + } + for _, fp := range fps { + set.Add(*fp) + } + } + } sets = append(sets, set) } diff --git a/storage/metric/matcher.go b/storage/metric/matcher.go new file mode 100644 index 0000000000..58203ad26a --- /dev/null +++ b/storage/metric/matcher.go @@ -0,0 +1,112 @@ +// Copyright 2014 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metric + +import ( + "regexp" + + clientmodel "github.com/prometheus/client_golang/model" +) + +// MatchType is an enum for label matching types. +type MatchType int + +// Possible MatchTypes. +const ( + Equal MatchType = iota + NotEqual + RegexMatch + RegexNoMatch +) + +func (m MatchType) String() string { + typeToStr := map[MatchType]string{ + Equal: "=", + NotEqual: "!=", + RegexMatch: "=~", + RegexNoMatch: "!~", + } + if str, ok := typeToStr[m]; ok { + return str + } + panic("unknown match type") +} + +// LabelMatchers is a slice of LabelMatcher objects. +type LabelMatchers []*LabelMatcher + +// LabelMatcher models the matching of a label. +type LabelMatcher struct { + Type MatchType + Name clientmodel.LabelName + Value clientmodel.LabelValue + re *regexp.Regexp +} + +// NewLabelMatcher returns a LabelMatcher object ready to use. +func NewLabelMatcher(matchType MatchType, name clientmodel.LabelName, value clientmodel.LabelValue) (*LabelMatcher, error) { + m := &LabelMatcher{ + Type: matchType, + Name: name, + Value: value, + } + if matchType == RegexMatch || matchType == RegexNoMatch { + re, err := regexp.Compile(string(value)) + if err != nil { + return nil, err + } + m.re = re + } + return m, nil +} + +// Match returns true if the label matcher matches the supplied label value. +func (m *LabelMatcher) Match(v clientmodel.LabelValue) bool { + switch m.Type { + case Equal: + return m.Value == v + case NotEqual: + return m.Value != v + case RegexMatch: + return m.re.MatchString(string(v)) + case RegexNoMatch: + return !m.re.MatchString(string(v)) + default: + panic("invalid match type") + } +} + +// Filter takes a list of label values and returns all label values which match +// the label matcher. +func (m *LabelMatcher) Filter(in clientmodel.LabelValues) clientmodel.LabelValues { + out := clientmodel.LabelValues{} + for _, v := range in { + if m.Match(v) { + out = append(out, v) + } + } + return out +} + +func labelMatchersFromLabelSet(l clientmodel.LabelSet) LabelMatchers { + m := make(LabelMatchers, 0, len(l)) + for k, v := range l { + m = append(m, &LabelMatcher{ + Type: Equal, + Name: k, + Value: v, + }) + } + return m +} diff --git a/storage/metric/memory.go b/storage/metric/memory.go index 568c65f39a..c21470b5cf 100644 --- a/storage/metric/memory.go +++ b/storage/metric/memory.go @@ -349,20 +349,45 @@ func (s *memorySeriesStorage) appendSamplesWithoutIndexing(fingerprint *clientmo series.add(samples) } -func (s *memorySeriesStorage) GetFingerprintsForLabelSet(l clientmodel.LabelSet) (clientmodel.Fingerprints, error) { +func (s *memorySeriesStorage) GetFingerprintsForLabelMatchers(labelMatchers LabelMatchers) (clientmodel.Fingerprints, error) { s.RLock() defer s.RUnlock() sets := []utility.Set{} - for k, v := range l { - set, ok := s.labelPairToFingerprints[LabelPair{ - Name: k, - Value: v, - }] - if !ok { - return nil, nil + for _, matcher := range labelMatchers { + switch matcher.Type { + case Equal: + set, ok := s.labelPairToFingerprints[LabelPair{ + Name: matcher.Name, + Value: matcher.Value, + }] + if !ok { + return nil, nil + } + sets = append(sets, set) + default: + values, err := s.GetLabelValuesForLabelName(matcher.Name) + if err != nil { + return nil, err + } + + matches := matcher.Filter(values) + if len(matches) == 0 { + return nil, nil + } + set := utility.Set{} + for _, v := range matches { + subset, ok := s.labelPairToFingerprints[LabelPair{ + Name: matcher.Name, + Value: v, + }] + if !ok { + return nil, nil + } + set = set.Union(subset) + } + sets = append(sets, set) } - sets = append(sets, set) } setCount := len(sets) diff --git a/storage/metric/memory_test.go b/storage/metric/memory_test.go index 5a9db88e9f..de477ca127 100644 --- a/storage/metric/memory_test.go +++ b/storage/metric/memory_test.go @@ -126,7 +126,7 @@ func TestDroppedSeriesIndexRegression(t *testing.T) { s.AppendSamples(samples) common := clientmodel.LabelSet{"common": "samevalue"} - fps, err := s.GetFingerprintsForLabelSet(common) + fps, err := s.GetFingerprintsForLabelMatchers(labelMatchersFromLabelSet(common)) if err != nil { t.Fatal(err) } @@ -144,7 +144,7 @@ func TestDroppedSeriesIndexRegression(t *testing.T) { t.Fatalf("Got %d disk samples, expected 1", len(diskSamples)) } - fps, err = s.GetFingerprintsForLabelSet(common) + fps, err = s.GetFingerprintsForLabelMatchers(labelMatchersFromLabelSet(common)) if err != nil { t.Fatal(err) } diff --git a/storage/metric/regressions_test.go b/storage/metric/regressions_test.go index 28815ca5d0..084a8eedaa 100644 --- a/storage/metric/regressions_test.go +++ b/storage/metric/regressions_test.go @@ -49,7 +49,7 @@ func GetFingerprintsForLabelSetUsesAndForLabelMatchingTests(p MetricPersistence, "percentile": "0.010000", } - fingerprints, err := p.GetFingerprintsForLabelSet(labelSet) + fingerprints, err := p.GetFingerprintsForLabelMatchers(labelMatchersFromLabelSet(labelSet)) if err != nil { t.Errorf("could not get labels: %s", err) } diff --git a/storage/metric/stochastic_test.go b/storage/metric/stochastic_test.go index b932c5ad36..028fe83fe4 100644 --- a/storage/metric/stochastic_test.go +++ b/storage/metric/stochastic_test.go @@ -40,14 +40,11 @@ func BasicLifecycleTests(p MetricPersistence, t test.Tester) { func ReadEmptyTests(p MetricPersistence, t test.Tester) { hasLabelPair := func(x int) (success bool) { - name := clientmodel.LabelName(string(x)) - value := clientmodel.LabelValue(string(x)) - - labelSet := clientmodel.LabelSet{ - name: value, - } - - fingerprints, err := p.GetFingerprintsForLabelSet(labelSet) + fingerprints, err := p.GetFingerprintsForLabelMatchers(LabelMatchers{{ + Type: Equal, + Name: clientmodel.LabelName(string(x)), + Value: clientmodel.LabelValue(string(x)), + }}) if err != nil { t.Error(err) return @@ -150,9 +147,11 @@ func AppendSampleAsSparseAppendWithReadsTests(p MetricPersistence, t test.Tester return } - fingerprints, err := p.GetFingerprintsForLabelSet(clientmodel.LabelSet{ - labelName: labelValue, - }) + fingerprints, err := p.GetFingerprintsForLabelMatchers(LabelMatchers{{ + Type: Equal, + Name: labelName, + Value: labelValue, + }}) if err != nil { t.Error(err) return @@ -329,11 +328,13 @@ func StochasticTests(persistenceMaker func() (MetricPersistence, test.Closer), t metricNewestSample[metricIndex] = newestSample for sharedLabelIndex := 0; sharedLabelIndex < numberOfSharedLabels; sharedLabelIndex++ { - labelPair := clientmodel.LabelSet{ - clientmodel.LabelName(fmt.Sprintf("shared_label_%d", sharedLabelIndex)): clientmodel.LabelValue(fmt.Sprintf("label_%d", sharedLabelIndex)), - } + matchers := LabelMatchers{{ + Type: Equal, + Name: clientmodel.LabelName(fmt.Sprintf("shared_label_%d", sharedLabelIndex)), + Value: clientmodel.LabelValue(fmt.Sprintf("label_%d", sharedLabelIndex)), + }} - fingerprints, err := p.GetFingerprintsForLabelSet(labelPair) + fingerprints, err := p.GetFingerprintsForLabelMatchers(matchers) if err != nil { t.Error(err) return @@ -349,11 +350,13 @@ func StochasticTests(persistenceMaker func() (MetricPersistence, test.Closer), t for unsharedLabelIndex := 0; unsharedLabelIndex < numberOfUnsharedLabels; unsharedLabelIndex++ { labelName := clientmodel.LabelName(fmt.Sprintf("metric_index_%d_private_label_%d", metricIndex, unsharedLabelIndex)) labelValue := clientmodel.LabelValue(fmt.Sprintf("private_label_%d", unsharedLabelIndex)) - labelSet := clientmodel.LabelSet{ - labelName: labelValue, - } + matchers := LabelMatchers{{ + Type: Equal, + Name: labelName, + Value: labelValue, + }} - fingerprints, err := p.GetFingerprintsForLabelSet(labelSet) + fingerprints, err := p.GetFingerprintsForLabelMatchers(matchers) if err != nil { t.Error(err) return diff --git a/storage/metric/tiered.go b/storage/metric/tiered.go index a8e03acc2b..f3dec377e6 100644 --- a/storage/metric/tiered.go +++ b/storage/metric/tiered.go @@ -673,9 +673,9 @@ func (t *TieredStorage) GetAllValuesForLabel(labelName clientmodel.LabelName) (c return values, nil } -// GetFingerprintsForLabelSet gets all of the metric fingerprints that are -// associated with the provided label set. -func (t *TieredStorage) GetFingerprintsForLabelSet(labelSet clientmodel.LabelSet) (clientmodel.Fingerprints, error) { +// GetFingerprintsForLabelMatchers gets all of the metric fingerprints that are +// associated with the provided label matchers. +func (t *TieredStorage) GetFingerprintsForLabelMatchers(matchers LabelMatchers) (clientmodel.Fingerprints, error) { t.mu.RLock() defer t.mu.RUnlock() @@ -683,11 +683,11 @@ func (t *TieredStorage) GetFingerprintsForLabelSet(labelSet clientmodel.LabelSet panic("Illegal State: Attempted to query non-running TieredStorage.") } - memFingerprints, err := t.memoryArena.GetFingerprintsForLabelSet(labelSet) + memFingerprints, err := t.memoryArena.GetFingerprintsForLabelMatchers(matchers) if err != nil { return nil, err } - diskFingerprints, err := t.DiskStorage.GetFingerprintsForLabelSet(labelSet) + diskFingerprints, err := t.DiskStorage.GetFingerprintsForLabelMatchers(matchers) if err != nil { return nil, err } diff --git a/storage/metric/tiered_test.go b/storage/metric/tiered_test.go index 75ea8a3e08..66a5f32649 100644 --- a/storage/metric/tiered_test.go +++ b/storage/metric/tiered_test.go @@ -803,7 +803,7 @@ func TestGetAllValuesForLabel(t *testing.T) { } } -func TestGetFingerprintsForLabelSet(t *testing.T) { +func TestGetFingerprintsForLabelMatchers(t *testing.T) { tiered, closer := NewTestTieredStorage(t) defer closer.Close() memorySample := &clientmodel.Sample{ @@ -821,40 +821,65 @@ func TestGetFingerprintsForLabelSet(t *testing.T) { tiered.Flush() scenarios := []struct { - labels clientmodel.LabelSet - fpCount int + matchers LabelMatchers + fpCount int }{ { - labels: clientmodel.LabelSet{}, - fpCount: 0, + matchers: LabelMatchers{}, + fpCount: 0, }, { - labels: clientmodel.LabelSet{ - clientmodel.MetricNameLabel: "http_requests", + matchers: LabelMatchers{ + { + Type: Equal, + Name: clientmodel.MetricNameLabel, + Value: "http_requests", + }, }, fpCount: 2, }, { - labels: clientmodel.LabelSet{ - clientmodel.MetricNameLabel: "http_requests", - "method": "/foo", + matchers: LabelMatchers{ + { + Type: Equal, + Name: clientmodel.MetricNameLabel, + Value: "http_requests", + }, { + Type: Equal, + Name: "method", + Value: "/foo", + }, }, fpCount: 1, }, { - labels: clientmodel.LabelSet{ - clientmodel.MetricNameLabel: "http_requests", - "method": "/bar", + matchers: LabelMatchers{ + { + Type: Equal, + Name: clientmodel.MetricNameLabel, + Value: "http_requests", + }, { + Type: Equal, + Name: "method", + Value: "/bar", + }, }, fpCount: 1, }, { - labels: clientmodel.LabelSet{ - clientmodel.MetricNameLabel: "http_requests", - "method": "/baz", + matchers: LabelMatchers{ + { + Type: Equal, + Name: clientmodel.MetricNameLabel, + Value: "http_requests", + }, { + Type: Equal, + Name: "method", + Value: "/baz", + }, }, fpCount: 0, }, } for i, scenario := range scenarios { - fingerprints, err := tiered.GetFingerprintsForLabelSet(scenario.labels) + fingerprints, err := tiered.GetFingerprintsForLabelMatchers(scenario.matchers) if err != nil { t.Fatalf("%d. Error getting metric names: %s", i, err) } diff --git a/utility/set.go b/utility/set.go index fee3b37cc7..eae156891b 100644 --- a/utility/set.go +++ b/utility/set.go @@ -13,16 +13,20 @@ package utility -type Set map[interface{}]bool +// Set is a type which models a set. +type Set map[interface{}]struct{} +// Add adds an item to the set. func (s Set) Add(v interface{}) { - s[v] = true + s[v] = struct{}{} } +// Remove removes an item from the set. func (s Set) Remove(v interface{}) { delete(s, v) } +// Elements returns a slice containing all elements in the set. func (s Set) Elements() []interface{} { result := make([]interface{}, 0, len(s)) @@ -33,20 +37,36 @@ func (s Set) Elements() []interface{} { return result } +// Has returns true if an element is contained in the set. func (s Set) Has(v interface{}) bool { _, p := s[v] return p } +// Intersection returns a new set with items that exist in both sets. func (s Set) Intersection(o Set) Set { - result := make(Set) + result := Set{} for k := range s { if o.Has(k) { - result[k] = true + result[k] = struct{}{} } } return result } + +// Union returns a new set with all items in both sets. +func (s Set) Union(o Set) Set { + result := Set{} + + for k := range s { + result[k] = struct{}{} + } + for k := range o { + result[k] = struct{}{} + } + + return result +}