1// Unless explicitly stated otherwise all files in this repository are licensed 2// under the Apache License Version 2.0. 3// This product includes software developed at Datadog (https://www.datadoghq.com/). 4// Copyright 2016 Datadog, Inc. 5 6package profiler 7 8import ( 9 "bytes" 10 "io" 11 "io/ioutil" 12 "testing" 13 "time" 14 15 pprofile "github.com/google/pprof/profile" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18) 19 20func TestRunProfile(t *testing.T) { 21 t.Run("heap", func(t *testing.T) { 22 defer func(old func(_ io.Writer) error) { writeHeapProfile = old }(writeHeapProfile) 23 writeHeapProfile = func(w io.Writer) error { 24 _, err := w.Write([]byte("my-heap-profile")) 25 return err 26 } 27 p, err := unstartedProfiler() 28 prof, err := p.runProfile(HeapProfile) 29 require.NoError(t, err) 30 assert.Equal(t, "heap.pprof", prof.name) 31 assert.Equal(t, []byte("my-heap-profile"), prof.data) 32 }) 33 34 t.Run("cpu", func(t *testing.T) { 35 defer func(old func(_ io.Writer) error) { startCPUProfile = old }(startCPUProfile) 36 startCPUProfile = func(w io.Writer) error { 37 _, err := w.Write([]byte("my-cpu-profile")) 38 return err 39 } 40 defer func(old func()) { stopCPUProfile = old }(stopCPUProfile) 41 stopCPUProfile = func() {} 42 43 p, err := unstartedProfiler(CPUDuration(10 * time.Millisecond)) 44 start := time.Now() 45 prof, err := p.runProfile(CPUProfile) 46 end := time.Now() 47 require.NoError(t, err) 48 assert.True(t, end.Sub(start) > 10*time.Millisecond) 49 assert.Equal(t, "cpu.pprof", prof.name) 50 assert.Equal(t, []byte("my-cpu-profile"), prof.data) 51 }) 52 53 t.Run("mutex", func(t *testing.T) { 54 defer func(old func(_ string, _ io.Writer, _ int) error) { lookupProfile = old }(lookupProfile) 55 lookupProfile = func(name string, w io.Writer, _ int) error { 56 _, err := w.Write([]byte(name)) 57 return err 58 } 59 60 p, err := unstartedProfiler() 61 prof, err := p.runProfile(MutexProfile) 62 require.NoError(t, err) 63 assert.Equal(t, "mutex.pprof", prof.name) 64 assert.Equal(t, []byte("mutex"), prof.data) 65 }) 66 67 t.Run("block", func(t *testing.T) { 68 defer func(old func(_ string, _ io.Writer, _ int) error) { lookupProfile = old }(lookupProfile) 69 lookupProfile = func(name string, w io.Writer, _ int) error { 70 _, err := w.Write([]byte(name)) 71 return err 72 } 73 74 p, err := unstartedProfiler() 75 prof, err := p.runProfile(BlockProfile) 76 require.NoError(t, err) 77 assert.Equal(t, "block.pprof", prof.name) 78 assert.Equal(t, []byte("block"), prof.data) 79 }) 80 81 t.Run("goroutine", func(t *testing.T) { 82 defer func(old func(_ string, _ io.Writer, _ int) error) { lookupProfile = old }(lookupProfile) 83 lookupProfile = func(name string, w io.Writer, _ int) error { 84 _, err := w.Write([]byte(name)) 85 return err 86 } 87 88 p, err := unstartedProfiler() 89 prof, err := p.runProfile(GoroutineProfile) 90 require.NoError(t, err) 91 assert.Equal(t, "goroutines.pprof", prof.name) 92 assert.Equal(t, []byte("goroutine"), prof.data) 93 }) 94 95 t.Run("goroutinewait", func(t *testing.T) { 96 const sample = ` 97goroutine 1 [running]: 98main.main() 99 /example/main.go:152 +0x3d2 100 101goroutine 2 [running]: 102badFunctionCall)( 103 104goroutine 3 [sleep, 1 minutes]: 105time.Sleep(0x3b9aca00) 106 /usr/local/Cellar/go/1.15.6/libexec/src/runtime/time.go:188 +0xbf 107created by main.indirectShortSleepLoop2 108 /example/main.go:185 +0x35 109 110goroutine 4 [running]: 111main.stackDump(0x62) 112 /example/max_frames.go:20 +0x131 113main.main() 114 /example/max_frames.go:11 +0x2a 115...additional frames elided... 116` 117 118 defer func(old func(_ string, _ io.Writer, _ int) error) { lookupProfile = old }(lookupProfile) 119 lookupProfile = func(name string, w io.Writer, _ int) error { 120 _, err := w.Write([]byte(sample)) 121 return err 122 } 123 124 p, err := unstartedProfiler() 125 prof, err := p.runProfile(expGoroutineWaitProfile) 126 require.NoError(t, err) 127 require.Equal(t, "goroutineswait.pprof", prof.name) 128 129 // pro tip: enable line below to inspect the pprof output using cli tools 130 // ioutil.WriteFile(prof.name, prof.data, 0644) 131 132 requireFunctions := func(t *testing.T, s *pprofile.Sample, want []string) { 133 t.Helper() 134 var got []string 135 for _, loc := range s.Location { 136 got = append(got, loc.Line[0].Function.Name) 137 } 138 require.Equal(t, want, got) 139 } 140 141 pp, err := pprofile.Parse(bytes.NewReader(prof.data)) 142 require.NoError(t, err) 143 // timestamp 144 require.NotEqual(t, int64(0), pp.TimeNanos) 145 // 1 sample type 146 require.Equal(t, 1, len(pp.SampleType)) 147 // 3 valid samples, 1 invalid sample (added as comment) 148 require.Equal(t, 3, len(pp.Sample)) 149 require.Equal(t, 1, len(pp.Comments)) 150 // Wait duration 151 require.Equal(t, []int64{time.Minute.Nanoseconds()}, pp.Sample[1].Value) 152 // Labels 153 require.Equal(t, []string{"running"}, pp.Sample[0].Label["state"]) 154 require.Equal(t, []string{"false"}, pp.Sample[0].Label["lockedm"]) 155 require.Equal(t, []int64{3}, pp.Sample[1].NumLabel["goid"]) 156 require.Equal(t, []string{"id"}, pp.Sample[1].NumUnit["goid"]) 157 // Virtual frame for "frames elided" goroutine 158 requireFunctions(t, pp.Sample[2], []string{ 159 "main.stackDump", 160 "main.main", 161 "...additional frames elided...", 162 }) 163 // Virtual frame go "created by" frame 164 requireFunctions(t, pp.Sample[1], []string{ 165 "time.Sleep", 166 "main.indirectShortSleepLoop2", 167 }) 168 }) 169} 170 171func Test_goroutineDebug2ToPprof_CrashSafety(t *testing.T) { 172 err := goroutineDebug2ToPprof(panicReader{}, ioutil.Discard, time.Time{}) 173 require.NotNil(t, err) 174 require.Equal(t, "panic: 42", err.Error()) 175} 176 177// panicReader is used to create a panic inside of stackparse.Parse() 178type panicReader struct{} 179 180func (c panicReader) Read(_ []byte) (int, error) { 181 panic("42") 182} 183