Skip to content

Commit b2e30f0

Browse files
committed
Support unnamed groups in carbon receiver regex parser
1 parent 6dc691e commit b2e30f0

File tree

3 files changed

+161
-4
lines changed

3 files changed

+161
-4
lines changed

receiver/carbonreceiver/protocol/regex_parser.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,19 @@ func (rpp *regexPathParser) ParsePath(path string, parsedPath *ParsedPath) error
168168
attributes := pcommon.NewMap()
169169

170170
for i := 1; i < len(ms); i++ {
171-
if strings.HasPrefix(nms[i], metricNameCapturePrefix) {
172-
metricNameLookup[nms[i]] = ms[i]
171+
groupName, groupValue := nms[i], ms[i]
172+
if groupName == "" {
173+
// Skip unnamed groups.
174+
continue
175+
}
176+
if groupValue == "" {
177+
// Skip unmatched groups.
178+
continue
179+
}
180+
if strings.HasPrefix(groupName, metricNameCapturePrefix) {
181+
metricNameLookup[groupName] = groupValue
173182
} else {
174-
attributes.PutStr(nms[i][len(keyCapturePrefix):], ms[i])
183+
attributes.PutStr(groupName[len(keyCapturePrefix):], groupValue)
175184
}
176185
}
177186

receiver/carbonreceiver/protocol/regex_parser_test.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,150 @@ func Test_regexParser_parsePath(t *testing.T) {
176176
}
177177
}
178178

179+
func Test_regexParser_parsePath_simple_unnamed_group(t *testing.T) {
180+
config := RegexParserConfig{
181+
Rules: []*RegexRule{
182+
183+
{
184+
Regexp: `(prefix\.)?(?P<key_svc>[^.]+)\.(?P<key_host>[^.]+)\.cpu\.seconds`,
185+
NamePrefix: "cpu_seconds",
186+
},
187+
},
188+
}
189+
190+
require.NoError(t, compileRegexRules(config.Rules))
191+
rp := &regexPathParser{
192+
rules: config.Rules,
193+
}
194+
195+
tests := []struct {
196+
name string
197+
path string
198+
wantName string
199+
wantAttributes pcommon.Map
200+
wantMetricType TargetMetricType
201+
wantErr bool
202+
}{
203+
{
204+
name: "no_rule_match",
205+
path: "service_name.host01.rpc.duration.seconds",
206+
wantName: "service_name.host01.rpc.duration.seconds",
207+
wantAttributes: pcommon.NewMap(),
208+
},
209+
{
210+
name: "match_no_prefix",
211+
path: "service_name.host00.cpu.seconds",
212+
wantName: "cpu_seconds",
213+
wantAttributes: func() pcommon.Map {
214+
m := pcommon.NewMap()
215+
m.PutStr("svc", "service_name")
216+
m.PutStr("host", "host00")
217+
return m
218+
}(),
219+
},
220+
{
221+
name: "match_optional_prefix",
222+
path: "prefix.service_name.host00.cpu.seconds",
223+
wantName: "cpu_seconds",
224+
wantAttributes: func() pcommon.Map {
225+
m := pcommon.NewMap()
226+
m.PutStr("svc", "service_name")
227+
m.PutStr("host", "host00")
228+
return m
229+
}(),
230+
},
231+
}
232+
233+
for _, tt := range tests {
234+
t.Run(tt.name, func(t *testing.T) {
235+
got := ParsedPath{}
236+
err := rp.ParsePath(tt.path, &got)
237+
if tt.wantErr {
238+
assert.Error(t, err)
239+
return
240+
}
241+
242+
assert.Equal(t, tt.wantName, got.MetricName)
243+
assert.Equal(t, tt.wantAttributes, got.Attributes)
244+
assert.Equal(t, tt.wantMetricType, got.MetricType)
245+
})
246+
}
247+
}
248+
249+
func Test_regexParser_parsePath_key_inside_unnamed_group(t *testing.T) {
250+
config := RegexParserConfig{
251+
Rules: []*RegexRule{
252+
253+
{
254+
Regexp: `(job=(?P<key_job>[^.]+).)?(?P<key_svc>[^.]+)\.(?P<key_host>[^.]+)\.cpu\.seconds`,
255+
NamePrefix: "cpu_seconds",
256+
MetricType: string(GaugeMetricType),
257+
},
258+
},
259+
}
260+
261+
require.NoError(t, compileRegexRules(config.Rules))
262+
rp := &regexPathParser{
263+
rules: config.Rules,
264+
}
265+
266+
tests := []struct {
267+
name string
268+
path string
269+
wantName string
270+
wantAttributes pcommon.Map
271+
wantMetricType TargetMetricType
272+
wantErr bool
273+
}{
274+
{
275+
name: "no_rule_match",
276+
path: "service_name.host01.rpc.duration.seconds",
277+
wantName: "service_name.host01.rpc.duration.seconds",
278+
wantAttributes: pcommon.NewMap(),
279+
},
280+
{
281+
name: "match_missing_optional_key",
282+
path: "service_name.host00.cpu.seconds",
283+
wantName: "cpu_seconds",
284+
wantAttributes: func() pcommon.Map {
285+
m := pcommon.NewMap()
286+
m.PutStr("svc", "service_name")
287+
m.PutStr("host", "host00")
288+
return m
289+
}(),
290+
wantMetricType: GaugeMetricType,
291+
},
292+
{
293+
name: "match_present_optional_key",
294+
path: "job=71972c09-de94-4a4e-a8a7-ad3de050a141.service_name.host00.cpu.seconds",
295+
wantName: "cpu_seconds",
296+
wantAttributes: func() pcommon.Map {
297+
m := pcommon.NewMap()
298+
m.PutStr("job", "71972c09-de94-4a4e-a8a7-ad3de050a141")
299+
m.PutStr("svc", "service_name")
300+
m.PutStr("host", "host00")
301+
return m
302+
}(),
303+
wantMetricType: GaugeMetricType,
304+
},
305+
}
306+
307+
for _, tt := range tests {
308+
t.Run(tt.name, func(t *testing.T) {
309+
got := ParsedPath{}
310+
err := rp.ParsePath(tt.path, &got)
311+
if tt.wantErr {
312+
assert.Error(t, err)
313+
return
314+
}
315+
316+
assert.Equal(t, tt.wantName, got.MetricName)
317+
assert.Equal(t, tt.wantAttributes, got.Attributes)
318+
assert.Equal(t, tt.wantMetricType, got.MetricType)
319+
})
320+
}
321+
}
322+
179323
var res struct {
180324
name string
181325
attributes pcommon.Map

receiver/carbonreceiver/testdata/config.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,11 @@ carbon/regex:
4848
# type is used to select the metric type to be set, the default is
4949
# "gauge", the other alternative is "cumulative".
5050
type: cumulative
51-
# The second rule for this "regex" parser.
51+
# The second rule matches metric with or without a prefix.
52+
- regexp: "(optional_prefix\\.)?(?P<key_just>test)\\.(?P<key_match>.*)"
53+
# The third rule parses an optional dimension with key "experiment".
54+
- regexp: "(experiment(?P<key_experiment>[0-9]+)\\.)?(?P<key_just>test)\\.(?P<key_match>.*)"
55+
# The forth rule for this "regex" parser.
5256
- regexp: "(?P<key_just>test)\\.(?P<key_match>.*)"
5357
# Name separator is used when concatenating named regular expression
5458
# captures prefixed with "name_"

0 commit comments

Comments
 (0)