Commit 01c6a19e authored by Austin Clements's avatar Austin Clements

runtime: add number of forced GCs to MemStats

This adds a counter for the number of times the application forced a
GC by, e.g., calling runtime.GC(). This is useful for detecting
applications that are overusing/abusing runtime.GC() or
debug.FreeOSMemory().

Fixes #18217.

Change-Id: I990ab7a313c1b3b7a50a3d44535c460d7c54f47d
Reviewed-on: https://go-review.googlesource.com/34067Reviewed-by: default avatarRuss Cox <rsc@golang.org>
parent d4177877
...@@ -238,6 +238,7 @@ pkg plugin, type Symbol interface {} ...@@ -238,6 +238,7 @@ pkg plugin, type Symbol interface {}
pkg reflect, func Swapper(interface{}) func(int, int) pkg reflect, func Swapper(interface{}) func(int, int)
pkg runtime, func MutexProfile([]BlockProfileRecord) (int, bool) pkg runtime, func MutexProfile([]BlockProfileRecord) (int, bool)
pkg runtime, func SetMutexProfileFraction(int) int pkg runtime, func SetMutexProfileFraction(int) int
pkg runtime, type MemStats struct, NumForcedGC uint32
pkg sort, func Slice(interface{}, func(int, int) bool) pkg sort, func Slice(interface{}, func(int, int) bool)
pkg sort, func SliceIsSorted(interface{}, func(int, int) bool) bool pkg sort, func SliceIsSorted(interface{}, func(int, int) bool) bool
pkg sort, func SliceStable(interface{}, func(int, int) bool) pkg sort, func SliceStable(interface{}, func(int, int) bool)
......
...@@ -13,6 +13,9 @@ import ( ...@@ -13,6 +13,9 @@ import (
) )
func TestMemStats(t *testing.T) { func TestMemStats(t *testing.T) {
// Make sure there's at least one forced GC.
GC()
// Test that MemStats has sane values. // Test that MemStats has sane values.
st := new(MemStats) st := new(MemStats)
ReadMemStats(st) ReadMemStats(st)
...@@ -24,7 +27,7 @@ func TestMemStats(t *testing.T) { ...@@ -24,7 +27,7 @@ func TestMemStats(t *testing.T) {
st.HeapInuse == 0 || st.HeapObjects == 0 || st.StackInuse == 0 || st.HeapInuse == 0 || st.HeapObjects == 0 || st.StackInuse == 0 ||
st.StackSys == 0 || st.MSpanInuse == 0 || st.MSpanSys == 0 || st.MCacheInuse == 0 || st.StackSys == 0 || st.MSpanInuse == 0 || st.MSpanSys == 0 || st.MCacheInuse == 0 ||
st.MCacheSys == 0 || st.BuckHashSys == 0 || st.GCSys == 0 || st.OtherSys == 0 || st.MCacheSys == 0 || st.BuckHashSys == 0 || st.GCSys == 0 || st.OtherSys == 0 ||
st.NextGC == 0 { st.NextGC == 0 || st.NumForcedGC == 0 {
t.Fatalf("Zero value: %+v", *st) t.Fatalf("Zero value: %+v", *st)
} }
...@@ -33,7 +36,7 @@ func TestMemStats(t *testing.T) { ...@@ -33,7 +36,7 @@ func TestMemStats(t *testing.T) {
st.HeapIdle > 1e10 || st.HeapInuse > 1e10 || st.HeapObjects > 1e10 || st.StackInuse > 1e10 || st.HeapIdle > 1e10 || st.HeapInuse > 1e10 || st.HeapObjects > 1e10 || st.StackInuse > 1e10 ||
st.StackSys > 1e10 || st.MSpanInuse > 1e10 || st.MSpanSys > 1e10 || st.MCacheInuse > 1e10 || st.StackSys > 1e10 || st.MSpanInuse > 1e10 || st.MSpanSys > 1e10 || st.MCacheInuse > 1e10 ||
st.MCacheSys > 1e10 || st.BuckHashSys > 1e10 || st.GCSys > 1e10 || st.OtherSys > 1e10 || st.MCacheSys > 1e10 || st.BuckHashSys > 1e10 || st.GCSys > 1e10 || st.OtherSys > 1e10 ||
st.NextGC > 1e10 || st.NumGC > 1e9 || st.PauseTotalNs > 1e11 { st.NextGC > 1e10 || st.NumGC > 1e9 || st.NumForcedGC > 1e9 || st.PauseTotalNs > 1e11 {
t.Fatalf("Insanely high value (overflow?): %+v", *st) t.Fatalf("Insanely high value (overflow?): %+v", *st)
} }
...@@ -72,6 +75,10 @@ func TestMemStats(t *testing.T) { ...@@ -72,6 +75,10 @@ func TestMemStats(t *testing.T) {
t.Fatalf("PauseTotalNs(%d) < sum PauseNs(%d)", st.PauseTotalNs, pauseTotal) t.Fatalf("PauseTotalNs(%d) < sum PauseNs(%d)", st.PauseTotalNs, pauseTotal)
} }
} }
if st.NumForcedGC > st.NumGC {
t.Fatalf("NumForcedGC(%d) > NumGC(%d)", st.NumForcedGC, st.NumGC)
}
} }
func TestStringConcatenationAllocs(t *testing.T) { func TestStringConcatenationAllocs(t *testing.T) {
......
...@@ -902,7 +902,7 @@ type gcMode int ...@@ -902,7 +902,7 @@ type gcMode int
const ( const (
gcBackgroundMode gcMode = iota // concurrent GC and sweep gcBackgroundMode gcMode = iota // concurrent GC and sweep
gcForceMode // stop-the-world GC now, concurrent sweep gcForceMode // stop-the-world GC now, concurrent sweep
gcForceBlockMode // stop-the-world GC now and STW sweep gcForceBlockMode // stop-the-world GC now and STW sweep (forced by user)
) )
// gcShouldStart returns true if the exit condition for the _GCoff // gcShouldStart returns true if the exit condition for the _GCoff
...@@ -966,6 +966,9 @@ func gcStart(mode gcMode, forceTrigger bool) { ...@@ -966,6 +966,9 @@ func gcStart(mode gcMode, forceTrigger bool) {
} }
} }
// For stats, check if this GC was forced by the user.
forced := mode != gcBackgroundMode
// In gcstoptheworld debug mode, upgrade the mode accordingly. // In gcstoptheworld debug mode, upgrade the mode accordingly.
// We do this after re-checking the transition condition so // We do this after re-checking the transition condition so
// that multiple goroutines that detect the heap trigger don't // that multiple goroutines that detect the heap trigger don't
...@@ -1070,6 +1073,10 @@ func gcStart(mode gcMode, forceTrigger bool) { ...@@ -1070,6 +1073,10 @@ func gcStart(mode gcMode, forceTrigger bool) {
work.tMark, work.tMarkTerm = t, t work.tMark, work.tMarkTerm = t, t
work.heapGoal = work.heap0 work.heapGoal = work.heap0
if forced {
memstats.numforcedgc++
}
// Perform mark termination. This will restart the world. // Perform mark termination. This will restart the world.
gcMarkTermination() gcMarkTermination()
} }
......
...@@ -77,6 +77,7 @@ type mstats struct { ...@@ -77,6 +77,7 @@ type mstats struct {
pause_ns [256]uint64 // circular buffer of recent gc pause lengths pause_ns [256]uint64 // circular buffer of recent gc pause lengths
pause_end [256]uint64 // circular buffer of recent gc end times (nanoseconds since 1970) pause_end [256]uint64 // circular buffer of recent gc end times (nanoseconds since 1970)
numgc uint32 numgc uint32
numforcedgc uint32 // number of user-forced GCs
gc_cpu_fraction float64 // fraction of CPU time used by GC gc_cpu_fraction float64 // fraction of CPU time used by GC
enablegc bool enablegc bool
debuggc bool debuggc bool
...@@ -100,8 +101,6 @@ type mstats struct { ...@@ -100,8 +101,6 @@ type mstats struct {
// must be complete. // must be complete.
gc_trigger uint64 gc_trigger uint64
_ uint32 // force 8-byte alignment of heap_live and prevent an alignment check crash on MIPS32.
// heap_live is the number of bytes considered live by the GC. // heap_live is the number of bytes considered live by the GC.
// That is: retained by the most recent GC plus allocated // That is: retained by the most recent GC plus allocated
// since then. heap_live <= heap_alloc, since heap_alloc // since then. heap_live <= heap_alloc, since heap_alloc
...@@ -365,6 +364,10 @@ type MemStats struct { ...@@ -365,6 +364,10 @@ type MemStats struct {
// NumGC is the number of completed GC cycles. // NumGC is the number of completed GC cycles.
NumGC uint32 NumGC uint32
// NumForcedGC is the number of GC cycles that were forced by
// the application calling the GC function.
NumForcedGC uint32
// GCCPUFraction is the fraction of this program's available // GCCPUFraction is the fraction of this program's available
// CPU time used by the GC since the program started. // CPU time used by the GC since the program started.
// //
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment