Skip to content

Commit

Permalink
feat(shed): lotus-shed msg --gas-stats (w/ tabular output) (#12817)
Browse files Browse the repository at this point in the history
  • Loading branch information
rvagg authored Jan 9, 2025
1 parent 9a696eb commit 0e4857a
Show file tree
Hide file tree
Showing 3 changed files with 295 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
- Lotus now reports the network name as a tag in most metrics. Some untagged metrics will be completed in a follow-up at a later date. ([filecoin-project/lotus#12733](https://github.com/filecoin-project/lotus/pull/12733))
- Refactored Ethereum API implementation into smaller, more manageable modules in a new `github.com/filecoin-project/lotus/node/impl/eth` package. ([filecoin-project/lotus#12796](https://github.com/filecoin-project/lotus/pull/12796))
- Generate the cli docs directly from the code instead compiling and executing binaries' `help` output. ([filecoin-project/lotus#12717](https://github.com/filecoin-project/lotus/pull/12717))
- Add `lotus-shed msg --gas-stats` to show summarised gas stats for a given message. ([filecoin-project/lotus#12817](https://github.com/filecoin-project/lotus/pull/12817))

# UNRELEASED v.1.32.0

Expand Down
173 changes: 166 additions & 7 deletions cmd/lotus-shed/msg.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"encoding/hex"
"encoding/json"
"fmt"
"io"
"sort"

"github.com/fatih/color"
"github.com/ipfs/go-cid"
Expand All @@ -19,6 +21,7 @@ import (
"github.com/filecoin-project/lotus/chain/consensus"
"github.com/filecoin-project/lotus/chain/types"
lcli "github.com/filecoin-project/lotus/cli"
"github.com/filecoin-project/lotus/lib/tablewriter"
)

var msgCmd = &cli.Command{
Expand All @@ -27,10 +30,19 @@ var msgCmd = &cli.Command{
Usage: "Translate message between various formats",
ArgsUsage: "Message in any form",
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "show-message",
Usage: "Print the message details",
Value: true,
},
&cli.BoolFlag{
Name: "exec-trace",
Usage: "Print the execution trace",
},
&cli.BoolFlag{
Name: "gas-stats",
Usage: "Print a summary of gas charges",
},
},
Action: func(cctx *cli.Context) error {
if cctx.NArg() != 1 {
Expand Down Expand Up @@ -82,16 +94,61 @@ var msgCmd = &cli.Command{
fmt.Printf("Return: %x\n", res.MsgRct.Return)
fmt.Printf("Gas Used: %d\n", res.MsgRct.GasUsed)
}

if cctx.Bool("gas-stats") {
var printTrace func(descPfx string, trace types.ExecutionTrace) error
printTrace = func(descPfx string, trace types.ExecutionTrace) error {
typ := "Message"
if descPfx != "" {
typ = "Subcall"
}
_, _ = fmt.Fprintln(cctx.App.Writer, color.New(color.Bold).Sprint(fmt.Sprintf("%s (%s%s) gas charges:", typ, descPfx, trace.Msg.To)))
if err := statsTable(cctx.App.Writer, trace, false); err != nil {
return err
}
for _, subtrace := range trace.Subcalls {
_, _ = fmt.Fprintln(cctx.App.Writer)
if err := printTrace(descPfx+trace.Msg.To.String()+"➜", subtrace); err != nil {
return err
}
}
return nil
}
if err := printTrace("", res.ExecutionTrace); err != nil {
return err
}
if len(res.ExecutionTrace.Subcalls) > 0 {
_, _ = fmt.Fprintln(cctx.App.Writer)
_, _ = fmt.Fprintln(cctx.App.Writer, color.New(color.Bold).Sprint("Total gas charges:"))
if err := statsTable(cctx.App.Writer, res.ExecutionTrace, true); err != nil {
return err
}
perCallTrace := gasTracesPerCall(res.ExecutionTrace)
_, _ = fmt.Fprintln(cctx.App.Writer)
_, _ = fmt.Fprintln(cctx.App.Writer, color.New(color.Bold).Sprint("Gas charges per call:"))
if err := statsTable(cctx.App.Writer, perCallTrace, false); err != nil {
return err
}
}
}
}

switch msg := msg.(type) {
case *types.SignedMessage:
return printSignedMessage(cctx, msg)
case *types.Message:
return printMessage(cctx, msg)
default:
return xerrors.Errorf("this error message can't be printed")
if cctx.Bool("show-message") {
switch msg := msg.(type) {
case *types.SignedMessage:
if err := printSignedMessage(cctx, msg); err != nil {
return err
}
case *types.Message:
if err := printMessage(cctx, msg); err != nil {
return err
}
default:
return xerrors.Errorf("this error message can't be printed")
}
}

return nil
},
}

Expand Down Expand Up @@ -335,3 +392,105 @@ func messageFromCID(cctx *cli.Context, c cid.Cid) (types.ChainMsg, error) {

return messageFromBytes(cctx, msgb)
}

type gasTally struct {
storageGas int64
computeGas int64
count int
}

func accumGasTallies(charges map[string]*gasTally, totals *gasTally, trace types.ExecutionTrace, recurse bool) {
for _, charge := range trace.GasCharges {
name := charge.Name
if _, ok := charges[name]; !ok {
charges[name] = &gasTally{}
}
charges[name].computeGas += charge.ComputeGas
charges[name].storageGas += charge.StorageGas
charges[name].count++
totals.computeGas += charge.ComputeGas
totals.storageGas += charge.StorageGas
totals.count++
}
if recurse {
for _, subtrace := range trace.Subcalls {
accumGasTallies(charges, totals, subtrace, recurse)
}
}
}

func statsTable(out io.Writer, trace types.ExecutionTrace, recurse bool) error {
tw := tablewriter.New(
tablewriter.Col("Type"),
tablewriter.Col("Count", tablewriter.RightAlign()),
tablewriter.Col("Storage Gas", tablewriter.RightAlign()),
tablewriter.Col("S%", tablewriter.RightAlign()),
tablewriter.Col("Compute Gas", tablewriter.RightAlign()),
tablewriter.Col("C%", tablewriter.RightAlign()),
tablewriter.Col("Total Gas", tablewriter.RightAlign()),
tablewriter.Col("T%", tablewriter.RightAlign()),
)

totals := &gasTally{}
charges := make(map[string]*gasTally)
accumGasTallies(charges, totals, trace, recurse)

// Sort by name
names := make([]string, 0, len(charges))
for name := range charges {
names = append(names, name)
}
sort.Strings(names)

for _, name := range names {
charge := charges[name]
tw.Write(map[string]interface{}{
"Type": name,
"Count": charge.count,
"Storage Gas": charge.storageGas,
"S%": fmt.Sprintf("%.2f", float64(charge.storageGas)/float64(totals.storageGas)*100),
"Compute Gas": charge.computeGas,
"C%": fmt.Sprintf("%.2f", float64(charge.computeGas)/float64(totals.computeGas)*100),
"Total Gas": charge.storageGas + charge.computeGas,
"T%": fmt.Sprintf("%.2f", float64(charge.storageGas+charge.computeGas)/float64(totals.storageGas+totals.computeGas)*100),
})
}
tw.Write(map[string]interface{}{
"Type": "Total",
"Count": totals.count,
"Storage Gas": totals.storageGas,
"S%": "100.00",
"Compute Gas": totals.computeGas,
"C%": "100.00",
"Total Gas": totals.storageGas + totals.computeGas,
"T%": "100.00",
})
return tw.Flush(out, tablewriter.WithBorders())
}

// Takes an execution trace and returns a new trace that groups all the gas charges by the message
// they were charged in, with the gas charges named per message; the output is partial and only
// suitable for calling statsTable() with.
func gasTracesPerCall(inTrace types.ExecutionTrace) types.ExecutionTrace {
outTrace := types.ExecutionTrace{
GasCharges: []*types.GasTrace{},
}
count := 1
var accum func(name string, trace types.ExecutionTrace)
accum = func(name string, trace types.ExecutionTrace) {
totals := &gasTally{}
charges := make(map[string]*gasTally)
accumGasTallies(charges, totals, trace, false)
outTrace.GasCharges = append(outTrace.GasCharges, &types.GasTrace{
Name: fmt.Sprintf("#%d %s", count, name),
ComputeGas: totals.computeGas,
StorageGas: totals.storageGas,
})
count++
for _, subtrace := range trace.Subcalls {
accum(name+"➜"+subtrace.Msg.To.String(), subtrace)
}
}
accum(inTrace.Msg.To.String(), inTrace)
return outTrace
}
Loading

0 comments on commit 0e4857a

Please sign in to comment.