Skip to content
This repository was archived by the owner on Aug 23, 2023. It is now read-only.

Commit 514301f

Browse files
authored
Merge pull request #1423 from grafana/implement_series_lookup_by_meta_tag
Implement series lookup and filtering by meta tag
2 parents d1c1cd8 + ea822b5 commit 514301f

24 files changed

+1980
-278
lines changed

api/graphite.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -1333,7 +1333,7 @@ func (s *Server) getMetaTagRecords(ctx *middleware.Context) {
13331333
}
13341334

13351335
func (s *Server) metaTagRecordUpsert(ctx *middleware.Context, upsertRequest models.MetaTagRecordUpsert) {
1336-
record, err := tagquery.ParseMetaTagRecord(upsertRequest.MetaTags, upsertRequest.Queries)
1336+
record, err := tagquery.ParseMetaTagRecord(upsertRequest.MetaTags, upsertRequest.Expressions)
13371337
if err != nil {
13381338
response.Write(ctx, response.WrapError(err))
13391339
return
@@ -1372,7 +1372,7 @@ func (s *Server) metaTagRecordUpsert(ctx *middleware.Context, upsertRequest mode
13721372
indexUpsertRequest := models.IndexMetaTagRecordUpsert{
13731373
OrgId: ctx.OrgId,
13741374
MetaTags: upsertRequest.MetaTags,
1375-
Queries: upsertRequest.Queries,
1375+
Queries: upsertRequest.Expressions,
13761376
}
13771377

13781378
results, errors := s.peerQuery(ctx.Req.Context(), indexUpsertRequest, "metaTagRecordUpsert", "/index/metaTags/upsert")

api/models/meta_records.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@ import (
88
)
99

1010
type MetaTagRecordUpsert struct {
11-
MetaTags []string `json:"metaTags" binding:"Required"`
12-
Queries []string `json:"queries" binding:"Required"`
13-
Propagate bool `json:"propagate"`
11+
MetaTags []string `json:"metaTags" binding:"Required"`
12+
Expressions []string `json:"expressions" binding:"Required"`
13+
Propagate bool `json:"propagate"`
1414
}
1515

1616
func (m MetaTagRecordUpsert) Trace(span opentracing.Span) {
1717
span.LogFields(
1818
traceLog.String("metaTags", fmt.Sprintf("%q", m.MetaTags)),
19-
traceLog.String("queries", fmt.Sprintf("%q", m.Queries)),
19+
traceLog.String("expressions", fmt.Sprintf("%q", m.Expressions)),
2020
traceLog.Bool("propagate", m.Propagate),
2121
)
2222
}

api/models/meta_records_gen.go

+31-31
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

expr/tagquery/expression.go

+15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package tagquery
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"regexp"
67
"sort"
@@ -52,6 +53,12 @@ func (e Expressions) Sort() {
5253
})
5354
}
5455

56+
// MarshalJSON satisfies the json.Marshaler interface
57+
// it is used by the api endpoint /metaTags to list the meta tag records
58+
func (e Expressions) MarshalJSON() ([]byte, error) {
59+
return json.Marshal(e.Strings())
60+
}
61+
5562
// Expression represents one expression inside a query of one or many expressions.
5663
// It provides all the necessary methods that are required to do a tag lookup from an index keyed by
5764
// tags & values, such as the type memory.TagIndex or the type memory.metaTagIndex.
@@ -134,6 +141,14 @@ type Expression interface {
134141
// Every valid query must have at least one expression requiring a non-empty value.
135142
RequiresNonEmptyValue() bool
136143

144+
// ResultIsSmallerWhenInverted returns a bool indicating whether the result set after evaluating
145+
// this expression will likely be bigger than half of the tested index entries or smaller.
146+
// This is never guaranteed to actually be correct, it is only an assumption based on which we
147+
// can optimize performance.
148+
// F.e. operators = / =~ / __tag= would return false
149+
// operators != / !=~ would return true
150+
ResultIsSmallerWhenInverted() bool
151+
137152
// Matches takes a string which should either be a tag key or value depending on the return
138153
// value of OperatesOnTag(), then it returns whether the given string satisfies this expression
139154
Matches(string) bool

expr/tagquery/expression_common.go

+5
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ func (e *expressionCommon) RequiresNonEmptyValue() bool {
2525
return true
2626
}
2727

28+
func (e *expressionCommon) ResultIsSmallerWhenInverted() bool {
29+
// by default assume false, unless a concrete type overrides this method
30+
return false
31+
}
32+
2833
func (e *expressionCommon) MatchesExactly() bool {
2934
return false
3035
}

expr/tagquery/expression_match_all.go

+4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ func (e *expressionMatchAll) RequiresNonEmptyValue() bool {
4040
return false
4141
}
4242

43+
func (e *expressionMatchAll) ResultIsSmallerWhenInverted() bool {
44+
return true
45+
}
46+
4347
func (e *expressionMatchAll) Matches(value string) bool {
4448
return true
4549
}

expr/tagquery/expression_not_equal.go

+4
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ func (e *expressionNotEqual) RequiresNonEmptyValue() bool {
3030
return false
3131
}
3232

33+
func (e *expressionNotEqual) ResultIsSmallerWhenInverted() bool {
34+
return true
35+
}
36+
3337
func (e *expressionNotEqual) Matches(value string) bool {
3438
return value != e.value
3539
}

expr/tagquery/expression_not_has_tag.go

+4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ func (e *expressionNotHasTag) RequiresNonEmptyValue() bool {
3434
return false
3535
}
3636

37+
func (e *expressionNotHasTag) ResultIsSmallerWhenInverted() bool {
38+
return true
39+
}
40+
3741
func (e *expressionNotHasTag) Matches(value string) bool {
3842
return value != e.key
3943
}

expr/tagquery/expression_not_match.go

+4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ func (e *expressionNotMatch) RequiresNonEmptyValue() bool {
4141
return e.matchesEmpty
4242
}
4343

44+
func (e *expressionNotMatch) ResultIsSmallerWhenInverted() bool {
45+
return true
46+
}
47+
4448
func (e *expressionNotMatch) Matches(value string) bool {
4549
return !e.valueRe.MatchString(value)
4650
}

expr/tagquery/meta_tag_record.go

+42-2
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package tagquery
33
import (
44
"fmt"
55
"strings"
6+
7+
"github.com/raintank/schema"
68
)
79

810
type MetaTagRecord struct {
9-
MetaTags Tags
10-
Expressions Expressions
11+
MetaTags Tags `json:"metaTags"`
12+
Expressions Expressions `json:"expressions"`
1113
}
1214

1315
func ParseMetaTagRecord(metaTags []string, expressions []string) (MetaTagRecord, error) {
@@ -24,6 +26,15 @@ func ParseMetaTagRecord(metaTags []string, expressions []string) (MetaTagRecord,
2426
return res, err
2527
}
2628

29+
// we don't actually need to instantiate a query at this point, but we want to verify
30+
// that it is possible to instantiate a query from the given meta record expressions.
31+
// if we can't instantiate a query from the given expressions, then the meta record
32+
// upsert request should be considered invalid and should get rejected.
33+
_, err = NewQuery(res.Expressions, 0)
34+
if err != nil {
35+
return res, fmt.Errorf("Failed to instantiate query from given expressions: %s", err)
36+
}
37+
2738
if len(res.Expressions) == 0 {
2839
return res, fmt.Errorf("Meta Tag Record must have at least one query")
2940
}
@@ -87,3 +98,32 @@ func (m *MetaTagRecord) EqualExpressions(other *MetaTagRecord) bool {
8798
func (m *MetaTagRecord) HasMetaTags() bool {
8899
return len(m.MetaTags) > 0
89100
}
101+
102+
func (m *MetaTagRecord) GetMetricDefinitionFilter(lookup IdTagLookup) MetricDefinitionFilter {
103+
filters := make([]MetricDefinitionFilter, len(m.Expressions))
104+
defaultDecisions := make([]FilterDecision, len(m.Expressions))
105+
for i, expr := range m.Expressions {
106+
filters[i] = expr.GetMetricDefinitionFilter(lookup)
107+
defaultDecisions[i] = expr.GetDefaultDecision()
108+
}
109+
110+
return func(id schema.MKey, name string, tags []string) FilterDecision {
111+
for i := range filters {
112+
decision := filters[i](id, name, tags)
113+
114+
if decision == None {
115+
return defaultDecisions[i]
116+
}
117+
118+
if decision == Pass {
119+
continue
120+
}
121+
122+
if decision == Fail {
123+
return Fail
124+
}
125+
}
126+
127+
return Pass
128+
}
129+
}

expr/tagquery/meta_tag_record_test.go

+9
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,12 @@ func TestErrorOnParsingMetaTagRecordWithoutQueryy(t *testing.T) {
5858
t.Fatalf("Expected an error, but did not get one")
5959
}
6060
}
61+
62+
func TestErrorOnParsingMetaTagRecordWithValidExpressionsButInvalidQuery(t *testing.T) {
63+
// the given expression is valid, but as a query this set of expressions is not valid
64+
// because every query must have at least one expression requiring a non-empty value
65+
_, err := ParseMetaTagRecord([]string{"meta=tag"}, []string{"a!=b"})
66+
if err == nil {
67+
t.Fatalf("Expected an error, but did not get one")
68+
}
69+
}

expr/tagquery/tag.go

+7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package tagquery
22

33
import (
4+
"encoding/json"
45
"fmt"
56
"sort"
67
"strings"
@@ -69,6 +70,12 @@ func (t Tags) Strings() []string {
6970
return res
7071
}
7172

73+
// MarshalJSON satisfies the json.Marshaler interface
74+
// it is used by the api endpoint /metaTags to list the meta tag records
75+
func (t Tags) MarshalJSON() ([]byte, error) {
76+
return json.Marshal(t.Strings())
77+
}
78+
7279
type Tag struct {
7380
Key string
7481
Value string

0 commit comments

Comments
 (0)