go mstats 源码

2022-07-15 浏览 (881)

golang mstats 代码

文件路径:/src/runtime/mstats.go

/ Copyright 2009 The Go Authors. All rights reserved.
/ Use of this source code is governed by a BSD-style
/ license that can be found in the LICENSE file.

/ Memory statistics

package runtime

import (
	"runtime/internal/atomic"
	"unsafe"
)

type mstats struct {
	/ Statistics about malloc heap.
	heapStats consistentHeapStats

	/ Statistics about stacks.
	stacks_sys sysMemStat / only counts newosproc0 stack in mstats; differs from MemStats.StackSys

	/ Statistics about allocation of low-level fixed-size structures.
	mspan_sys    sysMemStat
	mcache_sys   sysMemStat
	buckhash_sys sysMemStat / profiling bucket hash table

	/ Statistics about GC overhead.
	gcMiscSys sysMemStat / updated atomically or during STW

	/ Miscellaneous statistics.
	other_sys sysMemStat / updated atomically or during STW

	/ Statistics about the garbage collector.

	/ Protected by mheap or stopping the world during GC.
	last_gc_unix    uint64 / last gc (in unix time)
	pause_total_ns  uint64
	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)
	numgc           uint32
	numforcedgc     uint32  / number of user-forced GCs
	gc_cpu_fraction float64 / fraction of CPU time used by GC

	last_gc_nanotime uint64 / last gc (monotonic time)
	lastHeapInUse    uint64 / heapInUse at mark termination of the previous GC

	enablegc bool

	_ 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

