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

ci: add initial benchmark infrastructure #3084

Merged
merged 38 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
ad814e9
test: introduce initial wallet benchmark tests
maschad May 6, 2024
b3d98f1
Merge branch 'master' into tests/add-benchmark-tests
maschad May 29, 2024
00e7d6b
test: use clinic for flame and profile tests
maschad May 31, 2024
df3f7f6
test: added doctor and heap profiling
maschad May 31, 2024
352c33a
build: add dynamic scripts + account profiling
maschad Jun 1, 2024
2be51be
test: added more benchmark tests
maschad Jun 2, 2024
01338f5
Merge branch 'master' into mc/test/performance-tests
maschad Jun 2, 2024
5395454
test: update crypto benchmarking
maschad Jun 4, 2024
08e95fb
Merge branch 'master' into mc/test/performance-tests
maschad Sep 1, 2024
0703485
build: update lock file
maschad Sep 1, 2024
89f2878
Merge branch 'master' into mc/test/performance-tests
maschad Sep 2, 2024
b23cd12
build: resolve build issues
maschad Sep 2, 2024
bb8d58d
feat: added benchmarks for cost estimation
maschad Sep 3, 2024
7fefa3c
feat: added transaction results benchmarks
maschad Sep 3, 2024
135edfe
ci: add initial benchmark workflow
maschad Sep 3, 2024
2b0ef79
Merge branch 'master' into mc/test/performance-tests
maschad Sep 5, 2024
4d00d70
ci: integrate codespeed
maschad Sep 5, 2024
008dc78
build: move bench marks to internal folder
maschad Sep 5, 2024
6d7338c
test: remove unrelated abi coder tests
maschad Sep 7, 2024
54aa319
Merge branch 'master' into mc/test/performance-tests
maschad Sep 7, 2024
493d7ad
restore relevant abi-coder files
maschad Sep 7, 2024
b8fb315
fix: remove unnecessary delay
maschad Sep 7, 2024
c2f6036
build: update lockfile
maschad Sep 7, 2024
4277eab
restore account package
maschad Sep 7, 2024
c1cd693
ci: update benchmark command
maschad Sep 7, 2024
7ee57c2
docs: add changeset + pretest command
maschad Sep 7, 2024
a7d3e5e
lint: format forc.toml
maschad Sep 7, 2024
6ef92fd
Merge branch 'master' into mc/test/performance-tests
maschad Sep 8, 2024
6c4acd7
Merge branch 'master' into mc/test/performance-tests
maschad Sep 9, 2024
a0597b9
pr updates
maschad Sep 9, 2024
5d96aab
Merge branch 'master' into mc/test/performance-tests
maschad Sep 9, 2024
38a228a
resolve lockfile issue
maschad Sep 9, 2024
9281eae
ci: update benchmark workflow
maschad Sep 9, 2024
8385216
ci: remove browser benchmark job
maschad Sep 9, 2024
61807c4
docs: add docs for benchmarking and profiling
maschad Sep 9, 2024
c4bbf94
feat: add contract read, write, mint benches
maschad Sep 10, 2024
d1454d6
deps: remove 0x
maschad Sep 10, 2024
20ab594
Merge branch 'master' into mc/test/performance-tests
maschad Sep 12, 2024
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
23 changes: 23 additions & 0 deletions .github/workflows/bench.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Benchmarks
arboleya marked this conversation as resolved.
Show resolved Hide resolved
on:
pull_request:
maschad marked this conversation as resolved.
Show resolved Hide resolved
types:
- opened
- edited
- synchronize

jobs:
benchmarks:
arboleya marked this conversation as resolved.
Show resolved Hide resolved
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: CI Setup
uses: ./.github/actions/test-setup

- name: Run benchmarks
uses: CodSpeedHQ/action@v3
with:
run: pnpm exec vitest bench
token: ${{ secrets.CODSPEED_TOKEN }}
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ lib-cov
*.pid
*.gz
*.swp
*.0x/*

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
Expand Down Expand Up @@ -144,6 +145,10 @@ Forc.lock
**/out/release
**/test-predicate-*/index.ts

## Ignore perf test files
*clinic*
.clinic/*

# Ignore typegen test files
**/test/typegen

Expand Down
1 change: 1 addition & 0 deletions internal/benchmarks/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
test/fixtures/forc-projects/**/index.ts
1 change: 1 addition & 0 deletions internal/benchmarks/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A package for running benchmarks.
9 changes: 9 additions & 0 deletions internal/benchmarks/fuels.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createConfig } from 'fuels';

