diff --git a/substrate-rpc-tester/src/config.ts b/substrate-rpc-tester/src/config.ts index f6d3105..9ecc52f 100644 --- a/substrate-rpc-tester/src/config.ts +++ b/substrate-rpc-tester/src/config.ts @@ -39,7 +39,7 @@ const Config: AppConfig = { { // Bob transfers back to Alice tx: "api.tx.balances.transfer", - params: ["Chris", 1000000000000], + params: ["Chris", 2000000000000], signer: "Biden", }, { @@ -47,7 +47,7 @@ const Config: AppConfig = { tx: "api.tx.proxy.addProxy", // (address, Staking type, BlockNumber) params: ["Biden", "Staking", 16], - signer: "Alice", + signer: "Chris", }, ], }; diff --git a/substrate-rpc-tester/src/main.ts b/substrate-rpc-tester/src/main.ts index c0e6b8a..a70954c 100644 --- a/substrate-rpc-tester/src/main.ts +++ b/substrate-rpc-tester/src/main.ts @@ -7,7 +7,8 @@ async function main() { await substrateRpcTester.initialize(); await substrateRpcTester.executeTxs(); - substrateRpcTester.displayReport(); + substrateRpcTester.displayTxResults(); + substrateRpcTester.displayPerformance(); } main() diff --git a/substrate-rpc-tester/src/substrateRpcTester.ts b/substrate-rpc-tester/src/substrateRpcTester.ts index 2fa6d8a..0fe2d9c 100644 --- a/substrate-rpc-tester/src/substrateRpcTester.ts +++ b/substrate-rpc-tester/src/substrateRpcTester.ts @@ -7,7 +7,6 @@ import type { ISubmittableResult } from "polkadot-js/types/types/index.ts"; import { AppConfig, Tx } from "./types.ts"; import { UserNonces } from "./userNonces.ts"; import * as utils from "./utils.ts"; -import type { TimingRecord } from "./types.ts"; const DEV_SEED_PHRASE = "bottom drive obey lake curtain smoke basket hold race lonely fit walk"; const DEV_ACCTS = ["Alice", "Bob", "Charlie", "Dave", "Eve", "Fredie"]; @@ -20,7 +19,6 @@ class SubstrateRpcTester { signers: Map; mutex: MutexInterface; userNonces: UserNonces; - timings: Map; txResults: Map; constructor(_config: AppConfig) { @@ -30,7 +28,6 @@ class SubstrateRpcTester { this.signers = new Map(); this.mutex = withTimeout(new Mutex(), MUTEX_TIMEOUT, new Error("mutex time out")); this.userNonces = new UserNonces(); - this.timings = new Map(); this.txResults = new Map(); } @@ -40,7 +37,10 @@ class SubstrateRpcTester { const apiPromises: Promise[] = arr.map(() => ApiPromise.create({ provider: new WsProvider(this.config.endPoint) }) ); - this.apis = await Promise.all(apiPromises); + + await utils.measurePerformance("connectAPI", async () => { + this.apis = await Promise.all(apiPromises); + }); } setupSigners() { @@ -78,96 +78,95 @@ class SubstrateRpcTester { let txStr; let lastResult; - for (const tx of txs) { - if (typeof tx === "string") { - txStr = tx; - const txCall = utils.getTxCall(api, tx); - lastResult = await txCall.call(txCall); - } else if (!utils.isWriteOp(tx)) { - // tx is an Object but is a readOp - txStr = tx.tx; - const txCall = utils.getTxCall(api, txStr); - const transformedParams = Array.isArray(tx.params) - ? utils.transformParams(tx.params, this.signers) - : []; - lastResult = await txCall.call(txCall, ...transformedParams); - } else { - // tx is a writeOp - txStr = tx.tx; - const txCall = utils.getTxCall(api, txStr); - const transformedParams = Array.isArray(tx.params) - ? utils.transformParams(tx.params, this.signers) - : []; - - const signer = utils.getSigner(tx.signer, this.signers); - - // lock the mutex - const release = await this.mutex.acquire(); - const nonce = await this.userNonces.nextUserNonce(api, signer); - const { writeTxWait = false } = this.config; - - if (!writeTxWait || writeTxWait === "none") { - const txReceipt = await txCall - .call(txCall, ...transformedParams) - .signAndSend(signer, { nonce }); - - // release the mutex - release(); - lastResult = `txReceipt: ${txReceipt}`; + await utils.measurePerformance(`${idx + 1}-executeTxs`, async () => { + for (const tx of txs) { + if (typeof tx === "string") { + txStr = tx; + const txCall = utils.getTxCall(api, tx); + lastResult = await txCall.call(txCall); + } else if (!utils.isWriteOp(tx)) { + // tx is an Object but is a readOp + txStr = tx.tx; + const txCall = utils.getTxCall(api, txStr); + const transformedParams = Array.isArray(tx.params) + ? utils.transformParams(tx.params, this.signers) + : []; + lastResult = await txCall.call(txCall, ...transformedParams); } else { - lastResult = await new Promise((resolve, reject) => { - let unsub: () => void; - const buf: string[] = []; - - txCall + // tx is a writeOp + txStr = tx.tx; + const txCall = utils.getTxCall(api, txStr); + const transformedParams = Array.isArray(tx.params) + ? utils.transformParams(tx.params, this.signers) + : []; + + const signer = utils.getSigner(tx.signer, this.signers); + + // lock the mutex + const release = await this.mutex.acquire(); + const nonce = await this.userNonces.nextUserNonce(api, signer); + const { writeTxWait = false } = this.config; + + if (!writeTxWait || writeTxWait === "none") { + const txReceipt = await txCall .call(txCall, ...transformedParams) - .signAndSend(signer, { nonce }, (res: ISubmittableResult) => { - const { status, txHash } = res; + .signAndSend(signer, { nonce }); - if (res.isInBlock) { - buf.push(`inBlock: ${status.asInBlock}}`); - buf.push(`txHash: ${txHash.toHex()}`); - - if (writeTxWait === "inblock") { + // release the mutex + release(); + lastResult = `txReceipt: ${txReceipt}`; + } else { + lastResult = await new Promise((resolve, reject) => { + let unsub: () => void; + const buf: string[] = []; + + txCall + .call(txCall, ...transformedParams) + .signAndSend(signer, { nonce }, (res: ISubmittableResult) => { + const { status, txHash } = res; + + if (res.isInBlock) { + buf.push(`txHash: ${txHash.toHex()}`); + + if (writeTxWait === "inblock") { + buf.push(`block: ${status.asInBlock}`); + unsub(); + resolve(buf); + } + } + if (res.isFinalized && writeTxWait === "finalized") { + buf.push(`fnl: ${status.asFinalized}`); + unsub(); + resolve(buf); + } + if (res.isError) { + buf.push(`error: ${res.dispatchError}`); unsub(); - resolve(buf.join("\n")); + reject(buf); } - } - if (res.isFinalized && writeTxWait === "finalized") { - buf.push(`finalized: ${status.asFinalized}`); - unsub(); - resolve(buf.join("\n")); - } - if (res.isError) { - buf.push(`error: ${res.dispatchError}`); - unsub(); - reject(buf.join("\n")); - } - }) - .then((us: () => void) => (unsub = us)); + }) + .then((us: () => void) => (unsub = us)); - release(); - }); + release(); + }); + } } + + lastResult = utils.transformResult(lastResult); + this.appendExeLog( + idx, + `${utils.txDisplay(tx)}\n L ${utils.stringify(lastResult, 4)}\n`, + ); } + }); + } - lastResult = utils.transformResult(lastResult); - this.appendExeLog( - idx, - `${utils.txDisplay(tx)}\n L ${JSON.stringify(lastResult, undefined, 2)}`, - ); - } + displayTxResults() { + utils.displayTxResults(this.config.connections, this.txResults); } - displayReport() { - for (let idx = 0; idx < this.config.connections; idx++) { - console.log(`-- Connection ${idx + 1} --`); - const res = this.txResults.get(idx); - if (res) { - console.log(res.join("\n")); - } - console.log(); - } + displayPerformance() { + utils.displayPerformance(); } } diff --git a/substrate-rpc-tester/src/types.ts b/substrate-rpc-tester/src/types.ts index 8a5dff9..6964abf 100644 --- a/substrate-rpc-tester/src/types.ts +++ b/substrate-rpc-tester/src/types.ts @@ -20,5 +20,3 @@ export interface AppConfig { signers?: Record; txs: Tx[]; } - -export type TimingRecord = Record; diff --git a/substrate-rpc-tester/src/utils.ts b/substrate-rpc-tester/src/utils.ts index bb17a21..7e821fa 100644 --- a/substrate-rpc-tester/src/utils.ts +++ b/substrate-rpc-tester/src/utils.ts @@ -3,7 +3,7 @@ import chalk from "chalk"; import type { KeyringPair } from "polkadot-js/keyring/types.ts"; // Our own implementation -import type { TimingRecord, Tx, TxParam } from "./types.ts"; +import type { Tx, TxParam } from "./types.ts"; const API_PREFIX = "api"; @@ -59,33 +59,65 @@ export function getTxCall(api: ApiPromise, txStr: string): any { } export function txDisplay(tx: Tx): string { + const decorate = chalk.underline; const em = "🔗"; - if (typeof tx === "string") return `${em} ${tx}()`; + let text: string; - const paramsStr = tx.params ? tx.params.join(", ") : ""; + if (typeof tx === "string") { + text = `${tx}()`; + } else { + const paramsStr = tx.params ? tx.params.join(", ") : ""; + text = !tx.signer ? `${tx.tx}(${paramsStr})` : `${tx.tx}(${paramsStr}) | ✍️ ${tx.signer}`; + } + return `${em} ${decorate(text)}`; +} + +export function stringify( + result: string | number | boolean | object | Array, + spacing: number = 0, +): string { + if (Array.isArray(result)) { + if (result.length > 0 && typeof result[0] === "string") { + return result.join(`\n${" ".repeat(spacing)}`); + } + // an array of object or empty array + return JSON.stringify(result, undefined, 2); + } + + if (typeof result === "object") { + return JSON.stringify(result, undefined, 2); + } + + return result.toString(); +} - if (!tx.signer) return `${em} ${tx.tx}(${paramsStr})`; - return `${em} ${tx.tx}(${paramsStr}) | ✍️ ${tx.signer}`; +export async function measurePerformance(name: string, fnPromise: () => Promise) { + performance.mark(name); + await fnPromise(); + performance.measure(name, name); } -export function displayTimingReport(timings: TimingRecord): void { - const log = console.log; - // const mainTitle = chalk.bold.bgBlack.yellowBright.underline; - const mainTitle = chalk.bold.yellowBright.inverse; - const catTitle = chalk.bgBlack.yellow; - const keyF = chalk.cyan; - const valF = chalk.whiteBright; +// For display +const { log: display } = console; +const mainTitle = chalk.bold.yellowBright.inverse; +const catTitle = chalk.bgBlack.yellow; +// const keyF = chalk.cyan; +// const valF = chalk.whiteBright; - const displayStartEnd = (timings: TimingRecord, key: string) => { - log(` ${keyF("time taken")}: ${valF(timings[key + "End"] - timings[key + "Start"])}`); - }; +export function displayTxResults(conn: number, txResults: Map) { + for (let idx = 0; idx < conn; idx++) { + display(mainTitle(`-- Connection ${idx + 1} --`)); - log(); - log(mainTitle("--- Timing Report ---")); + const res = txResults.get(idx); + if (res && res.length > 0) display(res.join("\n")); + } +} - log(catTitle("Connecting to all endpoints")); - displayStartEnd(timings, "allConn"); +export function displayPerformance() { + display(mainTitle("-- Performance --")); - log(catTitle("Executing all transactions")); - displayStartEnd(timings, "allTxs"); + const measures = performance.getEntriesByType("measure"); + for (const measure of measures) { + display(catTitle(measure.name), `start: ${measure.startTime}, duration: ${measure.duration}`); + } }