package gometrics

import (
	"runtime"
	"time"

	"github.com/cactus/go-statsd-client/statsd"
)

// Monitor launches a background goroutine which sends runtime metrics to statsd continuously at the provided rate
// For reference of available data, refer to https://golang.org/pkg/runtime/#MemStats
func Monitor(stats statsd.StatSender, rate time.Duration) {
	go func() {
		var previousNumGC uint32
		var previousMallocs, previousFrees, previousGCTotal uint64

		memStats := &runtime.MemStats{}
		ticker := time.NewTicker(rate)
		defer ticker.Stop()

		for range ticker.C {
			runtime.ReadMemStats(memStats)

			stats.Gauge("go.num_goroutines", int64(runtime.NumGoroutine()), 1.0)

			stats.Gauge("go.mem.heap_objects", int64(memStats.HeapObjects), 1.0)
			stats.Gauge("go.mem.sys", int64(memStats.Sys), 1.0)
			stats.Gauge("go.mem.alloc", int64(memStats.Alloc), 1.0)
			stats.Inc("go.mem.mallocs", int64(memStats.Mallocs-previousMallocs), 1.0)
			stats.Inc("go.mem.frees", int64(memStats.Frees-previousFrees), 1.0)

			stats.Gauge("go.gc.percent_cpu", fractionToPercent(memStats.GCCPUFraction), 1.0)
			stats.Inc("go.gc.collections", int64(memStats.NumGC-previousNumGC), 1.0)
			stats.TimingDuration("go.gc.last_duration", time.Duration(memStats.PauseNs[(memStats.NumGC+255)%256]), 1.0)
			stats.TimingDuration("go.gc.total_duration", time.Duration(memStats.PauseTotalNs-previousGCTotal), 1.0)

			// heap
			stats.Gauge("go.mem.heap.sys", int64(memStats.HeapSys), 1.0)
			stats.Gauge("go.mem.heap.idle", int64(memStats.HeapIdle), 1.0)
			stats.Gauge("go.mem.heap.inuse", int64(memStats.HeapInuse), 1.0)
			stats.Gauge("go.mem.heap.released", int64(memStats.HeapReleased), 1.0)

			// stack
			stats.Gauge("go.mem.stack.sys", int64(memStats.StackSys), 1.0)
			stats.Gauge("go.mem.stack.inuse", int64(memStats.StackInuse), 1.0)

			// other
			stats.Gauge("go.mem.mspan.sys", int64(memStats.MSpanSys), 1.0)
			stats.Gauge("go.mem.mcache.sys", int64(memStats.MCacheSys), 1.0)
			stats.Gauge("go.mem.buckhash.sys", int64(memStats.BuckHashSys), 1.0)
			stats.Gauge("go.mem.other.sys", int64(memStats.OtherSys), 1.0)

			previousNumGC = memStats.NumGC
			previousMallocs = memStats.Mallocs
			previousFrees = memStats.Frees
			previousGCTotal = memStats.PauseTotalNs
		}
	}()
}

func fractionToPercent(fraction float64) int64 {
	return int64((fraction * 100) + 0.5)
}