export default createConfig({
workspace: './test/fixtures/forc-projects',
output: './test/typegen',
forcBuildFlags: ['--release'],
forcPath: 'fuels-forc',
fuelCorePath: 'fuels-core',
});
19 changes: 19 additions & 0 deletions internal/benchmarks/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"private": true,
"name": "@internal/benchmarks",
"files": [
"dist"
],
"scripts": {
"type:check": "tsc --noEmit",
"pretest": "run-s build:forc type:check",
"build:forc": "pnpm fuels build"
},
"license": "Apache-2.0",
"dependencies": {
"@fuel-ts/crypto": "workspace:*",
"@fuel-ts/account": "workspace:*",
maschad marked this conversation as resolved.
Show resolved Hide resolved
"fuels": "workspace:*"
},
"version": "1.0.0"
}
139 changes: 139 additions & 0 deletions internal/benchmarks/src/cost-estimation.bench.ts
maschad marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/* eslint-disable import/no-extraneous-dependencies */

import type { TransferParams } from 'fuels';
import { ScriptTransactionRequest, Wallet } from 'fuels';
import { launchTestNode, TestAssetId } from 'fuels/test-utils';
import { bench } from 'vitest';

import { CallTestContractFactory } from '../test/typegen/contracts';

/**
* @group node
*/
describe('Cost Estimation Benchmarks', () => {
bench(
'should successfully get transaction cost estimate for a single contract call',
async () => {
using launched = await launchTestNode({
maschad marked this conversation as resolved.
Show resolved Hide resolved
contractsConfigs: [
{
factory: CallTestContractFactory,
},
],
});

const {
contracts: [contract],
} = launched;

const cost = await contract.functions
.return_context_amount()
.callParams({
forward: [100, contract.provider.getBaseAssetId()],
})
.getTransactionCost();

expect(cost.minFee).toBeDefined();
expect(cost.maxFee).toBeDefined();
expect(cost.gasPrice).toBeDefined();
expect(cost.gasUsed).toBeDefined();
expect(cost.gasPrice).toBeDefined();
}
);

bench('should successfully get transaction cost estimate for multi contract calls', async () => {
using launched = await launchTestNode({
contractsConfigs: [{ factory: CallTestContractFactory }],
});

const {
contracts: [contract],
} = launched;

const invocationScope = contract.multiCall([
contract.functions.return_context_amount().callParams({
forward: [100, contract.provider.getBaseAssetId()],
}),
contract.functions.return_context_amount().callParams({
forward: [200, TestAssetId.A.value],
}),
]);

const cost = await invocationScope.getTransactionCost();

expect(cost.minFee).toBeDefined();
expect(cost.maxFee).toBeDefined();
expect(cost.gasPrice).toBeDefined();
expect(cost.gasUsed).toBeDefined();
expect(cost.gasPrice).toBeDefined();
});

bench('should successfully get transaction cost estimate for a single transfer', async () => {
using launched = await launchTestNode({
// Only deploying the contract so that we have the same config for both benchmarks
contractsConfigs: [{ factory: CallTestContractFactory }],
});

const { provider } = launched;

const request = new ScriptTransactionRequest({ gasLimit: 1000000 });

const recipient = Wallet.generate({
provider,
});
const sender = Wallet.fromPrivateKey(
'0x30bb0bc68f5d2ec3b523cee5a65503031b40679d9c72280cd8088c2cfbc34e38',
provider
);

request.addCoinOutput(recipient.address, 10, provider.getBaseAssetId());

const cost = await sender.getTransactionCost(request);

expect(cost.minFee).toBeDefined();
expect(cost.maxFee).toBeDefined();
expect(cost.gasPrice).toBeDefined();
expect(cost.gasUsed).toBeDefined();
expect(cost.gasPrice).toBeDefined();
});

bench('should successfully get transaction cost estimate for a batch transfer', async () => {
using launched = await launchTestNode({
contractsConfigs: [{ factory: CallTestContractFactory }],
});

const {
provider,
contracts: [contract],
} = launched;

const receiver1 = Wallet.generate({ provider });
const receiver2 = Wallet.generate({ provider });
const receiver3 = Wallet.generate({ provider });

const amountToTransfer1 = 989;
const amountToTransfer2 = 699;
const amountToTransfer3 = 122;

const transferParams: TransferParams[] = [
{
destination: receiver1.address,
amount: amountToTransfer1,
assetId: provider.getBaseAssetId(),
},
{ destination: receiver2.address, amount: amountToTransfer2, assetId: TestAssetId.A.value },
{ destination: receiver3.address, amount: amountToTransfer3, assetId: TestAssetId.B.value },
];

const cost = await contract.functions
.sum(40, 50)
.addBatchTransfer(transferParams)
.getTransactionCost();

expect(cost.minFee).toBeDefined();
expect(cost.maxFee).toBeDefined();
expect(cost.gasPrice).toBeDefined();
expect(cost.gasUsed).toBeDefined();
expect(cost.gasPrice).toBeDefined();
});
});
64 changes: 64 additions & 0 deletions internal/benchmarks/src/crypto.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/* eslint-disable import/no-extraneous-dependencies */
import type { Keystore } from '@fuel-ts/crypto';
import { bufferFromString, pbkdf2, computeHmac, encrypt, decrypt } from '@fuel-ts/crypto';
maschad marked this conversation as resolved.
Show resolved Hide resolved
import { bench } from 'vitest';

