Skip to content

Commit f1a626f

Browse files
authored
Feat: Auto-label profiles based on request baggage (#99)
1 parent ed0d159 commit f1a626f

File tree

9 files changed

+211
-9
lines changed

9 files changed

+211
-9
lines changed

Makefile

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
GO_VERSION_PRE20 := $(shell go version | awk '{print $$3}' | awk -F '.' '{print ($$1 == "go1" && int($$2) < 20)}')
2-
TEST_PACKAGES := ./... ./godeltaprof/compat/... ./godeltaprof/...
1+
GO_VERSION_PRE20 := $(shell go version | awk '{print $$3}' | awk -F '.' '{print ($$1 == "go1" && int($$2) < 20)}')
2+
TEST_PACKAGES := ./... ./godeltaprof/compat/... ./godeltaprof/... ./x/k6/...
33

44
.PHONY: test
55
test:
@@ -12,6 +12,7 @@ go/mod:
1212
GO111MODULE=on go mod tidy
1313
cd godeltaprof/compat/ && GO111MODULE=on go mod download
1414
cd godeltaprof/compat/ && GO111MODULE=on go mod tidy
15-
cd godeltaprof/ && GO111MODULE=on go mod download
15+
cd godeltaprof/ && GO111MODULE=on go mod download
1616
cd godeltaprof/ && GO111MODULE=on go mod tidy
17-
17+
cd x/k6/ && GO111MODULE=on go mod download
18+
cd x/k6/ && GO111MODULE=on go mod tidy

go.work

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ use (
44
.
55
godeltaprof
66
godeltaprof/compat
7+
x/k6
78
)

go.work.sum

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,32 @@ github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3I
66
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
77
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
88
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
9+
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
10+
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
11+
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
12+
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
913
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
1014
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
1115
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
1216
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
1317
github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk=
1418
github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY=
19+
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
20+
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
1521
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab h1:BA4a7pe6ZTd9F8kXETBoijjFJ/ntaa//1wiH9BZu4zU=
1622
github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
1723
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
1824
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
1925
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
2026
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
21-
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
22-
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
27+
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
28+
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
2329
github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=
2430
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
31+
go.opentelemetry.io/otel/metric v1.17.0 h1:iG6LGVz5Gh+IuO0jmgvpTB6YVrCGngi8QGm+pMd8Pdc=
32+
go.opentelemetry.io/otel/metric v1.17.0/go.mod h1:h4skoxdZI17AxwITdmdZjjYJQH5nzijUUjm+wtPph5o=
33+
go.opentelemetry.io/otel/trace v1.17.0 h1:/SWhSRHmDPOImIAetP1QAeMnZYiQXrTy4fMMYOdSKWQ=
34+
go.opentelemetry.io/otel/trace v1.17.0/go.mod h1:I/4vKTgFclIsXRVucpH25X0mpFSczM7aHeaz0ZBLWjY=
2535
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
2636
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
2737
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=

godeltaprof/compat/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ go 1.18
55
require (
66
github.com/google/pprof v0.0.0-20231127191134-f3a68a39ae15
77
github.com/grafana/pyroscope-go/godeltaprof v0.1.5
8-
github.com/stretchr/testify v1.8.4
8+
github.com/stretchr/testify v1.9.0
99
golang.org/x/tools v0.16.0
1010
)
1111

godeltaprof/compat/go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ github.com/grafana/pyroscope-go/godeltaprof v0.1.5 h1:gkFVqihFRL1Nro2FCC0u6mW47j
66
github.com/grafana/pyroscope-go/godeltaprof v0.1.5/go.mod h1:1HSPtjU8vLG0jE9JrTdzjgFqdJ/VgN7fvxBNq3luJko=
77
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
88
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
9-
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
10-
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
9+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
10+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
1111
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
1212
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
1313
golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=

x/k6/baggage.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package k6
2+
3+
import (
4+
"net/http"
5+
"runtime/pprof"
6+
"strings"
7+
8+
"github.com/grafana/pyroscope-go"
9+
"go.opentelemetry.io/otel/baggage"
10+
)
11+
12+
// LabelsFromBaggageHandler is a middleware that will extract key-value pairs
13+
// from the request baggage and make them profiling labels.
14+
func LabelsFromBaggageHandler(handler http.Handler) http.Handler {
15+
lh := &labelHandler{
16+
innerHandler: handler,
17+
}
18+
19+
return lh
20+
}
21+
22+
type labelHandler struct {
23+
innerHandler http.Handler
24+
}
25+
26+
func (lh *labelHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
27+
labels := getBaggageLabels(r)
28+
if labels == nil {
29+
lh.innerHandler.ServeHTTP(w, r)
30+
return
31+
}
32+
33+
// Inlined version of pryoscope.TagWrapper and pprof.Do to reduce noise in
34+
// the stack trace.
35+
ctx := r.Context()
36+
defer pprof.SetGoroutineLabels(ctx)
37+
ctx = pprof.WithLabels(ctx, *labels)
38+
pprof.SetGoroutineLabels(ctx)
39+
40+
lh.innerHandler.ServeHTTP(w, r.WithContext(ctx))
41+
}
42+
43+
// getBaggageLabels applies filters and transformations to request baggage and
44+
// returns the resulting LabelSet.
45+
func getBaggageLabels(r *http.Request) *pyroscope.LabelSet {
46+
b, err := baggage.Parse(r.Header.Get("Baggage"))
47+
if err != nil {
48+
return nil
49+
}
50+
51+
labels := baggageToLabels(b)
52+
return &labels
53+
}
54+
55+
// baggageToLabels converts request baggage to a LabelSet.
56+
func baggageToLabels(b baggage.Baggage) pyroscope.LabelSet {
57+
labelPairs := make([]string, 0, len(b.Members())*2)
58+
for _, m := range b.Members() {
59+
if !strings.HasPrefix(m.Key(), "k6.") {
60+
continue
61+
}
62+
63+
if m.Value() == "" {
64+
continue
65+
}
66+
67+
key := strings.ReplaceAll(m.Key(), ".", "_")
68+
labelPairs = append(labelPairs, key, m.Value())
69+
}
70+
71+
return pyroscope.Labels(labelPairs...)
72+
}

x/k6/baggage_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package k6
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"net/http/httptest"
7+
"runtime/pprof"
8+
"testing"
9+
10+
"github.com/stretchr/testify/require"
11+
"go.opentelemetry.io/otel/baggage"
12+
)
13+
14+
func Test_getBaggageLabels(t *testing.T) {
15+
t.Run("empty values are skipped", func(t *testing.T) {
16+
req := httptest.NewRequest("GET", "http://example.com", nil)
17+
req = testRequestWithBaggage(t, req, map[string]string{
18+
"blank": "",
19+
})
20+
21+
labelSet := getBaggageLabels(req)
22+
gotLabels := testPprofLabelsToMap(t, *labelSet)
23+
24+
expectedLabels := map[string]string{}
25+
require.Equal(t, expectedLabels, gotLabels)
26+
})
27+
28+
t.Run("with K6Options", func(t *testing.T) {
29+
req := httptest.NewRequest("GET", "http://example.com", nil)
30+
req = testRequestWithBaggage(t, req, map[string]string{
31+
"k6.test_run_id": "123",
32+
"not_k6.some_other_key": "value",
33+
})
34+
35+
labelSet := getBaggageLabels(req)
36+
gotLabels := testPprofLabelsToMap(t, *labelSet)
37+
38+
expectedLabels := map[string]string{
39+
"k6_test_run_id": "123",
40+
}
41+
require.Equal(t, expectedLabels, gotLabels)
42+
})
43+
44+
t.Run("does not allocate with failure to parse baggage", func(t *testing.T) {
45+
req := httptest.NewRequest("GET", "http://example.com", nil)
46+
req.Header.Add("Baggage", "invalid")
47+
48+
labelSet := getBaggageLabels(req)
49+
require.Nil(t, labelSet)
50+
})
51+
}
52+
53+
func testRequestWithBaggage(t *testing.T, req *http.Request, bag map[string]string) *http.Request {
54+
t.Helper()
55+
56+
members := []baggage.Member{}
57+
for k, v := range bag {
58+
member, err := baggage.NewMember(k, v)
59+
require.NoError(t, err)
60+
61+
members = append(members, member)
62+
}
63+
64+
b, err := baggage.New(members...)
65+
require.NoError(t, err)
66+
67+
req.Header.Add("Baggage", b.String())
68+
return req
69+
}
70+
71+
func testPprofLabelsToMap(t *testing.T, labelSet pprof.LabelSet) map[string]string {
72+
t.Helper()
73+
74+
gotLabels := map[string]string{}
75+
ctx := pprof.WithLabels(context.Background(), labelSet)
76+
pprof.ForLabels(ctx, func(key, value string) bool {
77+
gotLabels[key] = value
78+
return true
79+
})
80+
81+
return gotLabels
82+
}

