func (c *changeResult) SaveDAG() (string, error) {
	return save(c.result.dagSvg, c.filePattern("dag"))
}
SaveDAG saves the dag of the change result to an svg file
package main
import (
	"bytes"
	"flag"
	"fmt"
	"github.com/soteria-dag/soterd/soterutil"
	"html/template"
	"io/ioutil"
	"log"
	"math/big"
	"path/filepath"
	"sort"
	"strings"
	"time"
	"github.com/soteria-dag/soterd/blockdag"
	"github.com/soteria-dag/soterd/chaincfg"
	"github.com/soteria-dag/soterd/integration/rpctest"
	"github.com/wcharczuk/go-chart"
	"github.com/wcharczuk/go-chart/drawing"
)
const (
	defaultMinerCount = 4
	defaultMineTime = time.Second * 30
	defaultWait = time.Minute
)
var (
	defaultSoterdExtraArgs = []string{
	}
)
type nodeMetrics struct {
	blkGenCount int64
	blkGenTimes []float64
	blkHashes []string
	multiParentBlocks int
	meanMultiParentBlockDistance float64
}
type runResult struct {
	profile *runProfile
	nodeStats map[int]*nodeMetrics
	dagSvg []byte
	dagSynced bool
}
type changeResult struct {
	fields  []string
	amounts []int64
	units   []string
	result  *runResult
}
type resultSet struct {
	changes []*changeResult
}
type runProfile struct {
	dagNetParams chaincfg.Params
	dagNetMods []func(*chaincfg.Params) error
	minerCount int
	mineTime time.Duration
	wait time.Duration
	soterdExtraArgs []string
	soterdKeepLogs bool
}
func absInt(x int) int {
	if x < 0 {
		return -x
	} else {
		return x
	}
}
func meanFloat64(x []float64) float64 {
	total := float64(0)
	if len(x) == 0 {
		return total
	}
	for _, v := range x {
		total += v
	}
	mean := total / float64(len(x))
	return mean
}
func pctBigInt(x *big.Int, pct int) (n *big.Int) {
	bigHundred := big.NewInt(100)
	bigPct := big.NewInt(int64(absInt(pct)))
	change := new(big.Int).Mul(
		new(big.Int).Div(x, bigHundred),
		bigPct)
	if pct < 0 {
		n = new(big.Int).Sub(x, change)
	} else {
		n = new(big.Int).Add(x, change)
	}
	return n
}
func save(bytes []byte, pattern string) (string, error) {
	t, err := ioutil.TempFile("", pattern)
	if err != nil {
		return "", err
	}
	_, err = t.Write(bytes)
	if err != nil {
		return "", err
	}
	err = t.Close()
	if err != nil {
		return "", err
	}
	return t.Name(), nil
}
func modPowLimit(params *chaincfg.Params, pct int) {
	params.PowLimit = pctBigInt(params.PowLimit, pct)
	params.PowLimitBits = blockdag.BigToCompact(params.PowLimit)
}
func waitForDAGTick(nodes []*rpctest.Harness, wait time.Duration) {
	threshold := time.Now().Add(wait)
	tick := time.Tick(time.Second * 5)
	stop := make(chan int)
	go func() {
		_ = rpctest.WaitForDAG(nodes, wait)
		close(stop)
	}()
	for {
		select {
			case <-tick:
				if time.Now().After(threshold) {
					fmt.Println("!")
					return
				} else {
					fmt.Print(".")
				}
			case <-stop:
				fmt.Println()
				return
		}
	}
}
func (c *changeResult) amountStrings() []string {
	s := make([]string, 0)
	for _, amount := range c.amounts {
		s = append(s, fmt.Sprintf("%d", amount))
	}
	return s
}
func (c *changeResult) changeStrings() []string {
	s := make([]string, 0)
	for i, field := range c.fields {
		s = append(s, fmt.Sprintf("%s %d%s", field, c.amounts[i], c.units[i]))
	}
	return s
}
func (c *changeResult) filePattern(prefix string) string {
	return fmt.Sprintf("%s_%s_%s_*.svg",
		prefix,
		strings.Join(c.fields, "-"),
		strings.Join(c.amountStrings(), "_"))
}
func (c *changeResult) GraphNodeBlkGen() ([]byte, error) {
	var svg bytes.Buffer
	title := fmt.Sprintf("Blocks Generated Per Node (%s)",
		strings.Join(c.changeStrings(), ", "))
	values := make([]chart.Value, 0)
	for _, m := range c.result.miners() {
		v := chart.Value{
			Label: fmt.Sprintf("node %d", m),
			Value: float64(c.result.minerBlockCount(m)),
		}
		values = append(values, v)
	}
	ch := chart.BarChart{
		Title: title,
		TitleStyle: chart.StyleShow(),
		XAxis: chart.StyleShow(),
		YAxis: chart.YAxis{
			Name: "block count",
			NameStyle: chart.StyleShow(),
			Style: chart.StyleShow(),
		},
		Bars: values,
	}
	err := ch.Render(chart.SVG, &svg)
	if err != nil {
		return svg.Bytes(), err
	}
	return svg.Bytes(), nil
}
func (c *changeResult) Print() {
	fmt.Printf("blocks (total %v):\n", c.result.totalBlockCount())
	for _, m := range c.result.miners() {
		fmt.Printf("\tminer %v\tblkGen count %v\tblkGen mean %v ms\n",
			m, c.result.minerBlockCount(m), c.result.minerMeanGenTime(m))
	}
}
func (c *changeResult) SaveNodeBlkGen() (string, error) {
	svg, err := c.GraphNodeBlkGen()
	if err != nil {
		return "", err
	}
	return save(svg, c.filePattern("nodeBlkGen"))
}