Skip to content

Commit d39a89f

Browse files
committed
runtime,runtime/metrics: add metric for distribution of GC pauses
For #37112. Change-Id: Ibb0425c9c582ae3da3b2662d5bbe830d7df9079c Reviewed-on: https://go-review.googlesource.com/c/go/+/247047 Run-TryBot: Michael Knyszek <[email protected]> TryBot-Result: Go Bot <[email protected]> Trust: Michael Knyszek <[email protected]> Reviewed-by: Michael Pratt <[email protected]>
1 parent 36c5edd commit d39a89f

File tree

6 files changed

+54
-0
lines changed

6 files changed

+54
-0
lines changed

src/runtime/metrics.go

+9
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,15 @@ func initMetrics() {
102102
out.scalar = in.heapStats.numObjects
103103
},
104104
},
105+
"/gc/pauses:seconds": {
106+
compute: func(_ *statAggregate, out *metricValue) {
107+
hist := out.float64HistOrInit(timeHistBuckets)
108+
hist.counts[len(hist.counts)-1] = atomic.Load64(&memstats.gcPauseDist.overflow)
109+
for i := range hist.buckets {
110+
hist.counts[i] = atomic.Load64(&memstats.gcPauseDist.counts[i])
111+
}
112+
},
113+
},
105114
"/memory/classes/heap/free:bytes": {
106115
deps: makeStatDepSet(heapStatsDep),
107116
compute: func(in *statAggregate, out *metricValue) {

src/runtime/metrics/description.go

+5
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ var allDesc = []Description{
8888
Description: "Number of objects, live or unswept, occupying heap memory.",
8989
Kind: KindUint64,
9090
},
91+
{
92+
Name: "/gc/pauses:seconds",
93+
Description: "Distribution individual GC-related stop-the-world pause latencies.",
94+
Kind: KindFloat64Histogram,
95+
},
9196
{
9297
Name: "/memory/classes/heap/free:bytes",
9398
Description: "Memory that is available for allocation, and may be returned to the underlying system.",

src/runtime/metrics/doc.go

+3
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ Supported metrics
6565
/gc/heap/objects:objects
6666
Number of objects, live or unswept, occupying heap memory.
6767
68+
/gc/pauses:seconds
69+
Distribution individual GC-related stop-the-world pause latencies.
70+
6871
/memory/classes/heap/free:bytes
6972
Memory that is available for allocation, and may be returned
7073
to the underlying system.

src/runtime/metrics_test.go

+22
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ func TestReadMetricsConsistency(t *testing.T) {
9090
// things (e.g. allocating) so what we read can't reasonably compared
9191
// to runtime values.
9292

93+
// Run a few GC cycles to get some of the stats to be non-zero.
94+
runtime.GC()
95+
runtime.GC()
96+
runtime.GC()
97+
9398
// Read all the supported metrics through the metrics package.
9499
descs, samples := prepareAllMetricsSamples()
95100
metrics.Read(samples)
@@ -102,6 +107,10 @@ func TestReadMetricsConsistency(t *testing.T) {
102107
alloc, free *metrics.Float64Histogram
103108
total uint64
104109
}
110+
var gc struct {
111+
numGC uint64
112+
pauses uint64
113+
}
105114
for i := range samples {
106115
kind := samples[i].Value.Kind()
107116
if want := descs[samples[i].Name].Kind; kind != want {
@@ -128,6 +137,14 @@ func TestReadMetricsConsistency(t *testing.T) {
128137
objects.alloc = samples[i].Value.Float64Histogram()
129138
case "/gc/heap/frees-by-size:objects":
130139
objects.free = samples[i].Value.Float64Histogram()
140+
case "/gc/cycles:gc-cycles":
141+
gc.numGC = samples[i].Value.Uint64()
142+
case "/gc/pauses:seconds":
143+
h := samples[i].Value.Float64Histogram()
144+
gc.pauses = 0
145+
for i := range h.Counts {
146+
gc.pauses += h.Counts[i]
147+
}
131148
}
132149
}
133150
if totalVirtual.got != totalVirtual.want {
@@ -159,6 +176,11 @@ func TestReadMetricsConsistency(t *testing.T) {
159176
}
160177
}
161178
}
179+
// The current GC has at least 2 pauses per GC.
180+
// Check to see if that value makes sense.
181+
if gc.pauses < gc.numGC*2 {
182+
t.Errorf("fewer pauses than expected: got %d, want at least %d", gc.pauses, gc.numGC*2)
183+
}
162184
}
163185

164186
func BenchmarkReadMetricsLatency(b *testing.B) {

src/runtime/mgc.go

+3
Original file line numberDiff line numberDiff line change
@@ -1418,6 +1418,7 @@ func gcStart(trigger gcTrigger) {
14181418
now = startTheWorldWithSema(trace.enabled)
14191419
work.pauseNS += now - work.pauseStart
14201420
work.tMark = now
1421+
memstats.gcPauseDist.record(now - work.pauseStart)
14211422
})
14221423

14231424
// Release the world sema before Gosched() in STW mode
@@ -1565,6 +1566,7 @@ top:
15651566
systemstack(func() {
15661567
now := startTheWorldWithSema(true)
15671568
work.pauseNS += now - work.pauseStart
1569+
memstats.gcPauseDist.record(now - work.pauseStart)
15681570
})
15691571
semrelease(&worldsema)
15701572
goto top
@@ -1677,6 +1679,7 @@ func gcMarkTermination(nextTriggerRatio float64) {
16771679
unixNow := sec*1e9 + int64(nsec)
16781680
work.pauseNS += now - work.pauseStart
16791681
work.tEnd = now
1682+
memstats.gcPauseDist.record(now - work.pauseStart)
16801683
atomic.Store64(&memstats.last_gc_unix, uint64(unixNow)) // must be Unix time to make sense to user
16811684
atomic.Store64(&memstats.last_gc_nanotime, uint64(now)) // monotonic time for us
16821685
memstats.pause_ns[memstats.numgc%uint32(len(memstats.pause_ns))] = uint64(work.pauseNS)

src/runtime/mstats.go

+12
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,14 @@ type mstats struct {
157157

158158
// heapStats is a set of statistics
159159
heapStats consistentHeapStats
160+
161+
_ uint32 // ensure gcPauseDist is aligned
162+
163+
// gcPauseDist represents the distribution of all GC-related
164+
// application pauses in the runtime.
165+
//
166+
// Each individual pause is counted separately, unlike pause_ns.
167+
gcPauseDist timeHistogram
160168
}
161169

162170
var memstats mstats
@@ -443,6 +451,10 @@ func init() {
443451
println(offset)
444452
throw("memstats.heapStats not aligned to 8 bytes")
445453
}
454+
if offset := unsafe.Offsetof(memstats.gcPauseDist); offset%8 != 0 {
455+
println(offset)
456+
throw("memstats.gcPauseDist not aligned to 8 bytes")
457+
}
446458
// Ensure the size of heapStatsDelta causes adjacent fields/slots (e.g.
447459
// [3]heapStatsDelta) to be 8-byte aligned.
448460
if size := unsafe.Sizeof(heapStatsDelta{}); size%8 != 0 {

0 commit comments

Comments
 (0)