package issue

import (
	"context"
	"fmt"
	"io/ioutil"
	"runtime"
	"runtime/pprof"
	"sort"
	"sync"
	"testing"
	"time"
)

var sink interface{}

func BenchmarkGoroutineProfile(b *testing.B) {
	testcase := func(n int, profile bool) func(b *testing.B) {
		return func(b *testing.B) {
			var wg sync.WaitGroup
			ctx, cancel := context.WithCancel(context.Background())

			// create bystanders
			for i := 0; i < n; i++ {
				wg.Add(1)
				go func() {
					<-ctx.Done()
					wg.Done()
				}()
			}
			// give the bystanders a moment to start up
			time.Sleep(1000 * time.Millisecond)

			// our villain
			wg.Add(1)
			go func() {
				defer wg.Done()
				p := pprof.Lookup("goroutine")
				for ctx.Err() == nil {
					if profile {
						runtime.GoroutineProfile(nil)
						runtime.Gosched()
						continue
						if err := p.WriteTo(ioutil.Discard, 0); err != nil {
							b.Fatal(err)
						}
					} else {
						// no profile requested, do other work instead
						sink = fmt.Sprintf("hello %s", "world")
					}
				}
			}()

			// test the runtime's responsiveness to

			times := make([]float64, b.N)
			sleep := 1000 * time.Microsecond

			b.ResetTimer()
			prev := time.Now()
			for i := 0; i < b.N; i++ {
				time.Sleep(sleep)
				now := time.Now()
				times[i] = (now.Sub(prev) - sleep).Seconds() / time.Millisecond.Seconds()
				prev = now
			}
			b.StopTimer()

			cancel()
			wg.Wait()

			sort.Sort(sort.Reverse(sort.Float64Slice(times)))
			b.ReportMetric(times[0], "ms-max")
			b.ReportMetric(times[len(times)/2], "ms-p50")
			b.ReportMetric(times[len(times)/100], "ms-p99")
			b.ReportMetric(times[len(times)/1000], "ms-p99.9")
		}
	}

	b.Run("1e0 other", testcase(1e0, false))
	b.Run("1e1 other", testcase(1e1, false))
	b.Run("1e2 other", testcase(1e2, false))
	b.Run("1e3 other", testcase(1e3, false))
	b.Run("1e4 other", testcase(1e4, false))
	b.Run("1e5 other", testcase(1e5, false))
	b.Run("1e6 other", testcase(1e6, false))
	b.Run("1e0 profile", testcase(1e0, true))
	b.Run("1e1 profile", testcase(1e1, true))
	b.Run("1e2 profile", testcase(1e2, true))
	b.Run("1e3 profile", testcase(1e3, true))
	b.Run("1e4 profile", testcase(1e4, true))
	b.Run("1e5 profile", testcase(1e5, true))
	b.Run("1e6 profile", testcase(1e6, true))
}
