Skip to content

Commit

Permalink
feat(shed): lotus-shed msg --gas-stats (w/ tabular output)
Browse files Browse the repository at this point in the history
  • Loading branch information
rvagg committed Jan 8, 2025
1 parent 31c3a60 commit 6ddf0ec
Show file tree
Hide file tree
Showing 3 changed files with 284 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
162 changes: 155 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,50 @@ 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)
printTrace = func(descPfx string, trace types.ExecutionTrace) {
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)))
statsTable(cctx.App.Writer, trace, false)
for _, subtrace := range trace.Subcalls {
_, _ = fmt.Fprintln(cctx.App.Writer)
printTrace(descPfx+trace.Msg.To.String()+"➜", subtrace)
}
}
printTrace("", res.ExecutionTrace)
if len(res.ExecutionTrace.Subcalls) > 0 {
_, _ = fmt.Fprintln(cctx.App.Writer)
_, _ = fmt.Fprintln(cctx.App.Writer, color.New(color.Bold).Sprint("Total gas charges:"))
statsTable(cctx.App.Writer, res.ExecutionTrace, true)
perCallTrace := gasTracesPerCall(res.ExecutionTrace)
_, _ = fmt.Fprintln(cctx.App.Writer)
_, _ = fmt.Fprintln(cctx.App.Writer, color.New(color.Bold).Sprint("Gas charges per message:"))
statsTable(cctx.App.Writer, perCallTrace, false)
}
}
}

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 +381,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) {
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",
})
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
}
133 changes: 128 additions & 5 deletions lib/tablewriter/tablewriter.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,47 @@ type Column struct {
Name string
SeparateLine bool
Lines int
RightAlign bool
}

type tableCfg struct {
borders bool
}

type TableOption func(*tableCfg)

func WithBorders() TableOption {
return func(c *tableCfg) {
c.borders = true
}
}

type columnCfg struct {
rightAlign bool
}

type ColumnOption func(*columnCfg)

func RightAlign() ColumnOption {
return func(c *columnCfg) {
c.rightAlign = true
}
}

type TableWriter struct {
cols []Column
rows []map[int]string
}

func Col(name string) Column {
func Col(name string, opts ...ColumnOption) Column {
cfg := &columnCfg{}
for _, o := range opts {
o(cfg)
}
return Column{
Name: name,
SeparateLine: false,
RightAlign: cfg.rightAlign,
}
}

Expand Down Expand Up @@ -69,7 +99,12 @@ cloop:
w.rows = append(w.rows, byColID)
}

func (w *TableWriter) Flush(out io.Writer) error {
func (w *TableWriter) Flush(out io.Writer, opts ...TableOption) error {
cfg := &tableCfg{}
for _, o := range opts {
o(cfg)
}

colLengths := make([]int, len(w.cols))

header := map[int]string{}
Expand Down Expand Up @@ -99,21 +134,62 @@ func (w *TableWriter) Flush(out io.Writer) error {
}
}

for _, row := range w.rows {
if cfg.borders {
// top line
if _, err := fmt.Fprint(out, "┌"); err != nil {
return err
}
for ci, col := range w.cols {
if col.Lines == 0 {
continue
}
if _, err := fmt.Fprint(out, strings.Repeat("─", colLengths[ci]+2)); err != nil {
return err
}
if ci != len(w.cols)-1 {
if _, err := fmt.Fprint(out, "┬"); err != nil {
return err
}
}
}
if _, err := fmt.Fprintln(out, "┐"); err != nil {
return err
}
}

for lineNumber, row := range w.rows {
cols := make([]string, len(w.cols))

if cfg.borders {
if _, err := fmt.Fprint(out, "│ "); err != nil {
return err
}
}

for ci, col := range w.cols {
if col.Lines == 0 {
continue
}

e, _ := row[ci]
e := row[ci]
pad := colLengths[ci] - cliStringLength(e) + 2
if cfg.borders {
pad--
}
if !col.SeparateLine && col.Lines > 0 {
e = e + strings.Repeat(" ", pad)
if col.RightAlign {
e = strings.Repeat(" ", pad-1) + e + " "
} else {
e = e + strings.Repeat(" ", pad)
}
if _, err := fmt.Fprint(out, e); err != nil {
return err
}
if cfg.borders {
if _, err := fmt.Fprint(out, "│ "); err != nil {
return err
}
}
}

cols[ci] = e
Expand All @@ -132,6 +208,53 @@ func (w *TableWriter) Flush(out io.Writer) error {
return err
}
}

if lineNumber == 0 && cfg.borders {
// print bottom of header
if _, err := fmt.Fprint(out, "├"); err != nil {
return err
}
for ci, col := range w.cols {
if col.Lines == 0 {
continue
}

if _, err := fmt.Fprint(out, strings.Repeat("─", colLengths[ci]+2)); err != nil {
return err
}
if ci != len(w.cols)-1 {
if _, err := fmt.Fprint(out, "┼"); err != nil {
return err
}
}
}
if _, err := fmt.Fprintln(out, "┤"); err != nil {
return err
}
}
}

if cfg.borders {
// bottom line
if _, err := fmt.Fprint(out, "└"); err != nil {
return err
}
for ci, col := range w.cols {
if col.Lines == 0 {
continue
}
if _, err := fmt.Fprint(out, strings.Repeat("─", colLengths[ci]+2)); err != nil {
return err
}
if ci != len(w.cols)-1 {
if _, err := fmt.Fprint(out, "┴"); err != nil {
return err
}
}
}
if _, err := fmt.Fprintln(out, "┘"); err != nil {
return err
}
}

return nil
Expand Down

0 comments on commit 6ddf0ec

Please sign in to comment.