Skip to content

Commit

Permalink
feat(connector-besu): add continuous benchmarking with JMeter
Browse files Browse the repository at this point in the history
Primary Changes
---------------

1. Added continuous benchmarking using JMeter that reports performance
in cactus-plugin-ledger-connector-besu using one of its endpoint.

fixes: #2672

Signed-off-by: ruzell22 <ruzell.vince.aquino@accenture.com>
  • Loading branch information
ruzell22 authored and petermetz committed May 10, 2024
1 parent 25f6998 commit 379d41d
Show file tree
Hide file tree
Showing 4 changed files with 285 additions and 0 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,7 @@ jobs:
${{ runner.os }}-yarn-${{ hashFiles('./yarn.lock') }}
- run: ./tools/ci.sh
cactus-plugin-ledger-connector-besu:
permissions: write-all
continue-on-error: false
needs:
- build-dev
Expand Down Expand Up @@ -1002,6 +1003,38 @@ jobs:
restore-keys: |
${{ runner.os }}-yarn-${{ hashFiles('./yarn.lock') }}
- run: ./tools/ci.sh

- name: Ensure .tmp Directory Exists
run: mkdir -p .tmp/benchmark-results/plugin-ledger-connector-besu/

# Download previous benchmark result from cache (if exists)
- name: Download previous benchmark data
uses: actions/cache@v3.3.1
with:
path: .tmp/benchmark-results/plugin-ledger-connector-besu/
key: ${{ runner.os }}-benchmark

- name: Run Benchmarks
working-directory: ./packages/cactus-plugin-ledger-connector-besu/
run: yarn run benchmark

- name: Store benchmark result
uses: benchmark-action/github-action-benchmark@v1.19.2
with:
tool: 'benchmarkjs'
output-file-path: .tmp/benchmark-results/plugin-ledger-connector-besu/run-plugin-ledger-connector-besu-benchmark.ts.log
github-token: ${{ secrets.GITHUB_TOKEN }}

# Only push the benchmark results to gh-pages website if we are running on the main branch
# We do not want to clutter the benchmark results with intermediate results from PRs that could be drafts
auto-push: ${{ github.ref == 'refs/heads/main' }}

# Show alert with commit comment on detecting possible performance regression
alert-threshold: '5%'
comment-on-alert: true
fail-on-alert: true
alert-comment-cc-users: '@petermetz'

