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
2427var 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