x/k6/go.mod

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module github.com/grafana/pyroscope-go/x/k6
2+
3+
go 1.18
4+
5+
require (
6+
github.com/grafana/pyroscope-go v1.1.1
7+
github.com/stretchr/testify v1.9.0
8+
go.opentelemetry.io/otel v1.17.0
9+
)
10+
11+
require (
12+
github.com/davecgh/go-spew v1.1.1 // indirect
13+
github.com/grafana/pyroscope-go/godeltaprof v0.1.6 // indirect
14+
github.com/klauspost/compress v1.17.8 // indirect
15+
github.com/pmezard/go-difflib v1.0.0 // indirect
16+
gopkg.in/yaml.v3 v3.0.1 // indirect
17+
)

x/k6/go.sum

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/grafana/pyroscope-go v1.1.1 h1:PQoUU9oWtO3ve/fgIiklYuGilvsm8qaGhlY4Vw6MAcQ=
4+
github.com/grafana/pyroscope-go v1.1.1/go.mod h1:Mw26jU7jsL/KStNSGGuuVYdUq7Qghem5P8aXYXSXG88=
5+
github.com/grafana/pyroscope-go/godeltaprof v0.1.6 h1:nEdZ8louGAplSvIJi1HVp7kWvFvdiiYg3COLlTwJiFo=
6+
github.com/grafana/pyroscope-go/godeltaprof v0.1.6/go.mod h1:Tk376Nbldo4Cha9RgiU7ik8WKFkNpfds98aUzS8omLE=
7+
github.com/klauspost/compress v1.17.3/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
8+
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
9+
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
10+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
11+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
12+
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
13+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
14+
go.opentelemetry.io/otel v1.17.0 h1:MW+phZ6WZ5/uk2nd93ANk/6yJ+dVrvNWUjGhnnFU5jM=
15+
go.opentelemetry.io/otel v1.17.0/go.mod h1:I2vmBGtFaODIVMBSTPVDlJSzBDNf93k60E6Ft0nyjo0=
16+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
17+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
18+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
19+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)