Skip to content

Commit bfca88e

Browse files
support parsing of debug goroutine profiles
1 parent 63e63cd commit bfca88e

3 files changed

Lines changed: 129 additions & 12 deletions

File tree

convert.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package ppmerge
2+
3+
import "github.com/google/pprof/profile"
4+
5+
func (p *Profile) From(src *profile.Profile) {
6+
m := map[string]uint64{}
7+
8+
p.Sample = make([]*Sample, len(src.Sample))
9+
for i, sample := range src.Sample {
10+
p.Sample[i] = &Sample{
11+
Value: make([]int64, len(sample.Value)),
12+
}
13+
copy(p.Sample[i].Value, sample.Value)
14+
}
15+
p.Function = make([]*Function, len(src.Function))
16+
for i, f := range src.Function {
17+
p.Function[i] = &Function{
18+
Id: f.ID,
19+
Name: int64(p.putString(f.Name, m)),
20+
SystemName: int64(p.putString(f.SystemName, m)),
21+
Filename: int64(p.putString(f.Filename, m)),
22+
StartLine: f.StartLine,
23+
}
24+
}
25+
26+
p.Location = make([]*Location, len(src.Location))
27+
for i, loc := range src.Location {
28+
p.Location[i] = &Location{
29+
Id: loc.ID,
30+
Address: loc.Address,
31+
IsFolded: loc.IsFolded,
32+
}
33+
34+
if loc.Mapping != nil {
35+
p.Location[i].MappingId = loc.Mapping.ID
36+
}
37+
38+
if len(loc.Line) > 0 {
39+
p.Location[i].Line = make([]*Line, len(loc.Line))
40+
for j, l := range loc.Line {
41+
p.Location[i].Line[j] = &Line{
42+
Line: l.Line,
43+
}
44+
if l.Function != nil {
45+
p.Location[i].Line[j].FunctionId = l.Function.ID
46+
}
47+
}
48+
}
49+
}
50+
51+
p.Mapping = make([]*Mapping, len(src.Mapping))
52+
for i, sm := range src.Mapping {
53+
p.Mapping[i] = &Mapping{
54+
Id: sm.ID,
55+
MemoryStart: sm.Start,
56+
MemoryLimit: sm.Limit,
57+
FileOffset: sm.Offset,
58+
Filename: int64(p.putString(sm.File, m)),
59+
BuildId: int64(p.putString(sm.BuildID, m)),
60+
HasInlineFrames: sm.HasInlineFrames,
61+
HasLineNumbers: sm.HasLineNumbers,
62+
HasFunctions: sm.HasFunctions,
63+
HasFilenames: sm.HasFilenames,
64+
}
65+
}
66+
67+
p.SampleType = make([]*ValueType, len(src.SampleType))
68+
for i, t := range src.SampleType {
69+
p.SampleType[i] = &ValueType{
70+
Type: int64(p.putString(t.Type, m)),
71+
Unit: int64(p.putString(t.Unit, m)),
72+
}
73+
}
74+
75+
p.Comment = make([]int64, len(src.Comments))
76+
for i, c := range src.Comments {
77+
p.Comment[i] = int64(p.putString(c, m))
78+
}
79+
80+
p.DefaultSampleType = int64(p.putString(src.DefaultSampleType, m))
81+
p.KeepFrames = int64(p.putString(src.KeepFrames, m))
82+
p.DropFrames = int64(p.putString(src.DropFrames, m))
83+
p.DurationNanos = src.DurationNanos
84+
p.TimeNanos = src.TimeNanos
85+
p.Period = src.Period
86+
87+
p.StringTable = make([]string, len(m))
88+
for s, i := range m {
89+
p.StringTable[i] = s
90+
}
91+
}
92+
93+
func (p *Profile) putString(val string, m map[string]uint64) uint64 {
94+
if id, ok := m[val]; ok {
95+
return id
96+
}
97+
nextID := uint64(len(m))
98+
m[val] = nextID
99+
return nextID
100+
}

merge_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
)
1212

