Skip to content

Commit 6ddf0ec

Browse files
committed
feat(shed): lotus-shed msg --gas-stats (w/ tabular output)
1 parent 31c3a60 commit 6ddf0ec

File tree

3 files changed

+284
-12
lines changed

3 files changed

+284
-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

+155-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,50 @@ 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)
100+
printTrace = func(descPfx string, trace types.ExecutionTrace) {
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+
statsTable(cctx.App.Writer, trace, false)
107+
for _, subtrace := range trace.Subcalls {
108+
_, _ = fmt.Fprintln(cctx.App.Writer)
109+
printTrace(descPfx+trace.Msg.To.String()+"➜", subtrace)
110+
}
111+
}
112+
printTrace("", res.ExecutionTrace)
113+
if len(res.ExecutionTrace.Subcalls) > 0 {
114+
_, _ = fmt.Fprintln(cctx.App.Writer)
115+
_, _ = fmt.Fprintln(cctx.App.Writer, color.New(color.Bold).Sprint("Total gas charges:"))
116+
statsTable(cctx.App.Writer, res.ExecutionTrace, true)
117+
perCallTrace := gasTracesPerCall(res.ExecutionTrace)
118+
_, _ = fmt.Fprintln(cctx.App.Writer)
119+
_, _ = fmt.Fprintln(cctx.App.Writer, color.New(color.Bold).Sprint("Gas charges per message:"))
120+
statsTable(cctx.App.Writer, perCallTrace, false)
121+
}
122+
}
85123
}
86124

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")
125+
if cctx.Bool("show-message") {
126+
switch msg := msg.(type) {
127+
case *types.SignedMessage:
128+
if err := printSignedMessage(cctx, msg); err != nil {
129+
return err
130+
}
131+
case *types.Message:
132+
if err := printMessage(cctx, msg); err != nil {
133+
return err
134+
}
135+
default:
136+
return xerrors.Errorf("this error message can't be printed")
137+
}
94138
}
139+
140+
return nil
95141
},
96142
}
97143

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

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

lib/tablewriter/tablewriter.go

+128-5
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,47 @@ type Column struct {
1313
Name string
1414
SeparateLine bool
1515
Lines int
16+
RightAlign bool
17+
}
18+
19+
type tableCfg struct {
20+
borders bool
21+
}
22+
23+
type TableOption func(*tableCfg)
24+
25+
func WithBorders() TableOption {
26+
return func(c *tableCfg) {
27+
c.borders = true
28+
}
29+
}
30+
31+
type columnCfg struct {
32+
rightAlign bool
33+
}
34+
35+
type ColumnOption func(*columnCfg)
36+
37+
func RightAlign() ColumnOption {
38+
return func(c *columnCfg) {
39+
c.rightAlign = true
40+
}
1641
}
1742

1843
type TableWriter struct {
1944
cols []Column
2045
rows []map[int]string
2146
}
2247

23-
func Col(name string) Column {
48+
func Col(name string, opts ...ColumnOption) Column {
49+
cfg := &columnCfg{}
50+
for _, o := range opts {
51+
o(cfg)
52+
}
2453
return Column{
2554
Name: name,
2655
SeparateLine: false,
56+
RightAlign: cfg.rightAlign,
2757
}
2858
}
2959

@@ -69,7 +99,12 @@ cloop:
6999
w.rows = append(w.rows, byColID)
70100
}
71101

72-
func (w *TableWriter) Flush(out io.Writer) error {
102+
func (w *TableWriter) Flush(out io.Writer, opts ...TableOption) error {
103+
cfg := &tableCfg{}
104+
for _, o := range opts {
105+
o(cfg)
106+
}
107+
73108
colLengths := make([]int, len(w.cols))
74109

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

102-
for _, row := range w.rows {
137+
if cfg.borders {
138+
// top line
139+
if _, err := fmt.Fprint(out, "┌"); err != nil {
140+
return err
141+
}
142+
for ci, col := range w.cols {
143+
if col.Lines == 0 {
144+
continue
145+
}
146+
if _, err := fmt.Fprint(out, strings.Repeat("─", colLengths[ci]+2)); err != nil {
147+
return err
148+
}
149+
if ci != len(w.cols)-1 {
150+
if _, err := fmt.Fprint(out, "┬"); err != nil {
151+
return err
152+
}
153+
}
154+
}
155+
if _, err := fmt.Fprintln(out, "┐"); err != nil {
156+
return err
157+
}
158+
}
159+
160+
for lineNumber, row := range w.rows {
103161
cols := make([]string, len(w.cols))
104162

163+
if cfg.borders {
164+
if _, err := fmt.Fprint(out, "│ "); err != nil {
165+
return err
166+
}
167+
}
168+
105169
for ci, col := range w.cols {
106170
if col.Lines == 0 {
107171
continue
108172
}
109173

110-
e, _ := row[ci]
174+
e := row[ci]
111175
pad := colLengths[ci] - cliStringLength(e) + 2
176+
if cfg.borders {
177+
pad--
178+
}
112179
if !col.SeparateLine && col.Lines > 0 {
113-
e = e + strings.Repeat(" ", pad)
180+
if col.RightAlign {
181+
e = strings.Repeat(" ", pad-1) + e + " "
182+
} else {
183+
e = e + strings.Repeat(" ", pad)
184+
}
114185
if _, err := fmt.Fprint(out, e); err != nil {
115186
return err
116187
}
188+
if cfg.borders {
189+
if _, err := fmt.Fprint(out, "│ "); err != nil {
190+
return err
191+
}
192+
}
117193
}
118194

119195
cols[ci] = e
@@ -132,6 +208,53 @@ func (w *TableWriter) Flush(out io.Writer) error {
132208
return err
133209
}
134210
}
211+
212+
if lineNumber == 0 && cfg.borders {
213+
// print bottom of header
214+
if _, err := fmt.Fprint(out, "├"); err != nil {
215+
return err
216+
}
217+
for ci, col := range w.cols {
218+
if col.Lines == 0 {
219+
continue
220+
}
221+
222+
if _, err := fmt.Fprint(out, strings.Repeat("─", colLengths[ci]+2)); err != nil {
223+
return err
224+
}
225+
if ci != len(w.cols)-1 {
226+
if _, err := fmt.Fprint(out, "┼"); err != nil {
227+
return err
228+
}
229+
}
230+
}
231+
if _, err := fmt.Fprintln(out, "┤"); err != nil {
232+
return err
233+
}
234+
}
235+
}
236+
237+
if cfg.borders {
238+
// bottom line
239+
if _, err := fmt.Fprint(out, "└"); err != nil {
240+
return err
241+
}
242+
for ci, col := range w.cols {
243+
if col.Lines == 0 {
244+
continue
245+
}
246+
if _, err := fmt.Fprint(out, strings.Repeat("─", colLengths[ci]+2)); err != nil {
247+
return err
248+
}
249+
if ci != len(w.cols)-1 {
250+
if _, err := fmt.Fprint(out, "┴"); err != nil {
251+
return err
252+
}
253+
}
254+
}
255+
if _, err := fmt.Fprintln(out, "┘"); err != nil {
256+
return err
257+
}
135258
}
136259

137260
return nil

0 commit comments

Comments
 (0)