/**
* @group node
* @group browser
maschad marked this conversation as resolved.
Show resolved Hide resolved
*/
describe('crypto bench', () => {
bench(
'should correctly convert string to Uint8Array with base64 encoding in a node environment',
() => {
const string = 'aGVsbG8='; // "hello" in Base64
bufferFromString(string, 'base64');
}
);

bench('should compute the PBKDF2 hash', () => {
const passwordBuffer = bufferFromString(String('password123').normalize('NFKC'), 'utf-8');
const saltBuffer = bufferFromString(String('salt456').normalize('NFKC'), 'utf-8');
const iterations = 1000;
const keylen = 32;
const algo = 'sha256';

pbkdf2(passwordBuffer, saltBuffer, iterations, keylen, algo);
});

bench('should compute HMAC correctly', () => {
const key = '0x0102030405060708090a0b0c0d0e0f10';
const data = '0x11121314151617181920212223242526';
const sha256Length = 64;
const sha512Length = 128;
const prefix = '0x';

expect(computeHmac('sha256', key, data).length).toBe(sha256Length + prefix.length);
expect(computeHmac('sha512', key, data).length).toBe(sha512Length + prefix.length);
});

bench('Encrypt via aes-ctr', async () => {
const password = '0b540281-f87b-49ca-be37-2264c7f260f7';
const data = {
name: 'test',
};

const encryptedResult = await encrypt(password, data);
expect(encryptedResult.data).toBeTruthy();
expect(encryptedResult.iv).toBeTruthy();
expect(encryptedResult.salt).toBeTruthy();
});

bench('Decrypt via aes-ctr', async () => {
const password = '0b540281-f87b-49ca-be37-2264c7f260f7';
const data = {
name: 'test',
};
const encryptedResult: Keystore = {
data: 'vj1/JyHR+NiIaWXTpl5T',
iv: '0/lqnRVK5HE/5b1cQAHfqg==',
salt: 'nHdHXW2EmOEagAH2UUDYMRNhd7LJ5XLIcZoVQZMPSlU=',
};
await decrypt(password, encryptedResult);
});
});
110 changes: 110 additions & 0 deletions internal/benchmarks/src/transaction-results.bench.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/* eslint-disable import/no-extraneous-dependencies */

import { Wallet } from 'fuels';
import type { TransferParams } from 'fuels';
import { launchTestNode, TestAssetId } from 'fuels/test-utils';
import { bench } from 'vitest';

/**
* @group node
*/
describe('Transaction Submission Benchmarks', () => {
bench('should successfully transfer a single asset between wallets', async () => {
using launched = await launchTestNode();
maschad marked this conversation as resolved.
Show resolved Hide resolved

const {
provider,
wallets: [walletA],
} = launched;

const receiver = Wallet.generate({ provider });

const tx = await walletA.transfer(receiver.address, 100, provider.getBaseAssetId());

const { isStatusSuccess } = await tx.waitForResult();

expect(isStatusSuccess).toBeTruthy();
});

bench('should successfully conduct a custom transfer between wallets', async () => {
using launched = await launchTestNode();

const {
provider,
wallets: [wallet],
} = launched;

const receiver = Wallet.generate({ provider });

const txParams = {
tip: 4,
witnessLimit: 800,
maxFee: 70_000,
};

const pendingTx = await wallet.transfer(
receiver.address,
500,
provider.getBaseAssetId(),
txParams
);

const { transaction } = await pendingTx.waitForResult();

expect(transaction).toBeDefined();
});

bench('should successfully perform a batch transfer', async () => {
using launched = await launchTestNode();

const {
provider,
wallets: [wallet],
} = launched;

const receiver1 = Wallet.generate({ provider });
const receiver2 = Wallet.generate({ provider });
const receiver3 = Wallet.generate({ provider });

const amountToTransfer1 = 989;
const amountToTransfer2 = 699;
const amountToTransfer3 = 122;

const transferParams: TransferParams[] = [
{
destination: receiver1.address,
amount: amountToTransfer1,
assetId: provider.getBaseAssetId(),
},
{ destination: receiver2.address, amount: amountToTransfer2, assetId: TestAssetId.A.value },
{ destination: receiver3.address, amount: amountToTransfer3, assetId: TestAssetId.B.value },
];

const tx = await wallet.batchTransfer(transferParams);

const { isStatusSuccess } = await tx.waitForResult();

expect(isStatusSuccess).toBeTruthy();
});

bench('should successfully withdraw to the base layer', async () => {
using launched = await launchTestNode();

const {
provider,
wallets: [wallet],
} = launched;

const receiver = Wallet.generate({ provider });

const txParams = {
witnessLimit: 800,
maxFee: 100_000,
};

const pendingTx = await wallet.withdrawToBaseLayer(receiver.address, 500, txParams);
const { transaction } = await pendingTx.waitForResult();

expect(transaction).toBeDefined();
});
});
Loading
Loading