1313
func TestHeapMerge(t *testing.T) {
14-
profiles := getProfilesVtProto(t, "hprof1", "hprof2", "hprof3", "hprof4")
14+
profiles := getProfilesVtProto(t, false, "hprof1", "hprof2", "hprof3", "hprof4")
1515
profileMerger := NewProfileMerger()
1616

1717
// merge profiles
@@ -87,7 +87,7 @@ func TestHeapMerge(t *testing.T) {
8787
}
8888

8989
func TestMergeWrite(t *testing.T) {
90-
profiles := getProfilesVtProto(t, "hprof1", "hprof2", "hprof3", "hprof4")
90+
profiles := getProfilesVtProto(t, false, "hprof1", "hprof2", "hprof3", "hprof4")
9191

9292
profileMerger := NewProfileMerger()
9393
mergedProfile := profileMerger.Merge(profiles...)
@@ -112,7 +112,7 @@ func TestMergeWrite(t *testing.T) {
112112
require.Less(t, compressedBB.Len(), noCompactBB.Len())
113113

114114
// merge profiles with different sample types
115-
profiles = getProfilesVtProto(t, "parca_heap", "parca_cpu", "parca_goroutine")
115+
profiles = getProfilesVtProto(t, false, "parca_heap", "parca_cpu", "parca_goroutine")
116116
mergedProfile = profileMerger.Merge(profiles...)
117117
require.NotNil(t, mergedProfile)
118118

@@ -130,7 +130,7 @@ func TestMergeWrite(t *testing.T) {
130130

131131
func TestMergeUnpack(t *testing.T) {
132132
t.Run("general merge unpack", func(t *testing.T) {
133-
profiles := getProfilesVtProto(t, "hprof1", "hprof2", "hprof3", "hprof4")
133+
profiles := getProfilesVtProto(t, false, "hprof1", "hprof2", "hprof3", "hprof4")
134134

135135
profileMerger := NewProfileMerger()
136136
mergedProfile := profileMerger.Merge(profiles...)
@@ -200,7 +200,7 @@ func TestMergeUnpack(t *testing.T) {
200200

201201
func BenchmarkVtProtobufParsing(b *testing.B) {
202202
for i := 0; i < b.N; i++ {
203-
profiles := getProfilesVtProto(b, "hprof1", "hprof2", "hprof3", "hprof4")
203+
profiles := getProfilesVtProto(b, false, "hprof1", "hprof2", "hprof3", "hprof4")
204204
for _, p := range profiles {
205205
p.ReturnToVTPool()
206206
}
@@ -214,7 +214,7 @@ func BenchmarkProtobufParsing(b *testing.B) {
214214
}
215215

216216
func BenchmarkProfileMerger(b *testing.B) {
217-
profiles := getProfilesVtProto(b, "hprof1", "hprof2", "hprof3", "hprof4")
217+
profiles := getProfilesVtProto(b, false, "hprof1", "hprof2", "hprof3", "hprof4")
218218

219219
b.ResetTimer()
220220
for i := 0; i < b.N; i++ {
@@ -224,7 +224,7 @@ func BenchmarkProfileMerger(b *testing.B) {
224224
}
225225

226226
func BenchmarkProfileUnPacker(b *testing.B) {
227-
profiles := getProfilesVtProto(b, "hprof1", "hprof2", "hprof3", "hprof4")
227+
profiles := getProfilesVtProto(b, false, "hprof1", "hprof2", "hprof3", "hprof4")
228228

229229
profileMerger := NewProfileMerger()
230230
mergedProfile := profileMerger.Merge(profiles...)
@@ -252,13 +252,13 @@ func getProfiles(t require.TestingT, paths ...string) []*profile.Profile {
252252
return profiles
253253
}
254254

255-
func getProfilesVtProto(t require.TestingT, paths ...string) []*Profile {
255+
func getProfilesVtProto(t require.TestingT, debugGoroutine bool, paths ...string) []*Profile {
256256
dir := "./testdata/"
257257
var profiles []*Profile
258258
for _, profileName := range paths {
259259
file, err := os.OpenFile(dir+profileName, os.O_RDONLY, 0666)
260260
require.NoError(t, err)
261-
prof, err := ParseProfile(file)
261+
prof, err := ParseProfile(file, debugGoroutine)
262262
require.NoError(t, err)
263263
profiles = append(profiles, prof)
264264
}

pprof.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,15 @@ import (
66
"fmt"
77
"io"
88

9+
"github.com/google/pprof/profile"
910
"github.com/pkg/errors"
1011
)
1112

12-
func ParseProfileData(rawProfile []byte) (*Profile, error) {
13+
func ParseProfileData(rawProfile []byte, debugGoroutine bool) (*Profile, error) {
14+
if debugGoroutine {
15+
return parseGoroutineDebugProfile(rawProfile)
16+
}
17+
1318
if len(rawProfile) >= 2 && rawProfile[0] == 0x1f && rawProfile[1] == 0x8b {
1419
gz, err := gzip.NewReader(bytes.NewBuffer(rawProfile))
1520
if err == nil {
@@ -30,10 +35,22 @@ func ParseProfileData(rawProfile []byte) (*Profile, error) {
3035
return profile, nil
3136
}
3237

33-
func ParseProfile(rd io.Reader) (*Profile, error) {
38+
func ParseProfile(rd io.Reader, debugGoroutine bool) (*Profile, error) {
3439
b, err := io.ReadAll(rd)
3540
if err == nil {
36-
return ParseProfileData(b)
41+
return ParseProfileData(b, debugGoroutine)
3742
}
3843
return nil, errors.Errorf("could not read profile: %v", err)
3944
}
45+
46+
func parseGoroutineDebugProfile(rawProfile []byte) (*Profile, error) {
47+
p, err := profile.ParseData(rawProfile)
48+
if err != nil {
49+
return nil, err
50+
}
51+
52+
vtProfile := ProfileFromVTPool()
53+
vtProfile.From(p)
54+
55+
return vtProfile, nil
56+
}

0 commit comments

Comments
 (0)