/ A MemStats records statistics about the memory allocator.
type MemStats struct {
	/ General statistics.

	/ Alloc is bytes of allocated heap objects.
	/
	/ This is the same as HeapAlloc (see below).
	Alloc uint64

	/ TotalAlloc is cumulative bytes allocated for heap objects.
	/
	/ TotalAlloc increases as heap objects are allocated, but
	/ unlike Alloc and HeapAlloc, it does not decrease when
	/ objects are freed.
	TotalAlloc uint64

	/ Sys is the total bytes of memory obtained from the OS.
	/
	/ Sys is the sum of the XSys fields below. Sys measures the
	/ virtual address space reserved by the Go runtime for the
	/ heap, stacks, and other internal data structures. It's
	/ likely that not all of the virtual address space is backed
	/ by physical memory at any given moment, though in general
	/ it all was at some point.
	Sys uint64

	/ Lookups is the number of pointer lookups performed by the
	/ runtime.
	/
	/ This is primarily useful for debugging runtime internals.
	Lookups uint64

	/ Mallocs is the cumulative count of heap objects allocated.
	/ The number of live objects is Mallocs - Frees.
	Mallocs uint64

	/ Frees is the cumulative count of heap objects freed.
	Frees uint64

	/ Heap memory statistics.
	/
	/ Interpreting the heap statistics requires some knowledge of
	/ how Go organizes memory. Go divides the virtual address
	/ space of the heap into "spans", which are contiguous
	/ regions of memory 8K or larger. A span may be in one of
	/ three states:
	/
	/ An "idle" span contains no objects or other data. The
	/ physical memory backing an idle span can be released back
	/ to the OS (but the virtual address space never is), or it
	/ can be converted into an "in use" or "stack" span.
	/
	/ An "in use" span contains at least one heap object and may
	/ have free space available to allocate more heap objects.
	/
	/ A "stack" span is used for goroutine stacks. Stack spans
	/ are not considered part of the heap. A span can change
	/ between heap and stack memory; it is never used for both
	/ simultaneously.

	/ HeapAlloc is bytes of allocated heap objects.
	/
	/ "Allocated" heap objects include all reachable objects, as
	/ well as unreachable objects that the garbage collector has
	/ not yet freed. Specifically, HeapAlloc increases as heap
	/ objects are allocated and decreases as the heap is swept
	/ and unreachable objects are freed. Sweeping occurs
	/ incrementally between GC cycles, so these two processes
	/ occur simultaneously, and as a result HeapAlloc tends to
	/ change smoothly (in contrast with the sawtooth that is
	/ typical of stop-the-world garbage collectors).
	HeapAlloc uint64

	/ HeapSys is bytes of heap memory obtained from the OS.
	/
	/ HeapSys measures the amount of virtual address space
	/ reserved for the heap. This includes virtual address space
	/ that has been reserved but not yet used, which consumes no
	/ physical memory, but tends to be small, as well as virtual
	/ address space for which the physical memory has been
	/ returned to the OS after it became unused (see HeapReleased
	/ for a measure of the latter).
	/
	/ HeapSys estimates the largest size the heap has had.
	HeapSys uint64

	/ HeapIdle is bytes in idle (unused) spans.
	/
	/ Idle spans have no objects in them. These spans could be
	/ (and may already have been) returned to the OS, or they can
	/ be reused for heap allocations, or they can be reused as
	/ stack memory.
	/
	/ HeapIdle minus HeapReleased estimates the amount of memory
	/ that could be returned to the OS, but is being retained by
	/ the runtime so it can grow the heap without requesting more
	/ memory from the OS. If this difference is significantly
	/ larger than the heap size, it indicates there was a recent
	/ transient spike in live heap size.
	HeapIdle uint64

	/ HeapInuse is bytes in in-use spans.
	/
	/ In-use spans have at least one object in them. These spans
	/ can only be used for other objects of roughly the same
	/ size.
	/
	/ HeapInuse minus HeapAlloc estimates the amount of memory
	/ that has been dedicated to particular size classes, but is
	/ not currently being used. This is an upper bound on
	/ fragmentation, but in general this memory can be reused
	/ efficiently.
	HeapInuse uint64

	/ HeapReleased is bytes of physical memory returned to the OS.
	/
	/ This counts heap memory from idle spans that was returned
	/ to the OS and has not yet been reacquired for the heap.
	HeapReleased uint64

	/ HeapObjects is the number of allocated heap objects.
	/
	/ Like HeapAlloc, this increases as objects are allocated and
	/ decreases as the heap is swept and unreachable objects are
	/ freed.
	HeapObjects uint64

	/ Stack memory statistics.
	/
	/ Stacks are not considered part of the heap, but the runtime
	/ can reuse a span of heap memory for stack memory, and
	/ vice-versa.

	/ StackInuse is bytes in stack spans.
	/
	/ In-use stack spans have at least one stack in them. These
	/ spans can only be used for other stacks of the same size.
	/
	/ There is no StackIdle because unused stack spans are
	/ returned to the heap (and hence counted toward HeapIdle).
	StackInuse uint64

	/ StackSys is bytes of stack memory obtained from the OS.
	/
	/ StackSys is StackInuse, plus any memory obtained directly
	/ from the OS for OS thread stacks (which should be minimal).
	StackSys uint64

	/ Off-heap memory statistics.
	/
	/ The following statistics measure runtime-internal
	/ structures that are not allocated from heap memory (usually
	/ because they are part of implementing the heap). Unlike
	/ heap or stack memory, any memory allocated to these
	/ structures is dedicated to these structures.
	/
	/ These are primarily useful for debugging runtime memory
	/ overheads.

	/ MSpanInuse is bytes of allocated mspan structures.
	MSpanInuse uint64

	/ MSpanSys is bytes of memory obtained from the OS for mspan
	/ structures.
	MSpanSys uint64

	/ MCacheInuse is bytes of allocated mcache structures.
	MCacheInuse uint64

	/ MCacheSys is bytes of memory obtained from the OS for
	/ mcache structures.
	MCacheSys uint64

	/ BuckHashSys is bytes of memory in profiling bucket hash tables.
	BuckHashSys uint64

	/ GCSys is bytes of memory in garbage collection metadata.
	GCSys uint64

	/ OtherSys is bytes of memory in miscellaneous off-heap
	/ runtime allocations.
	OtherSys uint64

	/ Garbage collector statistics.

	/ NextGC is the target heap size of the next GC cycle.
	/
	/ The garbage collector's goal is to keep HeapAlloc ≤ NextGC.
	/ At the end of each GC cycle, the target for the next cycle
	/ is computed based on the amount of reachable data and the
	/ value of GOGC.
	NextGC uint64

	/ LastGC is the time the last garbage collection finished, as
	/ nanoseconds since 1970 (the UNIX epoch).
	LastGC uint64

	/ PauseTotalNs is the cumulative nanoseconds in GC
	/ stop-the-world pauses since the program started.
	/
	/ During a stop-the-world pause, all goroutines are paused
	/ and only the garbage collector can run.
	PauseTotalNs uint64

	/ PauseNs is a circular buffer of recent GC stop-the-world
	/ pause times in nanoseconds.
	/
	/ The most recent pause is at PauseNs[(NumGC+255)%256]. In
	/ general, PauseNs[N%256] records the time paused in the most
	/ recent N%256th GC cycle. There may be multiple pauses per
	/ GC cycle; this is the sum of all pauses during a cycle.
	PauseNs [256]uint64

	/ PauseEnd is a circular buffer of recent GC pause end times,
	/ as nanoseconds since 1970 (the UNIX epoch).
	/
	/ This buffer is filled the same way as PauseNs. There may be
	/ multiple pauses per GC cycle; this records the end of the
	/ last pause in a cycle.
	PauseEnd [256]uint64

	/ NumGC is the number of completed GC cycles.
	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
	/ CPU time used by the GC since the program started.
	/
	/ GCCPUFraction is expressed as a number between 0 and 1,
	/ where 0 means GC has consumed none of this program's CPU. A
	/ program's available CPU time is defined as the integral of
	/ GOMAXPROCS since the program started. That is, if
	/ GOMAXPROCS is 2 and a program has been running for 10
	/ seconds, its "available CPU" is 20 seconds. GCCPUFraction
	/ does not include CPU time used for write barrier activity.
	/
	/ This is the same as the fraction of CPU reported by
	/ GODEBUG=gctrace=1.
	GCCPUFraction float64

	/ EnableGC indicates that GC is enabled. It is always true,
	/ even if GOGC=off.
	EnableGC bool

	/ DebugGC is currently unused.
	DebugGC bool

	/ BySize reports per-size class allocation statistics.
	/
	/ BySize[N] gives statistics for allocations of size S where
	/ BySize[N-1].Size < S ≤ BySize[N].Size.
	/
	/ This does not report allocations larger than BySize[60].Size.
	BySize [61]struct {
		/ Size is the maximum byte size of an object in this
		/ size class.
		Size uint32

		/ Mallocs is the cumulative count of heap objects
		/ allocated in this size class. The cumulative bytes
		/ of allocation is Size*Mallocs. The number of live
		/ objects in this size class is Mallocs - Frees.
		Mallocs uint64

		/ Frees is the cumulative count of heap objects freed
		/ in this size class.
		Frees uint64
	}
}

func init() {
	if offset := unsafe.Offsetof(memstats.heapStats); offset%8 != 0 {
		println(offset)
		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.
	/ [3]heapStatsDelta) to be 8-byte aligned.
	if size := unsafe.Sizeof(heapStatsDelta{}); size%8 != 0 {
		println(size)
		throw("heapStatsDelta not a multiple of 8 bytes in size")
	}
}

/ ReadMemStats populates m with memory allocator statistics.
/
/ The returned memory allocator statistics are up to date as of the
/ call to ReadMemStats. This is in contrast with a heap profile,
/ which is a snapshot as of the most recently completed garbage
/ collection cycle.
func ReadMemStats(m *MemStats) {
	stopTheWorld("read mem stats")

	systemstack(func() {
		readmemstats_m(m)
	})

	startTheWorld()
}

/ readmemstats_m populates stats for internal runtime values.
/
/ The world must be stopped.
func readmemstats_m(stats *MemStats) {
	assertWorldStopped()

	/ Flush mcaches to mcentral before doing anything else.
	/
	/ Flushing to the mcentral may in general cause stats to
	/ change as mcentral data structures are manipulated.
	systemstack(flushallmcaches)

	/ Calculate memory allocator stats.
	/ During program execution we only count number of frees and amount of freed memory.
	/ Current number of alive objects in the heap and amount of alive heap memory
	/ are calculated by scanning all spans.
	/ Total number of mallocs is calculated as number of frees plus number of alive objects.
	/ Similarly, total amount of allocated memory is calculated as amount of freed memory
	/ plus amount of alive heap memory.

	/ Collect consistent stats, which are the source-of-truth in some cases.
	var consStats heapStatsDelta
	memstats.heapStats.unsafeRead(&consStats)

	/ Collect large allocation stats.
	totalAlloc := consStats.largeAlloc
	nMalloc := consStats.largeAllocCount
	totalFree := consStats.largeFree
	nFree := consStats.largeFreeCount

	/ Collect per-sizeclass stats.
	var bySize [_NumSizeClasses]struct {
		Size    uint32
		Mallocs uint64
		Frees   uint64
	}
	for i := range bySize {
		bySize[i].Size = uint32(class_to_size[i])

		/ Malloc stats.
		a := consStats.smallAllocCount[i]
		totalAlloc += a * uint64(class_to_size[i])
		nMalloc += a
		bySize[i].Mallocs = a

		/ Free stats.
		f := consStats.smallFreeCount[i]
		totalFree += f * uint64(class_to_size[i])
		nFree += f
		bySize[i].Frees = f
	}

	/ Account for tiny allocations.
	/ For historical reasons, MemStats includes tiny allocations
	/ in both the total free and total alloc count. This double-counts
	/ memory in some sense because their tiny allocation block is also
	/ counted. Tracking the lifetime of individual tiny allocations is
	/ currently not done because it would be too expensive.
	nFree += consStats.tinyAllocCount
	nMalloc += consStats.tinyAllocCount

	/ Calculate derived stats.

	stackInUse := uint64(consStats.inStacks)
	gcWorkBufInUse := uint64(consStats.inWorkBufs)
	gcProgPtrScalarBitsInUse := uint64(consStats.inPtrScalarBits)

	totalMapped := gcController.heapInUse.load() + gcController.heapFree.load() + gcController.heapReleased.load() +
		memstats.stacks_sys.load() + memstats.mspan_sys.load() + memstats.mcache_sys.load() +
		memstats.buckhash_sys.load() + memstats.gcMiscSys.load() + memstats.other_sys.load() +
		stackInUse + gcWorkBufInUse + gcProgPtrScalarBitsInUse

	heapGoal := gcController.heapGoal()

	/ The world is stopped, so the consistent stats (after aggregation)
	/ should be identical to some combination of memstats. In particular:
	/
	/ * memstats.heapInUse == inHeap
	/ * memstats.heapReleased == released
	/ * memstats.heapInUse + memstats.heapFree == committed - inStacks - inWorkBufs - inPtrScalarBits
	/ * memstats.totalAlloc == totalAlloc
	/ * memstats.totalFree == totalFree
	/
	/ Check if that's actually true.
	/
	/ TODO(mknyszek): Maybe don't throw here. It would be bad if a
	/ bug in otherwise benign accounting caused the whole application
	/ to crash.
	if gcController.heapInUse.load() != uint64(consStats.inHeap) {
		print("runtime: heapInUse=", gcController.heapInUse.load(), "\n")
		print("runtime: consistent value=", consStats.inHeap, "\n")
		throw("heapInUse and consistent stats are not equal")
	}
	if gcController.heapReleased.load() != uint64(consStats.released) {
		print("runtime: heapReleased=", gcController.heapReleased.load(), "\n")
		print("runtime: consistent value=", consStats.released, "\n")
		throw("heapReleased and consistent stats are not equal")
	}
	heapRetained := gcController.heapInUse.load() + gcController.heapFree.load()
	consRetained := uint64(consStats.committed - consStats.inStacks - consStats.inWorkBufs - consStats.inPtrScalarBits)
	if heapRetained != consRetained {
		print("runtime: global value=", heapRetained, "\n")
		print("runtime: consistent value=", consRetained, "\n")
		throw("measures of the retained heap are not equal")
	}
	if gcController.totalAlloc.Load() != totalAlloc {
		print("runtime: totalAlloc=", gcController.totalAlloc.Load(), "\n")
		print("runtime: consistent value=", totalAlloc, "\n")
		throw("totalAlloc and consistent stats are not equal")
	}
	if gcController.totalFree.Load() != totalFree {
		print("runtime: totalFree=", gcController.totalFree.Load(), "\n")
		print("runtime: consistent value=", totalFree, "\n")
		throw("totalFree and consistent stats are not equal")
	}
	/ Also check that mappedReady lines up with totalMapped - released.
	/ This isn't really the same type of "make sure consistent stats line up" situation,
	/ but this is an opportune time to check.
	if gcController.mappedReady.Load() != totalMapped-uint64(consStats.released) {
		print("runtime: mappedReady=", gcController.mappedReady.Load(), "\n")
		print("runtime: totalMapped=", totalMapped, "\n")
		print("runtime: released=", uint64(consStats.released), "\n")
		print("runtime: totalMapped-released=", totalMapped-uint64(consStats.released), "\n")
		throw("mappedReady and other memstats are not equal")
	}

	/ We've calculated all the values we need. Now, populate stats.

	stats.Alloc = totalAlloc - totalFree
	stats.TotalAlloc = totalAlloc
	stats.Sys = totalMapped
	stats.Mallocs = nMalloc
	stats.Frees = nFree
	stats.HeapAlloc = totalAlloc - totalFree
	stats.HeapSys = gcController.heapInUse.load() + gcController.heapFree.load() + gcController.heapReleased.load()
	/ By definition, HeapIdle is memory that was mapped
	/ for the heap but is not currently used to hold heap
	/ objects. It also specifically is memory that can be
	/ used for other purposes, like stacks, but this memory
	/ is subtracted out of HeapSys before it makes that
	/ transition. Put another way:
	/
	/ HeapSys = bytes allocated from the OS for the heap - bytes ultimately used for non-heap purposes
	/ HeapIdle = bytes allocated from the OS for the heap - bytes ultimately used for any purpose
	/
	/ or
	/
	/ HeapSys = sys - stacks_inuse - gcWorkBufInUse - gcProgPtrScalarBitsInUse
	/ HeapIdle = sys - stacks_inuse - gcWorkBufInUse - gcProgPtrScalarBitsInUse - heapInUse
	/
	/ => HeapIdle = HeapSys - heapInUse = heapFree + heapReleased
	stats.HeapIdle = gcController.heapFree.load() + gcController.heapReleased.load()
	stats.HeapInuse = gcController.heapInUse.load()
	stats.HeapReleased = gcController.heapReleased.load()
	stats.HeapObjects = nMalloc - nFree
	stats.StackInuse = stackInUse
	/ memstats.stacks_sys is only memory mapped directly for OS stacks.
	/ Add in heap-allocated stack memory for user consumption.
	stats.StackSys = stackInUse + memstats.stacks_sys.load()
	stats.MSpanInuse = uint64(mheap_.spanalloc.inuse)
	stats.MSpanSys = memstats.mspan_sys.load()
	stats.MCacheInuse = uint64(mheap_.cachealloc.inuse)
	stats.MCacheSys = memstats.mcache_sys.load()
	stats.BuckHashSys = memstats.buckhash_sys.load()
	/ MemStats defines GCSys as an aggregate of all memory related
	/ to the memory management system, but we track this memory
	/ at a more granular level in the runtime.
	stats.GCSys = memstats.gcMiscSys.load() + gcWorkBufInUse + gcProgPtrScalarBitsInUse
	stats.OtherSys = memstats.other_sys.load()
	stats.NextGC = heapGoal
	stats.LastGC = memstats.last_gc_unix
	stats.PauseTotalNs = memstats.pause_total_ns
	stats.PauseNs = memstats.pause_ns
	stats.PauseEnd = memstats.pause_end
	stats.NumGC = memstats.numgc
	stats.NumForcedGC = memstats.numforcedgc
	stats.GCCPUFraction = memstats.gc_cpu_fraction
	stats.EnableGC = true

	/ stats.BySize and bySize might not match in length.
	/ That's OK, stats.BySize cannot change due to backwards
	/ compatibility issues. copy will copy the minimum amount
	/ of values between the two of them.
	copy(stats.BySize[:], bySize[:])
}

