Skip to content

Commit 90db95b

Browse files
authored
fix vector matching (#155)
Signed-off-by: yeya24 <[email protected]>
1 parent 8bc5b55 commit 90db95b

File tree

2 files changed

+118
-30
lines changed

2 files changed

+118
-30
lines changed

walk.go

Lines changed: 116 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -108,20 +108,14 @@ func (s *PromQLSmith) walkBinaryExpr(depth int, valueTypes ...parser.ValueType)
108108
valueTypes = []parser.ValueType{parser.ValueTypeVector}
109109
expr.VectorMatching.Card = parser.CardManyToMany
110110
}
111-
expr.LHS = wrapParenExpr(s.walk(depth-1, valueTypes...))
112-
expr.RHS = wrapParenExpr(s.walk(depth-1, valueTypes...))
113-
lvt := expr.LHS.Type()
114-
rvt := expr.RHS.Type()
115-
// ReturnBool can only be set for comparison operator. It is
116-
// required to set to true if both expressions are scalar type.
117-
if expr.Op.IsComparisonOperator() {
118-
if lvt == parser.ValueTypeScalar && rvt == parser.ValueTypeScalar || s.rnd.Intn(2) == 0 {
119-
expr.ReturnBool = true
120-
}
121-
}
122111

123-
if !expr.Op.IsSetOperator() && s.enableVectorMatching && lvt == parser.ValueTypeVector &&
124-
rvt == parser.ValueTypeVector && s.rnd.Intn(2) == 0 {
112+
// Generate vector matching only if we know it asks for vector value type.
113+
if !expr.Op.IsSetOperator() && len(valueTypes) == 1 && valueTypes[0] == parser.ValueTypeVector && s.enableVectorMatching && s.rnd.Float64() > 0.8 {
114+
lhs, _ := s.walkExpr(VectorSelector, depth-1, valueTypes...)
115+
expr.LHS = wrapParenExpr(lhs)
116+
rhs, _ := s.walkExpr(VectorSelector, depth-1, valueTypes...)
117+
expr.RHS = wrapParenExpr(rhs)
118+
125119
leftSeriesSet, stop := getOutputSeries(expr.LHS)
126120
if stop {
127121
return expr
@@ -130,12 +124,25 @@ func (s *PromQLSmith) walkBinaryExpr(depth int, valueTypes ...parser.ValueType)
130124
if stop {
131125
return expr
132126
}
133-
s.walkVectorMatching(expr, leftSeriesSet, rightSeriesSet, s.rnd.Intn(4) == 0)
127+
s.walkVectorMatching(expr, leftSeriesSet, rightSeriesSet, s.rnd.Intn(2) == 0, s.rnd.Intn(4) == 0)
128+
} else {
129+
expr.LHS = wrapParenExpr(s.walk(depth-1, valueTypes...))
130+
expr.RHS = wrapParenExpr(s.walk(depth-1, valueTypes...))
131+
}
132+
133+
lvt := expr.LHS.Type()
134+
rvt := expr.RHS.Type()
135+
// ReturnBool can only be set for comparison operator. It is
136+
// required to set to true if both expressions are scalar type.
137+
if expr.Op.IsComparisonOperator() {
138+
if lvt == parser.ValueTypeScalar && rvt == parser.ValueTypeScalar || s.rnd.Intn(2) == 0 {
139+
expr.ReturnBool = true
140+
}
134141
}
135142
return expr
136143
}
137144

138-
func (s *PromQLSmith) walkVectorMatching(expr *parser.BinaryExpr, seriesSetA []labels.Labels, seriesSetB []labels.Labels, includeLabels bool) {
145+
func (s *PromQLSmith) walkVectorMatching(expr *parser.BinaryExpr, seriesSetA []labels.Labels, seriesSetB []labels.Labels, on, includeLabels bool) {
139146
sa := make(map[string]struct{})
140147
for _, series := range seriesSetA {
141148
series.Range(func(lbl labels.Label) {
@@ -155,48 +162,129 @@ func (s *PromQLSmith) walkVectorMatching(expr *parser.BinaryExpr, seriesSetA []l
155162
sb[lbl.Name] = struct{}{}
156163
})
157164
}
158-
expr.VectorMatching.On = true
159-
matchedLabels := make([]string, 0)
165+
166+
// Find all matching labels
167+
allMatchedLabels := make([]string, 0)
160168
for key := range sb {
161169
if _, ok := sa[key]; ok {
162-
matchedLabels = append(matchedLabels, key)
170+
allMatchedLabels = append(allMatchedLabels, key)
163171
}
164172
}
173+
// If there is no matching labels, we don't need to do vector matching.
174+
if len(allMatchedLabels) == 0 {
175+
return
176+
}
177+
178+
// Randomly select a subset of matched labels
179+
sort.Strings(allMatchedLabels) // Sort for deterministic selection
180+
numLabels := s.rnd.Intn(len(allMatchedLabels)) + 1 // Select at least 1 label
181+
selectedIndices := s.rnd.Perm(len(allMatchedLabels))[:numLabels]
182+
sort.Ints(selectedIndices) // Sort indices for consistent order
183+
184+
matchedLabels := make([]string, numLabels)
185+
for i, idx := range selectedIndices {
186+
matchedLabels[i] = allMatchedLabels[idx]
187+
}
188+
189+
expr.VectorMatching.On = on
190+
165191
// We are doing a very naive approach of guessing side cardinalities
166192
// by checking number of series each side.
167193
oneSideLabelsSet := sa
168-
if len(seriesSetA) > len(seriesSetB) {
194+
if expr.VectorMatching.On {
169195
expr.VectorMatching.MatchingLabels = matchedLabels
196+
} else {
197+
// For 'ignoring', we need to use all labels except the matched ones
198+
expr.VectorMatching.MatchingLabels = getDifference(getAllLabels(sa), matchedLabels)
199+
}
200+
201+
if len(seriesSetA) > len(seriesSetB) {
170202
expr.VectorMatching.Card = parser.CardManyToOne
171203
oneSideLabelsSet = sb
172204
} else if len(seriesSetA) < len(seriesSetB) {
173-
expr.VectorMatching.MatchingLabels = matchedLabels
174205
expr.VectorMatching.Card = parser.CardOneToMany
175206
}
207+
176208
// Otherwise we do 1:1 match.
177209

178-
// For simplicity, we always include all labels on the one side.
179210
if expr.VectorMatching.Card != parser.CardOneToOne && includeLabels {
180-
includeLabels := getIncludeLabels(oneSideLabelsSet, matchedLabels)
211+
includeLabels := getRandomIncludeLabels(s.rnd, oneSideLabelsSet, expr.VectorMatching.MatchingLabels)
181212
expr.VectorMatching.Include = includeLabels
182213
}
183214
}
184215

216+
// Helper function to get all labels from a map
217+
func getAllLabels(labelSet map[string]struct{}) []string {
218+
labels := make([]string, 0, len(labelSet))
219+
for label := range labelSet {
220+
labels = append(labels, label)
221+
}
222+
sort.Strings(labels)
223+
return labels
224+
}
225+
226+
// Helper function to get the difference between two sorted string slices
227+
func getDifference(all, exclude []string) []string {
228+
result := make([]string, 0)
229+
excludeMap := make(map[string]struct{})
230+
for _, e := range exclude {
231+
excludeMap[e] = struct{}{}
232+
}
233+
234+
for _, label := range all {
235+
if _, exists := excludeMap[label]; !exists {
236+
result = append(result, label)
237+
}
238+
}
239+
return result
240+
}
241+
242+
// Helper function to get all eligible labels that aren't in the matched set
185243
func getIncludeLabels(labelNameSet map[string]struct{}, matchedLabels []string) []string {
244+
// Create a map of matched labels for quick lookup
245+
matchedSet := make(map[string]struct{})
246+
for _, label := range matchedLabels {
247+
matchedSet[label] = struct{}{}
248+
}
249+
250+
// Collect all eligible labels that aren't in the matched set
186251
output := make([]string, 0)
187-
OUTER:
188252
for lbl := range labelNameSet {
189-
for _, matchedLabel := range matchedLabels {
190-
if lbl == matchedLabel {
191-
continue OUTER
192-
}
253+
if _, matched := matchedSet[lbl]; !matched {
254+
output = append(output, lbl)
193255
}
194-
output = append(output, lbl)
195256
}
257+
258+
// Sort for deterministic output
196259
sort.Strings(output)
197260
return output
198261
}
199262

263+
// Helper function to randomly select a subset of include labels
264+
func getRandomIncludeLabels(rnd *rand.Rand, labelNameSet map[string]struct{}, matchedLabels []string) []string {
265+
eligible := getIncludeLabels(labelNameSet, matchedLabels)
266+
if len(eligible) == 0 {
267+
return nil
268+
}
269+
270+
// Pick a random number of labels to include (at least 1 if available)
271+
numLabels := rnd.Intn(len(eligible)) + 1
272+
if numLabels > len(eligible) {
273+
numLabels = len(eligible)
274+
}
275+
276+
// Randomly select the labels
277+
indices := rnd.Perm(len(eligible))[:numLabels]
278+
sort.Ints(indices)
279+
280+
// Create the final selection
281+
result := make([]string, numLabels)
282+
for i, idx := range indices {
283+
result[i] = eligible[idx]
284+
}
285+
return result
286+
}
287+
200288
// Walk binary op based on whether vector value type is allowed or not.
201289
// Since Set operator only works with vector so if vector is disallowed
202290
// we will choose comparison operator that works both for scalar and vector.

walk_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,15 +175,15 @@ func TestWalkVectorMatching(t *testing.T) {
175175
require.False(t, stop)
176176
right, stop := getOutputSeries(rhs)
177177
require.False(t, stop)
178-
p.walkVectorMatching(binExpr, left, right, true)
178+
p.walkVectorMatching(binExpr, left, right, true, true)
179179
result := binExpr.Pretty(0)
180180
_, err := parser.ParseExpr(result)
181181
require.NoError(t, err)
182182
expected := &parser.VectorMatching{
183183
Card: parser.CardManyToOne,
184184
On: true,
185185
MatchingLabels: []string{"method"},
186-
Include: []string{},
186+
Include: []string(nil),
187187
}
188188
require.Equal(t, expected, binExpr.VectorMatching)
189189
}

0 commit comments

Comments
 (0)