diff --git a/godeltaprof/block.go b/godeltaprof/block.go index 825130b..349c77a 100644 --- a/godeltaprof/block.go +++ b/godeltaprof/block.go @@ -28,6 +28,7 @@ type BlockProfiler struct { mutex sync.Mutex runtimeProfile func([]runtime.BlockProfileRecord) (int, bool) scaleProfile pprof.MutexProfileScaler + options pprof.ProfileBuilderOptions } // NewMutexProfiler creates a new BlockProfiler instance for profiling mutex contention. @@ -42,11 +43,10 @@ func NewMutexProfiler() *BlockProfiler { return &BlockProfiler{ runtimeProfile: runtime.MutexProfile, scaleProfile: pprof.ScalerMutexProfile, - impl: pprof.DeltaMutexProfiler{ - Options: pprof.ProfileBuilderOptions{ - GenericsFrames: true, - LazyMapping: true, - }, + impl: pprof.DeltaMutexProfiler{}, + options: pprof.ProfileBuilderOptions{ + GenericsFrames: true, + LazyMapping: true, }, } } @@ -55,11 +55,10 @@ func NewMutexProfilerWithOptions(options ProfileOptions) *BlockProfiler { return &BlockProfiler{ runtimeProfile: runtime.MutexProfile, scaleProfile: pprof.ScalerMutexProfile, - impl: pprof.DeltaMutexProfiler{ - Options: pprof.ProfileBuilderOptions{ - GenericsFrames: options.GenericsFrames, - LazyMapping: options.LazyMappings, - }, + impl: pprof.DeltaMutexProfiler{}, + options: pprof.ProfileBuilderOptions{ + GenericsFrames: options.GenericsFrames, + LazyMapping: options.LazyMappings, }, } } @@ -76,11 +75,10 @@ func NewBlockProfiler() *BlockProfiler { return &BlockProfiler{ runtimeProfile: runtime.BlockProfile, scaleProfile: pprof.ScalerBlockProfile, - impl: pprof.DeltaMutexProfiler{ - Options: pprof.ProfileBuilderOptions{ - GenericsFrames: true, - LazyMapping: true, - }, + impl: pprof.DeltaMutexProfiler{}, + options: pprof.ProfileBuilderOptions{ + GenericsFrames: true, + LazyMapping: true, }, } } @@ -89,11 +87,10 @@ func NewBlockProfilerWithOptions(options ProfileOptions) *BlockProfiler { return &BlockProfiler{ runtimeProfile: runtime.BlockProfile, scaleProfile: pprof.ScalerBlockProfile, - impl: pprof.DeltaMutexProfiler{ - Options: pprof.ProfileBuilderOptions{ - GenericsFrames: options.GenericsFrames, - LazyMapping: options.LazyMappings, - }, + impl: pprof.DeltaMutexProfiler{}, + options: pprof.ProfileBuilderOptions{ + GenericsFrames: options.GenericsFrames, + LazyMapping: options.LazyMappings, }, } } @@ -115,5 +112,7 @@ func (d *BlockProfiler) Profile(w io.Writer) error { sort.Slice(p, func(i, j int) bool { return p[i].Cycles > p[j].Cycles }) - return d.impl.PrintCountCycleProfile(w, "contentions", "delay", d.scaleProfile, p) + stc := pprof.MutexProfileConfig() + b := pprof.NewProfileBuilder(w, &d.options, stc) + return d.impl.PrintCountCycleProfile(b, d.scaleProfile, p) } diff --git a/godeltaprof/compat/compression_test.go b/godeltaprof/compat/compression_test.go index 4e3cad8..e9305e4 100644 --- a/godeltaprof/compat/compression_test.go +++ b/godeltaprof/compat/compression_test.go @@ -10,14 +10,18 @@ import ( ) func BenchmarkHeapCompression(b *testing.B) { - dh := pprof.DeltaHeapProfiler{} + opt := &pprof.ProfileBuilderOptions{ + GenericsFrames: true, + LazyMapping: true, + } + dh := new(pprof.DeltaHeapProfiler) fs := generateMemProfileRecords(512, 32, 239) rng := rand.NewSource(239) objSize := fs[0].AllocBytes / fs[0].AllocObjects nMutations := int(uint(rng.Int63())) % len(fs) b.ResetTimer() for i := 0; i < b.N; i++ { - _ = dh.WriteHeapProto(io.Discard, fs, int64(runtime.MemProfileRate), "") + _ = WriteHeapProto(dh, opt, io.Discard, fs, int64(runtime.MemProfileRate)) for j := 0; j < nMutations; j++ { idx := int(uint(rng.Int63())) % len(fs) fs[idx].AllocObjects += 1 @@ -39,7 +43,11 @@ func BenchmarkMutexCompression(b *testing.B) { runtime.SetMutexProfileFraction(5) defer runtime.SetMutexProfileFraction(prevMutexProfileFraction) - dh := pprof.DeltaMutexProfiler{} + opt := &pprof.ProfileBuilderOptions{ + GenericsFrames: true, + LazyMapping: true, + } + dh := new(pprof.DeltaMutexProfiler) fs := generateBlockProfileRecords(512, 32, 239) rng := rand.NewSource(239) nMutations := int(uint(rng.Int63())) % len(fs) @@ -47,7 +55,7 @@ func BenchmarkMutexCompression(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { - _ = dh.PrintCountCycleProfile(io.Discard, "contentions", "delay", scaler, fs) + _ = PrintCountCycleProfile(dh, opt, io.Discard, scaler, fs) for j := 0; j < nMutations; j++ { idx := int(uint(rng.Int63())) % len(fs) fs[idx].Count += 1 @@ -58,3 +66,15 @@ func BenchmarkMutexCompression(b *testing.B) { } } + +func WriteHeapProto(dp *pprof.DeltaHeapProfiler, opt *pprof.ProfileBuilderOptions, w io.Writer, p []runtime.MemProfileRecord, rate int64) error { + stc := pprof.HeapProfileConfig(rate) + b := pprof.NewProfileBuilder(w, opt, stc) + return dp.WriteHeapProto(b, p, rate) +} + +func PrintCountCycleProfile(d *pprof.DeltaMutexProfiler, opt *pprof.ProfileBuilderOptions, w io.Writer, scaler pprof.MutexProfileScaler, records []runtime.BlockProfileRecord) error { + stc := pprof.MutexProfileConfig() + b := pprof.NewProfileBuilder(w, opt, stc) + return d.PrintCountCycleProfile(b, scaler, records) +} diff --git a/godeltaprof/compat/delta_test.go b/godeltaprof/compat/delta_test.go index 6e90efa..1dded69 100644 --- a/godeltaprof/compat/delta_test.go +++ b/godeltaprof/compat/delta_test.go @@ -2,6 +2,7 @@ package compat import ( "bytes" + "math/rand" "runtime" "testing" @@ -39,10 +40,11 @@ func TestDeltaHeap(t *testing.T) { const testMemProfileRate = 524288 const testObjectSize = 327680 - dh := pprof.DeltaHeapProfiler{} + dh := new(pprof.DeltaHeapProfiler) + opt := new(pprof.ProfileBuilderOptions) dump := func(r ...runtime.MemProfileRecord) *bytes.Buffer { buf := bytes.NewBuffer(nil) - err := dh.WriteHeapProto(buf, r, testMemProfileRate, "") + err := WriteHeapProto(dh, opt, buf, r, testMemProfileRate) assert.NoError(t, err) return buf } @@ -108,7 +110,8 @@ func TestDeltaBlockProfile(t *testing.T) { runtime.SetMutexProfileFraction(5) defer runtime.SetMutexProfileFraction(prevMutexProfileFraction) - dh := pprof.DeltaMutexProfiler{} + dh := new(pprof.DeltaMutexProfiler) + opt := new(pprof.ProfileBuilderOptions) scale := func(rcount, rcycles int64) (int64, int64) { count, nanosec := pprof.ScaleMutexProfile(scaler, rcount, float64(rcycles)/cpuGHz) @@ -117,7 +120,7 @@ func TestDeltaBlockProfile(t *testing.T) { } dump := func(r ...runtime.BlockProfileRecord) *bytes.Buffer { buf := bytes.NewBuffer(nil) - err := dh.PrintCountCycleProfile(buf, "contentions", "delay", scaler, r) + err := PrintCountCycleProfile(dh, opt, buf, scaler, r) assert.NoError(t, err) return buf } @@ -163,3 +166,69 @@ func TestDeltaBlockProfile(t *testing.T) { }) } } + +func BenchmarkHeapDelta(b *testing.B) { + dh := new(pprof.DeltaHeapProfiler) + fs := generateMemProfileRecords(512, 32, 239) + rng := rand.NewSource(239) + objSize := fs[0].AllocBytes / fs[0].AllocObjects + nMutations := int(uint(rng.Int63())) % len(fs) + builder := &noopBuilder{} + b.ResetTimer() + for i := 0; i < b.N; i++ { + _ = dh.WriteHeapProto(builder, fs, int64(runtime.MemProfileRate)) + for j := 0; j < nMutations; j++ { + idx := int(uint(rng.Int63())) % len(fs) + fs[idx].AllocObjects += 1 + fs[idx].AllocBytes += objSize + fs[idx].FreeObjects += 1 + fs[idx].FreeBytes += objSize + } + } +} + +func BenchmarkMutexDelta(b *testing.B) { + for i, scaler := range mutexProfileScalers { + name := "ScalerMutexProfile" + if i == 1 { + name = "ScalerBlockProfile" + } + b.Run(name, func(b *testing.B) { + prevMutexProfileFraction := runtime.SetMutexProfileFraction(-1) + runtime.SetMutexProfileFraction(5) + defer runtime.SetMutexProfileFraction(prevMutexProfileFraction) + + dh := new(pprof.DeltaMutexProfiler) + fs := generateBlockProfileRecords(512, 32, 239) + rng := rand.NewSource(239) + nMutations := int(uint(rng.Int63())) % len(fs) + oneBlockCycles := fs[0].Cycles / fs[0].Count + builder := &noopBuilder{} + b.ResetTimer() + + for i := 0; i < b.N; i++ { + _ = dh.PrintCountCycleProfile(builder, scaler, fs) + for j := 0; j < nMutations; j++ { + idx := int(uint(rng.Int63())) % len(fs) + fs[idx].Count += 1 + fs[idx].Cycles += oneBlockCycles + } + } + }) + + } +} + +type noopBuilder struct { +} + +func (b *noopBuilder) LocsForStack(_ []uintptr) []uint64 { + return nil +} +func (b *noopBuilder) Sample(_ []int64, _ []uint64, _ int64) { + +} + +func (b *noopBuilder) Build() { + +} diff --git a/godeltaprof/compat/reject_order_test.go b/godeltaprof/compat/reject_order_test.go index 3b23883..9bdd658 100644 --- a/godeltaprof/compat/reject_order_test.go +++ b/godeltaprof/compat/reject_order_test.go @@ -13,10 +13,11 @@ import ( ) func TestHeapReject(t *testing.T) { - dh := pprof.DeltaHeapProfiler{} + dh := new(pprof.DeltaHeapProfiler) + opt := new(pprof.ProfileBuilderOptions) fs := generateMemProfileRecords(512, 32, 239) p1 := bytes.NewBuffer(nil) - err := dh.WriteHeapProto(p1, fs, int64(runtime.MemProfileRate), "") + err := WriteHeapProto(dh, opt, p1, fs, int64(runtime.MemProfileRate)) assert.NoError(t, err) p1Size := p1.Len() profile, err := gprofile.Parse(p1) @@ -27,7 +28,7 @@ func TestHeapReject(t *testing.T) { t.Log("p1 size", p1Size) p2 := bytes.NewBuffer(nil) - err = dh.WriteHeapProto(p2, fs, int64(runtime.MemProfileRate), "") + err = WriteHeapProto(dh, opt, p2, fs, int64(runtime.MemProfileRate)) assert.NoError(t, err) p2Size := p2.Len() assert.Less(t, p2Size, 1000) @@ -40,16 +41,15 @@ func TestHeapReject(t *testing.T) { } func BenchmarkHeapRejectOrder(b *testing.B) { - dh := pprof.DeltaHeapProfiler{ - Options: pprof.ProfileBuilderOptions{ - GenericsFrames: false, - LazyMapping: true, - }, + opt := &pprof.ProfileBuilderOptions{ + GenericsFrames: false, + LazyMapping: true, } + dh := &pprof.DeltaHeapProfiler{} fs := generateMemProfileRecords(512, 32, 239) b.ResetTimer() for i := 0; i < b.N; i++ { - dh.WriteHeapProto(io.Discard, fs, int64(runtime.MemProfileRate), "") + WriteHeapProto(dh, opt, io.Discard, fs, int64(runtime.MemProfileRate)) } } @@ -69,10 +69,11 @@ func TestMutexReject(t *testing.T) { runtime.SetMutexProfileFraction(5) defer runtime.SetMutexProfileFraction(prevMutexProfileFraction) - dh := pprof.DeltaMutexProfiler{} + dh := new(pprof.DeltaMutexProfiler) + opt := new(pprof.ProfileBuilderOptions) fs := generateBlockProfileRecords(512, 32, 239) p1 := bytes.NewBuffer(nil) - err := dh.PrintCountCycleProfile(p1, "contentions", "delay", scaler, fs) + err := PrintCountCycleProfile(dh, opt, p1, scaler, fs) assert.NoError(t, err) p1Size := p1.Len() profile, err := gprofile.Parse(p1) @@ -83,7 +84,7 @@ func TestMutexReject(t *testing.T) { t.Log("p1 size", p1Size) p2 := bytes.NewBuffer(nil) - err = dh.PrintCountCycleProfile(p2, "contentions", "delay", scaler, fs) + err = PrintCountCycleProfile(dh, opt, p2, scaler, fs) assert.NoError(t, err) p2Size := p2.Len() assert.Less(t, p2Size, 1000) @@ -107,18 +108,16 @@ func BenchmarkMutexRejectOrder(b *testing.B) { prevMutexProfileFraction := runtime.SetMutexProfileFraction(-1) runtime.SetMutexProfileFraction(5) defer runtime.SetMutexProfileFraction(prevMutexProfileFraction) - - dh := pprof.DeltaMutexProfiler{ - Options: pprof.ProfileBuilderOptions{ - GenericsFrames: false, - LazyMapping: true, - }, + opt := &pprof.ProfileBuilderOptions{ + GenericsFrames: false, + LazyMapping: true, } + dh := &pprof.DeltaMutexProfiler{} fs := generateBlockProfileRecords(512, 32, 239) b.ResetTimer() for i := 0; i < b.N; i++ { - dh.PrintCountCycleProfile(io.Discard, "contentions", "delay", scaler, fs) + PrintCountCycleProfile(dh, opt, io.Discard, scaler, fs) } }) diff --git a/godeltaprof/heap.go b/godeltaprof/heap.go index 8f26755..964b8ad 100644 --- a/godeltaprof/heap.go +++ b/godeltaprof/heap.go @@ -28,28 +28,28 @@ import ( // ... // err := hp.Profile(someWriter) type HeapProfiler struct { - impl pprof.DeltaHeapProfiler - mutex sync.Mutex + impl pprof.DeltaHeapProfiler + mutex sync.Mutex + options pprof.ProfileBuilderOptions } func NewHeapProfiler() *HeapProfiler { return &HeapProfiler{ - impl: pprof.DeltaHeapProfiler{ - Options: pprof.ProfileBuilderOptions{ - GenericsFrames: true, - LazyMapping: true, - }, + impl: pprof.DeltaHeapProfiler{}, + options: pprof.ProfileBuilderOptions{ + GenericsFrames: true, + LazyMapping: true, }} } func NewHeapProfilerWithOptions(options ProfileOptions) *HeapProfiler { return &HeapProfiler{ - impl: pprof.DeltaHeapProfiler{ - Options: pprof.ProfileBuilderOptions{ - GenericsFrames: options.GenericsFrames, - LazyMapping: options.LazyMappings, - }, - }} + impl: pprof.DeltaHeapProfiler{}, + options: pprof.ProfileBuilderOptions{ + GenericsFrames: options.GenericsFrames, + LazyMapping: options.LazyMappings, + }, + } } func (d *HeapProfiler) Profile(w io.Writer) error { @@ -76,6 +76,7 @@ func (d *HeapProfiler) Profile(w io.Writer) error { } // Profile grew; try again. } - - return d.impl.WriteHeapProto(w, p, int64(runtime.MemProfileRate), "") + rate := int64(runtime.MemProfileRate) + b := pprof.NewProfileBuilder(w, &d.options, pprof.HeapProfileConfig(rate)) + return d.impl.WriteHeapProto(b, p, rate) } diff --git a/godeltaprof/internal/pprof/builder.go b/godeltaprof/internal/pprof/builder.go new file mode 100644 index 0000000..fc35cdc --- /dev/null +++ b/godeltaprof/internal/pprof/builder.go @@ -0,0 +1,18 @@ +package pprof + +type ProfileBuilder interface { + LocsForStack(stk []uintptr) (newLocs []uint64) + Sample(values []int64, locs []uint64, blockSize int64) + Build() +} + +type ProfileConfig struct { + PeriodType ValueType + Period int64 + SampleType []ValueType + DefaultSampleType string +} + +type ValueType struct { + Typ, Unit string +} diff --git a/godeltaprof/internal/pprof/delta_heap.go b/godeltaprof/internal/pprof/delta_heap.go index 47674a5..0392382 100644 --- a/godeltaprof/internal/pprof/delta_heap.go +++ b/godeltaprof/internal/pprof/delta_heap.go @@ -1,34 +1,17 @@ package pprof import ( - "io" "math" "runtime" "strings" ) type DeltaHeapProfiler struct { - m profMap - mem []memMap - Options ProfileBuilderOptions + m profMap } // WriteHeapProto writes the current heap profile in protobuf format to w. -func (d *DeltaHeapProfiler) WriteHeapProto(w io.Writer, p []runtime.MemProfileRecord, rate int64, defaultSampleType string) error { - if d.mem == nil || !d.Options.LazyMapping { - d.mem = readMapping() - } - b := newProfileBuilder(w, d.Options, d.mem) - b.pbValueType(tagProfile_PeriodType, "space", "bytes") - b.pb.int64Opt(tagProfile_Period, rate) - b.pbValueType(tagProfile_SampleType, "alloc_objects", "count") - b.pbValueType(tagProfile_SampleType, "alloc_space", "bytes") - b.pbValueType(tagProfile_SampleType, "inuse_objects", "count") - b.pbValueType(tagProfile_SampleType, "inuse_space", "bytes") - if defaultSampleType != "" { - b.pb.int64Opt(tagProfile_DefaultSampleType, b.stringIndex(defaultSampleType)) - } - +func (d *DeltaHeapProfiler) WriteHeapProto(b ProfileBuilder, p []runtime.MemProfileRecord, rate int64) error { values := []int64{0, 0, 0, 0} var locs []uint64 for _, r := range p { @@ -74,20 +57,16 @@ func (d *DeltaHeapProfiler) WriteHeapProto(w io.Writer, p []runtime.MemProfileRe break } } - locs = b.appendLocsForStack(locs[:0], stk) + locs = b.LocsForStack(stk) if len(locs) > 0 { break } hideRuntime = false // try again, and show all frames next time. } - b.pbSample(values, locs, func() { - if blockSize != 0 { - b.pbLabel(tagSample_Label, "bytes", "", blockSize) - } - }) + b.Sample(values, locs, blockSize) } - b.build() + b.Build() return nil } @@ -116,3 +95,17 @@ func scaleHeapSample(count, size, rate int64) (int64, int64) { return int64(float64(count) * scale), int64(float64(size) * scale) } + +func HeapProfileConfig(rate int64) ProfileConfig { + return ProfileConfig{ + PeriodType: ValueType{Typ: "space", Unit: "bytes"}, + Period: rate, + SampleType: []ValueType{ + {"alloc_objects", "count"}, + {"alloc_space", "bytes"}, + {"inuse_objects", "count"}, + {"inuse_space", "bytes"}, + }, + DefaultSampleType: "", + } +} diff --git a/godeltaprof/internal/pprof/delta_mutex.go b/godeltaprof/internal/pprof/delta_mutex.go index 40ae63f..0b2500a 100644 --- a/godeltaprof/internal/pprof/delta_mutex.go +++ b/godeltaprof/internal/pprof/delta_mutex.go @@ -1,14 +1,11 @@ package pprof import ( - "io" "runtime" ) type DeltaMutexProfiler struct { - m profMap - mem []memMap - Options ProfileBuilderOptions + m profMap } // PrintCountCycleProfile outputs block profile records (for block or mutex profiles) @@ -16,16 +13,7 @@ type DeltaMutexProfiler struct { // are done because The proto expects count and time (nanoseconds) instead of count // and the number of cycles for block, contention profiles. // Possible 'scaler' functions are scaleBlockProfile and scaleMutexProfile. -func (d *DeltaMutexProfiler) PrintCountCycleProfile(w io.Writer, countName, cycleName string, scaler MutexProfileScaler, records []runtime.BlockProfileRecord) error { - if d.mem == nil || !d.Options.LazyMapping { - d.mem = readMapping() - } - // Output profile in protobuf form. - b := newProfileBuilder(w, d.Options, d.mem) - b.pbValueType(tagProfile_PeriodType, countName, "count") - b.pb.int64Opt(tagProfile_Period, 1) - b.pbValueType(tagProfile_SampleType, countName, "count") - b.pbValueType(tagProfile_SampleType, cycleName, "nanoseconds") +func (d *DeltaMutexProfiler) PrintCountCycleProfile(b ProfileBuilder, scaler MutexProfileScaler, records []runtime.BlockProfileRecord) error { cpuGHz := float64(runtime_cyclesPerSecond()) / 1e9 @@ -51,9 +39,20 @@ func (d *DeltaMutexProfiler) PrintCountCycleProfile(w io.Writer, countName, cycl // For count profiles, all stack addresses are // return PCs, which is what appendLocsForStack expects. - locs = b.appendLocsForStack(locs[:0], r.Stack()) - b.pbSample(values, locs, nil) + locs = b.LocsForStack(r.Stack()) + b.Sample(values, locs, 0) } - b.build() + b.Build() return nil } + +func MutexProfileConfig() ProfileConfig { + return ProfileConfig{ + PeriodType: ValueType{"contentions", "count"}, + Period: 1, + SampleType: []ValueType{ + {"contentions", "count"}, + {"delay", "nanoseconds"}, + }, + } +} diff --git a/godeltaprof/internal/pprof/proto.go b/godeltaprof/internal/pprof/proto.go index 4b5f2b0..78246b0 100644 --- a/godeltaprof/internal/pprof/proto.go +++ b/godeltaprof/internal/pprof/proto.go @@ -25,6 +25,14 @@ type ProfileBuilderOptions struct { // pre 1.21 - always use runtime.Frame->Function - produces frames with generic types ommited [...] GenericsFrames bool LazyMapping bool + mem []memMap +} + +func (d *ProfileBuilderOptions) mapping() []memMap { + if d.mem == nil || !d.LazyMapping { + d.mem = readMapping() + } + return d.mem } // A profileBuilder writes a profile incrementally from a @@ -45,8 +53,9 @@ type profileBuilder struct { funcs map[string]int // Package path-qualified function name to Function.ID mem []memMap deck pcDeck + tmplocs []uint64 - opt ProfileBuilderOptions + opt *ProfileBuilderOptions } type memMap struct { @@ -162,13 +171,13 @@ func (b *profileBuilder) pbValueType(tag int, typ, unit string) { b.pb.endMessage(tag, start) } -// pbSample encodes a Sample message to b.pb. -func (b *profileBuilder) pbSample(values []int64, locs []uint64, labels func()) { +// Sample encodes a Sample message to b.pb. +func (b *profileBuilder) Sample(values []int64, locs []uint64, blockSize int64) { start := b.pb.startMessage() b.pb.int64s(tagSample_Value, values) b.pb.uint64s(tagSample_Location, locs) - if labels != nil { - labels() + if blockSize != 0 { + b.pbLabel(tagSample_Label, "bytes", "", blockSize) } b.pb.endMessage(tagProfile_Sample, start) b.flush() @@ -258,11 +267,11 @@ type locInfo struct { firstPCSymbolizeResult symbolizeFlag } -// newProfileBuilder returns a new profileBuilder. +// NewProfileBuilder returns a new profileBuilder. // CPU profiling data obtained from the runtime can be added // by calling b.addCPUData, and then the eventual profile // can be obtained by calling b.finish. -func newProfileBuilder(w io.Writer, opt ProfileBuilderOptions, mapping []memMap) *profileBuilder { +func NewProfileBuilder(w io.Writer, opt *ProfileBuilderOptions, stc ProfileConfig) ProfileBuilder { zw := newGzipWriter(w) b := &profileBuilder{ w: w, @@ -273,13 +282,22 @@ func newProfileBuilder(w io.Writer, opt ProfileBuilderOptions, mapping []memMap) locs: map[uintptr]locInfo{}, funcs: map[string]int{}, opt: opt, + tmplocs: make([]uint64, 0, 128), + } + b.mem = opt.mapping() + b.pbValueType(tagProfile_PeriodType, stc.PeriodType.Typ, stc.PeriodType.Unit) + b.pb.int64Opt(tagProfile_Period, stc.Period) + for _, st := range stc.SampleType { + b.pbValueType(tagProfile_SampleType, st.Typ, st.Unit) + } + if stc.DefaultSampleType != "" { + b.pb.int64Opt(tagProfile_DefaultSampleType, b.stringIndex(stc.DefaultSampleType)) } - b.mem = mapping return b } -// build completes and returns the constructed profile. -func (b *profileBuilder) build() { +// Build completes and returns the constructed profile. +func (b *profileBuilder) Build() { b.end = time.Now() b.pb.int64Opt(tagProfile_TimeNanos, b.start.UnixNano()) @@ -304,7 +322,7 @@ func (b *profileBuilder) build() { b.zw.Close() } -// appendLocsForStack appends the location IDs for the given stack trace to the given +// LocsForStack appends the location IDs for the given stack trace to the given // location ID slice, locs. The addresses in the stack are return PCs or 1 + the PC of // an inline marker as the runtime traceback function returns. // @@ -313,7 +331,8 @@ func (b *profileBuilder) build() { // get the right cumulative sample count. // // It may emit to b.pb, so there must be no message encoding in progress. -func (b *profileBuilder) appendLocsForStack(locs []uint64, stk []uintptr) (newLocs []uint64) { +func (b *profileBuilder) LocsForStack(stk []uintptr) (newLocs []uint64) { + locs := b.tmplocs[:0] b.deck.reset() // The last frame might be truncated. Recover lost inline frames.