Skip to content

Commit

Permalink
fix(godeltaprof): delta / reject sample order (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
korniltsev committed Nov 29, 2023
1 parent 3efa3c7 commit dc5db27
Show file tree
Hide file tree
Showing 10 changed files with 581 additions and 39 deletions.
165 changes: 165 additions & 0 deletions godeltaprof/compat/delta_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package compat

import (
"bytes"
"runtime"
"testing"

"github.com/grafana/pyroscope-go/godeltaprof/internal/pprof"
"github.com/stretchr/testify/assert"
)

var (
stack0 = [32]uintptr{}
stack0Marker string
stack1 = [32]uintptr{}
stack1Marker string
)

func init() {
fs := getFunctionPointers()
stack0 = [32]uintptr{fs[0], fs[1]}
stack1 = [32]uintptr{fs[2], fs[3]}
stack0Marker = runtime.FuncForPC(fs[1]).Name() + ";" + runtime.FuncForPC(fs[0]).Name()
stack1Marker = runtime.FuncForPC(fs[3]).Name() + ";" + runtime.FuncForPC(fs[2]).Name()
}

func TestDeltaHeap(t *testing.T) {
// scale 0 0 0
// scale 1 2 705084
// scale 2 4 1410169
// scale 3 6 2115253
// scale 4 8 2820338
// scale 5 10 3525422
// scale 6 12 4230507
// scale 7 15 4935592
// scale 8 17 5640676
// scale 9 19 6345761

const testMemProfileRate = 524288
const testObjectSize = 327680

dh := pprof.DeltaHeapProfiler{}
dump := func(r ...runtime.MemProfileRecord) *bytes.Buffer {
buf := bytes.NewBuffer(nil)
err := dh.WriteHeapProto(buf, r, testMemProfileRate, "")
assert.NoError(t, err)
return buf
}
r := func(AllocObjects, AllocBytes, FreeObjects, FreeBytes int64, s [32]uintptr) runtime.MemProfileRecord {
return runtime.MemProfileRecord{
AllocObjects: AllocObjects,
AllocBytes: AllocBytes,
FreeBytes: FreeBytes,
FreeObjects: FreeObjects,
Stack0: s,
}
}

p1 := dump(
r(0, 0, 0, 0, stack0),
r(0, 0, 0, 0, stack1),
)
expectEmptyProfile(t, p1)

p2 := dump(
r(5, 5*testObjectSize, 0, 0, stack0),
r(3, 3*testObjectSize, 3, 3*testObjectSize, stack1),
)
expectStackFrames(t, p2, stack0Marker, 10, 3525422, 10, 3525422)
expectStackFrames(t, p2, stack1Marker, 6, 2115253, 0, 0)

for i := 0; i < 3; i++ {
// if we write same data, stack0 is in use, stack1 should not be present
p3 := dump(
r(5, 5*testObjectSize, 0, 0, stack0),
r(3, 3*testObjectSize, 3, 3*testObjectSize, stack1),
)
expectStackFrames(t, p3, stack0Marker, 0, 0, 10, 3525422)
expectNoStackFrames(t, p3, stack1Marker)
}

p4 := dump(
r(5, 5*testObjectSize, 5, 5*testObjectSize, stack0),
r(3, 3*testObjectSize, 3, 3*testObjectSize, stack1),
)
expectEmptyProfile(t, p4)

p5 := dump(
r(8, 8*testObjectSize, 5, 5*testObjectSize, stack0),
r(3, 3*testObjectSize, 3, 3*testObjectSize, stack1),
)
// note, this value depends on scale order, it currently tests the current implementation, but we may change it
// to alloc objects to be scale(8) - scale(5) = 17-10 = 7
expectStackFrames(t, p5, stack0Marker, 6, 2115253, 6, 2115253)
expectNoStackFrames(t, p5, stack1Marker)
}

func TestDeltaBlockProfile(t *testing.T) {
cpuGHz := float64(pprof.Runtime_cyclesPerSecond()) / 1e9

for i, scaler := range mutexProfileScalers {
name := "ScalerMutexProfile"
if i == 1 {
name = "ScalerBlockProfile"
}
t.Run(name, func(t *testing.T) {
prevMutexProfileFraction := runtime.SetMutexProfileFraction(-1)
runtime.SetMutexProfileFraction(5)
defer runtime.SetMutexProfileFraction(prevMutexProfileFraction)

dh := pprof.DeltaMutexProfiler{}

scale := func(rcount, rcycles int64) (int64, int64) {
count, nanosec := pprof.ScaleMutexProfile(scaler, rcount, float64(rcycles)/cpuGHz)
inanosec := int64(nanosec)
return count, inanosec
}
dump := func(r ...runtime.BlockProfileRecord) *bytes.Buffer {
buf := bytes.NewBuffer(nil)
err := dh.PrintCountCycleProfile(buf, "contentions", "delay", scaler, r)
assert.NoError(t, err)
return buf
}
r := func(count, cycles int64, s [32]uintptr) runtime.BlockProfileRecord {
return runtime.BlockProfileRecord{
Count: count,
Cycles: cycles,
StackRecord: runtime.StackRecord{
Stack0: s,
},
}
}

p1 := dump(
r(0, 0, stack0),
r(0, 0, stack1),
)
expectEmptyProfile(t, p1)

const cycles = 42
p2 := dump(
r(239, 239*cycles, stack0),
r(0, 0, stack1),
)
count0, nanos0 := scale(239, 239*cycles)
expectStackFrames(t, p2, stack0Marker, count0, nanos0)
expectNoStackFrames(t, p2, stack1Marker)

for j := 0; j < 2; j++ {
p3 := dump(
r(239, 239*cycles, stack0),
r(0, 0, stack1),
)
expectEmptyProfile(t, p3)
}

count1, nanos1 := scale(240, 240*cycles)
p4 := dump(
r(240, 240*cycles, stack0),
)
expectStackFrames(t, p4, stack0Marker, count1-count0, nanos1-nanos0)
expectNoStackFrames(t, p4, stack1Marker)
})
}
}
116 changes: 116 additions & 0 deletions godeltaprof/compat/reject_order_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package compat

import (
"bytes"
"io"
"runtime"
"testing"

gprofile "github.com/google/pprof/profile"
"github.com/grafana/pyroscope-go/godeltaprof/internal/pprof"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestHeapReject(t *testing.T) {
dh := pprof.DeltaHeapProfiler{}
fs := generateMemProfileRecords(512, 32, 239)
p1 := bytes.NewBuffer(nil)
err := dh.WriteHeapProto(p1, fs, int64(runtime.MemProfileRate), "")
assert.NoError(t, err)
p1Size := p1.Len()
profile, err := gprofile.Parse(p1)
require.NoError(t, err)
ls := stackCollapseProfile(t, profile)
assert.Len(t, ls, 512)
assert.Len(t, profile.Location, 150)
t.Log("p1 size", p1Size)

p2 := bytes.NewBuffer(nil)
err = dh.WriteHeapProto(p2, fs, int64(runtime.MemProfileRate), "")
assert.NoError(t, err)
p2Size := p2.Len()
assert.Less(t, p2Size, 1000)
profile, err = gprofile.Parse(p2)
require.NoError(t, err)
ls = stackCollapseProfile(t, profile)
assert.Len(t, ls, 0)
assert.Len(t, profile.Location, 0)
t.Log("p2 size", p2Size)
}

func BenchmarkHeapRejectOrder(b *testing.B) {
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), "")
}
}

