6
6
"encoding/hex"
7
7
"encoding/json"
8
8
"fmt"
9
+ "io"
10
+ "sort"
9
11
10
12
"github.com/fatih/color"
11
13
"github.com/ipfs/go-cid"
@@ -19,6 +21,7 @@ import (
19
21
"github.com/filecoin-project/lotus/chain/consensus"
20
22
"github.com/filecoin-project/lotus/chain/types"
21
23
lcli "github.com/filecoin-project/lotus/cli"
24
+ "github.com/filecoin-project/lotus/lib/tablewriter"
22
25
)
23
26
24
27
var msgCmd = & cli.Command {
@@ -27,10 +30,19 @@ var msgCmd = &cli.Command{
27
30
Usage : "Translate message between various formats" ,
28
31
ArgsUsage : "Message in any form" ,
29
32
Flags : []cli.Flag {
33
+ & cli.BoolFlag {
34
+ Name : "show-message" ,
35
+ Usage : "Print the message details" ,
36
+ Value : true ,
37
+ },
30
38
& cli.BoolFlag {
31
39
Name : "exec-trace" ,
32
40
Usage : "Print the execution trace" ,
33
41
},
42
+ & cli.BoolFlag {
43
+ Name : "gas-stats" ,
44
+ Usage : "Print a summary of gas charges" ,
45
+ },
34
46
},
35
47
Action : func (cctx * cli.Context ) error {
36
48
if cctx .NArg () != 1 {
@@ -82,16 +94,61 @@ var msgCmd = &cli.Command{
82
94
fmt .Printf ("Return: %x\n " , res .MsgRct .Return )
83
95
fmt .Printf ("Gas Used: %d\n " , res .MsgRct .GasUsed )
84
96
}
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
+ }
85
134
}
86
135
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
+ }
94
149
}
150
+
151
+ return nil
95
152
},
96
153
}
97
154
@@ -335,3 +392,105 @@ func messageFromCID(cctx *cli.Context, c cid.Cid) (types.ChainMsg, error) {
335
392
336
393
return messageFromBytes (cctx , msgb )
337
394
}
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