// Copyright 2015 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.

package main

import (
	"fmt"
	"os"
	"runtime"
	"runtime/debug"
	"sync/atomic"
	"time"
	"unsafe"
)

func init() {
	register("GCFairness", GCFairness)
	register("GCFairness2", GCFairness2)
	register("GCSys", GCSys)
	register("GCPhys", GCPhys)
	register("DeferLiveness", DeferLiveness)
	register("GCZombie", GCZombie)
}

func GCSys() {
	runtime.GOMAXPROCS(1)
	memstats := new(runtime.MemStats)
	runtime.GC()
	runtime.ReadMemStats(memstats)
	sys := memstats.Sys

	runtime.MemProfileRate = 0 // disable profiler

	itercount := 100000
	for i := 0; i < itercount; i++ {
		workthegc()
	}

	// Should only be using a few MB.
	// We allocated 100 MB or (if not short) 1 GB.
	runtime.ReadMemStats(memstats)
	if sys > memstats.Sys {
		sys = 0
	} else {
		sys = memstats.Sys - sys
	}
	if sys > 16<<20 {
		fmt.Printf("using too much memory: %d bytes\n", sys)
		return
	}
	fmt.Printf("OK\n")
}

var sink []byte

func workthegc() []byte {
	sink = make([]byte, 1029)
	return sink
}

func GCFairness() {
	runtime.GOMAXPROCS(1)
	f, err := os.Open("/dev/null")
	if os.IsNotExist(err) {
		// This test tests what it is intended to test only if writes are fast.
		// If there is no /dev/null, we just don't execute the test.
		fmt.Println("OK")
		return
	}
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
	for i := 0; i < 2; i++ {
		go func() {
			for {
				f.Write([]byte("."))
			}
		}()
	}
	time.Sleep(10 * time.Millisecond)
	fmt.Println("OK")
}

func GCFairness2() {
	// Make sure user code can't exploit the GC's high priority
	// scheduling to make scheduling of user code unfair. See
	// issue #15706.
	runtime.GOMAXPROCS(1)
	debug.SetGCPercent(1)
	var count [3]int64
	var sink [3]interface{}
	for i := range count {
		go func(i int) {
			for {
				sink[i] = make([]byte, 1024)
				atomic.AddInt64(&count[i], 1)
			}
		}(i)
	}
	// Note: If the unfairness is really bad, it may not even get
	// past the sleep.
	//
	// If the scheduling rules change, this may not be enough time
	// to let all goroutines run, but for now we cycle through
	// them rapidly.
	//
	// OpenBSD's scheduler makes every usleep() take at least
	// 20ms, so we need a long time to ensure all goroutines have
	// run. If they haven't run after 30ms, give it another 1000ms
	// and check again.
	time.Sleep(30 * time.Millisecond)
	var fail bool
	for i := range count {
		if atomic.LoadInt64(&count[i]) == 0 {
			fail = true
		}
	}
	if fail {
		time.Sleep(1 * time.Second)
		for i := range count {
			if atomic.LoadInt64(&count[i]) == 0 {
				fmt.Printf("goroutine %d did not run\n", i)
				return
			}
		}
	}
	fmt.Println("OK")
}

