Skip to content

Commit 1310af4

Browse files
authored
Merge pull request #877 from onflow/bastian/computation-profiling
Add support for collecting computation profiles
2 parents a539468 + c47f6f5 commit 1310af4

File tree

8 files changed

+226
-19
lines changed

8 files changed

+226
-19
lines changed

cmd/emulator/start/start.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ type Config struct {
7474
RedisURL string `default:"" flag:"redis-url" info:"redis-server URL for persisting redis storage backend ( redis://[[username:]password@]host[:port][/database] ) "`
7575
SqliteURL string `default:"" flag:"sqlite-url" info:"sqlite db URL for persisting sqlite storage backend "`
7676
CoverageReportingEnabled bool `default:"false" flag:"coverage-reporting" info:"enable Cadence code coverage reporting"`
77+
ComputationProfilingEnabled bool `default:"false" flag:"computation-profiling" info:"enable Cadence computation profiling"`
7778
LegacyContractUpgradeEnabled bool `default:"false" flag:"legacy-upgrade" info:"enable Cadence legacy contract upgrade"`
7879
ForkHost string `default:"" flag:"fork-host" info:"gRPC access node address (host:port) to fork from"`
7980
ForkHeight uint64 `default:"0" flag:"fork-height" info:"height to pin fork; defaults to latest sealed"`
@@ -225,6 +226,7 @@ func Cmd(config StartConfig) *cobra.Command {
225226
ContractRemovalEnabled: conf.ContractRemovalEnabled,
226227
SqliteURL: conf.SqliteURL,
227228
CoverageReportingEnabled: conf.CoverageReportingEnabled,
229+
ComputationProfilingEnabled: conf.ComputationProfilingEnabled,
228230
ForkHost: conf.ForkHost,
229231
ForkHeight: conf.ForkHeight,
230232
CheckpointPath: conf.CheckpointPath,

emulator/blockchain.go

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,15 @@ func WithComputationReporting(enabled bool) Option {
314314
}
315315
}
316316

317+
// WithComputationProfile injects a ComputationProfile to collect coverage information.
318+
//
319+
// The default is nil.
320+
func WithComputationProfile(computationProfile *runtime.ComputationProfile) Option {
321+
return func(c *config) {
322+
c.ComputationProfile = computationProfile
323+
}
324+
}
325+
317326
func WithScheduledTransactions(enabled bool) Option {
318327
return func(c *config) {
319328
c.ScheduledTransactionsEnabled = enabled
@@ -401,6 +410,7 @@ type config struct {
401410
TransactionValidationEnabled bool
402411
ChainID flowgo.ChainID
403412
CoverageReport *runtime.CoverageReport
413+
ComputationProfile *runtime.ComputationProfile
404414
AutoMine bool
405415
Contracts []ContractDescription
406416
ComputationReportingEnabled bool
@@ -625,17 +635,29 @@ var _ environment.EntropyProvider = &blockHashEntropyProvider{}
625635
func configureFVM(blockchain *Blockchain, conf config, blocks *blocks) (*fvm.VirtualMachine, fvm.Context, error) {
626636
vm := fvm.NewVirtualMachine()
627637

628-
cadenceLogger := conf.Logger.Hook(CadenceHook{MainLogger: &conf.ServerLogger}).Level(zerolog.DebugLevel)
638+
cadenceLogger := conf.Logger.
639+
Hook(CadenceHook{
640+
MainLogger: &conf.ServerLogger,
641+
}).
642+
Level(zerolog.DebugLevel)
643+
644+
if conf.ExecutionEffortWeights != nil &&
645+
conf.ComputationProfile != nil {
646+
647+
conf.ComputationProfile.
648+
WithComputationWeights(conf.ExecutionEffortWeights)
649+
}
629650

630651
runtimeConfig := runtime.Config{
631-
Debugger: blockchain.debugger,
632-
CoverageReport: conf.CoverageReport,
652+
Debugger: blockchain.debugger,
653+
CoverageReport: conf.CoverageReport,
654+
ComputationProfile: conf.ComputationProfile,
633655
}
634656
rt := runtime.NewRuntime(runtimeConfig)
635657
customRuntimePool := reusableRuntime.NewCustomReusableCadenceRuntimePool(
636658
1,
637659
runtimeConfig,
638-
func(config runtime.Config) runtime.Runtime {
660+
func(_ runtime.Config) runtime.Runtime {
639661
return rt
640662
},
641663
)
@@ -775,6 +797,10 @@ func bootstrapLedger(
775797
fvm.ProcedureOutput,
776798
error,
777799
) {
800+
if conf.ComputationProfile != nil {
801+
conf.ComputationProfile.Reset()
802+
}
803+
778804
accountKey := conf.GetServiceKey().AccountKey()
779805
publicKey, _ := crypto.DecodePublicKey(
780806
accountKey.SigAlgo,
@@ -807,7 +833,12 @@ func bootstrapLedger(
807833
return executionSnapshot, output, nil
808834
}
809835

810-
func configureBootstrapProcedure(conf config, flowAccountKey flowgo.AccountPublicKey, supply cadence.UFix64) *fvm.BootstrapProcedure {
836+
func configureBootstrapProcedure(
837+
conf config,
838+
flowAccountKey flowgo.AccountPublicKey,
839+
supply cadence.UFix64,
840+
) *fvm.BootstrapProcedure {
841+
811842
options := make([]fvm.BootstrapProcedureOption, 0)
812843
options = append(options,
813844
fvm.WithInitialTokenSupply(supply),
@@ -1750,12 +1781,20 @@ func (b *Blockchain) CoverageReport() *runtime.CoverageReport {
17501781
return b.conf.CoverageReport
17511782
}
17521783

1784+
func (b *Blockchain) ResetCoverageReport() {
1785+
b.conf.CoverageReport.Reset()
1786+
}
1787+
17531788
func (b *Blockchain) ComputationReport() *ComputationReport {
17541789
return b.computationReport
17551790
}
17561791

1757-
func (b *Blockchain) ResetCoverageReport() {
1758-
b.conf.CoverageReport.Reset()
1792+
func (b *Blockchain) ComputationProfile() *runtime.ComputationProfile {
1793+
return b.conf.ComputationProfile
1794+
}
1795+
1796+
func (b *Blockchain) ResetComputationProfile() {
1797+
b.conf.ComputationProfile.Reset()
17591798
}
17601799

17611800
func (b *Blockchain) GetTransactionsByBlockID(blockID flowgo.Identifier) ([]*flowgo.TransactionBody, error) {
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Flow Emulator
3+
*
4+
* Copyright Flow Foundation
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package emulator_test
20+
21+
import (
22+
"context"
23+
"testing"
24+
25+
"github.com/onflow/cadence"
26+
"github.com/onflow/cadence/common"
27+
"github.com/onflow/cadence/runtime"
28+
flowsdk "github.com/onflow/flow-go-sdk"
29+
"github.com/onflow/flow-go/fvm/meter"
30+
flowgo "github.com/onflow/flow-go/model/flow"
31+
"github.com/rs/zerolog"
32+
"github.com/stretchr/testify/assert"
33+
"github.com/stretchr/testify/require"
34+
35+
"github.com/onflow/flow-emulator/adapters"
36+
"github.com/onflow/flow-emulator/emulator"
37+
)
38+
39+
func TestComputationProfile(t *testing.T) {
40+
41+
t.Parallel()
42+
43+
computationProfile := runtime.NewComputationProfile()
44+
b, err := emulator.New(
45+
emulator.WithComputationProfile(computationProfile),
46+
emulator.WithExecutionEffortWeights(
47+
meter.ExecutionEffortWeights{
48+
common.ComputationKindFunctionInvocation: 1,
49+
common.ComputationKindStatement: 1,
50+
common.ComputationKindLoop: 1,
51+
},
52+
),
53+
)
54+
require.NoError(t, err)
55+
56+
computationProfile.Reset()
57+
58+
logger := zerolog.Nop()
59+
adapter := adapters.NewSDKAdapter(&logger, b)
60+
61+
addTwoScript, counterAddress := DeployAndGenerateAddTwoScript(t, adapter)
62+
63+
tx := flowsdk.NewTransaction().
64+
SetScript([]byte(addTwoScript)).
65+
SetComputeLimit(flowgo.DefaultMaxTransactionGasLimit).
66+
SetProposalKey(b.ServiceKey().Address, b.ServiceKey().Index, b.ServiceKey().SequenceNumber).
67+
SetPayer(b.ServiceKey().Address).
68+
AddAuthorizer(b.ServiceKey().Address)
69+
70+
signer, err := b.ServiceKey().Signer()
71+
require.NoError(t, err)
72+
73+
err = tx.SignEnvelope(b.ServiceKey().Address, b.ServiceKey().Index, signer)
74+
require.NoError(t, err)
75+
76+
callScript := GenerateGetCounterCountScript(counterAddress, b.ServiceKey().Address)
77+
78+
// Sample call (value is 0)
79+
scriptResult, err := b.ExecuteScript([]byte(callScript), nil)
80+
require.NoError(t, err)
81+
assert.Equal(t, cadence.NewInt(0), scriptResult.Value)
82+
83+
// Submit tx (script adds 2)
84+
err = adapter.SendTransaction(context.Background(), *tx)
85+
require.NoError(t, err)
86+
87+
txResult, err := b.ExecuteNextTransaction()
88+
require.NoError(t, err)
89+
AssertTransactionSucceeded(t, txResult)
90+
91+
pprofProfile, err := runtime.NewPProfExporter(computationProfile).Export()
92+
require.NoError(t, err)
93+
require.NotNil(t, pprofProfile)
94+
95+
require.NotEmpty(t, pprofProfile.Function)
96+
require.NotEmpty(t, pprofProfile.Sample)
97+
}

emulator/coverage_report_test.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,8 @@ func TestCoverageReport(t *testing.T) {
8080
require.NoError(t, err)
8181
AssertTransactionSucceeded(t, txResult)
8282

83-
address, err := common.HexToAddress(counterAddress.Hex())
84-
require.NoError(t, err)
8583
location := common.AddressLocation{
86-
Address: address,
84+
Address: common.MustBytesToAddress(counterAddress.Bytes()),
8785
Name: "Counting",
8886
}
8987
coverage := coverageReport.Coverage[location]

emulator/emulator.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ type ComputationReportCapable interface {
104104
ComputationReport() *ComputationReport
105105
}
106106

107+
type ComputationProfileCapable interface {
108+
ComputationProfile() *runtime.ComputationProfile
109+
ResetComputationProfile()
110+
}
111+
107112
type DebuggingCapable interface {
108113
StartDebugger() *interpreter.Debugger
109114
EndDebugging()
@@ -190,6 +195,7 @@ type Emulator interface {
190195

191196
CoverageReportCapable
192197
ComputationReportCapable
198+
ComputationProfileCapable
193199
DebuggingCapable
194200
SnapshotCapable
195201
RollbackCapable

emulator/mocks/emulator.go

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/server.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ import (
3636
"github.com/psiemens/graceland"
3737
"github.com/rs/zerolog"
3838

39+
flowaccess "github.com/onflow/flow/protobuf/go/flow/access"
40+
"google.golang.org/grpc"
41+
"google.golang.org/grpc/credentials/insecure"
42+
3943
"github.com/onflow/flow-emulator/adapters"
4044
"github.com/onflow/flow-emulator/emulator"
4145
"github.com/onflow/flow-emulator/server/access"
@@ -46,9 +50,6 @@ import (
4650
"github.com/onflow/flow-emulator/storage/remote"
4751
"github.com/onflow/flow-emulator/storage/sqlite"
4852
"github.com/onflow/flow-emulator/storage/util"
49-
flowaccess "github.com/onflow/flow/protobuf/go/flow/access"
50-
"google.golang.org/grpc"
51-
"google.golang.org/grpc/credentials/insecure"
5253
)
5354

5455
// EmulatorServer is a local server that runs a Flow Emulator instance.
@@ -142,6 +143,8 @@ type Config struct {
142143
SqliteURL string
143144
// CoverageReportingEnabled enables/disables Cadence code coverage reporting.
144145
CoverageReportingEnabled bool
146+
// ComputationProfilingEnabled enables/disables Cadence computation profiling.
147+
ComputationProfilingEnabled bool
145148
// ForkHost is the gRPC access node address to fork from (host:port).
146149
ForkHost string
147150
// ForkHeight is the height at which to start the emulator when forking.
@@ -513,6 +516,13 @@ func configureBlockchain(logger *zerolog.Logger, chainID flowgo.ChainID, conf *C
513516
)
514517
}
515518

519+
if conf.ComputationProfilingEnabled {
520+
options = append(
521+
options,
522+
emulator.WithComputationProfile(runtime.NewComputationProfile()),
523+
)
524+
}
525+
516526
if conf.ComputationReportingEnabled {
517527
options = append(
518528
options,

0 commit comments

Comments
 (0)