/go:linkname readGCStats runtime/debug.readGCStats
func readGCStats(pauses *[]uint64) {
	systemstack(func() {
		readGCStats_m(pauses)
	})
}

/ readGCStats_m must be called on the system stack because it acquires the heap
/ lock. See mheap for details.
/
/go:systemstack
func readGCStats_m(pauses *[]uint64) {
	p := *pauses
	/ Calling code in runtime/debug should make the slice large enough.
	if cap(p) < len(memstats.pause_ns)+3 {
		throw("short slice passed to readGCStats")
	}

	/ Pass back: pauses, pause ends, last gc (absolute time), number of gc, total pause ns.
	lock(&mheap_.lock)

	n := memstats.numgc
	if n > uint32(len(memstats.pause_ns)) {
		n = uint32(len(memstats.pause_ns))
	}

	/ The pause buffer is circular. The most recent pause is at
	/ pause_ns[(numgc-1)%len(pause_ns)], and then backward
	/ from there to go back farther in time. We deliver the times
	/ most recent first (in p[0]).
	p = p[:cap(p)]
	for i := uint32(0); i < n; i++ {
		j := (memstats.numgc - 1 - i) % uint32(len(memstats.pause_ns))
		p[i] = memstats.pause_ns[j]
		p[n+i] = memstats.pause_end[j]
	}

	p[n+n] = memstats.last_gc_unix
	p[n+n+1] = uint64(memstats.numgc)
	p[n+n+2] = memstats.pause_total_ns
	unlock(&mheap_.lock)
	*pauses = p[:n+n+3]
}

