diff --git a/CHANGELOG.md b/CHANGELOG.md index 5196871581..b11153a3cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,7 +33,7 @@ Main (unreleased) - Add relevant golang environment variables to the support bundle (@dehaansa) ### Bugfixes - +- Fixed an issue in the `pyroscope.write` component to allow slashes in service names in the same way it is done in the Pyroscope push API (@marcsanmi) - Fixed an issue in the `prometheus.exporter.postgres` component that would leak goroutines when the target was not reachable (@dehaansa) - Fixed an issue in the `otelcol.exporter.prometheus` component that would set series value incorrectly for stale metrics (@YusifAghalar) diff --git a/internal/component/pyroscope/write/parser.go b/internal/component/pyroscope/write/parser.go index 385a241862..1558305bc5 100644 --- a/internal/component/pyroscope/write/parser.go +++ b/internal/component/pyroscope/write/parser.go @@ -205,7 +205,7 @@ func isTagKeyReserved(k string) bool { } func isTagKeyRuneAllowed(r rune) bool { - return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' || r == '.' + return (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' || r == '.' || r == '/' } func validateTagKey(k string) error { diff --git a/internal/component/pyroscope/write/parser_test.go b/internal/component/pyroscope/write/parser_test.go new file mode 100644 index 0000000000..66cd6e9cd0 --- /dev/null +++ b/internal/component/pyroscope/write/parser_test.go @@ -0,0 +1,150 @@ +package write + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseKey(t *testing.T) { + tests := []struct { + name string + input string + expected *Key + wantErr bool + }{ + { + name: "basic app name", + input: "simple-app", + expected: &Key{ + labels: map[string]string{ + "__name__": "simple-app", + }, + }, + }, + { + name: "app name with slashes and tags", + input: "my/service/name{environment=prod,version=1.0}", + expected: &Key{ + labels: map[string]string{ + "__name__": "my/service/name", + "environment": "prod", + "version": "1.0", + }, + }, + }, + { + name: "multiple slashes and special characters", + input: "app/service/v1.0-beta/component{region=us-west}", + expected: &Key{ + labels: map[string]string{ + "__name__": "app/service/v1.0-beta/component", + "region": "us-west", + }, + }, + }, + { + name: "empty app name", + input: "{}", + wantErr: true, + }, + { + name: "invalid characters in tag key", + input: "my/service/name{invalid@key=value}", + wantErr: true, + }, + { + name: "whitespace handling", + input: "my/service/name{ tag1 = value1 , tag2 = value2 }", + expected: &Key{ + labels: map[string]string{ + "__name__": "my/service/name", + "tag1": "value1", + "tag2": "value2", + }, + }, + }, + { + name: "dots in service name", + input: "my/service.name/v1.0{environment=prod}", + expected: &Key{ + labels: map[string]string{ + "__name__": "my/service.name/v1.0", + "environment": "prod", + }, + }, + }, + { + name: "app name with slashes", + input: "my/service/name{}", + expected: &Key{ + labels: map[string]string{ + "__name__": "my/service/name", + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ParseKey(tt.input) + + if tt.wantErr { + assert.Error(t, err) + return + } + + require.NoError(t, err) + assert.Equal(t, tt.expected, got) + }) + } +} + +func TestKey_Normalized(t *testing.T) { + tests := []struct { + name string + key *Key + expected string + }{ + { + name: "simple normalization", + key: &Key{ + labels: map[string]string{ + "__name__": "my/service/name", + }, + }, + expected: "my/service/name{}", + }, + { + name: "normalization with tags", + key: &Key{ + labels: map[string]string{ + "__name__": "my/service/name", + "environment": "prod", + "version": "1.0", + }, + }, + expected: "my/service/name{environment=prod,version=1.0}", + }, + { + name: "tags should be sorted", + key: &Key{ + labels: map[string]string{ + "__name__": "my/service/name", + "c": "3", + "b": "2", + "a": "1", + }, + }, + expected: "my/service/name{a=1,b=2,c=3}", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.key.Normalized() + assert.Equal(t, tt.expected, got) + }) + } +}