Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(benchmarks): runtime utility function + timestamp set benchmarks #330

Merged
merged 8 commits into from
Jan 16, 2024
Merged
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
9 changes: 8 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ build-dev: build-tinygo
@echo "Building \"runtime.wasm\""; \
WASMOPT="$(CURRENT_DIR)/$(WASMOPT_PATH)" $(TINYGO_BUILD_COMMAND) -o=$(BUILD_PATH) runtime/runtime.go

build-benchmarks: build-tinygo
@echo "Building \"runtime.wasm\" (no-debug)"; \
WASMOPT="$(CURRENT_DIR)/$(WASMOPT_PATH)" $(TINYGO_BUILD_COMMAND_NODEBUG) -tags benchmarks -o=$(BUILD_PATH) runtime/runtime.go

start-network:
cp build/runtime.wasm polkadot-sdk/substrate/bin/node-template/runtime.wasm; \
cd polkadot-sdk/substrate/bin/node-template/node; \
Expand All @@ -94,4 +98,7 @@ test-integration:

test-coverage:
@set -e; \
./scripts/coverage.sh
./scripts/coverage.sh

benchmark:
@GOMAXPROCS=1 go test --tags=nonwasmenv -run=XXX -bench=. -benchtime=20x ./runtime/...
187 changes: 187 additions & 0 deletions api/benchmarking/module.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package benchmarking

import (
"bytes"

sc "github.com/LimeChain/goscale"
"github.com/LimeChain/gosemble/execution/types"
"github.com/LimeChain/gosemble/frame/support"
"github.com/LimeChain/gosemble/frame/system"
"github.com/LimeChain/gosemble/primitives/benchmarking"
"github.com/LimeChain/gosemble/primitives/io"
"github.com/LimeChain/gosemble/primitives/log"
primitives "github.com/LimeChain/gosemble/primitives/types"
"github.com/LimeChain/gosemble/utils"
)

type Module struct {
systemModule system.Module
transactional support.Transactional[primitives.PostDispatchInfo]
decoder types.RuntimeDecoder
memUtils utils.WasmMemoryTranslator
hashing io.Hashing
logger log.Logger
}

func New(systemModule system.Module, decoder types.RuntimeDecoder, logger log.Logger) Module {
return Module{
systemModule: systemModule,
decoder: decoder,
transactional: support.NewTransactional[primitives.PostDispatchInfo](logger),
memUtils: utils.NewMemoryTranslator(),
hashing: io.NewHashing(),
logger: logger,
}
}

// TODO:
// Implement DbCommit, DbWipe once the state implementation
// in Gossamer supports caching and nested transactions.
// https://github.com/ChainSafe/gossamer/discussions/3646

func (m Module) Run(dataPtr int32, dataLen int32) int64 {
data := m.memUtils.GetWasmMemorySlice(dataPtr, dataLen)
buffer := bytes.NewBuffer(data)

benchmarkConfig, err := benchmarking.DecodeBenchmarkConfig(buffer)
if err != nil {
m.logger.Critical(err.Error())
}

opaqueExtrinsic := sc.SequenceU8ToBytes(benchmarkConfig.Extrinsic)
extrinsic, err := m.decoder.DecodeUncheckedExtrinsic(bytes.NewBuffer(opaqueExtrinsic))
if err != nil {
m.logger.Critical(err.Error())
}

function := extrinsic.Function()
args := function.Args()
accountId := m.accountIdFrom(extrinsic.Signature())
origin := m.originFrom(benchmarkConfig, accountId)

measuredDurations := []int64{}

benchmarking.StoreSnapshotDb()

// Always do at least one internal repeat.
repeats := int(benchmarkConfig.InternalRepeats)
if repeats < 1 {
repeats = 1
}
for i := 1; i <= repeats; i++ {
// The dispatch call is executed in a transactional context,
// allowing to rollback and reset the state after each iteration.
// as an alternative of providing before hook.

benchmarking.RestoreSnapshotDb()

// Does nothing, for now
benchmarking.WipeDb()

// Set up the externalities environment for the setup we want to
// benchmark.

// Sets the block number to 1 to allow emitting events
m.systemModule.StorageBlockNumberSet(1)

// Commit the externalities to the database, flushing the DB cache.
// This will enable worst case scenario for reading from the database.
// Does nothing, for now
benchmarking.CommitDb()

// Whitelist known storage keys.
benchmarking.SetWhitelist([]byte(":transaction_level:"))
benchmarking.SetWhitelist([]byte(":extrinsic_index"))

// Whitelist the signer account key.
keyStorageAccount := m.accountStorageKeyFrom(accountId.Value)
benchmarking.SetWhitelist(keyStorageAccount)

// Reset the read/write counter so we don't count
// operations in the setup process.
benchmarking.ResetReadWriteCount()

benchmarking.StartDbTracker()

var start, end int64

start = benchmarking.CurrentTime()
_, err := m.transactional.WithStorageLayer(
func() (primitives.PostDispatchInfo, error) {
return function.Dispatch(origin, args)
},
)
end = benchmarking.CurrentTime()
if err != nil {
m.logger.Critical(err.Error())
}

// Calculate the diff caused by the benchmark.
measuredDurations = append(measuredDurations, end-start)

benchmarking.StopDbTracker()

// Commit the changes to get proper write count.
// Does nothing, for now
benchmarking.CommitDb()
}

// Calculate the average time.
extrinsicTime := calculateAverageTime(measuredDurations)

benchmarkResult := benchmarking.BenchmarkResult{
ExtrinsicTime: sc.NewU128(extrinsicTime),
Reads: sc.U32(benchmarking.DbReadCount()),
Writes: sc.U32(benchmarking.DbWriteCount()),
}.Bytes()

return m.memUtils.BytesToOffsetAndSize(benchmarkResult)
}

