@@ -5,13 +5,20 @@ import (
55 "time"
66
77 googleProfile "github.com/grafana/pyroscope/api/gen/proto/go/google/v1"
8+ typesv1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1"
89 otelProfile "github.com/grafana/pyroscope/api/otlp/profiles/v1development"
10+ pyromodel "github.com/grafana/pyroscope/pkg/model"
911)
1012
1113const serviceNameKey = "service.name"
1214
15+ type convertedProfile struct {
16+ profile * googleProfile.Profile
17+ name * typesv1.LabelPair
18+ }
19+
1320// 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 ) {
1522 svc2Profile := make (map [string ]* profileBuilder )
1623 for _ , sample := range src .Sample {
1724 svc := serviceNameFromSample (src , sample )
@@ -20,17 +27,27 @@ func ConvertOtelToGoogle(src *otelProfile.Profile) map[string]*googleProfile.Pro
2027 p = newProfileBuilder (src )
2128 svc2Profile [svc ] = p
2229 }
23- p .convertSampleBack (sample )
30+ if _ , err := p .convertSampleBack (sample ); err != nil {
31+ return nil , err
32+ }
2433 }
2534
26- result := make (map [string ]* googleProfile. Profile )
35+ result := make (map [string ]convertedProfile )
2736 for svc , p := range svc2Profile {
28- result [svc ] = p .dst
37+ result [svc ] = convertedProfile { p .dst , p . name }
2938 }
3039
31- return result
40+ return result , nil
3241}
3342
43+ type sampleConversionType int
44+
45+ const (
46+ sampleConversionTypeNone sampleConversionType = 0
47+ sampleConversionTypeSamplesToNanos sampleConversionType = 1
48+ sampleConversionTypeSumEvents sampleConversionType = 2
49+ )
50+
3451type profileBuilder struct {
3552 src * otelProfile.Profile
3653 dst * googleProfile.Profile
@@ -39,7 +56,9 @@ type profileBuilder struct {
3956 unsymbolziedFuncNameMap map [string ]uint64
4057 locationMap map [* otelProfile.Location ]uint64
4158 mappingMap map [* otelProfile.Mapping ]uint64
42- cpuConversion bool
59+
60+ sampleProcessingTypes []sampleConversionType
61+ name * typesv1.LabelPair
4362}
4463
4564func newProfileBuilder (src * otelProfile.Profile ) * profileBuilder {
@@ -66,19 +85,36 @@ func newProfileBuilder(src *otelProfile.Profile) *profileBuilder {
6685 Unit : res .addstr ("ms" ),
6786 }}
6887 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 )
7692 if profileType == "samples:count:cpu:nanoseconds" {
77- res .dst .SampleType = [] * googleProfile.ValueType { {
93+ res .dst .SampleType [ i ] = & googleProfile.ValueType {
7894 Type : res .addstr ("cpu" ),
7995 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
82118 }
83119 }
84120
@@ -91,6 +127,22 @@ func newProfileBuilder(src *otelProfile.Profile) *profileBuilder {
91127 return res
92128}
93129
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+
94146func (p * profileBuilder ) addstr (s string ) int64 {
95147 if i , ok := p .stringMap [s ]; ok {
96148 return i
@@ -198,16 +250,32 @@ func (p *profileBuilder) convertFunctionBack(of *otelProfile.Function) uint64 {
198250 return gf .Id
199251}
200252
201- func (p * profileBuilder ) convertSampleBack (os * otelProfile.Sample ) * googleProfile.Sample {
253+ func (p * profileBuilder ) convertSampleBack (os * otelProfile.Sample ) ( * googleProfile.Sample , error ) {
202254 gs := & googleProfile.Sample {
203255 Value : os .Value ,
204256 }
205-
206257 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" )
210259 }
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+
211279 p .convertSampleAttributesToLabelsBack (os , gs )
212280
213281 for i := os .LocationsStartIndex ; i < os .LocationsStartIndex + os .LocationsLength ; i ++ {
@@ -216,7 +284,7 @@ func (p *profileBuilder) convertSampleBack(os *otelProfile.Sample) *googleProfil
216284
217285 p .dst .Sample = append (p .dst .Sample , gs )
218286
219- return gs
287+ return gs , nil
220288}
221289
222290func (p * profileBuilder ) convertSampleAttributesToLabelsBack (os * otelProfile.Sample , gs * googleProfile.Sample ) {
0 commit comments