var mutexProfileScalers = []pprof.MutexProfileScaler{
pprof.ScalerMutexProfile,
pprof.ScalerBlockProfile,
}

func TestMutexReject(t *testing.T) {
for i, scaler := range mutexProfileScalers {
name := "ScalerMutexProfile"
if i == 1 {
name = "ScalerBlockProfile"
}
t.Run(name, func(t *testing.T) {
prevMutexProfileFraction := runtime.SetMutexProfileFraction(-1)
runtime.SetMutexProfileFraction(5)
defer runtime.SetMutexProfileFraction(prevMutexProfileFraction)

dh := pprof.DeltaMutexProfiler{}
fs := generateBlockProfileRecords(512, 32, 239)
p1 := bytes.NewBuffer(nil)
err := dh.PrintCountCycleProfile(p1, "contentions", "delay", scaler, fs)
assert.NoError(t, err)
p1Size := p1.Len()
profile, err := gprofile.Parse(p1)
require.NoError(t, err)
ls := stackCollapseProfile(t, profile)
assert.Len(t, ls, 512)
assert.Len(t, profile.Location, 150)
t.Log("p1 size", p1Size)

p2 := bytes.NewBuffer(nil)
err = dh.PrintCountCycleProfile(p2, "contentions", "delay", scaler, fs)
assert.NoError(t, err)
p2Size := p2.Len()
assert.Less(t, p2Size, 1000)
profile, err = gprofile.Parse(p2)
require.NoError(t, err)
ls = stackCollapseProfile(t, profile)
assert.Len(t, ls, 0)
assert.Len(t, profile.Location, 0)
t.Log("p2 size", p2Size)
})
}
}