func calculateAverageTime(durations []int64) int64 {
var sum int64
for _, duration := range durations {
sum += duration
}
return sum / int64(len(durations))
}

func (m Module) accountIdFrom(signature sc.Option[primitives.ExtrinsicSignature]) sc.Option[primitives.AccountId] {
var accountId = sc.NewOption[primitives.AccountId](nil)
if signature.HasValue {
id, err := signature.Value.Signer.AsAccountId()
if err != nil {
m.logger.Critical(err.Error())
}
accountId.Value = id
}

return accountId
}

func (m Module) originFrom(benchmarkConfig benchmarking.BenchmarkConfig, accountId sc.Option[primitives.AccountId]) primitives.RawOrigin {
if benchmarkConfig.Origin.HasValue {
return benchmarkConfig.Origin.Value
} else {
return primitives.RawOriginFrom(accountId)
}
}

func (m Module) accountStorageKeyFrom(address primitives.AccountId) []byte {
// TODO:
// reuse already implemented storage keys generation ?
//
// support.NewHashStorageValue[primitives.AccountId](
// []byte("System"),
// []byte("Account"),
// primitives.DecodeAccountId,
// )
addressBytes := address.FixedSequence.Bytes()
keySystemHash := m.hashing.Twox128([]byte("System"))
keyAccountHash := m.hashing.Twox128([]byte("Account"))
addressHash := m.hashing.Blake128(addressBytes)
keyStorageAccount := append(keySystemHash, keyAccountHash...)
keyStorageAccount = append(keyStorageAccount, addressHash...)
keyStorageAccount = append(keyStorageAccount, addressBytes...)
return keyStorageAccount
}
Binary file modified build/runtime.wasm
Binary file not shown.
47 changes: 47 additions & 0 deletions env/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//go:build !benchmarks

package env

func ExtBenchmarkingCurrentTimeVersion1() int64 {
panic("not implemented")
}

func ExtBenchmarkingSetWhitelistVersion1(key int64) {
panic("not implemented")
}

func ExtBenchmarkingResetReadWriteCountVersion1() {
panic("not implemented")
}

func ExtBenchmarkingStartDbTrackerVersion1() {
panic("not implemented")
}

func ExtBenchmarkingStopDbTrackerVersion1() {
panic("not implemented")
}

func ExtBenchmarkingWipeDbVersion1() {
panic("not implemented")
}

func ExtBenchmarkingCommitDbVersion1() {
panic("not implemented")
}

func ExtBenchmarkingDbReadCountVersion1() int32 {
panic("not implemented")
}

func ExtBenchmarkingDbWriteCountVersion1() int32 {
panic("not implemented")
}

func ExtBenchmarkingStoreSnapshotDbVersion1() {
panic("not implemented")
}

func ExtBenchmarkingRestoreSnapshotDbVersion1() {
panic("not implemented")
}
36 changes: 36 additions & 0 deletions env/utils_benchmarks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//go:build benchmarks

package env

//go:wasmimport env ext_benchmarking_current_time_version_1
func ExtBenchmarkingCurrentTimeVersion1() int64

//go:wasmimport env ext_benchmarking_set_whitelist_version_1
func ExtBenchmarkingSetWhitelistVersion1(key int64)

//go:wasmimport env ext_benchmarking_reset_read_write_count_version_1
func ExtBenchmarkingResetReadWriteCountVersion1()

//go:wasmimport env ext_benchmarking_start_db_tracker_version_1
func ExtBenchmarkingStartDbTrackerVersion1()

//go:wasmimport env ext_benchmarking_stop_db_tracker_version_1
func ExtBenchmarkingStopDbTrackerVersion1()

//go:wasmimport env ext_benchmarking_wipe_db_version_1
func ExtBenchmarkingWipeDbVersion1()

//go:wasmimport env ext_benchmarking_commit_db_version_1
func ExtBenchmarkingCommitDbVersion1()

//go:wasmimport env ext_benchmarking_db_read_count_version_1
func ExtBenchmarkingDbReadCountVersion1() int32

//go:wasmimport env ext_benchmarking_db_write_count_version_1
func ExtBenchmarkingDbWriteCountVersion1() int32

//go:wasmimport env ext_benchmarking_store_snapshot_db_version_1
func ExtBenchmarkingStoreSnapshotDbVersion1()

//go:wasmimport env ext_benchmarking_restore_snapshot_db_version_1
func ExtBenchmarkingRestoreSnapshotDbVersion1()
1 change: 1 addition & 0 deletions frame/system/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ type Module interface {
StorageBlockHashExists(key sc.U64) bool

StorageBlockNumber() (sc.U64, error)
StorageBlockNumberSet(sc.U64)

StorageLastRuntimeUpgrade() (types.LastRuntimeUpgradeInfo, error)
StorageLastRuntimeUpgradeSet(lrui types.LastRuntimeUpgradeInfo)
Expand Down
Loading