Skip to content

Commit 0e4857a

Browse files
authored
feat(shed): lotus-shed msg --gas-stats (w/ tabular output) (#12817)
1 parent 9a696eb commit 0e4857a

File tree

3 files changed

+295
-12
lines changed

3 files changed

+295
-12
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
- 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))
1818
- 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))
1919
- 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))
20+
- 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))
2021

2122
# UNRELEASED v.1.32.0
2223

cmd/lotus-shed/msg.go

+166-7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"encoding/hex"
77
"encoding/json"
88
"fmt"
9+
"io"
10+
"sort"
911

1012
"github.com/fatih/color"
1113
"github.com/ipfs/go-cid"
@@ -19,6 +21,7 @@ import (
1921
"github.com/filecoin-project/lotus/chain/consensus"
2022
"github.com/filecoin-project/lotus/chain/types"
2123
lcli "github.com/filecoin-project/lotus/cli"
24+
"github.com/filecoin-project/lotus/lib/tablewriter"
2225
)
2326

2427
var msgCmd = &cli.Command{
@@ -27,10 +30,19 @@ var msgCmd = &cli.Command{
2730
Usage: "Translate message between various formats",
2831
ArgsUsage: "Message in any form",
2932
Flags: []cli.Flag{
33+
&cli.BoolFlag{
34+
Name: "show-message",
35+
Usage: "Print the message details",
36+
Value: true,
37+
},
3038
&cli.BoolFlag{
3139
Name: "exec-trace",
3240
Usage: "Print the execution trace",
3341
},
42+
&cli.BoolFlag{
43+
Name: "gas-stats",
44+
Usage: "Print a summary of gas charges",
45+
},
3446
},
3547
Action: func(cctx *cli.Context) error {
3648
if cctx.NArg() != 1 {
@@ -82,16 +94,61 @@ var msgCmd = &cli.Command{
8294
fmt.Printf("Return: %x\n", res.MsgRct.Return)
8395
fmt.Printf("Gas Used: %d\n", res.MsgRct.GasUsed)
8496
}
97+
98+
if cctx.Bool("gas-stats") {
99+
var printTrace func(descPfx string, trace types.ExecutionTrace) error
100+
printTrace = func(descPfx string, trace types.ExecutionTrace) error {
101+
typ := "Message"
102+
if descPfx != "" {
103+
typ = "Subcall"
104+
}
105+
_, _ = fmt.Fprintln(cctx.App.Writer, color.New(color.Bold).Sprint(fmt.Sprintf("%s (%s%s) gas charges:", typ, descPfx, trace.Msg.To)))
106+
if err := statsTable(cctx.App.Writer, trace, false); err != nil {
107+
return err
108+
}
109+
for _, subtrace := range trace.Subcalls {
110+
_, _ = fmt.Fprintln(cctx.App.Writer)
111+
if err := printTrace(descPfx+trace.Msg.To.String()+"➜", subtrace); err != nil {
112+
return err
113+
}
114+
}
115+
return nil
116+
}
117+
if err := printTrace("", res.ExecutionTrace); err != nil {
118+
return err
119+
}
120+
if len(res.ExecutionTrace.Subcalls) > 0 {
121+
_, _ = fmt.Fprintln(cctx.App.Writer)
122+
_, _ = fmt.Fprintln(cctx.App.Writer, color.New(color.Bold).Sprint("Total gas charges:"))
123+
if err := statsTable(cctx.App.Writer, res.ExecutionTrace, true); err != nil {
124+
return err
125+
}
126+
perCallTrace := gasTracesPerCall(res.ExecutionTrace)
127+
_, _ = fmt.Fprintln(cctx.App.Writer)
128+
_, _ = fmt.Fprintln(cctx.App.Writer, color.New(color.Bold).Sprint("Gas charges per call:"))
129+
if err := statsTable(cctx.App.Writer, perCallTrace, false); err != nil {
130+
return err
131+
}
132+
}
133+
}
85134
}
86135

87-
switch msg := msg.(type) {
88-
case *types.SignedMessage:
89-
return printSignedMessage(cctx, msg)
90-
case *types.Message:
91-
return printMessage(cctx, msg)
92-
default:
93-
return xerrors.Errorf("this error message can't be printed")
136+
if cctx.Bool("show-message") {
137+
switch msg := msg.(type) {
138+
case *types.SignedMessage:
139+
if err := printSignedMessage(cctx, msg); err != nil {
140+
return err
141+
}
142+
case *types.Message:
143+
if err := printMessage(cctx, msg); err != nil {
144+
return err
145+
}
146+
default:
147+
return xerrors.Errorf("this error message can't be printed")
148+
}
94149
}
150+
151+
return nil
95152
},
96153
}
97154

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

336393
return messageFromBytes(cctx, msgb)
337394
}
395+
396+
type gasTally struct {
397+
storageGas int64
398+
computeGas int64
399+
count int
400+
}
401+
402+
func accumGasTallies(charges map[string]*gasTally, totals *gasTally, trace types.ExecutionTrace, recurse bool) {
403+
for _, charge := range trace.GasCharges {
404+
name := charge.Name
405+
if _, ok := charges[name]; !ok {
406+
charges[name] = &gasTally{}
407+
}
408+
charges[name].computeGas += charge.ComputeGas
409+
charges[name].storageGas += charge.StorageGas
410+
charges[name].count++
411+
totals.computeGas += charge.ComputeGas
412+
totals.storageGas += charge.StorageGas
413+
totals.count++
414+
}
415+
if recurse {
416+
for _, subtrace := range trace.Subcalls {
417+
accumGasTallies(charges, totals, subtrace, recurse)
418+
}
419+
}
420+
}
421+
422+
func statsTable(out io.Writer, trace types.ExecutionTrace, recurse bool) error {
423+
tw := tablewriter.New(
424+
tablewriter.Col("Type"),
425+
tablewriter.Col("Count", tablewriter.RightAlign()),
426+
tablewriter.Col("Storage Gas", tablewriter.RightAlign()),
427+
tablewriter.Col("S%", tablewriter.RightAlign()),
428+
tablewriter.Col("Compute Gas", tablewriter.RightAlign()),
429+
tablewriter.Col("C%", tablewriter.RightAlign()),
430+
tablewriter.Col("Total Gas", tablewriter.RightAlign()),
431+
tablewriter.Col("T%", tablewriter.RightAlign()),
432+
)
433+
434+
totals := &gasTally{}
435+
charges := make(map[string]*gasTally)
436+
accumGasTallies(charges, totals, trace, recurse)
437+
438+
// Sort by name
439+
names := make([]string, 0, len(charges))
440+
for name := range charges {
441+
names = append(names, name)
442+
}
443+
sort.Strings(names)
444+
445+
for _, name := range names {
446+
charge := charges[name]
447+
tw.Write(map[string]interface{}{
448+
"Type": name,
449+
"Count": charge.count,
450+
"Storage Gas": charge.storageGas,
451+
"S%": fmt.Sprintf("%.2f", float64(charge.storageGas)/float64(totals.storageGas)*100),
452+
"Compute Gas": charge.computeGas,
453+
"C%": fmt.Sprintf("%.2f", float64(charge.computeGas)/float64(totals.computeGas)*100),
454+
"Total Gas": charge.storageGas + charge.computeGas,
455+
"T%": fmt.Sprintf("%.2f", float64(charge.storageGas+charge.computeGas)/float64(totals.storageGas+totals.computeGas)*100),
456+
})
457+
}
458+
tw.Write(map[string]interface{}{
459+
"Type": "Total",
460+
"Count": totals.count,
461+
"Storage Gas": totals.storageGas,
462+
"S%": "100.00",
463+
"Compute Gas": totals.computeGas,
464+
"C%": "100.00",
465+
"Total Gas": totals.storageGas + totals.computeGas,
466+
"T%": "100.00",
467+
})
468+
return tw.Flush(out, tablewriter.WithBorders())
469+
}
470+
471+
// Takes an execution trace and returns a new trace that groups all the gas charges by the message
472+
// they were charged in, with the gas charges named per message; the output is partial and only
473+
// suitable for calling statsTable() with.
474+
func gasTracesPerCall(inTrace types.ExecutionTrace) types.ExecutionTrace {
475+
outTrace := types.ExecutionTrace{
476+
GasCharges: []*types.GasTrace{},
477+
}
478+
count := 1
479+
var accum func(name string, trace types.ExecutionTrace)
480+
accum = func(name string, trace types.ExecutionTrace) {
481+
totals := &gasTally{}
482+
charges := make(map[string]*gasTally)
483+
accumGasTallies(charges, totals, trace, false)
484+
outTrace.GasCharges = append(outTrace.GasCharges, &types.GasTrace{
485+
Name: fmt.Sprintf("#%d %s", count, name),
486+
ComputeGas: totals.computeGas,
487+
StorageGas: totals.storageGas,
488+
})
489+
count++
490+
for _, subtrace := range trace.Subcalls {
491+
accum(name+"➜"+subtrace.Msg.To.String(), subtrace)
492+
}
493+
}
494+
accum(inTrace.Msg.To.String(), inTrace)
495+
return outTrace
496+
}

0 commit comments

Comments
 (0)