@@ -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,46 @@ 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
+ fmt .Println ("service.name: " , serviceName )
84
+
85
+ // Start with default labels
86
+ labels := getDefaultLabels (serviceName )
87
+
88
+ // Track processed attribute keys to avoid duplicates across levels
89
+ processedKeys := make (map [string ]bool )
90
+
91
+ // Add resource attributes
92
+ labels = appendAttributesUnique (labels , rp .Resource .GetAttributes (), processedKeys )
96
93
97
94
sps := rp .ScopeProfiles
98
95
for j := 0 ; j < len (sps ); j ++ {
99
96
sp := sps [j ]
97
+
98
+ // Add scope attributes
99
+ labels = appendAttributesUnique (labels , sp .Scope .GetAttributes (), processedKeys )
100
+
100
101
for k := 0 ; k < len (sp .Profiles ); k ++ {
101
102
p := sp .Profiles [k ]
102
103
104
+ // Add profile attributes
105
+ labels = appendAttributesUnique (labels , p .GetAttributes (), processedKeys )
106
+
107
+ // Add profile-specific attributes from samples/attributetable
108
+ labels = appendProfileLabels (labels , p .Profile , processedKeys )
109
+
103
110
pprofBytes , err := OprofToPprof (p .Profile )
104
111
if err != nil {
105
112
return & pprofileotlp.ExportProfilesServiceResponse {}, fmt .Errorf ("failed to convert from OTLP to legacy pprof: %w" , err )
@@ -110,7 +117,7 @@ func (h *ingestHandler) Export(ctx context.Context, er *pprofileotlp.ExportProfi
110
117
req := & pushv1.PushRequest {
111
118
Series : []* pushv1.RawProfileSeries {
112
119
{
113
- Labels : labelsDst ,
120
+ Labels : labels ,
114
121
Samples : []* pushv1.RawSample {{
115
122
RawProfile : pprofBytes ,
116
123
ID : uuid .New ().String (),
@@ -125,8 +132,101 @@ func (h *ingestHandler) Export(ctx context.Context, er *pprofileotlp.ExportProfi
125
132
}
126
133
}
127
134
}
128
-
129
135
}
130
136
131
137
return & pprofileotlp.ExportProfilesServiceResponse {}, nil
132
138
}
139
+
140
+ // getServiceNameFromAttributes extracts service name from OTLP resource attributes.
141
+ // Returns "unknown" if service name is not found or empty.
142
+ func getServiceNameFromAttributes (attrs []v1.KeyValue ) string {
143
+ for _ , attr := range attrs {
144
+ if attr .Key == "service.name" {
145
+ val := attr .GetValue ()
146
+ if sv := val .GetStringValue (); sv != "" {
147
+ return sv
148
+ }
149
+ break
150
+ }
151
+ }
152
+ return "unknown"
153
+ }
154
+
155
+ // getDefaultLabels returns the required base labels for Pyroscope profiles
156
+ func getDefaultLabels (serviceName string ) []* typesv1.LabelPair {
157
+ return []* typesv1.LabelPair {
158
+ {
159
+ Name : "__name__" ,
160
+ Value : "process_cpu" ,
161
+ },
162
+ {
163
+ Name : "service_name" ,
164
+ Value : serviceName ,
165
+ },
166
+ {
167
+ Name : "__delta__" ,
168
+ Value : "false" ,
169
+ },
170
+ {
171
+ Name : "pyroscope_spy" ,
172
+ Value : "unknown" ,
173
+ },
174
+ }
175
+ }
176
+
177
+ func appendAttributesUnique (labels []* typesv1.LabelPair , attrs []v1.KeyValue , processedKeys map [string ]bool ) []* typesv1.LabelPair {
178
+ for _ , attr := range attrs {
179
+ // Skip if we've already seen this key at any level
180
+ if processedKeys [attr .Key ] {
181
+ continue
182
+ }
183
+
184
+ val := attr .GetValue ()
185
+ if sv := val .GetStringValue (); sv != "" {
186
+ labels = append (labels , & typesv1.LabelPair {
187
+ Name : attr .Key ,
188
+ Value : sv ,
189
+ })
190
+ processedKeys [attr .Key ] = true
191
+ }
192
+ }
193
+ return labels
194
+ }
195
+
196
+ func appendProfileLabels (labels []* typesv1.LabelPair , profile * v1experimental.Profile , processedKeys map [string ]bool ) []* typesv1.LabelPair {
197
+ if profile == nil {
198
+ return labels
199
+ }
200
+
201
+ // Create mapping of attribute indices to their values
202
+ attrMap := make (map [uint64 ]v1.AnyValue )
203
+ for i , attr := range profile .GetAttributeTable () {
204
+ val := attr .GetValue ()
205
+ if val .GetValue () != nil {
206
+ attrMap [uint64 (i )] = val
207
+ }
208
+ }
209
+
210
+ // Process only attributes referenced in samples
211
+ for _ , sample := range profile .Sample {
212
+ for _ , attrIdx := range sample .GetAttributes () {
213
+ attr := profile .AttributeTable [attrIdx ]
214
+ // Skip if we've already processed this key at any level
215
+ if processedKeys [attr .Key ] {
216
+ continue
217
+ }
218
+
219
+ if value , exists := attrMap [attrIdx ]; exists {
220
+ if sv := value .GetStringValue (); sv != "" {
221
+ labels = append (labels , & typesv1.LabelPair {
222
+ Name : attr .Key ,
223
+ Value : sv ,
224
+ })
225
+ processedKeys [attr .Key ] = true
226
+ }
227
+ }
228
+ }
229
+ }
230
+
231
+ return labels
232
+ }
0 commit comments