@@ -9,13 +9,16 @@ import (
9
9
10
10
"connectrpc.com/connect"
11
11
"github.com/go-kit/log"
12
+ "github.com/go-kit/log/level"
12
13
"github.com/google/uuid"
13
14
"google.golang.org/grpc"
14
15
16
+ "github.com/grafana/dskit/user"
15
17
pushv1 "github.com/grafana/pyroscope/api/gen/proto/go/push/v1"
16
18
typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
17
19
pprofileotlp "github.com/grafana/pyroscope/api/otlp/collector/profiles/v1experimental"
18
- "github.com/grafana/pyroscope/pkg/tenant"
20
+ v1 "github.com/grafana/pyroscope/api/otlp/common/v1"
21
+ "github.com/grafana/pyroscope/api/otlp/profiles/v1experimental"
19
22
)
20
23
21
24
type ingestHandler struct {
@@ -25,7 +28,6 @@ type ingestHandler struct {
25
28
handler http.Handler
26
29
}
27
30
28
- // TODO(@petethepig): split http and grpc
29
31
type Handler interface {
30
32
http.Handler
31
33
pprofileotlp.ProfilesServiceServer
@@ -65,41 +67,45 @@ func (h *ingestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
65
67
66
68
// TODO(@petethepig): split http and grpc
67
69
func (h * ingestHandler ) Export (ctx context.Context , er * pprofileotlp.ExportProfilesServiceRequest ) (* pprofileotlp.ExportProfilesServiceResponse , error ) {
68
-
69
- // TODO(@petethepig): make it tenant-aware
70
- ctx = tenant .InjectTenantID (ctx , tenant .DefaultTenantID )
71
-
72
- h .log .Log ("msg" , "Export called" )
70
+ // Extracts user ID from the request metadata and returns and injects the user ID in the context
71
+ _ , ctx , err := user .ExtractFromGRPCRequest (ctx )
72
+ if err != nil {
73
+ level .Error (h .log ).Log ("msg" , "failed to extract tenant ID from GRPC request" , "err" , err )
74
+ return & pprofileotlp.ExportProfilesServiceResponse {}, fmt .Errorf ("failed to extract tenant ID from GRPC request: %w" , err )
75
+ }
73
76
74
77
rps := er .ResourceProfiles
75
78
for i := 0 ; i < len (rps ); i ++ {
76
79
rp := rps [i ]
77
80
78
- labelsDst := []* typesv1.LabelPair {}
79
- // TODO(@petethepig): make labels work
80
- labelsDst = append (labelsDst , & typesv1.LabelPair {
81
- Name : "__name__" ,
82
- Value : "process_cpu" ,
83
- })
84
- labelsDst = append (labelsDst , & typesv1.LabelPair {
85
- Name : "service_name" ,
86
- Value : "otlp_test_app4" ,
87
- })
88
- labelsDst = append (labelsDst , & typesv1.LabelPair {
89
- Name : "__delta__" ,
90
- Value : "false" ,
91
- })
92
- labelsDst = append (labelsDst , & typesv1.LabelPair {
93
- Name : "pyroscope_spy" ,
94
- Value : "unknown" ,
95
- })
81
+ // Get service name
82
+ serviceName := getServiceNameFromAttributes (rp .Resource .GetAttributes ())
83
+
84
+ // Start with default labels
85
+ labels := getDefaultLabels (serviceName )
86
+
87
+ // Track processed attribute keys to avoid duplicates across levels
88
+ processedKeys := make (map [string ]bool )
89
+
90
+ // Add resource attributes
91
+ labels = appendAttributesUnique (labels , rp .Resource .GetAttributes (), processedKeys )
96
92
97
93
sps := rp .ScopeProfiles
98
94
for j := 0 ; j < len (sps ); j ++ {
99
95
sp := sps [j ]
96
+
97
+ // Add scope attributes
98
+ labels = appendAttributesUnique (labels , sp .Scope .GetAttributes (), processedKeys )
99
+
100
100
for k := 0 ; k < len (sp .Profiles ); k ++ {
101
101
p := sp .Profiles [k ]
102
102
103
+ // Add profile attributes
104
+ labels = appendAttributesUnique (labels , p .GetAttributes (), processedKeys )
105
+
106
+ // Add profile-specific attributes from samples/attributetable
107
+ labels = appendProfileLabels (labels , p .Profile , processedKeys )
108
+
103
109
pprofBytes , err := OprofToPprof (p .Profile )
104
110
if err != nil {
105
111
return & pprofileotlp.ExportProfilesServiceResponse {}, fmt .Errorf ("failed to convert from OTLP to legacy pprof: %w" , err )
@@ -110,7 +116,7 @@ func (h *ingestHandler) Export(ctx context.Context, er *pprofileotlp.ExportProfi
110
116
req := & pushv1.PushRequest {
111
117
Series : []* pushv1.RawProfileSeries {
112
118
{
113
- Labels : labelsDst ,
119
+ Labels : labels ,
114
120
Samples : []* pushv1.RawSample {{
115
121
RawProfile : pprofBytes ,
116
122
ID : uuid .New ().String (),
@@ -125,8 +131,101 @@ func (h *ingestHandler) Export(ctx context.Context, er *pprofileotlp.ExportProfi
125
131
}
126
132
}
127
133
}
128
-
129
134
}
130
135
131
136
return & pprofileotlp.ExportProfilesServiceResponse {}, nil
132
137
}
138
+
139
+ // getServiceNameFromAttributes extracts service name from OTLP resource attributes.
140
+ // Returns "unknown" if service name is not found or empty.
141
+ func getServiceNameFromAttributes (attrs []v1.KeyValue ) string {
142
+ for _ , attr := range attrs {
143
+ if attr .Key == "service.name" {
144
+ val := attr .GetValue ()
145
+ if sv := val .GetStringValue (); sv != "" {
146
+ return sv
147
+ }
148
+ break
149
+ }
150
+ }
151
+ return "unknown"
152
+ }
153
+
154
+ // getDefaultLabels returns the required base labels for Pyroscope profiles
155
+ func getDefaultLabels (serviceName string ) []* typesv1.LabelPair {
156
+ return []* typesv1.LabelPair {
157
+ {
158
+ Name : "__name__" ,
159
+ Value : "process_cpu" ,
160
+ },
161
+ {
162
+ Name : "service_name" ,
163
+ Value : serviceName ,
164
+ },
165
+ {
166
+ Name : "__delta__" ,
167
+ Value : "false" ,
168
+ },
169
+ {
170
+ Name : "pyroscope_spy" ,
171
+ Value : "unknown" ,
172
+ },
173
+ }
174
+ }
175
+
176
+ func appendAttributesUnique (labels []* typesv1.LabelPair , attrs []v1.KeyValue , processedKeys map [string ]bool ) []* typesv1.LabelPair {
177
+ for _ , attr := range attrs {
178
+ // Skip if we've already seen this key at any level
179
+ if processedKeys [attr .Key ] {
180
+ continue
181
+ }
182
+
183
+ val := attr .GetValue ()
184
+ if sv := val .GetStringValue (); sv != "" {
185
+ labels = append (labels , & typesv1.LabelPair {
186
+ Name : attr .Key ,
187
+ Value : sv ,
188
+ })
189
+ processedKeys [attr .Key ] = true
190
+ }
191
+ }
192
+ return labels
193
+ }
194
+
195
+ func appendProfileLabels (labels []* typesv1.LabelPair , profile * v1experimental.Profile , processedKeys map [string ]bool ) []* typesv1.LabelPair {
196
+ if profile == nil {
197
+ return labels
198
+ }
199
+
200
+ // Create mapping of attribute indices to their values
201
+ attrMap := make (map [uint64 ]v1.AnyValue )
202
+ for i , attr := range profile .GetAttributeTable () {
203
+ val := attr .GetValue ()
204
+ if val .GetValue () != nil {
205
+ attrMap [uint64 (i )] = val
206
+ }
207
+ }
208
+
209
+ // Process only attributes referenced in samples
210
+ for _ , sample := range profile .Sample {
211
+ for _ , attrIdx := range sample .GetAttributes () {
212
+ attr := profile .AttributeTable [attrIdx ]
213
+ // Skip if we've already processed this key at any level
214
+ if processedKeys [attr .Key ] {
215
+ continue
216
+ }
217
+
218
+ if value , exists := attrMap [attrIdx ]; exists {
219
+ if sv := value .GetStringValue (); sv != "" {
220
+ labels = append (labels , & typesv1.LabelPair {
221
+ Name : attr .Key ,
222
+ Value : sv ,
223
+ })
224
+ processedKeys [attr .Key ] = true
225
+ }
226
+ }
227
+ }
228
+ }
229
+
230
+ return labels
231
+ }
0 commit comments