func BenchmarkMutexRejectOrder(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 := pprof.DeltaMutexProfiler{}
fs := generateBlockProfileRecords(512, 32, 239)
b.ResetTimer()

for i := 0; i < b.N; i++ {
dh.PrintCountCycleProfile(io.Discard, "contentions", "delay", scaler, fs)
}
})

}
}
6 changes: 3 additions & 3 deletions godeltaprof/compat/scale_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func TestScaleMutex(t *testing.T) {
profile, err := gprofile.Parse(buffer)
require.NoError(t, err)

res := stackCollapseProfile(profile)
res := stackCollapseProfile(t, profile)

my := findStack(t, res, "github.com/grafana/pyroscope-go/godeltaprof/compat.TestScaleMutex")
require.NotNil(t, my)
Expand Down Expand Up @@ -103,7 +103,7 @@ func TestScaleBlock(t *testing.T) {
profile, err := gprofile.Parse(buffer)
require.NoError(t, err)

res := stackCollapseProfile(profile)
res := stackCollapseProfile(t, profile)

my := findStack(t, res, "github.com/grafana/pyroscope-go/godeltaprof/compat.TestScaleBlock")
require.NotNil(t, my)
Expand Down Expand Up @@ -160,7 +160,7 @@ func TestScaleHeap(t *testing.T) {
profile, err := gprofile.Parse(buffer)
require.NoError(t, err)

res := stackCollapseProfile(profile)
res := stackCollapseProfile(t, profile)

my := findStack(t, res, "github.com/grafana/pyroscope-go/godeltaprof/compat.TestScaleHeap;github.com/grafana/pyroscope-go/godeltaprof/compat.appendBuf")
require.NotNil(t, my)
Expand Down
34 changes: 25 additions & 9 deletions godeltaprof/compat/stackcollapse.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package compat

import (
"bytes"
"io"
"regexp"
"sort"
Expand All @@ -18,14 +19,28 @@ type stack struct {
value []int64
}

func expectStackFrames(t *testing.T, buffer io.Reader, sfPattern string, values ...int64) {
func expectEmptyProfile(t *testing.T, buffer io.Reader) {
profile, err := gprofile.Parse(buffer)
require.NoError(t, err)
line := findStack(t, stackCollapseProfile(profile), sfPattern)
assert.NotNil(t, line)
ls := stackCollapseProfile(t, profile)
assert.Empty(t, ls)
}

func expectNoStackFrames(t *testing.T, buffer *bytes.Buffer, sfPattern string) {
profile, err := gprofile.ParseData(buffer.Bytes())
require.NoError(t, err)
line := findStack(t, stackCollapseProfile(t, profile), sfPattern)
assert.Nilf(t, line, "stack frame %s found", sfPattern)
}

func expectStackFrames(t *testing.T, buffer *bytes.Buffer, sfPattern string, values ...int64) {
profile, err := gprofile.ParseData(buffer.Bytes())
require.NoError(t, err)
line := findStack(t, stackCollapseProfile(t, profile), sfPattern)
assert.NotNilf(t, line, "stack frame %s not found", sfPattern)
if line != nil {
for i := range values {
assert.Equal(t, values[i], line.value[i])
assert.Equalf(t, values[i], line.value[i], "expected %v got %v", values, line.value)
}
}
}
Expand All @@ -37,14 +52,10 @@ func findStack(t *testing.T, res []stack, re string) *stack {
return &res[i]
}
}
t.Logf("no %s found", re)
for _, s := range res {
t.Log(s.line, s.value)
}
return nil
}

func stackCollapseProfile(p *gprofile.Profile) []stack {
func stackCollapseProfile(t testing.TB, p *gprofile.Profile) []stack {
var ret []stack
for _, s := range p.Sample {
var funcs []string
Expand Down Expand Up @@ -86,6 +97,11 @@ func stackCollapseProfile(p *gprofile.Profile) []stack {
unique = append(unique, s)

}
t.Log("============= stackCollapseProfile ================")
for _, s := range unique {
t.Log(s.line, s.value)
}
t.Log("===================================================")

return unique
}
Loading

0 comments on commit dc5db27

Please sign in to comment.