mirror of https://github.com/golang/go.git
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 <mknyszek@google.com> TryBot-Result: Go Bot <gobot@golang.org> Trust: Michael Knyszek <mknyszek@google.com> Reviewed-by: Michael Pratt <mpratt@google.com>
This commit is contained in:
parent
36c5edd8d9
commit
d39a89fd58
|
|
@ -102,6 +102,15 @@ func initMetrics() {
|
||||||
out.scalar = in.heapStats.numObjects
|
out.scalar = in.heapStats.numObjects
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"/gc/pauses:seconds": {
|
||||||
|
compute: func(_ *statAggregate, out *metricValue) {
|
||||||
|
hist := out.float64HistOrInit(timeHistBuckets)
|
||||||
|
hist.counts[len(hist.counts)-1] = atomic.Load64(&memstats.gcPauseDist.overflow)
|
||||||
|
for i := range hist.buckets {
|
||||||
|
hist.counts[i] = atomic.Load64(&memstats.gcPauseDist.counts[i])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
"/memory/classes/heap/free:bytes": {
|
"/memory/classes/heap/free:bytes": {
|
||||||
deps: makeStatDepSet(heapStatsDep),
|
deps: makeStatDepSet(heapStatsDep),
|
||||||
compute: func(in *statAggregate, out *metricValue) {
|
compute: func(in *statAggregate, out *metricValue) {
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,11 @@ var allDesc = []Description{
|
||||||
Description: "Number of objects, live or unswept, occupying heap memory.",
|
Description: "Number of objects, live or unswept, occupying heap memory.",
|
||||||
Kind: KindUint64,
|
Kind: KindUint64,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "/gc/pauses:seconds",
|
||||||
|
Description: "Distribution individual GC-related stop-the-world pause latencies.",
|
||||||
|
Kind: KindFloat64Histogram,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "/memory/classes/heap/free:bytes",
|
Name: "/memory/classes/heap/free:bytes",
|
||||||
Description: "Memory that is available for allocation, and may be returned to the underlying system.",
|
Description: "Memory that is available for allocation, and may be returned to the underlying system.",
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,9 @@ Supported metrics
|
||||||
/gc/heap/objects:objects
|
/gc/heap/objects:objects
|
||||||
Number of objects, live or unswept, occupying heap memory.
|
Number of objects, live or unswept, occupying heap memory.
|
||||||
|
|
||||||
|
/gc/pauses:seconds
|
||||||
|
Distribution individual GC-related stop-the-world pause latencies.
|
||||||
|
|
||||||
/memory/classes/heap/free:bytes
|
/memory/classes/heap/free:bytes
|
||||||
Memory that is available for allocation, and may be returned
|
Memory that is available for allocation, and may be returned
|
||||||
to the underlying system.
|
to the underlying system.
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,11 @@ func TestReadMetricsConsistency(t *testing.T) {
|
||||||
// things (e.g. allocating) so what we read can't reasonably compared
|
// things (e.g. allocating) so what we read can't reasonably compared
|
||||||
// to runtime values.
|
// to runtime values.
|
||||||
|
|
||||||
|
// Run a few GC cycles to get some of the stats to be non-zero.
|
||||||
|
runtime.GC()
|
||||||
|
runtime.GC()
|
||||||
|
runtime.GC()
|
||||||
|
|
||||||
// Read all the supported metrics through the metrics package.
|
// Read all the supported metrics through the metrics package.
|
||||||
descs, samples := prepareAllMetricsSamples()
|
descs, samples := prepareAllMetricsSamples()
|
||||||
metrics.Read(samples)
|
metrics.Read(samples)
|
||||||
|
|
@ -102,6 +107,10 @@ func TestReadMetricsConsistency(t *testing.T) {
|
||||||
alloc, free *metrics.Float64Histogram
|
alloc, free *metrics.Float64Histogram
|
||||||
total uint64
|
total uint64
|
||||||
}
|
}
|
||||||
|
var gc struct {
|
||||||
|
numGC uint64
|
||||||
|
pauses uint64
|
||||||
|
}
|
||||||
for i := range samples {
|
for i := range samples {
|
||||||
kind := samples[i].Value.Kind()
|
kind := samples[i].Value.Kind()
|
||||||
if want := descs[samples[i].Name].Kind; kind != want {
|
if want := descs[samples[i].Name].Kind; kind != want {
|
||||||
|
|
@ -128,6 +137,14 @@ func TestReadMetricsConsistency(t *testing.T) {
|
||||||
objects.alloc = samples[i].Value.Float64Histogram()
|
objects.alloc = samples[i].Value.Float64Histogram()
|
||||||
case "/gc/heap/frees-by-size:objects":
|
case "/gc/heap/frees-by-size:objects":
|
||||||
objects.free = samples[i].Value.Float64Histogram()
|
objects.free = samples[i].Value.Float64Histogram()
|
||||||
|
case "/gc/cycles:gc-cycles":
|
||||||
|
gc.numGC = samples[i].Value.Uint64()
|
||||||
|
case "/gc/pauses:seconds":
|
||||||
|
h := samples[i].Value.Float64Histogram()
|
||||||
|
gc.pauses = 0
|
||||||
|
for i := range h.Counts {
|
||||||
|
gc.pauses += h.Counts[i]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if totalVirtual.got != totalVirtual.want {
|
if totalVirtual.got != totalVirtual.want {
|
||||||
|
|
@ -159,6 +176,11 @@ func TestReadMetricsConsistency(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// The current GC has at least 2 pauses per GC.
|
||||||
|
// Check to see if that value makes sense.
|
||||||
|
if gc.pauses < gc.numGC*2 {
|
||||||
|
t.Errorf("fewer pauses than expected: got %d, want at least %d", gc.pauses, gc.numGC*2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkReadMetricsLatency(b *testing.B) {
|
func BenchmarkReadMetricsLatency(b *testing.B) {
|
||||||
|
|
|
||||||
|
|
@ -1418,6 +1418,7 @@ func gcStart(trigger gcTrigger) {
|
||||||
now = startTheWorldWithSema(trace.enabled)
|
now = startTheWorldWithSema(trace.enabled)
|
||||||
work.pauseNS += now - work.pauseStart
|
work.pauseNS += now - work.pauseStart
|
||||||
work.tMark = now
|
work.tMark = now
|
||||||
|
memstats.gcPauseDist.record(now - work.pauseStart)
|
||||||
})
|
})
|
||||||
|
|
||||||
// Release the world sema before Gosched() in STW mode
|
// Release the world sema before Gosched() in STW mode
|
||||||
|
|
@ -1565,6 +1566,7 @@ top:
|
||||||
systemstack(func() {
|
systemstack(func() {
|
||||||
now := startTheWorldWithSema(true)
|
now := startTheWorldWithSema(true)
|
||||||
work.pauseNS += now - work.pauseStart
|
work.pauseNS += now - work.pauseStart
|
||||||
|
memstats.gcPauseDist.record(now - work.pauseStart)
|
||||||
})
|
})
|
||||||
semrelease(&worldsema)
|
semrelease(&worldsema)
|
||||||
goto top
|
goto top
|
||||||
|
|
@ -1677,6 +1679,7 @@ func gcMarkTermination(nextTriggerRatio float64) {
|
||||||
unixNow := sec*1e9 + int64(nsec)
|
unixNow := sec*1e9 + int64(nsec)
|
||||||
work.pauseNS += now - work.pauseStart
|
work.pauseNS += now - work.pauseStart
|
||||||
work.tEnd = now
|
work.tEnd = now
|
||||||
|
memstats.gcPauseDist.record(now - work.pauseStart)
|
||||||
atomic.Store64(&memstats.last_gc_unix, uint64(unixNow)) // must be Unix time to make sense to user
|
atomic.Store64(&memstats.last_gc_unix, uint64(unixNow)) // must be Unix time to make sense to user
|
||||||
atomic.Store64(&memstats.last_gc_nanotime, uint64(now)) // monotonic time for us
|
atomic.Store64(&memstats.last_gc_nanotime, uint64(now)) // monotonic time for us
|
||||||
memstats.pause_ns[memstats.numgc%uint32(len(memstats.pause_ns))] = uint64(work.pauseNS)
|
memstats.pause_ns[memstats.numgc%uint32(len(memstats.pause_ns))] = uint64(work.pauseNS)
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,14 @@ type mstats struct {
|
||||||
|
|
||||||
// heapStats is a set of statistics
|
// heapStats is a set of statistics
|
||||||
heapStats consistentHeapStats
|
heapStats consistentHeapStats
|
||||||
|
|
||||||
|
_ uint32 // ensure gcPauseDist is aligned
|
||||||
|
|
||||||
|
// gcPauseDist represents the distribution of all GC-related
|
||||||
|
// application pauses in the runtime.
|
||||||
|
//
|
||||||
|
// Each individual pause is counted separately, unlike pause_ns.
|
||||||
|
gcPauseDist timeHistogram
|
||||||
}
|
}
|
||||||
|
|
||||||
var memstats mstats
|
var memstats mstats
|
||||||
|
|
@ -443,6 +451,10 @@ func init() {
|
||||||
println(offset)
|
println(offset)
|
||||||
throw("memstats.heapStats not aligned to 8 bytes")
|
throw("memstats.heapStats not aligned to 8 bytes")
|
||||||
}
|
}
|
||||||
|
if offset := unsafe.Offsetof(memstats.gcPauseDist); offset%8 != 0 {
|
||||||
|
println(offset)
|
||||||
|
throw("memstats.gcPauseDist not aligned to 8 bytes")
|
||||||
|
}
|
||||||
// Ensure the size of heapStatsDelta causes adjacent fields/slots (e.g.
|
// Ensure the size of heapStatsDelta causes adjacent fields/slots (e.g.
|
||||||
// [3]heapStatsDelta) to be 8-byte aligned.
|
// [3]heapStatsDelta) to be 8-byte aligned.
|
||||||
if size := unsafe.Sizeof(heapStatsDelta{}); size%8 != 0 {
|
if size := unsafe.Sizeof(heapStatsDelta{}); size%8 != 0 {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue