eth/tracers: use non-threaded tracechain (#24283)
This makes non-JS tracers execute all block txs on a single goroutine. In the previous implementation, we used to prepare every tx pre-state on one goroutine, and then run the transactions again with tracing enabled. Native tracers are usually faster, so it is faster overall to use their output as the pre-state for tracing the next transaction. Co-authored-by: Sina Mahmoodi <itz.s1na@gmail.com>
This commit is contained in:
parent
7a489623ac
commit
2c6dda5ad7
@ -333,7 +333,7 @@ func benchmarkNonModifyingCode(gas uint64, code []byte, name string, tracerCode
|
|||||||
cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
cfg.State, _ = state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||||
cfg.GasLimit = gas
|
cfg.GasLimit = gas
|
||||||
if len(tracerCode) > 0 {
|
if len(tracerCode) > 0 {
|
||||||
tracer, err := tracers.New(tracerCode, new(tracers.Context), nil)
|
tracer, err := tracers.DefaultDirectory.New(tracerCode, new(tracers.Context), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -832,7 +832,7 @@ func TestRuntimeJSTracer(t *testing.T) {
|
|||||||
statedb.SetCode(common.HexToAddress("0xee"), calleeCode)
|
statedb.SetCode(common.HexToAddress("0xee"), calleeCode)
|
||||||
statedb.SetCode(common.HexToAddress("0xff"), depressedCode)
|
statedb.SetCode(common.HexToAddress("0xff"), depressedCode)
|
||||||
|
|
||||||
tracer, err := tracers.New(jsTracer, new(tracers.Context), nil)
|
tracer, err := tracers.DefaultDirectory.New(jsTracer, new(tracers.Context), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -868,7 +868,7 @@ func TestJSTracerCreateTx(t *testing.T) {
|
|||||||
code := []byte{byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN)}
|
code := []byte{byte(vm.PUSH1), 0, byte(vm.PUSH1), 0, byte(vm.RETURN)}
|
||||||
|
|
||||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
|
||||||
tracer, err := tracers.New(jsTracer, new(tracers.Context), nil)
|
tracer, err := tracers.DefaultDirectory.New(jsTracer, new(tracers.Context), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -593,6 +593,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
|||||||
if block.NumberU64() == 0 {
|
if block.NumberU64() == 0 {
|
||||||
return nil, errors.New("genesis is not traceable")
|
return nil, errors.New("genesis is not traceable")
|
||||||
}
|
}
|
||||||
|
// Prepare base state
|
||||||
parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash())
|
parent, err := api.blockByNumberAndHash(ctx, rpc.BlockNumber(block.NumberU64()-1), block.ParentHash())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -607,23 +608,64 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
|||||||
}
|
}
|
||||||
defer release()
|
defer release()
|
||||||
|
|
||||||
|
// JS tracers have high overhead. In this case run a parallel
|
||||||
|
// process that generates states in one thread and traces txes
|
||||||
|
// in separate worker threads.
|
||||||
|
if config != nil && config.Tracer != nil && *config.Tracer != "" {
|
||||||
|
if isJS := DefaultDirectory.IsJS(*config.Tracer); isJS {
|
||||||
|
return api.traceBlockParallel(ctx, block, statedb, config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Native tracers have low overhead
|
||||||
|
var (
|
||||||
|
txs = block.Transactions()
|
||||||
|
blockHash = block.Hash()
|
||||||
|
is158 = api.backend.ChainConfig().IsEIP158(block.Number())
|
||||||
|
blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||||
|
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number())
|
||||||
|
results = make([]*txTraceResult, len(txs))
|
||||||
|
)
|
||||||
|
for i, tx := range txs {
|
||||||
|
// Generate the next state snapshot fast without tracing
|
||||||
|
msg, _ := tx.AsMessage(signer, block.BaseFee())
|
||||||
|
txctx := &Context{
|
||||||
|
BlockHash: blockHash,
|
||||||
|
TxIndex: i,
|
||||||
|
TxHash: tx.Hash(),
|
||||||
|
}
|
||||||
|
res, err := api.traceTx(ctx, msg, txctx, blockCtx, statedb, config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
results[i] = &txTraceResult{Result: res}
|
||||||
|
// Finalize the state so any modifications are written to the trie
|
||||||
|
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
|
||||||
|
statedb.Finalise(is158)
|
||||||
|
}
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// traceBlockParallel is for tracers that have a high overhead (read JS tracers). One thread
|
||||||
|
// runs along and executes txes without tracing enabled to generate their prestate.
|
||||||
|
// Worker threads take the tasks and the prestate and trace them.
|
||||||
|
func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, statedb *state.StateDB, config *TraceConfig) ([]*txTraceResult, error) {
|
||||||
// Execute all the transaction contained within the block concurrently
|
// Execute all the transaction contained within the block concurrently
|
||||||
var (
|
var (
|
||||||
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number())
|
txs = block.Transactions()
|
||||||
txs = block.Transactions()
|
blockHash = block.Hash()
|
||||||
results = make([]*txTraceResult, len(txs))
|
blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
||||||
pend sync.WaitGroup
|
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number())
|
||||||
|
results = make([]*txTraceResult, len(txs))
|
||||||
|
pend sync.WaitGroup
|
||||||
)
|
)
|
||||||
threads := runtime.NumCPU()
|
threads := runtime.NumCPU()
|
||||||
if threads > len(txs) {
|
if threads > len(txs) {
|
||||||
threads = len(txs)
|
threads = len(txs)
|
||||||
}
|
}
|
||||||
jobs := make(chan *txTraceTask, threads)
|
jobs := make(chan *txTraceTask, threads)
|
||||||
blockHash := block.Hash()
|
|
||||||
for th := 0; th < threads; th++ {
|
for th := 0; th < threads; th++ {
|
||||||
pend.Add(1)
|
pend.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
|
||||||
defer pend.Done()
|
defer pend.Done()
|
||||||
// Fetch and execute the next transaction trace tasks
|
// Fetch and execute the next transaction trace tasks
|
||||||
for task := range jobs {
|
for task := range jobs {
|
||||||
@ -645,7 +687,6 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
|
|||||||
|
|
||||||
// Feed the transactions into the tracers and return
|
// Feed the transactions into the tracers and return
|
||||||
var failed error
|
var failed error
|
||||||
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
|
|
||||||
txloop:
|
txloop:
|
||||||
for i, tx := range txs {
|
for i, tx := range txs {
|
||||||
// Send the trace task over for execution
|
// Send the trace task over for execution
|
||||||
@ -923,7 +964,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex
|
|||||||
// Default tracer is the struct logger
|
// Default tracer is the struct logger
|
||||||
tracer = logger.NewStructLogger(config.Config)
|
tracer = logger.NewStructLogger(config.Config)
|
||||||
if config.Tracer != nil {
|
if config.Tracer != nil {
|
||||||
tracer, err = New(*config.Tracer, txctx, config.TracerConfig)
|
tracer, err = DefaultDirectory.New(*config.Tracer, txctx, config.TracerConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -140,7 +140,7 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) {
|
|||||||
}
|
}
|
||||||
_, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
|
_, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
|
||||||
)
|
)
|
||||||
tracer, err := tracers.New(tracerName, new(tracers.Context), test.TracerConfig)
|
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create call tracer: %v", err)
|
t.Fatalf("failed to create call tracer: %v", err)
|
||||||
}
|
}
|
||||||
@ -243,7 +243,7 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) {
|
|||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
tracer, err := tracers.New(tracerName, new(tracers.Context), nil)
|
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("failed to create call tracer: %v", err)
|
b.Fatalf("failed to create call tracer: %v", err)
|
||||||
}
|
}
|
||||||
@ -309,7 +309,7 @@ func TestZeroValueToNotExitCall(t *testing.T) {
|
|||||||
}
|
}
|
||||||
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
|
_, statedb := tests.MakePreState(rawdb.NewMemoryDatabase(), alloc, false)
|
||||||
// Create the tracer, the EVM environment and run it
|
// Create the tracer, the EVM environment and run it
|
||||||
tracer, err := tracers.New("callTracer", nil, nil)
|
tracer, err := tracers.DefaultDirectory.New("callTracer", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create call tracer: %v", err)
|
t.Fatalf("failed to create call tracer: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -110,7 +110,7 @@ func testPrestateDiffTracer(tracerName string, dirPath string, t *testing.T) {
|
|||||||
}
|
}
|
||||||
_, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
|
_, statedb = tests.MakePreState(rawdb.NewMemoryDatabase(), test.Genesis.Alloc, false)
|
||||||
)
|
)
|
||||||
tracer, err := tracers.New(tracerName, new(tracers.Context), test.TracerConfig)
|
tracer, err := tracers.DefaultDirectory.New(tracerName, new(tracers.Context), test.TracerConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create call tracer: %v", err)
|
t.Fatalf("failed to create call tracer: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,16 @@ func init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
tracers.RegisterLookup(true, newJsTracer)
|
type ctorFn = func(*tracers.Context, json.RawMessage) (tracers.Tracer, error)
|
||||||
|
lookup := func(code string) ctorFn {
|
||||||
|
return func(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
|
||||||
|
return newJsTracer(code, ctx, cfg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for name, code := range assetTracers {
|
||||||
|
tracers.DefaultDirectory.Register(name, lookup(code), true)
|
||||||
|
}
|
||||||
|
tracers.DefaultDirectory.RegisterJSEval(newJsTracer)
|
||||||
}
|
}
|
||||||
|
|
||||||
// bigIntProgram is compiled once and the exported function mostly invoked to convert
|
// bigIntProgram is compiled once and the exported function mostly invoked to convert
|
||||||
@ -122,16 +131,14 @@ type jsTracer struct {
|
|||||||
frameResultValue goja.Value
|
frameResultValue goja.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
// newJsTracer instantiates a new JS tracer instance. code is either
|
// newJsTracer instantiates a new JS tracer instance. code is a
|
||||||
// the name of a built-in JS tracer or a Javascript snippet which
|
// Javascript snippet which evaluates to an expression returning
|
||||||
// evaluates to an expression returning an object with certain methods.
|
// an object with certain methods:
|
||||||
|
//
|
||||||
// The methods `result` and `fault` are required to be present.
|
// The methods `result` and `fault` are required to be present.
|
||||||
// The methods `step`, `enter`, and `exit` are optional, but note that
|
// The methods `step`, `enter`, and `exit` are optional, but note that
|
||||||
// `enter` and `exit` always go together.
|
// `enter` and `exit` always go together.
|
||||||
func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
|
func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
|
||||||
if c, ok := assetTracers[code]; ok {
|
|
||||||
code = c
|
|
||||||
}
|
|
||||||
vm := goja.New()
|
vm := goja.New()
|
||||||
// By default field names are exported to JS as is, i.e. capitalized.
|
// By default field names are exported to JS as is, i.e. capitalized.
|
||||||
vm.SetFieldNameMapper(goja.UncapFieldNameMapper())
|
vm.SetFieldNameMapper(goja.UncapFieldNameMapper())
|
||||||
|
@ -28,7 +28,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
register("4byteTracer", newFourByteTracer)
|
tracers.DefaultDirectory.Register("4byteTracer", newFourByteTracer, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// fourByteTracer searches for 4byte-identifiers, and collects them for post-processing.
|
// fourByteTracer searches for 4byte-identifiers, and collects them for post-processing.
|
||||||
|
@ -32,7 +32,7 @@ import (
|
|||||||
//go:generate go run github.com/fjl/gencodec -type callFrame -field-override callFrameMarshaling -out gen_callframe_json.go
|
//go:generate go run github.com/fjl/gencodec -type callFrame -field-override callFrameMarshaling -out gen_callframe_json.go
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
register("callTracer", newCallTracer)
|
tracers.DefaultDirectory.Register("callTracer", newCallTracer, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
type callLog struct {
|
type callLog struct {
|
||||||
|
@ -26,7 +26,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
register("muxTracer", newMuxTracer)
|
tracers.DefaultDirectory.Register("muxTracer", newMuxTracer, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// muxTracer is a go implementation of the Tracer interface which
|
// muxTracer is a go implementation of the Tracer interface which
|
||||||
@ -47,7 +47,7 @@ func newMuxTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, er
|
|||||||
objects := make([]tracers.Tracer, 0, len(config))
|
objects := make([]tracers.Tracer, 0, len(config))
|
||||||
names := make([]string, 0, len(config))
|
names := make([]string, 0, len(config))
|
||||||
for k, v := range config {
|
for k, v := range config {
|
||||||
t, err := tracers.New(k, ctx, v)
|
t, err := tracers.DefaultDirectory.New(k, ctx, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
register("noopTracer", newNoopTracer)
|
tracers.DefaultDirectory.Register("noopTracer", newNoopTracer, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// noopTracer is a go implementation of the Tracer interface which
|
// noopTracer is a go implementation of the Tracer interface which
|
||||||
|
@ -32,7 +32,7 @@ import (
|
|||||||
//go:generate go run github.com/fjl/gencodec -type account -field-override accountMarshaling -out gen_account_json.go
|
//go:generate go run github.com/fjl/gencodec -type account -field-override accountMarshaling -out gen_account_json.go
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
register("prestateTracer", newPrestateTracer)
|
tracers.DefaultDirectory.Register("prestateTracer", newPrestateTracer, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
type state = map[common.Address]*account
|
type state = map[common.Address]*account
|
||||||
|
@ -1,79 +0,0 @@
|
|||||||
// Copyright 2021 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// Package native is a collection of tracers written in go.
|
|
||||||
//
|
|
||||||
// In order to add a native tracer and have it compiled into the binary, a new
|
|
||||||
// file needs to be added to this folder, containing an implementation of the
|
|
||||||
// `eth.tracers.Tracer` interface.
|
|
||||||
//
|
|
||||||
// Aside from implementing the tracer, it also needs to register itself, using the
|
|
||||||
// `register` method -- and this needs to be done in the package initialization.
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
//
|
|
||||||
// func init() {
|
|
||||||
// register("noopTracerNative", newNoopTracer)
|
|
||||||
// }
|
|
||||||
package native
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/eth/tracers"
|
|
||||||
)
|
|
||||||
|
|
||||||
// init registers itself this packages as a lookup for tracers.
|
|
||||||
func init() {
|
|
||||||
tracers.RegisterLookup(false, lookup)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ctorFn is the constructor signature of a native tracer.
|
|
||||||
type ctorFn = func(*tracers.Context, json.RawMessage) (tracers.Tracer, error)
|
|
||||||
|
|
||||||
/*
|
|
||||||
ctors is a map of package-local tracer constructors.
|
|
||||||
|
|
||||||
We cannot be certain about the order of init-functions within a package,
|
|
||||||
The go spec (https://golang.org/ref/spec#Package_initialization) says
|
|
||||||
|
|
||||||
> To ensure reproducible initialization behavior, build systems
|
|
||||||
> are encouraged to present multiple files belonging to the same
|
|
||||||
> package in lexical file name order to a compiler.
|
|
||||||
|
|
||||||
Hence, we cannot make the map in init, but must make it upon first use.
|
|
||||||
*/
|
|
||||||
var ctors map[string]ctorFn
|
|
||||||
|
|
||||||
// register is used by native tracers to register their presence.
|
|
||||||
func register(name string, ctor ctorFn) {
|
|
||||||
if ctors == nil {
|
|
||||||
ctors = make(map[string]ctorFn)
|
|
||||||
}
|
|
||||||
ctors[name] = ctor
|
|
||||||
}
|
|
||||||
|
|
||||||
// lookup returns a tracer, if one can be matched to the given name.
|
|
||||||
func lookup(name string, ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
|
|
||||||
if ctors == nil {
|
|
||||||
ctors = make(map[string]ctorFn)
|
|
||||||
}
|
|
||||||
if ctor, ok := ctors[name]; ok {
|
|
||||||
return ctor(ctx, cfg)
|
|
||||||
}
|
|
||||||
return nil, errors.New("no tracer found")
|
|
||||||
}
|
|
@ -19,7 +19,6 @@ package tracers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/vm"
|
"github.com/ethereum/go-ethereum/core/vm"
|
||||||
@ -42,31 +41,55 @@ type Tracer interface {
|
|||||||
Stop(err error)
|
Stop(err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type lookupFunc func(string, *Context, json.RawMessage) (Tracer, error)
|
type ctorFn func(*Context, json.RawMessage) (Tracer, error)
|
||||||
|
type jsCtorFn func(string, *Context, json.RawMessage) (Tracer, error)
|
||||||
|
|
||||||
var (
|
type elem struct {
|
||||||
lookups []lookupFunc
|
ctor ctorFn
|
||||||
)
|
isJS bool
|
||||||
|
}
|
||||||
|
|
||||||
// RegisterLookup registers a method as a lookup for tracers, meaning that
|
// DefaultDirectory is the collection of tracers bundled by default.
|
||||||
// users can invoke a named tracer through that lookup. If 'wildcard' is true,
|
var DefaultDirectory = directory{elems: make(map[string]elem)}
|
||||||
// then the lookup will be placed last. This is typically meant for interpreted
|
|
||||||
// engines (js) which can evaluate dynamic user-supplied code.
|
// directory provides functionality to lookup a tracer by name
|
||||||
func RegisterLookup(wildcard bool, lookup lookupFunc) {
|
// and a function to instantiate it. It falls back to a JS code evaluator
|
||||||
if wildcard {
|
// if no tracer of the given name exists.
|
||||||
lookups = append(lookups, lookup)
|
type directory struct {
|
||||||
} else {
|
elems map[string]elem
|
||||||
lookups = append([]lookupFunc{lookup}, lookups...)
|
jsEval jsCtorFn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register registers a method as a lookup for tracers, meaning that
|
||||||
|
// users can invoke a named tracer through that lookup.
|
||||||
|
func (d *directory) Register(name string, f ctorFn, isJS bool) {
|
||||||
|
d.elems[name] = elem{ctor: f, isJS: isJS}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterJSEval registers a tracer that is able to parse
|
||||||
|
// dynamic user-provided JS code.
|
||||||
|
func (d *directory) RegisterJSEval(f jsCtorFn) {
|
||||||
|
d.jsEval = f
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a new instance of a tracer, by iterating through the
|
// New returns a new instance of a tracer, by iterating through the
|
||||||
// registered lookups.
|
// registered lookups. Name is either name of an existing tracer
|
||||||
func New(code string, ctx *Context, cfg json.RawMessage) (Tracer, error) {
|
// or an arbitrary JS code.
|
||||||
for _, lookup := range lookups {
|
func (d *directory) New(name string, ctx *Context, cfg json.RawMessage) (Tracer, error) {
|
||||||
if tracer, err := lookup(code, ctx, cfg); err == nil {
|
if elem, ok := d.elems[name]; ok {
|
||||||
return tracer, nil
|
return elem.ctor(ctx, cfg)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil, errors.New("tracer not found")
|
// Assume JS code
|
||||||
|
return d.jsEval(name, ctx, cfg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsJS will return true if the given tracer will evaluate
|
||||||
|
// JS code. Because code evaluation has high overhead, this
|
||||||
|
// info will be used in determining fast and slow code paths.
|
||||||
|
func (d *directory) IsJS(name string) bool {
|
||||||
|
if elem, ok := d.elems[name]; ok {
|
||||||
|
return elem.isJS
|
||||||
|
}
|
||||||
|
// JS eval will execute JS code
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user