1
0
mirror of https://github.com/golang/go synced 2024-09-24 03:10:16 -06:00

runtime/metrics: add tiny allocs metric

Currently tiny allocations are not represented in either MemStats or
runtime/metrics, but they're represented in MemStats (indirectly) via
Mallocs. Add them to runtime/metrics by first merging
memstats.tinyallocs into consistentHeapStats (just for simplicity; it's
monotonic so metrics would still be self-consistent if we just read it
atomically) and then adding /gc/heap/tiny/allocs:objects to the list of
supported metrics.

Change-Id: Ie478006ab942a3e877b4a79065ffa43569722f3d
Reviewed-on: https://go-review.googlesource.com/c/go/+/312909
Trust: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Michael Knyszek <mknyszek@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Michael Pratt <mpratt@google.com>
This commit is contained in:
Michael Anthony Knyszek 2021-04-21 23:50:58 +00:00 committed by Michael Knyszek
parent 7d22c2181b
commit 0b9ca4d907
7 changed files with 64 additions and 13 deletions

View File

@ -393,7 +393,7 @@ func ReadMemStatsSlow() (base, slow MemStats) {
bySize[i].Mallocs += uint64(m.smallFreeCount[i])
smallFree += uint64(m.smallFreeCount[i]) * uint64(class_to_size[i])
}
slow.Frees += memstats.tinyallocs + uint64(m.largeFreeCount)
slow.Frees += uint64(m.tinyAllocCount) + uint64(m.largeFreeCount)
slow.Mallocs += slow.Frees
slow.TotalAlloc = slow.Alloc + uint64(m.largeFree) + smallFree

View File