/ flushmcache flushes the mcache of allp[i].
/
/ The world must be stopped.
/
/go:nowritebarrier
func flushmcache(i int) {
	assertWorldStopped()

	p := allp[i]
	c := p.mcache
	if c == nil {
		return
	}
	c.releaseAll()
	stackcache_clear(c)
}

/ flushallmcaches flushes the mcaches of all Ps.
/
/ The world must be stopped.
/
/go:nowritebarrier
func flushallmcaches() {
	assertWorldStopped()

	for i := 0; i < int(gomaxprocs); i++ {
		flushmcache(i)
	}
}

/ sysMemStat represents a global system statistic that is managed atomically.
/
/ This type must structurally be a uint64 so that mstats aligns with MemStats.
type sysMemStat uint64

/ load atomically reads the value of the stat.
/
/ Must be nosplit as it is called in runtime initialization, e.g. newosproc0.
/
/go:nosplit
func (s *sysMemStat) load() uint64 {
	return atomic.Load64((*uint64)(s))
}

/ add atomically adds the sysMemStat by n.
/
/ Must be nosplit as it is called in runtime initialization, e.g. newosproc0.
/
/go:nosplit
func (s *sysMemStat) add(n int64) {
	val := atomic.Xadd64((*uint64)(s), n)
	if (n > 0 && int64(val) < n) || (n < 0 && int64(val)+n < n) {
		print("runtime: val=", val, " n=", n, "\n")
		throw("sysMemStat overflow")
	}
}

/ heapStatsDelta contains deltas of various runtime memory statistics
/ that need to be updated together in order for them to be kept
/ consistent with one another.
type heapStatsDelta struct {
	/ Memory stats.
	committed       int64 / byte delta of memory committed
	released        int64 / byte delta of released memory generated
	inHeap          int64 / byte delta of memory placed in the heap
	inStacks        int64 / byte delta of memory reserved for stacks
	inWorkBufs      int64 / byte delta of memory reserved for work bufs
	inPtrScalarBits int64 / byte delta of memory reserved for unrolled GC prog bits

	/ Allocator stats.
	/
	/ These are all uint64 because they're cumulative, and could quickly wrap
	/ around otherwise.
	tinyAllocCount  uint64                  / number of tiny allocations
	largeAlloc      uint64                  / bytes allocated for large objects
	largeAllocCount uint64                  / number of large object allocations
	smallAllocCount [_NumSizeClasses]uint64 / number of allocs for small objects
	largeFree       uint64                  / bytes freed for large objects (>maxSmallSize)
	largeFreeCount  uint64                  / number of frees for large objects (>maxSmallSize)
	smallFreeCount  [_NumSizeClasses]uint64 / number of frees for small objects (<=maxSmallSize)

	/ NOTE: This struct must be a multiple of 8 bytes in size because it
	/ is stored in an array. If it's not, atomic accesses to the above
	/ fields may be unaligned and fail on 32-bit platforms.
}

