func TrackMemoryStatistics(bufferSize int, sampleIntervalMs int64, memStatsRequestChannel <-chan chan []TimedMemStats, quitChannel chan bool) {
	memStatsReceiveChannel := make(chan TimedMemStats, 20)
	log.Print("Starting memory profiling goroutines")
	go func() {
		for {
			select {
			case <-time.After(time.Duration(sampleIntervalMs) * time.Millisecond):
				var stats TimedMemStats
				runtime.ReadMemStats(&stats.MemStats)
				stats.TimeEpochMs = time.Now().UnixNano() / 1000000
				memStatsReceiveChannel <- stats
			case <-quitChannel:
				log.Print("Stopping memory profiling goroutine (1 of 2)")
				return
			}
		}
	}()
	go func() {
		var memStats TimedMemStats
		var responseChan chan []TimedMemStats
		sampleData := make([]TimedMemStats, bufferSize)
		size := 0
		tail := -1
		for {
			select {
			case memStats = <-memStatsReceiveChannel:
				tail++
				if tail >= bufferSize {
					tail -= bufferSize
				}
				if size < bufferSize {
					size++
				}
				sampleData[tail] = memStats
			case responseChan = <-memStatsRequestChannel:
				response := make([]TimedMemStats, size)
				writeIndex := 0
				if size == bufferSize && tail < (bufferSize-1) {
					for i := tail + 1; i < bufferSize; i++ {
						response[writeIndex] = sampleData[i]
						writeIndex++
					}
				}
				for i := 0; i <= tail; i++ {
					response[writeIndex] = sampleData[i]
					writeIndex++
				}
				responseChan <- response
			case <-quitChannel:
				log.Print("Stopping memory profiling goroutine (2 of 2)")
				return
			}
		}
	}()
}
TrackMemoryStatistics keeps track of runtime.MemStats.
Sample memory statistics every [sampleIntervalMs] milliseconds, keeping the last [bufferSize]
samples. Returns a request/response channel to get recent memory statistics and a quit channel
to stop polling.
Parameters:
bufferSize: 				number of samples to keep
sampleIntervalMs: 			number of milliseconds to wait between polling
memStatsRequestChannel:		request/reply channel to report back the most recent N samples
quitChannel: 				once closed, all polling stops
package profiler
import (
	"log"
	"runtime"
	"time"
)
type TimedMemStats struct {
	TimeEpochMs int64
	MemStats runtime.MemStats
}
type HeapMemStat struct {
	TimeMsAgo      int64  
	SysKb          uint64
	HeapSysKb      uint64
	HeapAllocKb    uint64
	HeapIdleKb     uint64
	HeapReleasedKb uint64
}
func timedMemStatsToHeapMemStats(timedMemStats []TimedMemStats) []HeapMemStat {
	result := make([]HeapMemStat, len(timedMemStats))
	nowMs := time.Now().UnixNano() / 1000000
	for index := range timedMemStats {
		result[index] = HeapMemStat{
			TimeMsAgo:      timedMemStats[index].TimeEpochMs - nowMs,
			SysKb:          timedMemStats[index].MemStats.Sys / uint64(1000),
			HeapSysKb:      timedMemStats[index].MemStats.HeapSys / uint64(1000),
			HeapAllocKb:    timedMemStats[index].MemStats.HeapAlloc / uint64(1000),
			HeapIdleKb:     timedMemStats[index].MemStats.HeapIdle / uint64(1000),
			HeapReleasedKb: timedMemStats[index].MemStats.HeapReleased / uint64(1000),
		}
	}
	return result
}