@@ -5,13 +5,20 @@ import (
5
5
"time"
6
6
7
7
googleProfile "github.com/grafana/pyroscope/api/gen/proto/go/google/v1"
8
+ typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
8
9
otelProfile "github.com/grafana/pyroscope/api/otlp/profiles/v1development"
10
+ pyromodel "github.com/grafana/pyroscope/pkg/model"
9
11
)
10
12
11
13
const serviceNameKey = "service.name"
12
14
15
+ type convertedProfile struct {
16
+ profile * googleProfile.Profile
17
+ name * typesv1.LabelPair
18
+ }
19
+
13
20
// ConvertOtelToGoogle converts an OpenTelemetry profile to a Google profile.
14
- func ConvertOtelToGoogle (src * otelProfile.Profile ) map [string ]* googleProfile. Profile {
21
+ func ConvertOtelToGoogle (src * otelProfile.Profile ) ( map [string ]convertedProfile , error ) {
15
22
svc2Profile := make (map [string ]* profileBuilder )
16
23
for _ , sample := range src .Sample {
17
24
svc := serviceNameFromSample (src , sample )
@@ -20,17 +27,27 @@ func ConvertOtelToGoogle(src *otelProfile.Profile) map[string]*googleProfile.Pro
20
27
p = newProfileBuilder (src )
21
28
svc2Profile [svc ] = p
22
29
}
23
- p .convertSampleBack (sample )
30
+ if _ , err := p .convertSampleBack (sample ); err != nil {
31
+ return nil , err
32
+ }
24
33
}
25
34
26
- result := make (map [string ]* googleProfile. Profile )
35
+ result := make (map [string ]convertedProfile )
27
36
for svc , p := range svc2Profile {
28
- result [svc ] = p .dst
37
+ result [svc ] = convertedProfile { p .dst , p . name }
29
38
}
30
39
31
- return result
40
+ return result , nil
32
41
}
33
42
43
+ type sampleConversionType int
44
+
45
+ const (
46
+ sampleConversionTypeNone sampleConversionType = 0
47
+ sampleConversionTypeSamplesToNanos sampleConversionType = 1
48
+ sampleConversionTypeSumEvents sampleConversionType = 2
49
+ )
50
+
34
51
type profileBuilder struct {
35
52
src * otelProfile.Profile
36
53
dst * googleProfile.Profile
@@ -39,7 +56,9 @@ type profileBuilder struct {
39
56
unsymbolziedFuncNameMap map [string ]uint64
40
57
locationMap map [* otelProfile.Location ]uint64
41
58
mappingMap map [* otelProfile.Mapping ]uint64
42
- cpuConversion bool
59
+
60
+ sampleProcessingTypes []sampleConversionType
61
+ name * typesv1.LabelPair
43
62
}
44
63
45
64
func newProfileBuilder (src * otelProfile.Profile ) * profileBuilder {
@@ -66,19 +85,36 @@ func newProfileBuilder(src *otelProfile.Profile) *profileBuilder {
66
85
Unit : res .addstr ("ms" ),
67
86
}}
68
87
res .dst .DefaultSampleType = res .addstr ("samples" )
69
- } else if len (res .dst .SampleType ) == 1 && res .dst .PeriodType != nil && res .dst .Period != 0 {
70
- profileType := fmt .Sprintf ("%s:%s:%s:%s" ,
71
- res .dst .StringTable [res .dst .SampleType [0 ].Type ],
72
- res .dst .StringTable [res .dst .SampleType [0 ].Unit ],
73
- res .dst .StringTable [res .dst .PeriodType .Type ],
74
- res .dst .StringTable [res .dst .PeriodType .Unit ],
75
- )
88
+ }
89
+ res .sampleProcessingTypes = make ([]sampleConversionType , len (res .dst .SampleType ))
90
+ for i := 0 ; i < len (res .dst .SampleType ); i ++ {
91
+ profileType := res .profileType (i )
76
92
if profileType == "samples:count:cpu:nanoseconds" {
77
- res .dst .SampleType = [] * googleProfile.ValueType { {
93
+ res .dst .SampleType [ i ] = & googleProfile.ValueType {
78
94
Type : res .addstr ("cpu" ),
79
95
Unit : res .addstr ("nanoseconds" ),
80
- }}
81
- res .cpuConversion = true
96
+ }
97
+ if len (res .dst .SampleType ) == 1 {
98
+ res .name = & typesv1.LabelPair {
99
+ Name : pyromodel .LabelNameProfileName ,
100
+ Value : "process_cpu" ,
101
+ }
102
+ }
103
+ res .sampleProcessingTypes [i ] = sampleConversionTypeSamplesToNanos
104
+ }
105
+ // Identify off cpu profiles
106
+ if profileType == "events:nanoseconds::" && len (res .dst .SampleType ) == 1 {
107
+ res .sampleProcessingTypes [i ] = sampleConversionTypeSumEvents
108
+ res .name = & typesv1.LabelPair {
109
+ Name : pyromodel .LabelNameProfileName ,
110
+ Value : pyromodel .ProfileNameOffCpu ,
111
+ }
112
+ }
113
+ }
114
+ if res .name == nil {
115
+ res .name = & typesv1.LabelPair {
116
+ Name : pyromodel .LabelNameProfileName ,
117
+ Value : "process_cpu" , // guess
82
118
}
83
119
}
84
120
@@ -91,6 +127,22 @@ func newProfileBuilder(src *otelProfile.Profile) *profileBuilder {
91
127
return res
92
128
}
93
129
130
+ func (p * profileBuilder ) profileType (idx int ) string {
131
+ var (
132
+ periodType , periodUnit string
133
+ )
134
+ if p .dst .PeriodType != nil && p .dst .Period != 0 {
135
+ periodType = p .dst .StringTable [p .dst .PeriodType .Type ]
136
+ periodUnit = p .dst .StringTable [p .dst .PeriodType .Unit ]
137
+ }
138
+ return fmt .Sprintf ("%s:%s:%s:%s" ,
139
+ p .dst .StringTable [p .dst .SampleType [idx ].Type ],
140
+ p .dst .StringTable [p .dst .SampleType [idx ].Unit ],
141
+ periodType ,
142
+ periodUnit ,
143
+ )
144
+ }
145
+
94
146
func (p * profileBuilder ) addstr (s string ) int64 {
95
147
if i , ok := p .stringMap [s ]; ok {
96
148
return i
@@ -198,16 +250,32 @@ func (p *profileBuilder) convertFunctionBack(of *otelProfile.Function) uint64 {
198
250
return gf .Id
199
251
}
200
252
201
- func (p * profileBuilder ) convertSampleBack (os * otelProfile.Sample ) * googleProfile.Sample {
253
+ func (p * profileBuilder ) convertSampleBack (os * otelProfile.Sample ) ( * googleProfile.Sample , error ) {
202
254
gs := & googleProfile.Sample {
203
255
Value : os .Value ,
204
256
}
205
-
206
257
if len (gs .Value ) == 0 {
207
- gs .Value = []int64 {int64 (len (os .TimestampsUnixNano ))}
208
- } else if len (gs .Value ) == 1 && p .cpuConversion {
209
- gs .Value [0 ] *= p .src .Period
258
+ return nil , fmt .Errorf ("sample value is required" )
210
259
}
260
+
261
+ for i , typ := range p .sampleProcessingTypes {
262
+ switch typ {
263
+ case sampleConversionTypeSamplesToNanos :
264
+ gs .Value [i ] *= p .src .Period
265
+ case sampleConversionTypeSumEvents :
266
+ // For off-CPU profiles, aggregate all sample values into a single sum
267
+ // since pprof cannot represent variable-length sample values
268
+ sum := int64 (0 )
269
+ for _ , v := range gs .Value {
270
+ sum += v
271
+ }
272
+ gs .Value = []int64 {sum }
273
+ }
274
+ }
275
+ if p .dst .Period != 0 && p .dst .PeriodType != nil && len (gs .Value ) != len (p .dst .SampleType ) {
276
+ return nil , fmt .Errorf ("sample values length mismatch %d %d" , len (gs .Value ), len (p .dst .SampleType ))
277
+ }
278
+
211
279
p .convertSampleAttributesToLabelsBack (os , gs )
212
280
213
281
for i := os .LocationsStartIndex ; i < os .LocationsStartIndex + os .LocationsLength ; i ++ {
@@ -216,7 +284,7 @@ func (p *profileBuilder) convertSampleBack(os *otelProfile.Sample) *googleProfil
216
284
217
285
p .dst .Sample = append (p .dst .Sample , gs )
218
286
219
- return gs
287
+ return gs , nil
220
288
}
221
289
222
290
func (p * profileBuilder ) convertSampleAttributesToLabelsBack (os * otelProfile.Sample , gs * googleProfile.Sample ) {
0 commit comments