/ merge adds in the deltas from b into a.
func (a *heapStatsDelta) merge(b *heapStatsDelta) {
	a.committed += b.committed
	a.released += b.released
	a.inHeap += b.inHeap
	a.inStacks += b.inStacks
	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 {
		a.smallAllocCount[i] += b.smallAllocCount[i]
	}
	a.largeFree += b.largeFree
	a.largeFreeCount += b.largeFreeCount
	for i := range b.smallFreeCount {
		a.smallFreeCount[i] += b.smallFreeCount[i]
	}
}

/ consistentHeapStats represents a set of various memory statistics
/ whose updates must be viewed completely to get a consistent
/ state of the world.
/
/ To write updates to memory stats use the acquire and release
/ methods. To obtain a consistent global snapshot of these statistics,
/ use read.
type consistentHeapStats struct {
	/ stats is a ring buffer of heapStatsDelta values.
	/ Writers always atomically update the delta at index gen.
	/
	/ Readers operate by rotating gen (0 -> 1 -> 2 -> 0 -> ...)
	/ and synchronizing with writers by observing each P's
	/ statsSeq field. If the reader observes a P not writing,
	/ it can be sure that it will pick up the new gen value the
	/ next time it writes.
	/
	/ The reader then takes responsibility by clearing space
	/ in the ring buffer for the next reader to rotate gen to
	/ that space (i.e. it merges in values from index (gen-2) mod 3
	/ to index (gen-1) mod 3, then clears the former).
	/
	/ Note that this means only one reader can be reading at a time.
	/ There is no way for readers to synchronize.
	/
	/ This process is why we need a ring buffer of size 3 instead
	/ of 2: one is for the writers, one contains the most recent
	/ data, and the last one is clear so writers can begin writing
	/ to it the moment gen is updated.
	stats [3]heapStatsDelta

	/ gen represents the current index into which writers
	/ are writing, and can take on the value of 0, 1, or 2.
	/ This value is updated atomically.
	gen uint32

	/ noPLock is intended to provide mutual exclusion for updating
	/ stats when no P is available. It does not block other writers
	/ with a P, only other writers without a P and the reader. Because
	/ stats are usually updated when a P is available, contention on
	/ this lock should be minimal.
	noPLock mutex
}

/ acquire returns a heapStatsDelta to be updated. In effect,
/ it acquires the shard for writing. release must be called
/ as soon as the relevant deltas are updated.
/
/ The returned heapStatsDelta must be updated atomically.
/
/ The caller's P must not change between acquire and
/ release. This also means that the caller should not
/ acquire a P or release its P in between. A P also must
/ not acquire a given consistentHeapStats if it hasn't
/ yet released it.
/
/ nosplit because a stack growth in this function could
/ lead to a stack allocation that could reenter the
/ function.
/
/go:nosplit
func (m *consistentHeapStats) acquire() *heapStatsDelta {
	if pp := getg().m.p.ptr(); pp != nil {
		seq := atomic.Xadd(&pp.statsSeq, 1)
		if seq%2 == 0 {
			/ Should have been incremented to odd.
			print("runtime: seq=", seq, "\n")
			throw("bad sequence number")
		}
	} else {
		lock(&m.noPLock)
	}
	gen := atomic.Load(&m.gen) % 3
	return &m.stats[gen]
}