func GCPhys() {
	// This test ensures that heap-growth scavenging is working as intended.
	//
	// It sets up a specific scenario: it allocates two pairs of objects whose
	// sizes sum to size. One object in each pair is "small" (though must be
	// large enough to be considered a large object by the runtime) and one is
	// large. The small objects are kept while the large objects are freed,
	// creating two large unscavenged holes in the heap. The heap goal should
	// also be small as a result (so size must be at least as large as the
	// minimum heap size). We then allocate one large object, bigger than both
	// pairs of objects combined. This allocation, because it will tip
	// HeapSys-HeapReleased well above the heap goal, should trigger heap-growth
	// scavenging and scavenge most, if not all, of the large holes we created
	// earlier.
	const (
		// Size must be also large enough to be considered a large
		// object (not in any size-segregated span).
		size    = 4 << 20
		split   = 64 << 10
		objects = 2

		// The page cache could hide 64 8-KiB pages from the scavenger today.
		maxPageCache = (8 << 10) * 64

		// Reduce GOMAXPROCS down to 4 if it's greater. We need to bound the amount
		// of memory held in the page cache because the scavenger can't reach it.
		// The page cache will hold at most maxPageCache of memory per-P, so this
		// bounds the amount of memory hidden from the scavenger to 4*maxPageCache
		// at most.
		maxProcs = 4
	)
	// Set GOGC so that this test operates under consistent assumptions.
	debug.SetGCPercent(100)
	procs := runtime.GOMAXPROCS(-1)
	if procs > maxProcs {
		defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(maxProcs))
		procs = runtime.GOMAXPROCS(-1)
	}
	// Save objects which we want to survive, and condemn objects which we don't.
	// Note that we condemn objects in this way and release them all at once in
	// order to avoid having the GC start freeing up these objects while the loop
	// is still running and filling in the holes we intend to make.
	saved := make([][]byte, 0, objects+1)
	condemned := make([][]byte, 0, objects)
	for i := 0; i < 2*objects; i++ {
		if i%2 == 0 {
			saved = append(saved, make([]byte, split))
		} else {
			condemned = append(condemned, make([]byte, size-split))
		}
	}
	condemned = nil
	// Clean up the heap. This will free up every other object created above
	// (i.e. everything in condemned) creating holes in the heap.
	// Also, if the condemned objects are still being swept, its possible that
	// the scavenging that happens as a result of the next allocation won't see
	// the holes at all. We call runtime.GC() twice here so that when we allocate
	// our large object there's no race with sweeping.
	runtime.GC()
	runtime.GC()
	// Perform one big allocation which should also scavenge any holes.
	//
	// The heap goal will rise after this object is allocated, so it's very
	// important that we try to do all the scavenging in a single allocation
	// that exceeds the heap goal. Otherwise the rising heap goal could foil our
	// test.
	saved = append(saved, make([]byte, objects*size))
	// Clean up the heap again just to put it in a known state.
	runtime.GC()
	// heapBacked is an estimate of the amount of physical memory used by
	// this test. HeapSys is an estimate of the size of the mapped virtual
	// address space (which may or may not be backed by physical pages)
	// whereas HeapReleased is an estimate of the amount of bytes returned
	// to the OS. Their difference then roughly corresponds to the amount
	// of virtual address space that is backed by physical pages.
	var stats runtime.MemStats
	runtime.ReadMemStats(&stats)
	heapBacked := stats.HeapSys - stats.HeapReleased
	// If heapBacked does not exceed the heap goal by more than retainExtraPercent
	// then the scavenger is working as expected; the newly-created holes have been
	// scavenged immediately as part of the allocations which cannot fit in the holes.
	//
	// Since the runtime should scavenge the entirety of the remaining holes,
	// theoretically there should be no more free and unscavenged memory. However due
	// to other allocations that happen during this test we may still see some physical
	// memory over-use.
	overuse := (float64(heapBacked) - float64(stats.HeapAlloc)) / float64(stats.HeapAlloc)
	// Compute the threshold.
	//
	// In theory, this threshold should just be zero, but that's not possible in practice.
	// Firstly, the runtime's page cache can hide up to maxPageCache of free memory from the
	// scavenger per P. To account for this, we increase the threshold by the ratio between the
	// total amount the runtime could hide from the scavenger to the amount of memory we expect
	// to be able to scavenge here, which is (size-split)*objects. This computation is the crux
	// GOMAXPROCS above; if GOMAXPROCS is too high the threshold just becomes 100%+ since the
	// amount of memory being allocated is fixed. Then we add 5% to account for noise, such as
	// other allocations this test may have performed that we don't explicitly account for The
	// baseline threshold here is around 11% for GOMAXPROCS=1, capping out at around 30% for
	// GOMAXPROCS=4.
	threshold := 0.05 + float64(procs)*maxPageCache/float64((size-split)*objects)
	if overuse <= threshold {
		fmt.Println("OK")
		return
	}
	// Physical memory utilization exceeds the threshold, so heap-growth scavenging
	// did not operate as expected.
	//
	// In the context of this test, this indicates a large amount of
	// fragmentation with physical pages that are otherwise unused but not
	// returned to the OS.
	fmt.Printf("exceeded physical memory overuse threshold of %3.2f%%: %3.2f%%\n"+
		"(alloc: %d, goal: %d, sys: %d, rel: %d, objs: %d)\n", threshold*100, overuse*100,
		stats.HeapAlloc, stats.NextGC, stats.HeapSys, stats.HeapReleased, len(saved))
	runtime.KeepAlive(saved)
}

// Test that defer closure is correctly scanned when the stack is scanned.
func DeferLiveness() {
	var x [10]int
	escape(&x)
	fn := func() {
		if x[0] != 42 {
			panic("FAIL")
		}
	}
	defer fn()

	x[0] = 42
	runtime.GC()
	runtime.GC()
	runtime.GC()
}

//go:noinline
func escape(x interface{}) { sink2 = x; sink2 = nil }

var sink2 interface{}

// Test zombie object detection and reporting.
func GCZombie() {
	// Allocate several objects of unusual size (so free slots are
	// unlikely to all be re-allocated by the runtime).
	const size = 190
	const count = 8192 / size
	keep := make([]*byte, 0, (count+1)/2)
	free := make([]uintptr, 0, (count+1)/2)
	zombies := make([]*byte, 0, len(free))
	for i := 0; i < count; i++ {
		obj := make([]byte, size)
		p := &obj[0]
		if i%2 == 0 {
			keep = append(keep, p)
		} else {
			free = append(free, uintptr(unsafe.Pointer(p)))
		}
	}

	// Free the unreferenced objects.
	runtime.GC()

	// Bring the free objects back to life.
	for _, p := range free {
		zombies = append(zombies, (*byte)(unsafe.Pointer(p)))
	}

	// GC should detect the zombie objects.
	runtime.GC()
	println("failed")
	runtime.KeepAlive(keep)
	runtime.KeepAlive(zombies)
}
