diff --git a/Makefile b/Makefile index 0d731774..09977ccd 100644 --- a/Makefile +++ b/Makefile @@ -230,6 +230,8 @@ smoketest: @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/mcp3008 @$(MD5SUM) test.hex + $(TINYGO) build -size short -o test.hex -target=pca10040 examples/memstats + @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=microbit examples/microbit-blink @$(MD5SUM) test.hex $(TINYGO) build -size short -o test.hex -target=pca10040 examples/pininterrupt diff --git a/src/examples/memstats/memstats.go b/src/examples/memstats/memstats.go new file mode 100644 index 00000000..f0224ac0 --- /dev/null +++ b/src/examples/memstats/memstats.go @@ -0,0 +1,32 @@ +package main + +import ( + "math/rand" + "runtime" + "time" +) + +func main() { + + ms := runtime.MemStats{} + + for { + escapesToHeap() + runtime.ReadMemStats(&ms) + println("Heap before GC. Used: ", ms.HeapInuse, " Free: ", ms.HeapIdle, " Meta: ", ms.GCSys) + runtime.GC() + runtime.ReadMemStats(&ms) + println("Heap after GC. Used: ", ms.HeapInuse, " Free: ", ms.HeapIdle, " Meta: ", ms.GCSys) + time.Sleep(5 * time.Second) + } + +} + +func escapesToHeap() { + n := rand.Intn(100) + println("Doing ", n, " iterations") + for i := 0; i < n; i++ { + s := make([]byte, i) + _ = append(s, 42) + } +} diff --git a/src/runtime/gc_conservative.go b/src/runtime/gc_conservative.go index 7638fb0d..e9682592 100644 --- a/src/runtime/gc_conservative.go +++ b/src/runtime/gc_conservative.go @@ -51,7 +51,7 @@ const ( ) var ( - metadataStart unsafe.Pointer // pointer to the start of the heap + metadataStart unsafe.Pointer // pointer to the start of the heap metadata nextAlloc gcBlock // the next block that should be tried by the allocator endBlock gcBlock // the block just past the end of the available space ) diff --git a/src/runtime/mstats.go b/src/runtime/mstats.go new file mode 100644 index 00000000..d2723fe9 --- /dev/null +++ b/src/runtime/mstats.go @@ -0,0 +1,69 @@ +// +build gc.conservative + +package runtime + +// Memory statistics + +// Subset of memory statistics from upstream Go. +// Works with conservative gc only. + +// A MemStats records statistics about the memory allocator. +type MemStats struct { + // General statistics. + + // Sys is the total bytes of memory obtained from the OS. + // + // Sys is the sum of the XSys fields below. Sys measures the + // address space reserved by the runtime for the + // heap, stacks, and other internal data structures. + Sys uint64 + + // Heap memory statistics. + + // HeapSys is bytes of heap memory, total. + // + // In TinyGo unlike upstream Go, we make no distinction between + // regular heap blocks used by escaped-to-the-heap variables and + // blocks occupied by goroutine stacks, + // all such blocks are marked as in-use, see HeapInuse below. + HeapSys uint64 + + // HeapIdle is bytes in idle (unused) blocks. + HeapIdle uint64 + + // HeapInuse is bytes in in-use blocks. + HeapInuse uint64 + + // HeapReleased is bytes of physical memory returned to the OS. + HeapReleased 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). + + // GCSys is bytes of memory in garbage collection metadata. + GCSys uint64 +} + +// ReadMemStats populates m with memory statistics. +// +// The returned memory statistics are up to date as of the +// call to ReadMemStats. This would not do GC implicitly for you. +func ReadMemStats(m *MemStats) { + m.HeapIdle = 0 + m.HeapInuse = 0 + for block := gcBlock(0); block < endBlock; block++ { + bstate := block.state() + if bstate == blockStateFree { + m.HeapIdle += uint64(bytesPerBlock) + } else { + m.HeapInuse += uint64(bytesPerBlock) + } + } + m.HeapReleased = 0 // always 0, we don't currently release memory back to the OS. + m.HeapSys = m.HeapInuse + m.HeapIdle + m.GCSys = uint64(heapEnd - uintptr(metadataStart)) + m.Sys = uint64(heapEnd - heapStart) +}