package main

import (
	"context"
	"flag"
	"fmt"
	"log"
	"os"
	"runtime"
	"runtime/pprof"
	"sync"
	"time"
)

func main() {
	profileName := flag.String("profile", "./bumps.pb.gz", "File name for CPU profile")
	tickTime := flag.Duration("tick", 5*time.Millisecond, "How long to work before creating a new thread")
	duration := flag.Duration("duration", 5*time.Second, "Total duration of the test")
	flag.Parse()

	pf, err := os.Create(*profileName)
	if err != nil {
		log.Fatalf("Create; err = %v", err)
	}
	defer func() {
		err := pf.Close()
		if err != nil {
			log.Fatalf("Close; err = %v", err)
		}
	}()
	err = pprof.StartCPUProfile(pf)
	if err != nil {
		log.Fatalf("StartCPUProfile; err = %v", err)
	}
	defer pprof.StopCPUProfile()

	ctx, cancel := context.WithTimeout(context.Background(), *duration)
	defer cancel()

	tick := time.Tick(*tickTime)

	var p int64 = 1
	workUntilTick := func() {
		for {
			select {
			case <-tick:
				return
			default:
			}

			work(&p)
		}
	}

	// All work takes place on goroutine 1. Lock it to the current thread so
	// we're sure the
	runtime.LockOSThread()

	var wg sync.WaitGroup
	for ctx.Err() == nil {
		workUntilTick()

		wg.Add(1)
		go func() {
			defer wg.Done()

			// Have this code run on its own thread, force the runtime to
			// terminate the thread when this goroutine completes (so the next
			// run will be on a different thread).
			runtime.LockOSThread()
			defer runtime.Goexit()

			// Yield for a moment, try to trigger a call to runtime·execute on
			// this thread.
			runtime.Gosched()
		}()
		wg.Wait()
	}

	fmt.Printf("%d\n", p)
}

func work(p *int64) {
	for i := 0; i < 1e5; i++ {
		*p *= 3
	}
}
