Skip to content

Commit 0e34df1

Browse files
authored
fix(otel): split otel profile to multiple pprof profile if samples have service.name attribute (#3792)
1 parent bbe05cc commit 0e34df1

File tree

3 files changed

+381
-156
lines changed

3 files changed

+381
-156
lines changed

‎pkg/ingester/otlp/convert.go

Lines changed: 157 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -6,197 +6,238 @@ import (
66

77
googleProfile "github.com/grafana/pyroscope/api/gen/proto/go/google/v1"
88
otelProfile "github.com/grafana/pyroscope/api/otlp/profiles/v1experimental"
9-
10-
"google.golang.org/protobuf/proto"
119
)
1210

13-
func OprofToPprof(p *otelProfile.Profile) ([]byte, error) {
14-
dst := ConvertOtelToGoogle(p)
15-
return proto.Marshal(dst)
16-
}
11+
const serviceNameKey = "service.name"
1712

1813
// ConvertOtelToGoogle converts an OpenTelemetry profile to a Google profile.
19-
func ConvertOtelToGoogle(src *otelProfile.Profile) *googleProfile.Profile {
20-
dst := &googleProfile.Profile{
21-
SampleType: convertSampleTypesBack(src.SampleType),
22-
StringTable: src.StringTable[:],
23-
TimeNanos: src.TimeNanos,
24-
DurationNanos: src.DurationNanos,
25-
PeriodType: convertValueTypeBack(src.PeriodType),
26-
Period: src.Period,
27-
DefaultSampleType: src.DefaultSampleType,
28-
DropFrames: src.DropFrames,
29-
KeepFrames: src.KeepFrames,
30-
Comment: src.Comment,
31-
Mapping: convertMappingsBack(src.Mapping),
32-
}
33-
stringmap := make(map[string]int)
34-
addstr := func(s string) int64 {
35-
if _, ok := stringmap[s]; !ok {
36-
stringmap[s] = len(dst.StringTable)
37-
dst.StringTable = append(dst.StringTable, s)
14+
func ConvertOtelToGoogle(src *otelProfile.Profile) map[string]*googleProfile.Profile {
15+
svc2Profile := make(map[string]*profileBuilder)
16+
for _, sample := range src.Sample {
17+
svc := serviceNameFromSample(src, sample)
18+
p, ok := svc2Profile[svc]
19+
if !ok {
20+
p = newProfileBuilder(src)
21+
svc2Profile[svc] = p
3822
}
39-
return int64(stringmap[s])
23+
p.convertSampleBack(sample)
4024
}
41-
addstr("")
4225

43-
if dst.TimeNanos == 0 {
44-
dst.TimeNanos = time.Now().UnixNano()
45-
}
46-
if dst.DurationNanos == 0 {
47-
dst.DurationNanos = (time.Second * 10).Nanoseconds()
26+
result := make(map[string]*googleProfile.Profile)
27+
for svc, p := range svc2Profile {
28+
result[svc] = p.dst
4829
}
4930

50-
// attribute_table
51-
// attribute_units
52-
// link_table
31+
return result
32+
}
33+
34+
type profileBuilder struct {
35+
src *otelProfile.Profile
36+
dst *googleProfile.Profile
37+
stringMap map[string]int64
38+
functionMap map[*otelProfile.Function]uint64
39+
unsymbolziedFuncNameMap map[string]uint64
40+
locationMap map[*otelProfile.Location]uint64
41+
mappingMap map[*otelProfile.Mapping]uint64
42+
}
5343

54-
dst.Function = []*googleProfile.Function{}
55-
for i, funcItem := range src.Function {
56-
gf := convertFunctionBack(funcItem)
57-
gf.Id = uint64(i + 1)
58-
dst.Function = append(dst.Function, gf)
44+
func newProfileBuilder(src *otelProfile.Profile) *profileBuilder {
45+
res := &profileBuilder{
46+
src: src,
47+
stringMap: make(map[string]int64),
48+
functionMap: make(map[*otelProfile.Function]uint64),
49+
locationMap: make(map[*otelProfile.Location]uint64),
50+
mappingMap: make(map[*otelProfile.Mapping]uint64),
51+
unsymbolziedFuncNameMap: make(map[string]uint64),
52+
dst: &googleProfile.Profile{
53+
TimeNanos: src.TimeNanos,
54+
DurationNanos: src.DurationNanos,
55+
Period: src.Period,
56+
DefaultSampleType: src.DefaultSampleType,
57+
DropFrames: src.DropFrames,
58+
KeepFrames: src.KeepFrames,
59+
Comment: src.Comment,
60+
},
61+
}
62+
res.addstr("")
63+
res.dst.SampleType = res.convertSampleTypesBack(src.SampleType)
64+
res.dst.PeriodType = res.convertValueTypeBack(src.PeriodType)
65+
if len(res.dst.SampleType) == 0 {
66+
res.dst.SampleType = []*googleProfile.ValueType{{
67+
Type: res.addstr("samples"),
68+
Unit: res.addstr("ms"),
69+
}}
70+
res.dst.DefaultSampleType = res.addstr("samples")
5971
}
60-
funcmap := map[string]uint64{}
61-
addfunc := func(s string) uint64 {
62-
if _, ok := funcmap[s]; !ok {
63-
funcmap[s] = uint64(len(dst.Function)) + 1
64-
dst.Function = append(dst.Function, &googleProfile.Function{
65-
Id: funcmap[s],
66-
Name: addstr(s),
67-
})
68-
}
69-
return funcmap[s]
70-
}
71-
72-
dst.Location = []*googleProfile.Location{}
73-
// Convert locations and mappings
74-
for i, loc := range src.Location {
75-
gl := convertLocationBack(loc)
76-
gl.Id = uint64(i + 1)
77-
if len(gl.Line) == 0 {
78-
m := src.Mapping[loc.MappingIndex]
79-
gl.Line = append(gl.Line, &googleProfile.Line{
80-
FunctionId: addfunc(fmt.Sprintf("%s 0x%x", src.StringTable[m.Filename], loc.Address)),
81-
})
82-
}
83-
dst.Location = append(dst.Location, gl)
72+
if res.dst.TimeNanos == 0 {
73+
res.dst.TimeNanos = time.Now().UnixNano()
8474
}
75+
if res.dst.DurationNanos == 0 {
76+
res.dst.DurationNanos = (time.Second * 10).Nanoseconds()
77+
}
78+
return res
79+
}
8580

86-
// Convert samples
87-
for _, sample := range src.Sample {
88-
gs := convertSampleBack(src, sample, src.LocationIndices, addstr)
89-
dst.Sample = append(dst.Sample, gs)
81+
func (p *profileBuilder) addstr(s string) int64 {
82+
if i, ok := p.stringMap[s]; ok {
83+
return i
9084
}
85+
idx := int64(len(p.dst.StringTable))
86+
p.stringMap[s] = idx
87+
p.dst.StringTable = append(p.dst.StringTable, s)
88+
return idx
89+
}
9190

92-
if len(dst.SampleType) == 0 {
93-
dst.SampleType = []*googleProfile.ValueType{{
94-
Type: addstr("samples"),
95-
Unit: addstr("ms"),
96-
}}
97-
dst.DefaultSampleType = addstr("samples")
91+
func (p *profileBuilder) addfunc(s string) uint64 {
92+
if i, ok := p.unsymbolziedFuncNameMap[s]; ok {
93+
return i
94+
}
95+
idx := uint64(len(p.dst.Function)) + 1
96+
p.unsymbolziedFuncNameMap[s] = idx
97+
gf := &googleProfile.Function{
98+
Id: idx,
99+
Name: p.addstr(s),
98100
}
101+
p.dst.Function = append(p.dst.Function, gf)
102+
return idx
103+
}
99104

100-
return dst
105+
func serviceNameFromSample(p *otelProfile.Profile, sample *otelProfile.Sample) string {
106+
for _, attributeIndex := range sample.Attributes {
107+
attribute := p.AttributeTable[attributeIndex]
108+
if attribute.Key == serviceNameKey {
109+
return attribute.Value.GetStringValue()
110+
}
111+
}
112+
return ""
101113
}
102114

103-
func convertSampleTypesBack(ost []*otelProfile.ValueType) []*googleProfile.ValueType {
115+
func (p *profileBuilder) convertSampleTypesBack(ost []*otelProfile.ValueType) []*googleProfile.ValueType {
104116
var gst []*googleProfile.ValueType
105117
for _, st := range ost {
106118
gst = append(gst, &googleProfile.ValueType{
107-
Type: st.Type,
108-
Unit: st.Unit,
119+
Type: p.addstr(p.src.StringTable[st.Type]),
120+
Unit: p.addstr(p.src.StringTable[st.Unit]),
109121
})
110122
}
111123
return gst
112124
}
113125

114-
func convertValueTypeBack(ovt *otelProfile.ValueType) *googleProfile.ValueType {
126+
func (p *profileBuilder) convertValueTypeBack(ovt *otelProfile.ValueType) *googleProfile.ValueType {
115127
if ovt == nil {
116128
return nil
117129
}
118130
return &googleProfile.ValueType{
119-
Type: ovt.Type,
120-
Unit: ovt.Unit,
131+
Type: p.addstr(p.src.StringTable[ovt.Type]),
132+
Unit: p.addstr(p.src.StringTable[ovt.Unit]),
121133
}
122134
}
123135

124-
func convertLocationBack(ol *otelProfile.Location) *googleProfile.Location {
136+
func (p *profileBuilder) convertLocationBack(ol *otelProfile.Location) uint64 {
137+
if i, ok := p.locationMap[ol]; ok {
138+
return i
139+
}
140+
om := p.src.Mapping[ol.MappingIndex]
125141
gl := &googleProfile.Location{
126-
MappingId: ol.MappingIndex + 1,
142+
MappingId: p.convertMappingBack(om),
127143
Address: ol.Address,
128144
Line: make([]*googleProfile.Line, len(ol.Line)),
129145
IsFolded: ol.IsFolded,
130146
}
131147
for i, line := range ol.Line {
132-
gl.Line[i] = convertLineBack(line)
148+
gl.Line[i] = p.convertLineBack(line)
133149
}
134-
return gl
150+
151+
if len(gl.Line) == 0 {
152+
gl.Line = append(gl.Line, &googleProfile.Line{
153+
FunctionId: p.addfunc(fmt.Sprintf("%s 0x%x", p.src.StringTable[om.Filename], ol.Address)),
154+
})
155+
}
156+
157+
p.dst.Location = append(p.dst.Location, gl)
158+
gl.Id = uint64(len(p.dst.Location))
159+
p.locationMap[ol] = gl.Id
160+
return gl.Id
135161
}
136162

137163
// convertLineBack converts an OpenTelemetry Line to a Google Line.
138-
func convertLineBack(ol *otelProfile.Line) *googleProfile.Line {
164+
func (p *profileBuilder) convertLineBack(ol *otelProfile.Line) *googleProfile.Line {
139165
return &googleProfile.Line{
140-
FunctionId: ol.FunctionIndex + 1,
166+
FunctionId: p.convertFunctionBack(p.src.Function[ol.FunctionIndex]),
141167
Line: ol.Line,
142168
}
143169
}
144170

145-
func convertFunctionBack(of *otelProfile.Function) *googleProfile.Function {
146-
return &googleProfile.Function{
147-
Name: of.Name,
148-
SystemName: of.SystemName,
149-
Filename: of.Filename,
171+
func (p *profileBuilder) convertFunctionBack(of *otelProfile.Function) uint64 {
172+
if i, ok := p.functionMap[of]; ok {
173+
return i
174+
}
175+
gf := &googleProfile.Function{
176+
Name: p.addstr(p.src.StringTable[of.Name]),
177+
SystemName: p.addstr(p.src.StringTable[of.SystemName]),
178+
Filename: p.addstr(p.src.StringTable[of.Filename]),
150179
StartLine: of.StartLine,
151180
}
181+
p.dst.Function = append(p.dst.Function, gf)
182+
gf.Id = uint64(len(p.dst.Function))
183+
p.functionMap[of] = gf.Id
184+
return gf.Id
152185
}
153186

154-
func convertSampleBack(p *otelProfile.Profile, os *otelProfile.Sample, locationIndexes []int64, addstr func(s string) int64) *googleProfile.Sample {
187+
func (p *profileBuilder) convertSampleBack(os *otelProfile.Sample) *googleProfile.Sample {
155188
gs := &googleProfile.Sample{
156189
Value: os.Value,
157190
}
158191

159192
if len(gs.Value) == 0 {
160193
gs.Value = []int64{int64(len(os.TimestampsUnixNano))}
161194
}
162-
convertSampleAttributesToLabelsBack(p, os, gs, addstr)
195+
p.convertSampleAttributesToLabelsBack(os, gs)
163196

164197
for i := os.LocationsStartIndex; i < os.LocationsStartIndex+os.LocationsLength; i++ {
165-
gs.LocationId = append(gs.LocationId, uint64(locationIndexes[i]+1))
198+
gs.LocationId = append(gs.LocationId, p.convertLocationBack(p.src.Location[p.src.LocationIndices[i]]))
166199
}
167200

201+
p.dst.Sample = append(p.dst.Sample, gs)
202+
168203
return gs
169204
}
170205

171-
func convertSampleAttributesToLabelsBack(p *otelProfile.Profile, os *otelProfile.Sample, gs *googleProfile.Sample, addstr func(s string) int64) {
206+
func (p *profileBuilder) convertSampleAttributesToLabelsBack(os *otelProfile.Sample, gs *googleProfile.Sample) {
172207
gs.Label = make([]*googleProfile.Label, 0, len(os.Attributes))
173208
for _, attribute := range os.Attributes {
174-
att := p.AttributeTable[attribute]
209+
att := p.src.AttributeTable[attribute]
210+
if att.Key == serviceNameKey {
211+
continue
212+
}
175213
if att.Value.GetStringValue() != "" {
176214
gs.Label = append(gs.Label, &googleProfile.Label{
177-
Key: addstr(att.Key),
178-
Str: addstr(att.Value.GetStringValue()),
215+
Key: p.addstr(att.Key),
216+
Str: p.addstr(att.Value.GetStringValue()),
179217
})
180218
}
181219
}
182220
}
183221

184222
// convertMappingsBack converts a slice of OpenTelemetry Mapping entries to Google Mapping entries.
185-
func convertMappingsBack(otelMappings []*otelProfile.Mapping) []*googleProfile.Mapping {
186-
googleMappings := make([]*googleProfile.Mapping, len(otelMappings))
187-
for i, om := range otelMappings {
188-
googleMappings[i] = &googleProfile.Mapping{
189-
Id: uint64(i + 1), // Assuming direct mapping of IDs
190-
MemoryStart: om.MemoryStart,
191-
MemoryLimit: om.MemoryLimit,
192-
FileOffset: om.FileOffset,
193-
Filename: om.Filename, // Assume direct use; may need conversion if using indices
194-
BuildId: om.BuildId,
195-
HasFunctions: om.HasFunctions,
196-
HasFilenames: om.HasFilenames,
197-
HasLineNumbers: om.HasLineNumbers,
198-
HasInlineFrames: om.HasInlineFrames,
199-
}
200-
}
201-
return googleMappings
223+
func (p *profileBuilder) convertMappingBack(om *otelProfile.Mapping) uint64 {
224+
if i, ok := p.mappingMap[om]; ok {
225+
return i
226+
}
227+
228+
gm := &googleProfile.Mapping{
229+
MemoryStart: om.MemoryStart,
230+
MemoryLimit: om.MemoryLimit,
231+
FileOffset: om.FileOffset,
232+
Filename: p.addstr(p.src.StringTable[om.Filename]),
233+
BuildId: p.addstr(p.src.StringTable[om.BuildId]),
234+
HasFunctions: om.HasFunctions,
235+
HasFilenames: om.HasFilenames,
236+
HasLineNumbers: om.HasLineNumbers,
237+
HasInlineFrames: om.HasInlineFrames,
238+
}
239+
p.dst.Mapping = append(p.dst.Mapping, gm)
240+
gm.Id = uint64(len(p.dst.Mapping))
241+
p.mappingMap[om] = gm.Id
242+
return gm.Id
202243
}

0 commit comments

Comments
 (0)