cactus-plugin-ledger-connector-polkadot:
continue-on-error: false
env:
Expand Down
7 changes: 7 additions & 0 deletions packages/cactus-plugin-ledger-connector-besu/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"dist/*"
],
"scripts": {
"benchmark": "tsx ./src/test/typescript/benchmark/run-plugin-ledger-connector-besu-benchmark.ts .tmp/benchmark-results/plugin-ledger-connector-besu/run-plugin-ledger-connector-besu-benchmark.ts.log",
"codegen": "run-p 'codegen:*'",
"codegen:openapi": "npm run generate-sdk",
"generate-sdk": "run-p 'generate-sdk:*'",
Expand Down Expand Up @@ -77,13 +78,19 @@
"devDependencies": {
"@hyperledger/cactus-plugin-keychain-memory": "2.0.0-alpha.2",
"@hyperledger/cactus-test-tooling": "2.0.0-alpha.2",
"@types/benchmark": "2.1.5",
"@types/body-parser": "1.19.4",
"@types/express": "4.17.19",
"@types/fs-extra": "9.0.13",
"@types/http-errors": "2.0.4",
"@types/uuid": "9.0.8",
"benchmark": "2.1.4",
"body-parser": "1.20.2",
"fs-extra": "10.1.0",
"key-encoder": "2.0.3",
"protobufjs": "7.2.5",
"socket.io": "4.5.4",
"tsx": "4.7.0",
"uuid": "9.0.1",
"web3-core": "1.6.1",
"web3-eth": "1.6.1"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
import path from "path";
import { EOL } from "os";
import * as Benchmark from "benchmark";

import { v4 as uuidv4 } from "uuid";
import { Server as SocketIoServer } from "socket.io";
import fse from "fs-extra";
import KeyEncoder from "key-encoder";
import express from "express";
import bodyParser from "body-parser";
import http from "http";
import { AddressInfo } from "net";

import {
PluginLedgerConnectorBesu,
BesuApiClient,
IPluginLedgerConnectorBesuOptions,
} from "../../../main/typescript/public-api";
import HelloWorldContractJson from "../../solidity/hello-world-contract/HelloWorld.json";
import { BesuApiClientOptions } from "../../../main/typescript/api-client/besu-api-client";
import OAS from "../../../main/json/openapi.json";

import {
IListenOptions,
KeyFormat,
LogLevelDesc,
Logger,
LoggerProvider,
Secp256k1Keys,
Servers,
} from "@hyperledger/cactus-common";
import { Constants } from "@hyperledger/cactus-core-api";
import { PluginRegistry } from "@hyperledger/cactus-core";
import { installOpenapiValidationMiddleware } from "@hyperledger/cactus-core";
import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory";
import { BesuTestLedger } from "@hyperledger/cactus-test-tooling";

const LOG_TAG =
"[packages/cactus-plugin-ledger-connector-besu/src/test/typescript/benchmark/run-plugin-ledger-connector-besu-benchmark.ts]";

const createTestInfrastructure = async (opts: {
readonly logLevel: LogLevelDesc;
}) => {
const logLevel = opts.logLevel || "DEBUG";
const keyEncoder: KeyEncoder = new KeyEncoder("secp256k1");
const keychainIdForSigned = uuidv4();
const keychainIdForUnsigned = uuidv4();
const keychainRefForSigned = uuidv4();
const keychainRefForUnsigned = uuidv4();

const besuTestLedger = new BesuTestLedger();
await besuTestLedger.start();
const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost();
const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost();

const testEthAccount1 = await besuTestLedger.createEthTestAccount();

// keychainPlugin for signed transactions
const { privateKey } = Secp256k1Keys.generateKeyPairsBuffer();
const keyHex = privateKey.toString("hex");
const pem = keyEncoder.encodePrivate(keyHex, KeyFormat.Raw, KeyFormat.PEM);
const signedKeychainPlugin = new PluginKeychainMemory({
instanceId: uuidv4(),
keychainId: keychainIdForSigned,
backend: new Map([[keychainRefForSigned, pem]]),
logLevel,
});

// keychainPlugin for unsigned transactions
const keychainEntryValue = testEthAccount1.privateKey;
const unsignedKeychainPlugin = new PluginKeychainMemory({
instanceId: uuidv4(),
keychainId: keychainIdForUnsigned,
backend: new Map([[keychainRefForUnsigned, keychainEntryValue]]),
logLevel,
});
unsignedKeychainPlugin.set(
HelloWorldContractJson.contractName,
JSON.stringify(HelloWorldContractJson),
);

const pluginRegistry = new PluginRegistry({
plugins: [signedKeychainPlugin, unsignedKeychainPlugin],
});

const options: IPluginLedgerConnectorBesuOptions = {
instanceId: uuidv4(),
rpcApiHttpHost,
rpcApiWsHost,
pluginRegistry,
logLevel,
};
const connector = new PluginLedgerConnectorBesu(options);
pluginRegistry.add(connector);

const expressApp = express();
expressApp.use(bodyParser.json({ limit: "250mb" }));
const server = http.createServer(expressApp);

const wsApi = new SocketIoServer(server, {
path: Constants.SocketIoConnectionPathV1,
});

const listenOptions: IListenOptions = {
hostname: "127.0.0.1",
port: 0,
server,
};
const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo;
const { address, port } = addressInfo;
const apiHost = `http://${address}:${port}`;

const besuApiClientOptions = new BesuApiClientOptions({
basePath: apiHost,
});
const apiClient = new BesuApiClient(besuApiClientOptions);

await installOpenapiValidationMiddleware({
logLevel,
app: expressApp,
apiSpec: OAS,
});

await connector.getOrCreateWebServices();
await connector.registerWebServices(expressApp, wsApi);

return {
httpApi: apiClient,
apiServer: connector,
besuTestLedger,
};
};

const main = async (opts: { readonly argv: Readonly<Array<string>> }) => {
const logLevel: LogLevelDesc = "INFO";

const { apiServer, httpApi, besuTestLedger } = await createTestInfrastructure(
{ logLevel },
);

const level = apiServer.options.logLevel || "INFO";
const label = apiServer.className;
const log: Logger = LoggerProvider.getOrCreate({ level, label });

try {
const gitRootPath = path.join(
__dirname,
"../../../../../../", // walk back up to the project root
);

log.info("%s gitRootPath=%s", LOG_TAG, gitRootPath);

const DEFAULT_OUTPUT_FILE_RELATIVE_PATH =
".tmp/benchmark-results/plugin-ledger-connector-besu/run-plugin-ledger-connector-besu-benchmark.ts.log";

const relativeOutputFilePath =
opts.argv[2] === undefined
? DEFAULT_OUTPUT_FILE_RELATIVE_PATH
: opts.argv[2];

log.info(
"%s DEFAULT_OUTPUT_FILE_RELATIVE_PATH=%s",
LOG_TAG,
DEFAULT_OUTPUT_FILE_RELATIVE_PATH,
);

log.info("%s opts.argv[2]=%s", LOG_TAG, opts.argv[2]);

log.info("%s relativeOutputFilePath=%s", LOG_TAG, relativeOutputFilePath);

const absoluteOutputFilePath = path.join(
gitRootPath,
relativeOutputFilePath,
);

log.info("%s absoluteOutputFilePath=%s", LOG_TAG, absoluteOutputFilePath);

const absoluteOutputDirPath = path.dirname(absoluteOutputFilePath);
log.info("%s absoluteOutputDirPath=%s", LOG_TAG, absoluteOutputDirPath);

await fse.mkdirp(absoluteOutputDirPath);
log.info("%s mkdir -p OK: %s", LOG_TAG, absoluteOutputDirPath);

const minSamples = 100;
const suite = new Benchmark.Suite({});

const cycles: string[] = [];

await new Promise((resolve, reject) => {
suite
.add("plugin-ledger-connector-besu_HTTP_GET_getOpenApiSpecV1", {
defer: true,
minSamples,
fn: async function (deferred: Benchmark.Deferred) {
await httpApi.getOpenApiSpecV1();
deferred.resolve();
},
})
.on("cycle", (event: { target: unknown }) => {
// Output benchmark result by converting benchmark result to string
// Example line on stdout:
// plugin-ledger-connector-besu_HTTP_GET_getOpenApiSpecV1 x 1,020 ops/sec ±2.25% (177 runs sampled)
const cycle = String(event.target);
log.info("%s Benchmark.js CYCLE: %s", LOG_TAG, cycle);
cycles.push(cycle);
})
.on("complete", function () {
log.info("%s Benchmark.js COMPLETE.", LOG_TAG);
resolve(suite);
})
.on("error", async (ex: unknown) => {
log.info("%s Benchmark.js ERROR: %o", LOG_TAG, ex);
reject(ex);
})
.run();
});

const data = cycles.join(EOL);
log.info("%s Writing results...", LOG_TAG);
await fse.writeFile(absoluteOutputFilePath, data, { encoding: "utf-8" });
log.info("%s Wrote results to %s", LOG_TAG, absoluteOutputFilePath);
} finally {
await apiServer.shutdown();
log.info("%s Shut down API server OK", LOG_TAG);

await besuTestLedger.stop();
await besuTestLedger.destroy();
}
};

main({ argv: process.argv })
.then(async () => {
console.log("%s Script execution completed successfully", LOG_TAG);
process.exit(0);
})
.catch((ex) => {
console.error("%s process crashed with:", LOG_TAG, ex);
process.exit(1);
});
6 changes: 6 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -8529,22 +8529,28 @@ __metadata:
"@hyperledger/cactus-core-api": "npm:2.0.0-alpha.2"
"@hyperledger/cactus-plugin-keychain-memory": "npm:2.0.0-alpha.2"
"@hyperledger/cactus-test-tooling": "npm:2.0.0-alpha.2"
"@types/benchmark": "npm:2.1.5"
"@types/body-parser": "npm:1.19.4"
"@types/express": "npm:4.17.19"
"@types/fs-extra": "npm:9.0.13"
"@types/http-errors": "npm:2.0.4"
"@types/uuid": "npm:9.0.8"
axios: "npm:1.6.0"
benchmark: "npm:2.1.4"
body-parser: "npm:1.20.2"
express: "npm:4.19.2"
fs-extra: "npm:10.1.0"
http-errors: "npm:2.0.0"
joi: "npm:17.9.1"
key-encoder: "npm:2.0.3"
openapi-types: "npm:12.1.3"
prom-client: "npm:13.2.0"
protobufjs: "npm:7.2.5"
run-time-error-cjs: "npm:1.4.0"
rxjs: "npm:7.8.1"
socket.io: "npm:4.5.4"
socket.io-client-fixed-types: "npm:4.5.4"
tsx: "npm:4.7.0"
typescript-optional: "npm:2.0.1"
uuid: "npm:9.0.1"
web3: "npm:1.6.1"
Expand Down

0 comments on commit 379d41d

Please sign in to comment.