2020-04-02 21:18:57 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2020-06-26 21:38:06 +00:00
|
|
|
"bufio"
|
2020-04-02 21:18:57 +00:00
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2020-06-23 03:08:19 +00:00
|
|
|
"io"
|
2020-04-02 21:18:57 +00:00
|
|
|
"io/ioutil"
|
2020-06-15 20:02:57 +00:00
|
|
|
"math"
|
2020-06-26 20:18:43 +00:00
|
|
|
"net/http"
|
|
|
|
_ "net/http/pprof"
|
2020-04-02 21:18:57 +00:00
|
|
|
"os"
|
2020-06-15 23:05:29 +00:00
|
|
|
"runtime"
|
2020-04-02 21:18:57 +00:00
|
|
|
"runtime/pprof"
|
2020-04-23 23:48:54 +00:00
|
|
|
"sort"
|
2020-04-02 21:18:57 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/filecoin-project/lotus/api"
|
|
|
|
"github.com/filecoin-project/lotus/chain/stmgr"
|
|
|
|
"github.com/filecoin-project/lotus/chain/store"
|
|
|
|
"github.com/filecoin-project/lotus/chain/types"
|
|
|
|
"github.com/filecoin-project/lotus/chain/vm"
|
2020-05-15 20:01:45 +00:00
|
|
|
_ "github.com/filecoin-project/lotus/lib/sigs/bls"
|
|
|
|
_ "github.com/filecoin-project/lotus/lib/sigs/secp"
|
2020-04-02 21:18:57 +00:00
|
|
|
"github.com/filecoin-project/sector-storage/ffiwrapper"
|
|
|
|
"github.com/filecoin-project/specs-actors/actors/abi"
|
|
|
|
"golang.org/x/xerrors"
|
|
|
|
|
|
|
|
"github.com/ipfs/go-datastore"
|
|
|
|
badger "github.com/ipfs/go-ds-badger2"
|
|
|
|
blockstore "github.com/ipfs/go-ipfs-blockstore"
|
2020-06-02 18:12:53 +00:00
|
|
|
"github.com/urfave/cli/v2"
|
2020-04-02 21:18:57 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type TipSetExec struct {
|
|
|
|
TipSet types.TipSetKey
|
|
|
|
Trace []*api.InvocResult
|
|
|
|
Duration time.Duration
|
|
|
|
}
|
|
|
|
|
|
|
|
var importBenchCmd = &cli.Command{
|
|
|
|
Name: "import",
|
|
|
|
Usage: "benchmark chain import and validation",
|
2020-04-23 23:48:54 +00:00
|
|
|
Subcommands: []*cli.Command{
|
|
|
|
importAnalyzeCmd,
|
|
|
|
},
|
2020-04-02 21:18:57 +00:00
|
|
|
Flags: []cli.Flag{
|
|
|
|
&cli.Int64Flag{
|
|
|
|
Name: "height",
|
|
|
|
Usage: "halt validation after given height",
|
|
|
|
},
|
2020-06-15 23:05:29 +00:00
|
|
|
&cli.IntFlag{
|
|
|
|
Name: "batch-seal-verify-threads",
|
|
|
|
Usage: "set the parallelism factor for batch seal verification",
|
|
|
|
Value: runtime.NumCPU(),
|
|
|
|
},
|
2020-04-02 21:18:57 +00:00
|
|
|
},
|
|
|
|
Action: func(cctx *cli.Context) error {
|
2020-06-15 23:05:29 +00:00
|
|
|
vm.BatchSealVerifyParallelism = cctx.Int("batch-seal-verify-threads")
|
2020-04-02 21:18:57 +00:00
|
|
|
if !cctx.Args().Present() {
|
|
|
|
fmt.Println("must pass car file of chain to benchmark importing")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
cfi, err := os.Open(cctx.Args().First())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-05-27 20:53:20 +00:00
|
|
|
defer cfi.Close() //nolint:errcheck // read only file
|
2020-04-02 21:18:57 +00:00
|
|
|
|
|
|
|
tdir, err := ioutil.TempDir("", "lotus-import-bench")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
bds, err := badger.NewDatastore(tdir, nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
bs := blockstore.NewBlockstore(bds)
|
2020-05-16 18:31:14 +00:00
|
|
|
cbs, err := blockstore.CachedBlockstore(context.TODO(), bs, blockstore.DefaultCacheOpts())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
bs = cbs
|
2020-04-02 21:18:57 +00:00
|
|
|
ds := datastore.NewMapDatastore()
|
|
|
|
cs := store.NewChainStore(bs, ds, vm.Syscalls(ffiwrapper.ProofVerifier))
|
|
|
|
stm := stmgr.NewStateManager(cs)
|
|
|
|
|
|
|
|
prof, err := os.Create("import-bench.prof")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-05-27 20:53:20 +00:00
|
|
|
defer prof.Close() //nolint:errcheck
|
2020-04-02 21:18:57 +00:00
|
|
|
|
|
|
|
if err := pprof.StartCPUProfile(prof); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
head, err := cs.Import(cfi)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if h := cctx.Int64("height"); h != 0 {
|
2020-05-05 17:06:05 +00:00
|
|
|
tsh, err := cs.GetTipsetByHeight(context.TODO(), abi.ChainEpoch(h), head, true)
|
2020-04-02 21:18:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
head = tsh
|
|
|
|
}
|
|
|
|
|
|
|
|
ts := head
|
|
|
|
tschain := []*types.TipSet{ts}
|
|
|
|
for ts.Height() != 0 {
|
|
|
|
next, err := cs.LoadTipSet(ts.Parents())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
tschain = append(tschain, next)
|
|
|
|
ts = next
|
|
|
|
}
|
|
|
|
|
2020-06-23 03:08:19 +00:00
|
|
|
ibj, err := os.Create("import-bench.json")
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer ibj.Close() //nolint:errcheck
|
|
|
|
|
|
|
|
enc := json.NewEncoder(ibj)
|
|
|
|
|
|
|
|
var lastTse *TipSetExec
|
2020-04-02 21:18:57 +00:00
|
|
|
|
|
|
|
lastState := tschain[len(tschain)-1].ParentState()
|
|
|
|
for i := len(tschain) - 2; i >= 0; i-- {
|
|
|
|
cur := tschain[i]
|
|
|
|
log.Infof("computing state (height: %d, ts=%s)", cur.Height(), cur.Cids())
|
|
|
|
if cur.ParentState() != lastState {
|
2020-06-23 03:08:19 +00:00
|
|
|
lastTrace := lastTse.Trace
|
2020-05-15 20:01:45 +00:00
|
|
|
d, err := json.MarshalIndent(lastTrace, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
fmt.Println("TRACE")
|
|
|
|
fmt.Println(string(d))
|
|
|
|
return xerrors.Errorf("tipset chain had state mismatch at height %d (%s != %s)", cur.Height(), cur.ParentState(), lastState)
|
2020-04-02 21:18:57 +00:00
|
|
|
}
|
|
|
|
start := time.Now()
|
|
|
|
st, trace, err := stm.ExecutionTrace(context.TODO(), cur)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-06-25 14:46:50 +00:00
|
|
|
stripCallers(trace)
|
2020-06-23 03:08:19 +00:00
|
|
|
|
|
|
|
lastTse = &TipSetExec{
|
2020-04-02 21:18:57 +00:00
|
|
|
TipSet: cur.Key(),
|
|
|
|
Trace: trace,
|
|
|
|
Duration: time.Since(start),
|
2020-06-23 03:08:19 +00:00
|
|
|
}
|
2020-04-02 21:18:57 +00:00
|
|
|
lastState = st
|
2020-06-23 03:08:19 +00:00
|
|
|
if err := enc.Encode(lastTse); err != nil {
|
|
|
|
return xerrors.Errorf("failed to write out tipsetexec: %w", err)
|
|
|
|
}
|
2020-04-02 21:18:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pprof.StopCPUProfile()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
},
|
|
|
|
}
|
2020-04-23 23:48:54 +00:00
|
|
|
|
2020-06-25 14:46:50 +00:00
|
|
|
func walkExecutionTrace(et *types.ExecutionTrace) {
|
|
|
|
for _, gc := range et.GasCharges {
|
|
|
|
gc.Callers = nil
|
|
|
|
}
|
|
|
|
for _, sub := range et.Subcalls {
|
2020-06-25 14:58:55 +00:00
|
|
|
walkExecutionTrace(&sub) //nolint:scopelint,gosec
|
2020-06-25 14:46:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func stripCallers(trace []*api.InvocResult) {
|
|
|
|
for _, t := range trace {
|
|
|
|
walkExecutionTrace(&t.ExecutionTrace)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-23 23:48:54 +00:00
|
|
|
type Invocation struct {
|
|
|
|
TipSet types.TipSetKey
|
|
|
|
Invoc *api.InvocResult
|
|
|
|
}
|
|
|
|
|
2020-06-15 20:02:57 +00:00
|
|
|
const GasPerNs = 10
|
|
|
|
|
|
|
|
func countGasCosts(et *types.ExecutionTrace) (int64, int64) {
|
|
|
|
var cgas, vgas int64
|
|
|
|
|
|
|
|
for _, gc := range et.GasCharges {
|
|
|
|
cgas += gc.ComputeGas
|
|
|
|
vgas += gc.VirtualComputeGas
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, sub := range et.Subcalls {
|
|
|
|
c, v := countGasCosts(&sub)
|
|
|
|
cgas += c
|
|
|
|
vgas += v
|
|
|
|
}
|
|
|
|
|
|
|
|
return cgas, vgas
|
|
|
|
}
|
|
|
|
|
2020-06-26 23:12:43 +00:00
|
|
|
func compStats(vals []float64) (float64, float64) {
|
|
|
|
var sum float64
|
2020-06-15 20:02:57 +00:00
|
|
|
|
|
|
|
for _, v := range vals {
|
|
|
|
sum += v
|
|
|
|
}
|
|
|
|
|
2020-06-26 23:12:43 +00:00
|
|
|
av := sum / float64(len(vals))
|
2020-06-15 20:02:57 +00:00
|
|
|
|
2020-06-26 23:12:43 +00:00
|
|
|
var varsum float64
|
2020-06-15 20:02:57 +00:00
|
|
|
for _, v := range vals {
|
|
|
|
delta := av - v
|
|
|
|
varsum += delta * delta
|
|
|
|
}
|
|
|
|
|
2020-06-27 11:38:11 +00:00
|
|
|
return av, math.Sqrt(varsum / float64(len(vals)))
|
2020-06-15 20:02:57 +00:00
|
|
|
}
|
|
|
|
|
2020-06-26 21:08:06 +00:00
|
|
|
type stats struct {
|
|
|
|
count float64
|
|
|
|
mean float64
|
|
|
|
dSqr float64
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *stats) AddPoint(value float64) {
|
|
|
|
s.count++
|
|
|
|
meanDiff := (value - s.mean) / s.count
|
|
|
|
newMean := s.mean + meanDiff
|
|
|
|
|
|
|
|
dSqrtInc := (value - newMean) * (value - s.mean)
|
|
|
|
s.dSqr += dSqrtInc
|
|
|
|
s.mean = newMean
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s *stats) variance() float64 {
|
|
|
|
return s.dSqr / (s.count - 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (s1 *stats) Combine(s2 *stats) {
|
|
|
|
if s1.count == 0 {
|
|
|
|
*s1 = *s2
|
|
|
|
}
|
2020-06-27 11:55:29 +00:00
|
|
|
if s2.count == 0 {
|
|
|
|
return
|
|
|
|
}
|
2020-06-26 21:08:06 +00:00
|
|
|
newCount := s1.count + s2.count
|
|
|
|
newMean := s1.count*s1.mean + s2.count*s2.mean
|
|
|
|
newMean /= newCount
|
|
|
|
newVar := s1.count * (s1.variance() + (s1.mean-newMean)*(s1.mean-newMean))
|
|
|
|
newVar += s2.count * (s2.variance() + (s2.mean-newMean)*(s2.mean-newMean))
|
|
|
|
newVar /= newCount
|
|
|
|
s1.count = newCount
|
|
|
|
s1.mean = newMean
|
|
|
|
s1.dSqr = newVar * (newCount - 1)
|
|
|
|
}
|
|
|
|
|
2020-06-26 23:09:35 +00:00
|
|
|
func tallyGasCharges(charges map[string]*stats, et types.ExecutionTrace) {
|
2020-06-15 23:05:29 +00:00
|
|
|
for _, gc := range et.GasCharges {
|
|
|
|
|
|
|
|
compGas := gc.ComputeGas + gc.VirtualComputeGas
|
2020-06-26 23:49:01 +00:00
|
|
|
if compGas == 0 {
|
|
|
|
compGas = 1
|
|
|
|
}
|
|
|
|
|
2020-06-26 21:08:06 +00:00
|
|
|
ratio := float64(compGas) / float64(gc.TimeTaken.Nanoseconds())
|
2020-06-27 11:55:29 +00:00
|
|
|
ratio = 1 / ratio
|
|
|
|
if math.IsNaN(ratio) {
|
|
|
|
log.Errorf("NaN: comGas: %f, taken: %d", compGas, gc.TimeTaken.Nanoseconds())
|
|
|
|
}
|
2020-06-15 23:05:29 +00:00
|
|
|
|
2020-06-26 21:08:06 +00:00
|
|
|
s := charges[gc.Name]
|
|
|
|
if s == nil {
|
|
|
|
s = new(stats)
|
|
|
|
charges[gc.Name] = s
|
|
|
|
}
|
|
|
|
|
2020-06-27 11:55:29 +00:00
|
|
|
s.AddPoint(ratio)
|
2020-06-15 23:05:29 +00:00
|
|
|
//fmt.Printf("%s: %d, %s: %0.2f\n", gc.Name, compGas, gc.TimeTaken, 1/(ratio/GasPerNs))
|
|
|
|
}
|
2020-06-26 23:18:06 +00:00
|
|
|
for _, sub := range et.Subcalls {
|
|
|
|
tallyGasCharges(charges, sub)
|
|
|
|
}
|
2020-06-15 23:05:29 +00:00
|
|
|
}
|
|
|
|
|
2020-04-23 23:48:54 +00:00
|
|
|
var importAnalyzeCmd = &cli.Command{
|
|
|
|
Name: "analyze",
|
|
|
|
Action: func(cctx *cli.Context) error {
|
|
|
|
if !cctx.Args().Present() {
|
|
|
|
fmt.Println("must pass bench file to analyze")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-06-26 20:18:43 +00:00
|
|
|
go func() {
|
|
|
|
http.ListenAndServe("localhost:6060", nil)
|
|
|
|
}()
|
|
|
|
|
2020-04-23 23:48:54 +00:00
|
|
|
fi, err := os.Open(cctx.Args().First())
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-06-26 19:54:20 +00:00
|
|
|
const nWorkers = 16
|
2020-06-26 21:38:06 +00:00
|
|
|
jsonIn := make(chan []byte, 2*nWorkers)
|
2020-06-26 19:54:20 +00:00
|
|
|
type result struct {
|
|
|
|
totalTime time.Duration
|
2020-06-26 21:08:06 +00:00
|
|
|
chargeStats map[string]*stats
|
2020-06-26 19:54:20 +00:00
|
|
|
expensiveInvocs []Invocation
|
|
|
|
}
|
|
|
|
|
|
|
|
results := make(chan result, nWorkers)
|
|
|
|
|
|
|
|
for i := 0; i < nWorkers; i++ {
|
|
|
|
go func() {
|
2020-06-26 21:08:06 +00:00
|
|
|
chargeStats := make(map[string]*stats)
|
2020-06-26 19:54:20 +00:00
|
|
|
var totalTime time.Duration
|
2020-06-26 21:38:06 +00:00
|
|
|
const invocsKeep = 32
|
|
|
|
var expensiveInvocs = make([]Invocation, 0, 8*invocsKeep)
|
2020-06-26 19:54:20 +00:00
|
|
|
var leastExpensiveInvoc = time.Duration(0)
|
|
|
|
|
|
|
|
for {
|
2020-06-26 21:38:06 +00:00
|
|
|
b, ok := <-jsonIn
|
2020-06-26 19:54:20 +00:00
|
|
|
if !ok {
|
|
|
|
results <- result{
|
|
|
|
totalTime: totalTime,
|
2020-06-26 21:08:06 +00:00
|
|
|
chargeStats: chargeStats,
|
2020-06-26 19:54:20 +00:00
|
|
|
expensiveInvocs: expensiveInvocs,
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2020-06-26 20:18:43 +00:00
|
|
|
|
2020-06-26 23:39:41 +00:00
|
|
|
var tse TipSetExec
|
|
|
|
err := json.Unmarshal(b, &tse)
|
|
|
|
if err != nil {
|
|
|
|
log.Warnf("error unmarshaling tipset: %+v", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2020-06-26 19:54:20 +00:00
|
|
|
totalTime += tse.Duration
|
|
|
|
for _, inv := range tse.Trace {
|
|
|
|
if inv.Duration > leastExpensiveInvoc {
|
|
|
|
expensiveInvocs = append(expensiveInvocs, Invocation{
|
|
|
|
TipSet: tse.TipSet,
|
|
|
|
Invoc: inv,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-06-26 23:09:35 +00:00
|
|
|
tallyGasCharges(chargeStats, inv.ExecutionTrace)
|
2020-06-26 19:54:20 +00:00
|
|
|
}
|
2020-06-26 21:38:06 +00:00
|
|
|
if len(expensiveInvocs) > 4*invocsKeep {
|
|
|
|
sort.Slice(expensiveInvocs, func(i, j int) bool {
|
|
|
|
return expensiveInvocs[i].Invoc.Duration > expensiveInvocs[j].Invoc.Duration
|
|
|
|
})
|
2020-06-26 19:54:20 +00:00
|
|
|
leastExpensiveInvoc = expensiveInvocs[len(expensiveInvocs)-1].Invoc.Duration
|
2020-06-26 19:57:57 +00:00
|
|
|
n := 30
|
|
|
|
if len(expensiveInvocs) < n {
|
|
|
|
n = len(expensiveInvocs)
|
|
|
|
}
|
|
|
|
expensiveInvocs = expensiveInvocs[:n]
|
2020-06-26 19:54:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
var totalTipsets int64
|
2020-06-26 21:38:06 +00:00
|
|
|
reader := bufio.NewReader(fi)
|
2020-06-23 03:08:19 +00:00
|
|
|
for {
|
2020-06-26 21:38:06 +00:00
|
|
|
b, err := reader.ReadBytes('\n')
|
|
|
|
if err != nil && err != io.EOF {
|
|
|
|
if e, ok := err.(*json.SyntaxError); ok {
|
|
|
|
log.Warnf("syntax error at byte offset %d", e.Offset)
|
2020-06-23 03:08:19 +00:00
|
|
|
}
|
2020-06-26 21:38:06 +00:00
|
|
|
return err
|
2020-06-23 03:08:19 +00:00
|
|
|
}
|
2020-06-26 19:54:20 +00:00
|
|
|
totalTipsets++
|
2020-06-26 21:38:06 +00:00
|
|
|
jsonIn <- b
|
|
|
|
fmt.Printf("\rProcessed %d tipsets", totalTipsets)
|
|
|
|
if err == io.EOF {
|
|
|
|
break
|
2020-06-26 19:54:20 +00:00
|
|
|
}
|
2020-04-23 23:48:54 +00:00
|
|
|
}
|
2020-06-26 21:38:06 +00:00
|
|
|
close(jsonIn)
|
2020-06-26 19:54:20 +00:00
|
|
|
fmt.Printf("\n")
|
|
|
|
fmt.Printf("Collecting results\n")
|
2020-06-15 20:02:57 +00:00
|
|
|
|
2020-04-23 23:48:54 +00:00
|
|
|
var invocs []Invocation
|
|
|
|
var totalTime time.Duration
|
2020-06-26 19:54:20 +00:00
|
|
|
var keys []string
|
2020-06-26 21:08:06 +00:00
|
|
|
var charges = make(map[string]*stats)
|
2020-06-26 19:54:20 +00:00
|
|
|
for i := 0; i < nWorkers; i++ {
|
|
|
|
fmt.Printf("\rProcessing results from worker %d/%d", i+1, nWorkers)
|
|
|
|
res := <-results
|
|
|
|
invocs = append(invocs, res.expensiveInvocs...)
|
2020-06-26 21:08:06 +00:00
|
|
|
for k, v := range res.chargeStats {
|
|
|
|
s := charges[k]
|
|
|
|
if s == nil {
|
|
|
|
s = new(stats)
|
|
|
|
charges[k] = s
|
|
|
|
}
|
|
|
|
s.Combine(v)
|
2020-04-23 23:48:54 +00:00
|
|
|
}
|
2020-06-26 19:54:20 +00:00
|
|
|
totalTime += res.totalTime
|
2020-04-23 23:48:54 +00:00
|
|
|
}
|
|
|
|
|
2020-06-26 19:54:20 +00:00
|
|
|
fmt.Printf("\nCollecting gas keys\n")
|
2020-06-26 21:08:06 +00:00
|
|
|
for k := range charges {
|
2020-06-15 20:02:57 +00:00
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Println("Gas Price Deltas")
|
|
|
|
sort.Strings(keys)
|
|
|
|
for _, k := range keys {
|
2020-06-26 21:08:06 +00:00
|
|
|
s := charges[k]
|
|
|
|
fmt.Printf("%s: incr by %f (%f)\n", k, s.mean, math.Sqrt(s.variance()))
|
2020-06-15 20:02:57 +00:00
|
|
|
}
|
|
|
|
|
2020-04-23 23:48:54 +00:00
|
|
|
sort.Slice(invocs, func(i, j int) bool {
|
|
|
|
return invocs[i].Invoc.Duration > invocs[j].Invoc.Duration
|
|
|
|
})
|
|
|
|
|
|
|
|
fmt.Println("Total time: ", totalTime)
|
2020-06-26 19:54:20 +00:00
|
|
|
fmt.Println("Average time per epoch: ", totalTime/time.Duration(totalTipsets))
|
2020-04-23 23:48:54 +00:00
|
|
|
|
|
|
|
n := 30
|
2020-06-26 18:42:29 +00:00
|
|
|
if len(invocs) < n {
|
|
|
|
n = len(invocs)
|
|
|
|
}
|
2020-04-23 23:48:54 +00:00
|
|
|
fmt.Printf("Top %d most expensive calls:\n", n)
|
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
inv := invocs[i].Invoc
|
|
|
|
fmt.Printf("%s: %s %s %d %s\n", inv.Duration, inv.Msg.From, inv.Msg.To, inv.Msg.Method, invocs[i].TipSet)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|