Skip to content

Commit

Permalink
Refactored the code (#5)
Browse files Browse the repository at this point in the history
* updated

* Display result and performance report
  • Loading branch information
jimmychu0807 authored Mar 3, 2024
1 parent 9a0ba84 commit 500d7ba
Show file tree
Hide file tree
Showing 5 changed files with 139 additions and 109 deletions.
4 changes: 2 additions & 2 deletions substrate-rpc-tester/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,15 @@ const Config: AppConfig = {
{
// Bob transfers back to Alice
tx: "api.tx.balances.transfer",
params: ["Chris", 1000000000000],
params: ["Chris", 2000000000000],
signer: "Biden",
},
{
// Alice adding Bob as proxy
tx: "api.tx.proxy.addProxy",
// (address, Staking type, BlockNumber)
params: ["Biden", "Staking", 16],
signer: "Alice",
signer: "Chris",
},
],
};
Expand Down
3 changes: 2 additions & 1 deletion substrate-rpc-tester/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ async function main() {
await substrateRpcTester.initialize();
await substrateRpcTester.executeTxs();

substrateRpcTester.displayReport();
substrateRpcTester.displayTxResults();
substrateRpcTester.displayPerformance();
}

main()
Expand Down
165 changes: 82 additions & 83 deletions substrate-rpc-tester/src/substrateRpcTester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"];
Expand All @@ -20,7 +19,6 @@ class SubstrateRpcTester {
signers: Map<string, KeyringPair>;
mutex: MutexInterface;
userNonces: UserNonces;
timings: Map<number, TimingRecord>;
txResults: Map<number, string[]>;

constructor(_config: AppConfig) {
Expand All @@ -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();
}

Expand All @@ -40,7 +37,10 @@ class SubstrateRpcTester {
const apiPromises: Promise<ApiPromise>[] = 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() {
Expand Down Expand Up @@ -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();
}
}

Expand Down
2 changes: 0 additions & 2 deletions substrate-rpc-tester/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,3 @@ export interface AppConfig {
signers?: Record<string, string>;
txs: Tx[];
}

export type TimingRecord = Record<string, number>;
74 changes: 53 additions & 21 deletions substrate-rpc-tester/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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<unknown>,
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<unknown>) {
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<number, string[]>) {
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}`);
}
}

0 comments on commit 500d7ba

Please sign in to comment.