aboutsummaryrefslogtreecommitdiff
path: root/src/runtime/pprof
diff options
context:
space:
mode:
authorVlad Saioc <vsaioc@uber.com>2025-10-02 11:57:58 +0000
committerGopher Robot <gobot@golang.org>2025-10-02 11:06:21 -0700
commit8c68a1c1abade565c6719159858e76f9b122ddc8 (patch)
tree3da970e4a670a4ac23d748df30a87cd8daf9555a /src/runtime/pprof
parent84db201ae18c889acdefe20c8a903b188328f16d (diff)
downloadgo-8c68a1c1abade565c6719159858e76f9b122ddc8.tar.xz
runtime,net/http/pprof: goroutine leak detection by using the garbage collector
Proposal #74609 Change-Id: I97a754b128aac1bc5b7b9ab607fcd5bb390058c8 GitHub-Last-Rev: 60f2a192badf415112246de8bc6c0084085314f6 GitHub-Pull-Request: golang/go#74622 Reviewed-on: https://go-review.googlesource.com/c/go/+/688335 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: t hepudds <thepudds1460@gmail.com> Auto-Submit: Michael Knyszek <mknyszek@google.com> Reviewed-by: Michael Knyszek <mknyszek@google.com> Reviewed-by: Carlos Amedee <carlos@golang.org>
Diffstat (limited to 'src/runtime/pprof')
-rw-r--r--src/runtime/pprof/pprof.go45
-rw-r--r--src/runtime/pprof/runtime.go6
2 files changed, 45 insertions, 6 deletions
diff --git a/src/runtime/pprof/pprof.go b/src/runtime/pprof/pprof.go
index 55563009b3..b524e992b8 100644
--- a/src/runtime/pprof/pprof.go
+++ b/src/runtime/pprof/pprof.go
@@ -80,6 +80,7 @@ import (
"cmp"
"fmt"
"internal/abi"
+ "internal/goexperiment"
"internal/profilerecord"
"io"
"runtime"
@@ -105,12 +106,13 @@ import (
//
// Each Profile has a unique name. A few profiles are predefined:
//
-// goroutine - stack traces of all current goroutines
-// heap - a sampling of memory allocations of live objects
-// allocs - a sampling of all past memory allocations
-// threadcreate - stack traces that led to the creation of new OS threads
-// block - stack traces that led to blocking on synchronization primitives
-// mutex - stack traces of holders of contended mutexes
+// goroutine - stack traces of all current goroutines
+// goroutineleak - stack traces of all leaked goroutines
+// allocs - a sampling of all past memory allocations
+// heap - a sampling of memory allocations of live objects
+// threadcreate - stack traces that led to the creation of new OS threads
+// block - stack traces that led to blocking on synchronization primitives
+// mutex - stack traces of holders of contended mutexes
//
// These predefined profiles maintain themselves and panic on an explicit
// [Profile.Add] or [Profile.Remove] method call.
@@ -169,6 +171,7 @@ import (
// holds a lock for 1s while 5 other goroutines are waiting for the entire
// second to acquire the lock, its unlock call stack will report 5s of
// contention.
+
type Profile struct {
name string
mu sync.Mutex
@@ -189,6 +192,12 @@ var goroutineProfile = &Profile{
write: writeGoroutine,
}
+var goroutineLeakProfile = &Profile{
+ name: "goroutineleak",
+ count: runtime_goroutineleakcount,
+ write: writeGoroutineLeak,
+}
+
var threadcreateProfile = &Profile{
name: "threadcreate",
count: countThreadCreate,
@@ -231,6 +240,9 @@ func lockProfiles() {
"block": blockProfile,
"mutex": mutexProfile,
}
+ if goexperiment.GoroutineLeakProfile {
+ profiles.m["goroutineleak"] = goroutineLeakProfile
+ }
}
}
@@ -275,6 +287,7 @@ func Profiles() []*Profile {
all := make([]*Profile, 0, len(profiles.m))
for _, p := range profiles.m {
+
all = append(all, p)
}
@@ -747,6 +760,23 @@ func writeGoroutine(w io.Writer, debug int) error {
return writeRuntimeProfile(w, debug, "goroutine", pprof_goroutineProfileWithLabels)
}
+// writeGoroutineLeak first invokes a GC cycle that performs goroutine leak detection.
+// It then writes the goroutine profile, filtering for leaked goroutines.
+func writeGoroutineLeak(w io.Writer, debug int) error {
+ // Run the GC with leak detection first so that leaked goroutines
+ // may transition to the leaked state.
+ runtime_goroutineLeakGC()
+
+ // If the debug flag is set sufficiently high, just defer to writing goroutine stacks
+ // like in a regular goroutine profile. Include non-leaked goroutines, too.
+ if debug >= 2 {
+ return writeGoroutineStacks(w)
+ }
+
+ // Otherwise, write the goroutine leak profile.
+ return writeRuntimeProfile(w, debug, "goroutineleak", pprof_goroutineLeakProfileWithLabels)
+}
+
func writeGoroutineStacks(w io.Writer) error {
// We don't know how big the buffer needs to be to collect
// all the goroutines. Start with 1 MB and try a few times, doubling each time.
@@ -969,6 +999,9 @@ func writeProfileInternal(w io.Writer, debug int, name string, runtimeProfile fu
//go:linkname pprof_goroutineProfileWithLabels runtime.pprof_goroutineProfileWithLabels
func pprof_goroutineProfileWithLabels(p []profilerecord.StackRecord, labels []unsafe.Pointer) (n int, ok bool)
+//go:linkname pprof_goroutineLeakProfileWithLabels runtime.pprof_goroutineLeakProfileWithLabels
+func pprof_goroutineLeakProfileWithLabels(p []profilerecord.StackRecord, labels []unsafe.Pointer) (n int, ok bool)
+
//go:linkname pprof_cyclesPerSecond runtime/pprof.runtime_cyclesPerSecond
func pprof_cyclesPerSecond() int64
diff --git a/src/runtime/pprof/runtime.go b/src/runtime/pprof/runtime.go
index 8d37c7d3ad..690cab81ab 100644
--- a/src/runtime/pprof/runtime.go
+++ b/src/runtime/pprof/runtime.go
@@ -29,6 +29,12 @@ func runtime_setProfLabel(labels unsafe.Pointer)
// runtime_getProfLabel is defined in runtime/proflabel.go.
func runtime_getProfLabel() unsafe.Pointer
+// runtime_goroutineleakcount is defined in runtime/proc.go.
+func runtime_goroutineleakcount() int
+
+// runtime_goroutineLeakGC is defined in runtime/mgc.go.
+func runtime_goroutineLeakGC()
+
// SetGoroutineLabels sets the current goroutine's labels to match ctx.
// A new goroutine inherits the labels of the goroutine that created it.
// This is a lower-level API than [Do], which should be used instead when possible.