@ -176,18 +176,18 @@ func (c *mcache) refill(spc spanClass) {
// mcache. If it gets uncached, we'll adjust this.
stats := memstats.heapStats.acquire()
atomic.Xadduintptr(&stats.smallAllocCount[spc.sizeclass()], uintptr(s.nelems)-uintptr(s.allocCount))
// Flush tinyAllocs.
if spc == tinySpanClass {
atomic.Xadduintptr(&stats.tinyAllocCount, c.tinyAllocs)
c.tinyAllocs = 0
}
memstats.heapStats.release()
// Update gcController.heapLive with the same assumption.
usedBytes := uintptr(s.allocCount) * s.elemsize
atomic.Xadd64(&gcController.heapLive, int64(s.npages*pageSize)-int64(usedBytes))
// Flush tinyAllocs.
if spc == tinySpanClass {
atomic.Xadd64(&memstats.tinyallocs, int64(c.tinyAllocs))
c.tinyAllocs = 0
}
// While we're here, flush scanAlloc, since we have to call
// revise anyway.
atomic.Xadd64(&gcController.heapScan, int64(c.scanAlloc))
@ -280,8 +280,12 @@ func (c *mcache) releaseAll() {
// Clear tinyalloc pool.
c.tiny = 0
c.tinyoffset = 0
atomic.Xadd64(&memstats.tinyallocs, int64(c.tinyAllocs))
// Flush tinyAllocs.
stats := memstats.heapStats.acquire()
atomic.Xadduintptr(&stats.tinyAllocCount, c.tinyAllocs)
c.tinyAllocs = 0
memstats.heapStats.release()
// Updated heapScan and possible gcController.heapLive.
if gcBlackenEnabled != 0 {

View File

@ -124,6 +124,13 @@ func initMetrics() {
out.scalar = in.heapStats.numObjects
},
},
"/gc/heap/tiny/allocs:objects": {
deps: makeStatDepSet(heapStatsDep),
compute: func(in *statAggregate, out *metricValue) {
out.kind = metricKindUint64
out.scalar = uint64(in.heapStats.tinyAllocCount)
},
},
"/gc/pauses:seconds": {
compute: func(_ *statAggregate, out *metricValue) {
hist := out.float64HistOrInit(timeHistBuckets)

View File

@ -91,6 +91,16 @@ var allDesc = []Description{
Description: "Number of objects, live or unswept, occupying heap memory.",
Kind: KindUint64,
},
{
Name: "/gc/heap/tiny/allocs:objects",
Description: "Count of small allocations that are packed together into blocks. " +
"These allocations are counted separately from other allocations " +
"because each individual allocation is not tracked by the runtime, " +
"only their block. Each block is already accounted for in " +
"allocs-by-size and frees-by-size.",
Kind: KindUint64,
Cumulative: true,
},
{
Name: "/gc/pauses:seconds",
Description: "Distribution individual GC-related stop-the-world pause latencies.",

View File

@ -72,6 +72,13 @@ Below is the full list of supported metrics, ordered lexicographically.
/gc/heap/objects:objects
Number of objects, live or unswept, occupying heap memory.
/gc/heap/tiny/allocs:objects
Count of small allocations that are packed together into blocks.
These allocations are counted separately from other allocations
because each individual allocation is not tracked by the runtime,
only their block. Each block is already accounted for in
allocs-by-size and frees-by-size.
/gc/pauses:seconds
Distribution individual GC-related stop-the-world pause latencies.

View File

@ -40,6 +40,8 @@ func TestReadMetrics(t *testing.T) {
}
// Check to make sure the values we read line up with other values we read.
var allocsBySize *metrics.Float64Histogram
var tinyAllocs uint64
for i := range samples {
switch name := samples[i].Name; name {
case "/memory/classes/heap/free:bytes":
@ -84,6 +86,7 @@ func TestReadMetrics(t *testing.T) {
t.Errorf("histogram counts do not much BySize for class %d: got %d, want %d", i, c, m)
}
}
allocsBySize = hist
case "/gc/heap/frees-by-size:bytes":
hist := samples[i].Value.Float64Histogram()
// Skip size class 0 in BySize, because it's always empty and not represented
@ -95,9 +98,20 @@ func TestReadMetrics(t *testing.T) {
continue
}
if c, f := hist.Counts[i], sc.Frees; c != f {
t.Errorf("histogram counts do not much BySize for class %d: got %d, want %d", i, c, f)
t.Errorf("histogram counts do not match BySize for class %d: got %d, want %d", i, c, f)
}
}
case "/gc/heap/tiny/allocs:objects":
// Currently, MemStats adds tiny alloc count to both Mallocs AND Frees.
// The reason for this is because MemStats couldn't be extended at the time
// but there was a desire to have Mallocs at least be a little more representative,
// while having Mallocs - Frees still represent a live object count.
// Unfortunately, MemStats doesn't actually export a large allocation count,
// so it's impossible to pull this number out directly.
//
// Check tiny allocation count outside of this loop, by using the allocs-by-size
// histogram in order to figure out how many large objects there are.
tinyAllocs = samples[i].Value.Uint64()
case "/gc/heap/objects:objects":
checkUint64(t, name, samples[i].Value.Uint64(), mstats.HeapObjects)
case "/gc/heap/goal:bytes":
@ -110,6 +124,13 @@ func TestReadMetrics(t *testing.T) {
checkUint64(t, name, samples[i].Value.Uint64(), uint64(mstats.NumGC))
}
}
// Check tinyAllocs.
nonTinyAllocs := uint64(0)
for _, c := range allocsBySize.Counts {
nonTinyAllocs += c
}
checkUint64(t, "/gc/heap/tiny/allocs:objects", tinyAllocs, mstats.Mallocs-nonTinyAllocs)
}
func TestReadMetricsConsistency(t *testing.T) {

View File

@ -8,6 +8,7 @@ package runtime
import (
"runtime/internal/atomic"
"runtime/internal/sys"
"unsafe"
)
@ -86,7 +87,6 @@ type mstats struct {
_ [1 - _NumSizeClasses%2]uint32
last_gc_nanotime uint64 // last gc (monotonic time)
tinyallocs uint64 // number of tiny allocations that didn't cause actual allocation; not exported to go directly
last_heap_inuse uint64 // heap_inuse at mark termination of the previous GC
// heapStats is a set of statistics
@ -586,8 +586,8 @@ func updatememstats() {
}
// Account for tiny allocations.
memstats.nfree += memstats.tinyallocs
memstats.nmalloc += memstats.tinyallocs
memstats.nfree += uint64(consStats.tinyAllocCount)
memstats.nmalloc += uint64(consStats.tinyAllocCount)
// Calculate derived stats.
memstats.total_alloc = totalAlloc
@ -703,6 +703,7 @@ type heapStatsDelta struct {
inPtrScalarBits int64 // byte delta of memory reserved for unrolled GC prog bits
// Allocator stats.
tinyAllocCount uintptr // number of tiny allocations
largeAlloc uintptr // bytes allocated for large objects
largeAllocCount uintptr // number of large object allocations
smallAllocCount [_NumSizeClasses]uintptr // number of allocs for small objects
@ -712,7 +713,7 @@ type heapStatsDelta struct {
// Add a uint32 to ensure this struct is a multiple of 8 bytes in size.
// Only necessary on 32-bit platforms.
// _ [(sys.PtrSize / 4) % 2]uint32
_ [(sys.PtrSize / 4) % 2]uint32
}
// merge adds in the deltas from b into a.
@ -724,6 +725,7 @@ func (a *heapStatsDelta) merge(b *heapStatsDelta) {
a.inWorkBufs += b.inWorkBufs
a.inPtrScalarBits += b.inPtrScalarBits
a.tinyAllocCount += b.tinyAllocCount
a.largeAlloc += b.largeAlloc
a.largeAllocCount += b.largeAllocCount
for i := range b.smallAllocCount {