func parsePerf(fileName, by string) []event {
	f, err := perffile.Open(fileName)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error loading profile: %s\n", err)
		os.Exit(1)
	}
	defer f.Close()
	byPC := by == "pc"
	const requiredFormat = perffile.SampleFormatTime | perffile.SampleFormatAddr | perffile.SampleFormatWeight | perffile.SampleFormatDataSrc
	events := make([]event, 0)
	rs := f.Records(perffile.RecordsTimeOrder)
	for rs.Next() {
		r := rs.Record
		switch r := r.(type) {
		case *perffile.RecordSample:
			if r.Format&requiredFormat != requiredFormat {
				break
			}
			level := r.DataSrc.Level
			if r.DataSrc.Miss {
				level <<= 1
			}
			addr := r.Addr
			if byPC {
				addr = r.IP
			}
			events = append(events, event{r.Time, addr, r.Weight, levelToPanel[level]})
		}
	}
	return events
}
parsePerf parses a perf.data profile and returns the cache miss
events.
package main
import (
	"flag"
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/png"
	"io/ioutil"
	"log"
	"math"
	"os"
	"runtime/pprof"
	"sort"
	"github.com/aclements/go-perf/perffile"
	"github.com/golang/freetype"
)
const pageBytes = 4096
func main() {
	var (
		flagInput      = flag.String("i", "perf.data", "read memory latency profile from `file`")
		flagBy         = flag.String("by", "address", "`layout` by \"address\" or \"pc\"")
		flagFPS        = flag.Int("fps", 24, "frames per second")
		flagDilation   = flag.Float64("dilation", 1, "time dilation factor")
		flagWidth      = flag.Int("w", 512, "output width/height; must be a power of 2")
		flagCpuProfile = flag.String("cpuprofile", "", "write cpu profile to file")
	)
	flag.Parse()
	if flag.NArg() > 0 {
		flag.Usage()
		os.Exit(1)
	}
	if *flagWidth <= 0 || *flagWidth&(*flagWidth-1) != 0 {
		fmt.Fprintln(os.Stderr, "width must be a power of two")
		os.Exit(1)
	}
	if !(*flagBy == "address" || *flagBy == "pc") {
		fmt.Fprintln(os.Stderr, "-by must be address or pc")
		os.Exit(1)
	}
	if *flagCpuProfile != "" {
		f, err := os.Create(*flagCpuProfile)
		if err != nil {
			fmt.Fprintln(os.Stderr, err)
			os.Exit(1)
		}
		pprof.StartCPUProfile(f)
		defer pprof.StopCPUProfile()
	}
	events := parsePerf(*flagInput, *flagBy)
	imgSize := *flagWidth
	mapper := newAddrMapper(events, uint64(imgSize*imgSize-1))
	normalizeWeight(events)
	zeroTime(events)
	lastTime := events[len(events)-1].time
	fontCtx := freetype.NewContext()
	fontData, err := ioutil.ReadFile("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf")
	if err != nil {
		log.Fatal(err)
	}
	font, err := freetype.ParseFont(fontData)
	if err != nil {
		log.Fatal(err)
	}
	fontCtx.SetFontSize(12)
	fontCtx.SetSrc(image.Black)
	fontCtx.SetFont(font)
	fontBounds := font.Bounds(fontCtx.PointToFixed(12))
	labelHeight := int((fontBounds.Max.Y - fontBounds.Min.Y) >> 6)
	img := image.NewNRGBA(image.Rect(0, 0, (imgSize+1)*numPanels-1, imgSize+labelHeight))
	draw.Draw(img, img.Bounds(), image.White, image.ZP, draw.Over)
	fontCtx.SetDst(img)
	fontCtx.SetClip(img.Bounds())
	levelImgs := make([]*image.NRGBA, numPanels)
	for i := range levelImgs {
		left := (imgSize + 1) * i
		levelImgs[i] = img.SubImage(image.Rect(left, labelHeight, left+imgSize, imgSize+labelHeight)).(*image.NRGBA)
		levelImgs[i].Rect = levelImgs[i].Rect.Sub(levelImgs[i].Rect.Min)
		if i > 0 {
			for y := img.Rect.Min.Y; y < img.Rect.Max.Y; y++ {
				img.Set(left-1, y, color.Black)
			}
		}
		fontCtx.DrawString(">= "+panelLevels[i].String(), freetype.Pt(left+2, 12))
	}
	if false {
		addrStep := int(math.Floor(1 / mapper.normFactor))
		for pfn := range mapper.pageBase {
			for offset := 0; offset < pageBytes; offset += addrStep {
				addr := pfn*pageBytes + uint64(offset)
				x, y := hilbert(imgSize, int(mapper.mapAddr(addr)))
				naddr := float64(addr%(1<<48)) / (1 << 48) * 2 * math.Pi
				cb, cr := math.Cos(naddr), -math.Sin(naddr)
				r, g, b := color.YCbCrToRGB(127, uint8((cb+1)*127), uint8((cr+1)*127))
				img.SetNRGBA(x, y, color.NRGBA{r, g, b, 255})
			}
		}
		writePNG("addr.png", img)
	}
	nsPerFrame := int(1000000000 / (float64(*flagFPS) * *flagDilation))
	lastIndex := 0
	for frame := 0; ; frame++ {
		t0 := uint64(frame * nsPerFrame)
		t1 := uint64((frame + 1) * nsPerFrame)
		if t0 > lastTime {
			break
		}
		log.Println("frame", frame)
		for _, levelImg := range levelImgs {
			for y := 0; y < levelImg.Rect.Dy(); y++ {
				scan := levelImg.Pix[y*levelImg.Stride : y*levelImg.Stride+levelImg.Rect.Dx()*4]
				for i, p := range scan {
					scan[i] = uint8(255 - (int(255-p) * 3 / 4))
				}
			}
		}
		for evIndex, ev := range events[lastIndex:] {
			if ev.time < t0 {
				panic("time went backwards")
			}
			if t1 <= ev.time {
				lastIndex += evIndex
				break
			}
			addr := mapper.mapAddr(ev.addr)
			x, y := hilbert(imgSize, int(addr))
			color := color.NRGBA{0, 0, 0, 255}
			for level := 0; level <= ev.level; level++ {
				levelImgs[level].SetNRGBA(x, y, color)
			}
		}
		writePNG(fmt.Sprintf("f%08d.png", frame), img)
	}
	fmt.Printf("%g bytes/pixel\n", 1/mapper.normFactor)
	fmt.Printf("%g pixels/page\n", mapper.normFactor*pageBytes)
	fmt.Printf("To combine frames:\n  mencoder 'mf://f*.png' -mf fps=%d -nosound -of lavf -lavfopts format=mp4 -ovc x264 -o out.mp4\n", *flagFPS)
}
type event struct {
	time   uint64
	addr   uint64
	weight uint64
	level  int
}