/ release indicates that the writer is done modifying
/ the delta. The value returned by the corresponding
/ acquire must no longer be accessed or modified after
/ release is called.
/
/ The caller's P must not change between acquire and
/ release. This also means that the caller should not
/ acquire a P or release its P in between.
/
/ nosplit because a stack growth in this function could
/ lead to a stack allocation that causes another acquire
/ before this operation has completed.
/
/go:nosplit
func (m *consistentHeapStats) release() {
	if pp := getg().m.p.ptr(); pp != nil {
		seq := atomic.Xadd(&pp.statsSeq, 1)
		if seq%2 != 0 {
			/ Should have been incremented to even.
			print("runtime: seq=", seq, "\n")
			throw("bad sequence number")
		}
	} else {
		unlock(&m.noPLock)
	}
}

/ unsafeRead aggregates the delta for this shard into out.
/
/ Unsafe because it does so without any synchronization. The
/ world must be stopped.
func (m *consistentHeapStats) unsafeRead(out *heapStatsDelta) {
	assertWorldStopped()

	for i := range m.stats {
		out.merge(&m.stats[i])
	}
}

/ unsafeClear clears the shard.
/
/ Unsafe because the world must be stopped and values should
/ be donated elsewhere before clearing.
func (m *consistentHeapStats) unsafeClear() {
	assertWorldStopped()

	for i := range m.stats {
		m.stats[i] = heapStatsDelta{}
	}
}

/ read takes a globally consistent snapshot of m
/ and puts the aggregated value in out. Even though out is a
/ heapStatsDelta, the resulting values should be complete and
/ valid statistic values.
/
/ Not safe to call concurrently. The world must be stopped
/ or metricsSema must be held.
func (m *consistentHeapStats) read(out *heapStatsDelta) {
	/ Getting preempted after this point is not safe because
	/ we read allp. We need to make sure a STW can't happen
	/ so it doesn't change out from under us.
	mp := acquirem()

	/ Get the current generation. We can be confident that this
	/ will not change since read is serialized and is the only
	/ one that modifies currGen.
	currGen := atomic.Load(&m.gen)
	prevGen := currGen - 1
	if currGen == 0 {
		prevGen = 2
	}

	/ Prevent writers without a P from writing while we update gen.
	lock(&m.noPLock)

	/ Rotate gen, effectively taking a snapshot of the state of
	/ these statistics at the point of the exchange by moving
	/ writers to the next set of deltas.
	/
	/ This exchange is safe to do because we won't race
	/ with anyone else trying to update this value.
	atomic.Xchg(&m.gen, (currGen+1)%3)

	/ Allow P-less writers to continue. They'll be writing to the
	/ next generation now.
	unlock(&m.noPLock)

	for _, p := range allp {
		/ Spin until there are no more writers.
		for atomic.Load(&p.statsSeq)%2 != 0 {
		}
	}

	/ At this point we've observed that each sequence
	/ number is even, so any future writers will observe
	/ the new gen value. That means it's safe to read from
	/ the other deltas in the stats buffer.

	/ Perform our responsibilities and free up
	/ stats[prevGen] for the next time we want to take
	/ a snapshot.
	m.stats[currGen].merge(&m.stats[prevGen])
	m.stats[prevGen] = heapStatsDelta{}

	/ Finally, copy out the complete delta.
	*out = m.stats[currGen]

	releasem(mp)
}

相关信息

go 源码目录

相关文章

go abi_test 源码

go alg 源码

go align_runtime_test 源码

go align_test 源码

go asan 源码

go asan0 源码

go atomic_pointer 源码

go auxv_none 源码

go callers_test 源码

go cgo 源码

^