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 }