Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/emulator/start/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ type Config struct {
RedisURL string `default:"" flag:"redis-url" info:"redis-server URL for persisting redis storage backend ( redis://[[username:]password@]host[:port][/database] ) "`
SqliteURL string `default:"" flag:"sqlite-url" info:"sqlite db URL for persisting sqlite storage backend "`
CoverageReportingEnabled bool `default:"false" flag:"coverage-reporting" info:"enable Cadence code coverage reporting"`
ComputationProfilingEnabled bool `default:"false" flag:"computation-profiling" info:"enable Cadence computation profiling"`
LegacyContractUpgradeEnabled bool `default:"false" flag:"legacy-upgrade" info:"enable Cadence legacy contract upgrade"`
StartBlockHeight uint64 `default:"0" flag:"start-block-height" info:"block height to start the emulator at. only valid when forking Mainnet or Testnet"`
RPCHost string `default:"" flag:"rpc-host" info:"rpc host to query when forking Mainnet or Testnet"`
Expand Down Expand Up @@ -217,6 +218,7 @@ func Cmd(config StartConfig) *cobra.Command {
ContractRemovalEnabled: conf.ContractRemovalEnabled,
SqliteURL: conf.SqliteURL,
CoverageReportingEnabled: conf.CoverageReportingEnabled,
ComputationProfilingEnabled: conf.ComputationProfilingEnabled,
StartBlockHeight: conf.StartBlockHeight,
RPCHost: conf.RPCHost,
CheckpointPath: conf.CheckpointPath,
Expand Down
53 changes: 46 additions & 7 deletions emulator/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,15 @@ func WithComputationReporting(enabled bool) Option {
}
}

// WithComputationProfile injects a ComputationProfile to collect coverage information.
//
// The default is nil.
func WithComputationProfile(computationProfile *runtime.ComputationProfile) Option {
return func(c *config) {
c.ComputationProfile = computationProfile
}
}

func WithScheduledTransactions(enabled bool) Option {
return func(c *config) {
c.ScheduledTransactionsEnabled = enabled
Expand Down Expand Up @@ -402,6 +411,7 @@ type config struct {
TransactionValidationEnabled bool
ChainID flowgo.ChainID
CoverageReport *runtime.CoverageReport
ComputationProfile *runtime.ComputationProfile
AutoMine bool
Contracts []ContractDescription
ComputationReportingEnabled bool
Expand Down Expand Up @@ -627,17 +637,29 @@ var _ environment.EntropyProvider = &blockHashEntropyProvider{}
func configureFVM(blockchain *Blockchain, conf config, blocks *blocks) (*fvm.VirtualMachine, fvm.Context, error) {
vm := fvm.NewVirtualMachine()

cadenceLogger := conf.Logger.Hook(CadenceHook{MainLogger: &conf.ServerLogger}).Level(zerolog.DebugLevel)
cadenceLogger := conf.Logger.
Hook(CadenceHook{
MainLogger: &conf.ServerLogger,
}).
Level(zerolog.DebugLevel)

if conf.ExecutionEffortWeights != nil &&
conf.ComputationProfile != nil {

conf.ComputationProfile.
WithComputationWeights(conf.ExecutionEffortWeights)
}

runtimeConfig := runtime.Config{
Debugger: blockchain.debugger,
CoverageReport: conf.CoverageReport,
Debugger: blockchain.debugger,
CoverageReport: conf.CoverageReport,
ComputationProfile: conf.ComputationProfile,
}
rt := runtime.NewRuntime(runtimeConfig)
customRuntimePool := reusableRuntime.NewCustomReusableCadenceRuntimePool(
1,
runtimeConfig,
func(config runtime.Config) runtime.Runtime {
func(_ runtime.Config) runtime.Runtime {
return rt
},
)
Expand Down Expand Up @@ -776,6 +798,10 @@ func bootstrapLedger(
fvm.ProcedureOutput,
error,
) {
if conf.ComputationProfile != nil {
conf.ComputationProfile.Reset()
}

accountKey := conf.GetServiceKey().AccountKey()
publicKey, _ := crypto.DecodePublicKey(
accountKey.SigAlgo,
Expand Down Expand Up @@ -808,7 +834,12 @@ func bootstrapLedger(
return executionSnapshot, output, nil
}

func configureBootstrapProcedure(conf config, flowAccountKey flowgo.AccountPublicKey, supply cadence.UFix64) *fvm.BootstrapProcedure {
func configureBootstrapProcedure(
conf config,
flowAccountKey flowgo.AccountPublicKey,
supply cadence.UFix64,
) *fvm.BootstrapProcedure {

options := make([]fvm.BootstrapProcedureOption, 0)
options = append(options,
fvm.WithInitialTokenSupply(supply),
Expand Down Expand Up @@ -1712,12 +1743,20 @@ func (b *Blockchain) CoverageReport() *runtime.CoverageReport {
return b.conf.CoverageReport
}

func (b *Blockchain) ResetCoverageReport() {
b.conf.CoverageReport.Reset()
}

func (b *Blockchain) ComputationReport() *ComputationReport {
return b.computationReport
}

func (b *Blockchain) ResetCoverageReport() {
b.conf.CoverageReport.Reset()
func (b *Blockchain) ComputationProfile() *runtime.ComputationProfile {
return b.conf.ComputationProfile
}

func (b *Blockchain) ResetComputationProfile() {
b.conf.ComputationProfile.Reset()
}

func (b *Blockchain) GetTransactionsByBlockID(blockID flowgo.Identifier) ([]*flowgo.TransactionBody, error) {
Expand Down
4 changes: 1 addition & 3 deletions emulator/coverage_report_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,8 @@ func TestCoverageReport(t *testing.T) {
require.NoError(t, err)
AssertTransactionSucceeded(t, txResult)

address, err := common.HexToAddress(counterAddress.Hex())
require.NoError(t, err)
location := common.AddressLocation{
Address: address,
Address: common.MustBytesToAddress(counterAddress.Bytes()),
Name: "Counting",
}
coverage := coverageReport.Coverage[location]
Expand Down
6 changes: 6 additions & 0 deletions emulator/emulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ type ComputationReportCapable interface {
ComputationReport() *ComputationReport
}

type ComputationProfileCapable interface {
ComputationProfile() *runtime.ComputationProfile
ResetComputationProfile()
}

type DebuggingCapable interface {
StartDebugger() *interpreter.Debugger
EndDebugging()
Expand Down Expand Up @@ -182,6 +187,7 @@ type Emulator interface {

CoverageReportCapable
ComputationReportCapable
ComputationProfileCapable
DebuggingCapable
SnapshotCapable
RollbackCapable
Expand Down
26 changes: 26 additions & 0 deletions emulator/mocks/emulator.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ type Config struct {
SqliteURL string
// CoverageReportingEnabled enables/disables Cadence code coverage reporting.
CoverageReportingEnabled bool
// ComputationProfilingEnabled enables/disables Cadence computation profiling.
ComputationProfilingEnabled bool
// RPCHost is the address of the access node to use when using a forked network.
RPCHost string
// StartBlockHeight is the height at which to start the emulator.
Expand Down Expand Up @@ -463,6 +465,13 @@ func configureBlockchain(logger *zerolog.Logger, conf *Config, store storage.Sto
)
}

if conf.ComputationProfilingEnabled {
options = append(
options,
emulator.WithComputationProfile(runtime.NewComputationProfile()),
)
}

if conf.ComputationReportingEnabled {
options = append(
options,
Expand Down
39 changes: 34 additions & 5 deletions server/utils/emulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,9 @@ import (
"strconv"

"github.com/gorilla/mux"
"github.com/onflow/cadence/runtime"
flowgo "github.com/onflow/flow-go/model/flow"

"github.com/onflow/flow-emulator/adapters"
"github.com/onflow/flow-emulator/emulator"
)
Expand Down Expand Up @@ -64,6 +66,9 @@ func NewEmulatorAPIServer(emulator emulator.Emulator, adapter *adapters.AccessAd
router.HandleFunc("/emulator/codeCoverage", r.CodeCoverage).Methods("GET")
router.HandleFunc("/emulator/codeCoverage/reset", r.ResetCodeCoverage).Methods("PUT")

router.HandleFunc("/emulator/computationProfile", r.ComputationProfile).Methods("GET")
router.HandleFunc("/emulator/computationProfile/reset", r.ResetComputationProfile).Methods("PUT")

router.HandleFunc("/emulator/computationReport", r.ComputationReport).Methods("GET")

return r
Expand All @@ -86,7 +91,7 @@ func (m EmulatorAPIServer) Config(w http.ResponseWriter, _ *http.Request) {
_, _ = w.Write(s)
}

func (m EmulatorAPIServer) CommitBlock(w http.ResponseWriter, r *http.Request) {
func (m EmulatorAPIServer) CommitBlock(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, err := m.emulator.CommitBlock()
if err != nil {
Expand Down Expand Up @@ -230,7 +235,7 @@ func (m EmulatorAPIServer) SnapshotCreate(w http.ResponseWriter, r *http.Request
m.latestBlockResponse(name, w)
}

func (m EmulatorAPIServer) CodeCoverage(w http.ResponseWriter, r *http.Request) {
func (m EmulatorAPIServer) CodeCoverage(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")

err := json.NewEncoder(w).Encode(m.emulator.CoverageReport())
Expand All @@ -240,7 +245,13 @@ func (m EmulatorAPIServer) CodeCoverage(w http.ResponseWriter, r *http.Request)
}
}

func (m EmulatorAPIServer) ComputationReport(w http.ResponseWriter, r *http.Request) {
func (m EmulatorAPIServer) ResetCodeCoverage(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
m.emulator.ResetCoverageReport()
w.WriteHeader(http.StatusOK)
}

func (m EmulatorAPIServer) ComputationReport(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")

err := json.NewEncoder(w).Encode(m.emulator.ComputationReport())
Expand All @@ -250,9 +261,27 @@ func (m EmulatorAPIServer) ComputationReport(w http.ResponseWriter, r *http.Requ
}
}

func (m EmulatorAPIServer) ResetCodeCoverage(w http.ResponseWriter, r *http.Request) {
func (m EmulatorAPIServer) ComputationProfile(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/gzip")

computationProfile := m.emulator.ComputationProfile()

pprofProfile, err := runtime.NewPProfExporter(computationProfile).Export()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}

err = pprofProfile.Write(w)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
}

func (m EmulatorAPIServer) ResetComputationProfile(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Content-Type", "application/json")
m.emulator.ResetCoverageReport()
m.emulator.ResetComputationProfile()
w.WriteHeader(http.StatusOK)
}

Expand Down