func transform(stats set) gcDataTables {
	data := make(gcDataTables)
	for bname, stats := range stats {
		values := make(map[string][]interface{})
	L:
		for _, s := range stats {
			if _, ok := values[s.Name]; !ok {
				values[s.Name] = append(values[s.Name], s.Name)
			}
			switch s.Unit {
			case "MB/s":
				if *omitBandwidth {
					continue L
				}
			case "B/op":  
				if *omitMem {
					continue L
				}
			case "allocs/op":  
				if *omitAllocs {
					continue L
				}
			}
			values[s.Name] = append(values[s.Name], s.Value)
		}
		data[bname] = append(data[bname], []interface{}{"Name", "ns/op"})
		if !*omitBandwidth {
			data[bname][0] = append(data[bname][0], "MB/s")
		}
		if !*omitMem {
			data[bname][0] = append(data[bname][0], "B/op")
		}
		if !*omitAllocs {
			data[bname][0] = append(data[bname][0], "allocs/op")
		}
		for _, v := range values {
			data[bname] = append(data[bname], v)
		}
	}
	return data
}
transform converts a stats set to a 2D data-table
interpretable by the Google Charts API.
package main
import (
	"bufio"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"
)
var (
	omitMem       = flag.Bool("omit-mem", false, "omit B/op stat")
	omitAllocs    = flag.Bool("omit-allocs", false, "omit allocs/op stat")
	omitBandwidth = flag.Bool("omit-bandwidth", false, "omit MB/s stat")
	flagInput     = flag.String("in", "benchstats.csv", "csv-formatted benchstat file")
	flagOutput    = flag.String("out", "benchstats.json", "json-formatted data table file")
)
func usage() {
	fmt.Fprintf(os.Stderr, "usage: benchparse [options]\n")
	fmt.Fprintf(os.Stderr, "options:\n")
	flag.PrintDefaults()
	os.Exit(2)
}
type set map[string][]benchStat
type benchStat struct {
	Name  string
	Value float64
	Unit  string
}
type gcDataTables map[string][][]interface{}
func main() {
	log.SetPrefix("benchparse: ")
	log.SetFlags(0)
	flag.Usage = usage
	flag.Parse()
	file, err := os.Open(*flagInput)
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()
	stats, err := parse(bufio.NewScanner(file))
	if err != nil {
		log.Fatal(err)
	}
	out, err := os.OpenFile(*flagOutput, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
	if err != nil {
		log.Fatal(err)
	}
	defer out.Close()
	data := transform(stats)
	enc := json.NewEncoder(out)
	if err := enc.Encode(data); err != nil {
		log.Fatal(err)
	}
	if err := out.Sync(); err != nil {
		log.Fatal(err)
	}
	os.Exit(0)
}
func parse(scanr *bufio.Scanner) (set, error) {
	var (
		i     int
		unit  string
		stats = make(set)
	)
	nextIsHdr := true
	for ; scanr.Scan(); i++ {
		line := scanr.Text()
		if len(line) == 0 {
			nextIsHdr = true
			continue
		}
		r := strings.Split(line, ",")
		if nextIsHdr {
			if len(r) != 2 {
				return nil, fmt.Errorf("invalid header at line %d", i)
			}
			s := strings.Split(r[1], " ")[1]
			unit = strings.Trim(s, "()")
			nextIsHdr = false
			continue
		}
		idx := strings.Index(r[0], "/")
		if idx == -1 {
			return nil, fmt.Errorf("invalid run name at line %d: no separator", i)
		}
		bname, rname := r[0][:idx], r[0][idx+1:]
		if idx := strings.LastIndexByte(rname, '-'); idx != -1 {
			rname = rname[:idx]
		}
		if _, ok := stats[bname]; !ok {
			stats[bname] = nil
		}
		if len(r) <= 1 {
			continue
		}
		f, err := strconv.ParseFloat(r[1], 64)
		if err != nil {
			return nil, fmt.Errorf("invalid float value at line %d: %s", i, err)
		}
		stats[bname] = append(stats[bname], benchStat{
			Name:  rname,
			Value: f,
			Unit:  unit,
		})
	}
	if err := scanr.Err(); err != nil {
		return nil, err
	}
	return stats, nil
}