mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-25 22:26:42 +00:00
Event based pprof (#732)
* feature * Save pprof to /tmp --------- Co-authored-by: Jerry <jerrycgh@gmail.com>
This commit is contained in:
parent
67843e17ec
commit
22fa4033e8
6 changed files with 247 additions and 4 deletions
32
common/context.go
Normal file
32
common/context.go
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
unique "github.com/ethereum/go-ethereum/common/set"
|
||||||
|
)
|
||||||
|
|
||||||
|
type key struct{}
|
||||||
|
|
||||||
|
var (
|
||||||
|
labelsKey key
|
||||||
|
)
|
||||||
|
|
||||||
|
func WithLabels(ctx context.Context, labels ...string) context.Context {
|
||||||
|
if len(labels) == 0 {
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
labels = append(labels, Labels(ctx)...)
|
||||||
|
|
||||||
|
return context.WithValue(ctx, labelsKey, unique.Deduplicate(labels))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Labels(ctx context.Context) []string {
|
||||||
|
labels, ok := ctx.Value(labelsKey).([]string)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return labels
|
||||||
|
}
|
||||||
107
common/context_test.go
Normal file
107
common/context_test.go
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWithLabels(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
initial []string
|
||||||
|
new []string
|
||||||
|
expected []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"nil-nil",
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"nil-something",
|
||||||
|
nil,
|
||||||
|
[]string{"one", "two"},
|
||||||
|
[]string{"one", "two"},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"something-nil",
|
||||||
|
[]string{"one", "two"},
|
||||||
|
nil,
|
||||||
|
[]string{"one", "two"},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"something-something",
|
||||||
|
[]string{"one", "two"},
|
||||||
|
[]string{"three", "four"},
|
||||||
|
[]string{"one", "two", "three", "four"},
|
||||||
|
},
|
||||||
|
|
||||||
|
// deduplication
|
||||||
|
{
|
||||||
|
"with duplicates nil-something",
|
||||||
|
nil,
|
||||||
|
[]string{"one", "two", "one"},
|
||||||
|
[]string{"one", "two"},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"with duplicates something-nil",
|
||||||
|
[]string{"one", "two", "one"},
|
||||||
|
nil,
|
||||||
|
[]string{"one", "two"},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"with duplicates something-something",
|
||||||
|
[]string{"one", "two"},
|
||||||
|
[]string{"three", "one"},
|
||||||
|
[]string{"one", "two", "three"},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"with duplicates something-something",
|
||||||
|
[]string{"one", "two", "three"},
|
||||||
|
[]string{"three", "four", "two"},
|
||||||
|
[]string{"one", "two", "three", "four"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range cases {
|
||||||
|
c := c
|
||||||
|
|
||||||
|
t.Run(c.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
ctx = WithLabels(ctx, c.initial...)
|
||||||
|
ctx = WithLabels(ctx, c.new...)
|
||||||
|
|
||||||
|
got := Labels(ctx)
|
||||||
|
|
||||||
|
if len(got) != len(c.expected) {
|
||||||
|
t.Errorf("case %s. expected %v, got %v", c.name, c.expected, got)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gotSorted := sort.StringSlice(got)
|
||||||
|
gotSorted.Sort()
|
||||||
|
|
||||||
|
expectedSorted := sort.StringSlice(c.expected)
|
||||||
|
expectedSorted.Sort()
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(gotSorted, expectedSorted) {
|
||||||
|
t.Errorf("case %s. expected %v, got %v", c.name, expectedSorted, gotSorted)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,3 +9,20 @@ func New[T comparable](slice []T) map[T]struct{} {
|
||||||
|
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ToSlice[T comparable](m map[T]struct{}) []T {
|
||||||
|
slice := make([]T, len(m))
|
||||||
|
|
||||||
|
var i int
|
||||||
|
|
||||||
|
for k := range m {
|
||||||
|
slice[i] = k
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
|
||||||
|
return slice
|
||||||
|
}
|
||||||
|
|
||||||
|
func Deduplicate[T comparable](slice []T) []T {
|
||||||
|
return ToSlice(New(slice))
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1052,7 +1052,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Execute the trace
|
// Execute the trace
|
||||||
msg, err := args.ToMessage(api.backend.RPCGasCap(), block.BaseFee())
|
msg, err := args.ToMessage(ctx, api.backend.RPCGasCap(), block.BaseFee())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,11 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime/pprof"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
|
@ -1005,7 +1009,7 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
// Get a new instance of the EVM.
|
// Get a new instance of the EVM.
|
||||||
msg, err := args.ToMessage(globalGasCap, header.BaseFee)
|
msg, err := args.ToMessage(ctx, globalGasCap, header.BaseFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -1028,15 +1032,83 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the timer caused an abort, return an appropriate error message
|
// If the timer caused an abort, return an appropriate error message
|
||||||
|
timeoutMu.Lock()
|
||||||
if evm.Cancelled() {
|
if evm.Cancelled() {
|
||||||
|
timeoutErrors++
|
||||||
|
|
||||||
|
if timeoutErrors >= pprofThreshold {
|
||||||
|
timeoutNoErrors = 0
|
||||||
|
|
||||||
|
if !isRunning {
|
||||||
|
runProfile()
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Warn("[eth_call] timeout",
|
||||||
|
"timeoutErrors", timeoutErrors,
|
||||||
|
"timeoutNoErrors", timeoutNoErrors,
|
||||||
|
"args", args,
|
||||||
|
"blockNrOrHash", blockNrOrHash,
|
||||||
|
"overrides", overrides,
|
||||||
|
"timeout", timeout,
|
||||||
|
"globalGasCap", globalGasCap)
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutMu.Unlock()
|
||||||
|
|
||||||
return nil, fmt.Errorf("execution aborted (timeout = %v)", timeout)
|
return nil, fmt.Errorf("execution aborted (timeout = %v)", timeout)
|
||||||
|
} else {
|
||||||
|
if timeoutErrors >= pprofStopThreshold {
|
||||||
|
timeoutErrors = 0
|
||||||
|
timeoutNoErrors = 0
|
||||||
|
|
||||||
|
if isRunning {
|
||||||
|
pprof.StopCPUProfile()
|
||||||
|
isRunning = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isRunning && time.Since(pprofTime) >= pprofDuration {
|
||||||
|
timeoutErrors = 0
|
||||||
|
timeoutNoErrors = 0
|
||||||
|
|
||||||
|
pprof.StopCPUProfile()
|
||||||
|
|
||||||
|
isRunning = false
|
||||||
|
}
|
||||||
|
|
||||||
|
timeoutMu.Unlock()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, fmt.Errorf("err: %w (supplied gas %d)", err, msg.Gas())
|
return result, fmt.Errorf("err: %w (supplied gas %d)", err, msg.Gas())
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runProfile() {
|
||||||
|
pprofTime = time.Now()
|
||||||
|
|
||||||
|
name := fmt.Sprintf("profile_eth_call-count-%d-time-%s.prof",
|
||||||
|
number, pprofTime.Format("2006-01-02-15-04-05"))
|
||||||
|
|
||||||
|
name = filepath.Join(os.TempDir(), name)
|
||||||
|
|
||||||
|
f, err := os.Create(name)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("[eth_call] can't create profile file", "name", name, "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = pprof.StartCPUProfile(f); err != nil {
|
||||||
|
log.Error("[eth_call] can't start profiling", "name", name, "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isRunning = true
|
||||||
|
number++
|
||||||
|
}
|
||||||
|
|
||||||
func newRevertError(result *core.ExecutionResult) *revertError {
|
func newRevertError(result *core.ExecutionResult) *revertError {
|
||||||
reason, errUnpack := abi.UnpackRevert(result.Revert())
|
reason, errUnpack := abi.UnpackRevert(result.Revert())
|
||||||
err := errors.New("execution reverted")
|
err := errors.New("execution reverted")
|
||||||
|
|
@ -1067,6 +1139,21 @@ func (e *revertError) ErrorData() interface{} {
|
||||||
return e.reason
|
return e.reason
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
number int
|
||||||
|
timeoutErrors int // count for timeout errors
|
||||||
|
timeoutNoErrors int
|
||||||
|
timeoutMu sync.Mutex
|
||||||
|
isRunning bool
|
||||||
|
pprofTime time.Time
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
pprofThreshold = 3
|
||||||
|
pprofStopThreshold = 3
|
||||||
|
pprofDuration = time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
// Call executes the given transaction on the state for the given block number.
|
// Call executes the given transaction on the state for the given block number.
|
||||||
//
|
//
|
||||||
// Additionally, the caller can specify a batch of contract for fields overriding.
|
// Additionally, the caller can specify a batch of contract for fields overriding.
|
||||||
|
|
@ -1573,7 +1660,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH
|
||||||
statedb := db.Copy()
|
statedb := db.Copy()
|
||||||
// Set the accesslist to the last al
|
// Set the accesslist to the last al
|
||||||
args.AccessList = &accessList
|
args.AccessList = &accessList
|
||||||
msg, err := args.ToMessage(b.RPCGasCap(), header.BaseFee)
|
msg, err := args.ToMessage(ctx, b.RPCGasCap(), header.BaseFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, nil, err
|
return nil, 0, nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,7 @@ func (args *TransactionArgs) setDefaults(ctx context.Context, b Backend) error {
|
||||||
// ToMessage converts the transaction arguments to the Message type used by the
|
// ToMessage converts the transaction arguments to the Message type used by the
|
||||||
// core evm. This method is used in calls and traces that do not require a real
|
// core evm. This method is used in calls and traces that do not require a real
|
||||||
// live transaction.
|
// live transaction.
|
||||||
func (args *TransactionArgs) ToMessage(globalGasCap uint64, baseFee *big.Int) (types.Message, error) {
|
func (args *TransactionArgs) ToMessage(_ context.Context, globalGasCap uint64, baseFee *big.Int) (types.Message, error) {
|
||||||
// Reject invalid combinations of pre- and post-1559 fee styles
|
// Reject invalid combinations of pre- and post-1559 fee styles
|
||||||
if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) {
|
if args.GasPrice != nil && (args.MaxFeePerGas != nil || args.MaxPriorityFeePerGas != nil) {
|
||||||
return types.Message{}, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
|
return types.Message{}, errors.New("both gasPrice and (maxFeePerGas or maxPriorityFeePerGas) specified")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue