github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/agent/pprof/pprof.go (about)

     1  // Copyright 2010 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package pprof writes runtime profiling data in the format expected
     6  // by the pprof visualization tool.
     7  //
     8  // Profiling a Go program
     9  //
    10  // The first step to profiling a Go program is to enable profiling.
    11  // Support for profiling benchmarks built with the standard testing
    12  // package is built into go test. For example, the following command
    13  // runs benchmarks in the current directory and writes the CPU and
    14  // memory profiles to cpu.prof and mem.prof:
    15  //
    16  //     go test -cpuprofile cpu.prof -memprofile mem.prof -bench .
    17  //
    18  // To add equivalent profiling support to a standalone program, add
    19  // code like the following to your main function:
    20  //
    21  //    var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to `file`")
    22  //    var memprofile = flag.String("memprofile", "", "write memory profile to `file`")
    23  //
    24  //    func main() {
    25  //        flag.Parse()
    26  //        if *cpuprofile != "" {
    27  //            f, err := os.Create(*cpuprofile)
    28  //            if err != nil {
    29  //                log.Fatal("could not create CPU profile: ", err)
    30  //            }
    31  //            defer f.Close() // error handling omitted for example
    32  //            if err := pprof.StartCPUProfile(f); err != nil {
    33  //                log.Fatal("could not start CPU profile: ", err)
    34  //            }
    35  //            defer pprof.StopCPUProfile()
    36  //        }
    37  //
    38  //        // ... rest of the program ...
    39  //
    40  //        if *memprofile != "" {
    41  //            f, err := os.Create(*memprofile)
    42  //            if err != nil {
    43  //                log.Fatal("could not create memory profile: ", err)
    44  //            }
    45  //            defer f.Close() // error handling omitted for example
    46  //            runtime.GC() // get up-to-date statistics
    47  //            if err := pprof.WriteHeapProfile(f); err != nil {
    48  //                log.Fatal("could not write memory profile: ", err)
    49  //            }
    50  //        }
    51  //    }
    52  //
    53  // There is also a standard HTTP interface to profiling data. Adding
    54  // the following line will install handlers under the /debug/pprof/
    55  // URL to download live profiles:
    56  //
    57  //    import _ "net/http/pprof"
    58  //
    59  // See the net/http/pprof package for more details.
    60  //
    61  // Profiles can then be visualized with the pprof tool:
    62  //
    63  //    go tool pprof cpu.prof
    64  //
    65  // There are many commands available from the pprof command line.
    66  // Commonly used commands include "top", which prints a summary of the
    67  // top program hot-spots, and "web", which opens an interactive graph
    68  // of hot-spots and their call graphs. Use "help" for information on
    69  // all pprof commands.
    70  //
    71  // For more information about pprof, see
    72  // https://github.com/google/pprof/blob/master/doc/README.md.
    73  package pprof
    74  
    75  import (
    76  	"bufio"
    77  	"fmt"
    78  	"io"
    79  	"math"
    80  	"runtime"
    81  	"sort"
    82  	"strings"
    83  	"sync"
    84  	"text/tabwriter"
    85  	"time"
    86  	"unsafe"
    87  )
    88  
    89  // printStackRecord prints the function + source line information
    90  // for a single stack trace.
    91  func printStackRecord(w io.Writer, stk []uintptr, allFrames bool) {
    92  	show := allFrames
    93  	frames := runtime.CallersFrames(stk)
    94  	for {
    95  		frame, more := frames.Next()
    96  		name := frame.Function
    97  		if name == "" {
    98  			show = true
    99  			fmt.Fprintf(w, "#\t%#x\n", frame.PC)
   100  		} else if name != "runtime.goexit" && (show || !strings.HasPrefix(name, "runtime.")) {
   101  			// Hide runtime.goexit and any runtime functions at the beginning.
   102  			// This is useful mainly for allocation traces.
   103  			show = true
   104  			fmt.Fprintf(w, "#\t%#x\t%s+%#x\t%s:%d\n", frame.PC, name, frame.PC-frame.Entry, frame.File, frame.Line)
   105  		}
   106  		if !more {
   107  			break
   108  		}
   109  	}
   110  	if !show {
   111  		// We didn't print anything; do it again,
   112  		// and this time include runtime functions.
   113  		printStackRecord(w, stk, true)
   114  		return
   115  	}
   116  	fmt.Fprintf(w, "\n")
   117  }
   118  
   119  // writeHeapProto writes the current heap profile in protobuf format to w.
   120  func writeHeapProto(w io.Writer, p []runtime.MemProfileRecord, rate int64, defaultSampleType string) error {
   121  	b := newProfileBuilder(w)
   122  	b.pbValueType(tagProfile_PeriodType, "space", "bytes")
   123  	b.pb.int64Opt(tagProfile_Period, rate)
   124  	b.pbValueType(tagProfile_SampleType, "alloc_objects", "count")
   125  	b.pbValueType(tagProfile_SampleType, "alloc_space", "bytes")
   126  	b.pbValueType(tagProfile_SampleType, "inuse_objects", "count")
   127  	b.pbValueType(tagProfile_SampleType, "inuse_space", "bytes")
   128  	if defaultSampleType != "" {
   129  		b.pb.int64Opt(tagProfile_DefaultSampleType, b.stringIndex(defaultSampleType))
   130  	}
   131  
   132  	values := []int64{0, 0, 0, 0}
   133  	var locs []uint64
   134  	for _, r := range p {
   135  		hideRuntime := true
   136  		for tries := 0; tries < 2; tries++ {
   137  			stk := r.Stack()
   138  			// For heap profiles, all stack
   139  			// addresses are return PCs, which is
   140  			// what appendLocsForStack expects.
   141  			if hideRuntime {
   142  				for i, addr := range stk {
   143  					if f := runtime.FuncForPC(addr); f != nil && strings.HasPrefix(f.Name(), "runtime.") {
   144  						continue
   145  					}
   146  					// Found non-runtime. Show any runtime uses above it.
   147  					stk = stk[i:]
   148  					break
   149  				}
   150  			}
   151  			locs = b.appendLocsForStack(locs[:0], stk)
   152  			if len(locs) > 0 {
   153  				break
   154  			}
   155  			hideRuntime = false // try again, and show all frames next time.
   156  		}
   157  
   158  		values[0], values[1] = scaleHeapSample(r.AllocObjects, r.AllocBytes, rate)
   159  		values[2], values[3] = scaleHeapSample(r.InUseObjects(), r.InUseBytes(), rate)
   160  		var blockSize int64
   161  		if r.AllocObjects > 0 {
   162  			blockSize = r.AllocBytes / r.AllocObjects
   163  		}
   164  		b.pbSample(values, locs, func() {
   165  			if blockSize != 0 {
   166  				b.pbLabel(tagSample_Label, "bytes", "", blockSize)
   167  			}
   168  		})
   169  	}
   170  	b.build()
   171  	return nil
   172  }
   173  
   174  // scaleHeapSample adjusts the data from a heap Sample to
   175  // account for its probability of appearing in the collected
   176  // data. heap profiles are a sampling of the memory allocations
   177  // requests in a program. We estimate the unsampled value by dividing
   178  // each collected sample by its probability of appearing in the
   179  // profile. heap profiles rely on a poisson process to determine
   180  // which samples to collect, based on the desired average collection
   181  // rate R. The probability of a sample of size S to appear in that
   182  // profile is 1-exp(-S/R).
   183  func scaleHeapSample(count, size, rate int64) (int64, int64) {
   184  	if count == 0 || size == 0 {
   185  		return 0, 0
   186  	}
   187  
   188  	if rate <= 1 {
   189  		// if rate==1 all samples were collected so no adjustment is needed.
   190  		// if rate<1 treat as unknown and skip scaling.
   191  		return count, size
   192  	}
   193  
   194  	avgSize := float64(size) / float64(count)
   195  	scale := 1 / (1 - math.Exp(-avgSize/float64(rate)))
   196  
   197  	return int64(float64(count) * scale), int64(float64(size) * scale)
   198  }
   199  
   200  // WriteHeapProfile is shorthand for Lookup("heap").WriteTo(w, 0).
   201  // It is preserved for backwards compatibility.
   202  func WriteHeapProfile(w io.Writer) error {
   203  	return writeHeap(w, 0)
   204  }
   205  
   206  // writeHeap writes the current runtime heap profile to w.
   207  func writeHeap(w io.Writer, debug int) error {
   208  	return writeHeapInternal(w, debug, "")
   209  }
   210  
   211  func writeHeapInternal(w io.Writer, debug int, defaultSampleType string) error {
   212  	var memStats *runtime.MemStats
   213  	if debug != 0 {
   214  		// Read mem stats first, so that our other allocations
   215  		// do not appear in the statistics.
   216  		memStats = new(runtime.MemStats)
   217  		runtime.ReadMemStats(memStats)
   218  	}
   219  
   220  	// Find out how many records there are (MemProfile(nil, true)),
   221  	// allocate that many records, and get the data.
   222  	// There's a race—more records might be added between
   223  	// the two calls—so allocate a few extra records for safety
   224  	// and also try again if we're very unlucky.
   225  	// The loop should only execute one iteration in the common case.
   226  	var p []runtime.MemProfileRecord
   227  	n, ok := runtime.MemProfile(nil, true)
   228  	for {
   229  		// Allocate room for a slightly bigger profile,
   230  		// in case a few more entries have been added
   231  		// since the call to MemProfile.
   232  		p = make([]runtime.MemProfileRecord, n+50)
   233  		n, ok = runtime.MemProfile(p, true)
   234  		if ok {
   235  			p = p[0:n]
   236  			break
   237  		}
   238  		// Profile grew; try again.
   239  	}
   240  
   241  	if debug == 0 {
   242  		return writeHeapProto(w, p, int64(runtime.MemProfileRate), defaultSampleType)
   243  	}
   244  
   245  	sort.Slice(p, func(i, j int) bool { return p[i].InUseBytes() > p[j].InUseBytes() })
   246  
   247  	b := bufio.NewWriter(w)
   248  	tw := tabwriter.NewWriter(b, 1, 8, 1, '\t', 0)
   249  	w = tw
   250  
   251  	var total runtime.MemProfileRecord
   252  	for i := range p {
   253  		r := &p[i]
   254  		total.AllocBytes += r.AllocBytes
   255  		total.AllocObjects += r.AllocObjects
   256  		total.FreeBytes += r.FreeBytes
   257  		total.FreeObjects += r.FreeObjects
   258  	}
   259  
   260  	// Technically the rate is MemProfileRate not 2*MemProfileRate,
   261  	// but early versions of the C++ heap profiler reported 2*MemProfileRate,
   262  	// so that's what pprof has come to expect.
   263  	fmt.Fprintf(w, "heap profile: %d: %d [%d: %d] @ heap/%d\n",
   264  		total.InUseObjects(), total.InUseBytes(),
   265  		total.AllocObjects, total.AllocBytes,
   266  		2*runtime.MemProfileRate)
   267  
   268  	for i := range p {
   269  		r := &p[i]
   270  		fmt.Fprintf(w, "%d: %d [%d: %d] @",
   271  			r.InUseObjects(), r.InUseBytes(),
   272  			r.AllocObjects, r.AllocBytes)
   273  		for _, pc := range r.Stack() {
   274  			fmt.Fprintf(w, " %#x", pc)
   275  		}
   276  		fmt.Fprintf(w, "\n")
   277  		printStackRecord(w, r.Stack(), false)
   278  	}
   279  
   280  	// Print memstats information too.
   281  	// Pprof will ignore, but useful for people
   282  	s := memStats
   283  	fmt.Fprintf(w, "\n# runtime.MemStats\n")
   284  	fmt.Fprintf(w, "# Alloc = %d\n", s.Alloc)
   285  	fmt.Fprintf(w, "# TotalAlloc = %d\n", s.TotalAlloc)
   286  	fmt.Fprintf(w, "# Sys = %d\n", s.Sys)
   287  	fmt.Fprintf(w, "# Lookups = %d\n", s.Lookups)
   288  	fmt.Fprintf(w, "# Mallocs = %d\n", s.Mallocs)
   289  	fmt.Fprintf(w, "# Frees = %d\n", s.Frees)
   290  
   291  	fmt.Fprintf(w, "# HeapAlloc = %d\n", s.HeapAlloc)
   292  	fmt.Fprintf(w, "# HeapSys = %d\n", s.HeapSys)
   293  	fmt.Fprintf(w, "# HeapIdle = %d\n", s.HeapIdle)
   294  	fmt.Fprintf(w, "# HeapInuse = %d\n", s.HeapInuse)
   295  	fmt.Fprintf(w, "# HeapReleased = %d\n", s.HeapReleased)
   296  	fmt.Fprintf(w, "# HeapObjects = %d\n", s.HeapObjects)
   297  
   298  	fmt.Fprintf(w, "# Stack = %d / %d\n", s.StackInuse, s.StackSys)
   299  	fmt.Fprintf(w, "# MSpan = %d / %d\n", s.MSpanInuse, s.MSpanSys)
   300  	fmt.Fprintf(w, "# MCache = %d / %d\n", s.MCacheInuse, s.MCacheSys)
   301  	fmt.Fprintf(w, "# BuckHashSys = %d\n", s.BuckHashSys)
   302  	fmt.Fprintf(w, "# GCSys = %d\n", s.GCSys)
   303  	fmt.Fprintf(w, "# OtherSys = %d\n", s.OtherSys)
   304  
   305  	fmt.Fprintf(w, "# NextGC = %d\n", s.NextGC)
   306  	fmt.Fprintf(w, "# LastGC = %d\n", s.LastGC)
   307  	fmt.Fprintf(w, "# PauseNs = %d\n", s.PauseNs)
   308  	fmt.Fprintf(w, "# PauseEnd = %d\n", s.PauseEnd)
   309  	fmt.Fprintf(w, "# NumGC = %d\n", s.NumGC)
   310  	fmt.Fprintf(w, "# NumForcedGC = %d\n", s.NumForcedGC)
   311  	fmt.Fprintf(w, "# GCCPUFraction = %v\n", s.GCCPUFraction)
   312  	fmt.Fprintf(w, "# DebugGC = %v\n", s.DebugGC)
   313  
   314  	tw.Flush()
   315  	return b.Flush()
   316  }
   317  
   318  var cpu struct {
   319  	sync.Mutex
   320  	profiling bool
   321  	done      chan bool
   322  }
   323  
   324  // StartCPUProfile enables CPU profiling for the current process.
   325  // While profiling, the profile will be buffered and written to w.
   326  // StartCPUProfile returns an error if profiling is already enabled.
   327  //
   328  // On Unix-like systems, StartCPUProfile does not work by default for
   329  // Go code built with -buildmode=c-archive or -buildmode=c-shared.
   330  // StartCPUProfile relies on the SIGPROF signal, but that signal will
   331  // be delivered to the main program's SIGPROF signal handler (if any)
   332  // not to the one used by Go. To make it work, call os/signal.Notify
   333  // for syscall.SIGPROF, but note that doing so may break any profiling
   334  // being done by the main program.
   335  func StartCPUProfile(w io.Writer, hz uint32) error {
   336  	// The runtime routines allow a variable profiling rate,
   337  	// but in practice operating systems cannot trigger signals
   338  	// at more than about 500 Hz, and our processing of the
   339  	// signal is not cheap (mostly getting the stack trace).
   340  	// 100 Hz is a reasonable choice: it is frequent enough to
   341  	// produce useful data, rare enough not to bog down the
   342  	// system, and a nice round number to make it easy to
   343  	// convert sample counts to seconds. Instead of requiring
   344  	// each client to specify the frequency, we hard code it.
   345  	cpu.Lock()
   346  	defer cpu.Unlock()
   347  	if cpu.done == nil {
   348  		cpu.done = make(chan bool)
   349  	}
   350  	// Double-check.
   351  	if cpu.profiling {
   352  		return fmt.Errorf("cpu profiling already in use")
   353  	}
   354  	cpu.profiling = true
   355  	runtime.SetCPUProfileRate(int(hz))
   356  	go profileWriter(w)
   357  	return nil
   358  }
   359  
   360  // readProfile, provided by the runtime, returns the next chunk of
   361  // binary CPU profiling stack trace data, blocking until data is available.
   362  // If profiling is turned off and all the profile data accumulated while it was
   363  // on has been returned, readProfile returns eof=true.
   364  // The caller must save the returned data and tags before calling readProfile again.
   365  //go:linkname readProfile runtime/pprof.readProfile
   366  func readProfile() (data []uint64, tags []unsafe.Pointer, eof bool)
   367  
   368  func profileWriter(w io.Writer) {
   369  	b := newProfileBuilder(w)
   370  	var err error
   371  	for {
   372  		time.Sleep(100 * time.Millisecond)
   373  		data, tags, eof := readProfile()
   374  		if e := b.addCPUData(data, tags); e != nil && err == nil {
   375  			err = e
   376  		}
   377  		if eof {
   378  			break
   379  		}
   380  	}
   381  	if err != nil {
   382  		// The runtime should never produce an invalid or truncated profile.
   383  		// It drops records that can't fit into its log buffers.
   384  		panic("runtime/pprof: converting profile: " + err.Error())
   385  	}
   386  	b.build()
   387  	cpu.done <- true
   388  }
   389  
   390  // StopCPUProfile stops the current CPU profile, if any.
   391  // StopCPUProfile only returns after all the writes for the
   392  // profile have completed.
   393  func StopCPUProfile() {
   394  	cpu.Lock()
   395  	defer cpu.Unlock()
   396  
   397  	if !cpu.profiling {
   398  		return
   399  	}
   400  	cpu.profiling = false
   401  	runtime.SetCPUProfileRate(0)
   402  	<-cpu.done
   403  }