Skip to content

Commit

Permalink
chore: add latency detection scripts (#3583)
Browse files Browse the repository at this point in the history
  • Loading branch information
Torres-ssf authored Jan 28, 2025
1 parent 56adcc5 commit b4f5279
Show file tree
Hide file tree
Showing 16 changed files with 613 additions and 211 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ TESTNET_WALLET_PVT_KEY=
PUBLISHED_NPM_TAG=
NETWORK_TEST_URL=
NETWORK_TEST_PVT_KEY=
PERFORMANCE_ANALYSIS_TEST_URL=
PERFORMANCE_ANALYSIS_PVT_KEY=
PERFORMANCE_ANALYSIS_CONTRACT_ADDRESS=
60 changes: 60 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,66 @@ Once the environment is set up, run the network tests using the following comman
pnpm test:network
```

# Transaction Time Measure

A script designed to run locally is available to measure the time required to submit and process different types of transactions on a specified network.

The script code is located at: `packages/fuel-gauge/scripts/latency-detection/main.ts`.

### Setup Instructions

Before running the tests, you need to configure the `.env` file:

1. Copy the `.env.example` file:

```sh
cp .env.example .env
```

2. Set the values for the following environment variables in the `.env` file:

```env
PERFORMANCE_ANALYSIS_TEST_URL=https://testnet.fuel.network/v1/graphql
PERFORMANCE_ANALYSIS_PVT_KEY=...
PERFORMANCE_ANALYSIS_CONTRACT_ADDRESS=...
```

- `PERFORMANCE_ANALYSIS_TEST_URL`: The URL of which network the test should run (e.g., Fuel Testnet endpoint).
- `PERFORMANCE_ANALYSIS_PVT_KEY`: Your private key for the network.
- `PERFORMANCE_ANALYSIS_CONTRACT_ADDRESS`: The address of the contract used by the script. If this variable is left empty, the script will deploy the contract before running the time measurement tests. The deployed contract address will be logged, allowing you to add it here for subsequent test runs to avoid re-deployment.

### Running the Test Suite

Once the environment is set up, run the network tests using the following command:

```sh
pnpm tx:perf
```

### Output and Results

The test results are saved in the snapshots directory as a CSV file. The filename follows a timestamp format, such as:

```
2025-01-23T13:23.csv
```

A sample of the results is shown below:

| Tag | Time (in seconds) |
| ---------------------------- | ----------------- |
| `script` | 1.907 |
| `missing-output-variable` | 2.159 |
| `missing-4x-output-variable` | 3.072 |
| `script-with-predicate` | 1.997 |

### Notes on Transaction Types

- `script`: Represents a script transaction, such as a simple contract call performing one asset transfer.
- `missing-output-variable`: A similar contract call as the `script` case, but without specifying the `OutputVariable`, resulting in one additional dry run.
- `missing-4x-output-variable`: Executes an asset transfer transaction to four destinations without specifying `OutputVariable`, leading to four additional dry runs.
- `script-with-predicate`: Performs the contract asset transfer transaction to one address and adds the `OutputVariable` before hand. The account submitting the transaction is a predicate, which it will result in the additional request to `estimatePredicates`.

# FAQ

### Why is the prefix `fuels` and not `fuel`?
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"lint:package-jsons": "tsx ./scripts/lint-package-jsons",
"type:check": "turbo run type:check",
"type:check-tests": "tsc -p tsconfig.test.json",
"tx:perf": "tsx packages/fuel-gauge/scripts/latency-detection/main.ts",
"prettier:check": "prettier --check packages --check apps/docs",
"prettier:format": "prettier --write packages --write apps/docs",
"verify:package-exports": "tsx ./scripts/verify-package-exports",
Expand Down
3 changes: 2 additions & 1 deletion packages/fuel-gauge/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"@fuel-ts/account": "workspace:*",
"@fuel-ts/errors": "workspace:*",
"@fuel-ts/merkle": "workspace:*",
"@fuel-ts/utils": "workspace:*"
"@fuel-ts/utils": "workspace:*",
"ora": "5.4.1"
}
}
148 changes: 148 additions & 0 deletions packages/fuel-gauge/scripts/latency-detection/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { Provider, Wallet } from 'fuels';
import ora from 'ora';

import { TransferContract, TransferContractFactory } from '../../test/typegen/contracts';
import { PredicateWithConfigurable } from '../../test/typegen/predicates';

import type {
MeasureResponse,
Operation,
PerformanceOperationParams,
PerformanceResult,
} from './types';

/**
* Function to measure the execution time (in seconds) of an asynchronous operation.
*
* @param operation - The asynchronous function (Promise) that will be executed and measured.
* @returns An object containing the `response` of the operation and the `duration` in seconds.
*/
export async function measure<T>(operation: () => Promise<T>): Promise<MeasureResponse<T>> {
const start = Date.now();
const response = await operation();
const end = Date.now();

return {
response,
duration: (end - start) / 1000,
};
}

/**
* Executes preparatory steps for latency detection.
*
* This function performs the following steps:
* 1. Retrieves the provider URL and private key from environment variables.
* 2. Initializes a logger to indicate the progress of the preparatory steps.
* 3. Creates a provider and account using the retrieved credentials.
* 4. Retrieves the base asset ID from the provider.
* 5. Deploys a transfer contract and waits for the deployment result.
* 6. Instantiates a predicate with configurable constants.
* 7. Funds the predicate with a specified amount.
*
*
* @returns The parameters to run a performance operation.
*/
export const preparatorySteps = async (): Promise<PerformanceOperationParams> => {
// Preparatory steps
const providerUrl = process.env.PERFORMANCE_ANALYSIS_TEST_URL;
const privateKey = process.env.PERFORMANCE_ANALYSIS_PVT_KEY;
const contractAddress = process.env.PERFORMANCE_ANALYSIS_CONTRACT_ADDRESS;

if (!providerUrl || !privateKey) {
throw new Error(
'Please provide PERFORMANCE_ANALYSIS_TEST_URL and PERFORMANCE_ANALYSIS_PVT_KEY in the .env file'
);
}

const logger = ora({
text: 'Running preparatory steps..',
color: 'green',
}).start();

try {
const provider = new Provider(providerUrl);
const account = Wallet.fromPrivateKey(privateKey, provider);
const baseAssetId = await provider.getBaseAssetId();
const amount = 100;
const callParams = [
{
recipient: { Address: { bits: account.address.toB256() } },
asset_id: { bits: baseAssetId },
amount,
},
];

// Deploying contract that will be executed
let contract: TransferContract;
let deployedMsg = '';
if (!contractAddress) {
const factory = new TransferContractFactory(account);
const deploy = await factory.deploy();
const result = await deploy.waitForResult();
contract = result.contract;
deployedMsg = `\n- contract deployed on address: ${contract.id.toB256()}`;
} else {
contract = new TransferContract(contractAddress, account);
}

// Instantiating predicate
const predicate = new PredicateWithConfigurable({
provider: contract.provider,
data: [10, account.address.toString()],
configurableConstants: {
ADDRESS: account.address.toString(),
FEE: 10,
},
});

// Funding predicate
const res = await account.transfer(predicate.address, 3000, baseAssetId);
await res.waitForResult();

logger.succeed(`Preparatory steps done${deployedMsg}`);

return { account, baseAssetId, provider, contract, callParams, predicate };
} catch (e) {
logger.fail('Failed to run preparatory steps');
throw e;
}
};

/**
* Executes a series of performance measure operations and returns their results.
*
* @param operations - An array of operations to be performed.
* @param params - Parameters to be passed to each operation.
* @returns A promise that resolves to an array of performance results.
*/
export const runOperations = async (
operations: Array<Operation>,
params: PerformanceOperationParams
) => {
const results: PerformanceResult[] = [];

// Performing measure operations
for (const operation of operations) {
const logger = ora({
text: `Performing operation: ${operation.name}`,
color: 'green',
}).start();
try {
// Clear chain info cache
Provider.clearChainAndNodeCaches();

const result = await operation(params);

logger.text = `Operation: ${operation.name} completed`;
logger.succeed();

results.push(result);
} catch (e) {
logger.fail(`Operation: ${operation.name} failed`);
throw e;
}
}

return results;
};
35 changes: 35 additions & 0 deletions packages/fuel-gauge/scripts/latency-detection/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'dotenv/config';
import fs from 'fs';

import { preparatorySteps, runOperations } from './helpers';
import { missing4xOutputVariableCall } from './missing-4x-variable-output-call';
import { missingOutputVariableCall } from './missing-variable-output-call';
import { scriptCall } from './script-call';
import { scriptWithPredicateCall } from './script-with-predicate-call';

const { log, error } = console;

const DIR_NAME = 'snapshots';

const main = async () => {
const operations = [
scriptCall,
missingOutputVariableCall,
missing4xOutputVariableCall,
scriptWithPredicateCall,
];

const operationsParams = await preparatorySteps();
const results = await runOperations(operations, operationsParams);

const date = new Date();

const filename = `${date.toISOString().slice(0, 16)}.json`;

fs.mkdirSync(DIR_NAME, { recursive: true });
fs.writeFileSync(`${DIR_NAME}/${filename}`, JSON.stringify(results, null, 2));

log(`Snapshots saved into "${DIR_NAME}/${filename}"`);
};

main().catch(error);
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { measure } from './helpers';
import type { PerformanceOperationParams, PerformanceResult } from './types';
import { TagEnum } from './types';

export async function missing4xOutputVariableCall(
params: PerformanceOperationParams
): Promise<PerformanceResult> {
const { account, baseAssetId, contract } = params;

const callParams = [
{
recipient: { Address: { bits: account.address.toB256() } },
asset_id: { bits: baseAssetId },
amount: 25,
},
{
recipient: { Address: { bits: account.address.toB256() } },
asset_id: { bits: baseAssetId },
amount: 25,
},
{
recipient: { Address: { bits: account.address.toB256() } },
asset_id: { bits: baseAssetId },
amount: 25,
},
{
recipient: { Address: { bits: account.address.toB256() } },
asset_id: { bits: baseAssetId },
amount: 25,
},
];

const { duration } = await measure(async () => {
const call = await contract.functions
.execute_transfer(callParams)
.callParams({ forward: [1000, baseAssetId] })
.call();

return call.waitForResult();
});

return { tag: TagEnum.Missing4xOutputVariable, duration };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { measure } from './helpers';
import type { PerformanceOperationParams, PerformanceResult } from './types';
import { TagEnum } from './types';

export async function missingOutputVariableCall(
params: PerformanceOperationParams
): Promise<PerformanceResult> {
const { baseAssetId, contract, callParams } = params;

const { duration } = await measure(async () => {
const call = await contract.functions
.execute_transfer(callParams)
.callParams({ forward: [100, baseAssetId] })
.call();

return call.waitForResult();
});

return { tag: TagEnum.MissingOutputVariable, duration };
}
19 changes: 19 additions & 0 deletions packages/fuel-gauge/scripts/latency-detection/script-call.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { measure } from './helpers';
import type { PerformanceOperationParams, PerformanceResult } from './types';
import { TagEnum } from './types';

export async function scriptCall(params: PerformanceOperationParams): Promise<PerformanceResult> {
const { baseAssetId, contract, callParams } = params;

const { duration } = await measure(async () => {
const call = await contract.functions
.execute_transfer(callParams)
.txParams({ variableOutputs: 1 })
.callParams({ forward: [100, baseAssetId] })
.call();

return call.waitForResult();
});

return { tag: TagEnum.Script, duration };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { measure } from './helpers';
import type { PerformanceOperationParams, PerformanceResult } from './types';
import { TagEnum } from './types';

export async function scriptWithPredicateCall(
params: PerformanceOperationParams
): Promise<PerformanceResult> {
const { baseAssetId, contract, callParams, predicate } = params;

const { duration } = await measure(async () => {
contract.account = predicate;
const call = await contract.functions
.execute_transfer(callParams)
.txParams({ variableOutputs: 1 })
.callParams({ forward: [100, baseAssetId] })
.call();

return call.waitForResult();
});

return { tag: TagEnum.ScriptWithPredicate, duration };
}
Loading

0 comments on commit b4f5279

Please sign in to comment.