Skip to content

Commit 436bb90

Browse files
committed
feat(connector-besu): add continuous benchmarking with JMeter
Primary Changes --------------- 1. Added continuous benchmarking using JMeter that reports performance in cactus-plugin-ledger-connector-besu using one of its endpoint. fixes: hyperledger-cacti#2672 Signed-off-by: ruzell22 <[email protected]>
1 parent db1aef8 commit 436bb90

File tree

4 files changed

+275
-0
lines changed

4 files changed

+275
-0
lines changed

.github/workflows/ci.yaml

+33
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,7 @@ jobs:
973973
${{ runner.os }}-yarn-${{ hashFiles('./yarn.lock') }}
974974
- run: ./tools/ci.sh
975975
cactus-plugin-ledger-connector-besu:
976+
permissions: write-all
976977
continue-on-error: false
977978
needs:
978979
- build-dev
@@ -1002,6 +1003,38 @@ jobs:
10021003
restore-keys: |
10031004
${{ runner.os }}-yarn-${{ hashFiles('./yarn.lock') }}
10041005
- run: ./tools/ci.sh
1006+
1007+
- name: Ensure .tmp Directory Exists
1008+
run: mkdir -p .tmp/benchmark-results/plugin-ledger-connector-besu/
1009+
1010+
# Download previous benchmark result from cache (if exists)
1011+
- name: Download previous benchmark data
1012+
uses: actions/[email protected]
1013+
with:
1014+
path: .tmp/benchmark-results/plugin-ledger-connector-besu/
1015+
key: ${{ runner.os }}-benchmark
1016+
1017+
- name: Run Benchmarks
1018+
working-directory: ./packages/cactus-plugin-ledger-connector-besu/
1019+
run: yarn run benchmark
1020+
1021+
- name: Store benchmark result
1022+
uses: benchmark-action/[email protected]
1023+
with:
1024+
tool: 'benchmarkjs'
1025+
output-file-path: .tmp/benchmark-results/plugin-ledger-connector-besu/run-plugin-ledger-connector-besu-benchmark.ts.log
1026+
github-token: ${{ secrets.GITHUB_TOKEN }}
1027+
1028+
# Only push the benchmark results to gh-pages website if we are running on the main branch
1029+
# We do not want to clutter the benchmark results with intermediate results from PRs that could be drafts
1030+
auto-push: ${{ github.ref == 'refs/heads/main' }}
1031+
1032+
# Show alert with commit comment on detecting possible performance regression
1033+
alert-threshold: '5%'
1034+
comment-on-alert: true
1035+
fail-on-alert: true
1036+
alert-comment-cc-users: '@petermetz'
1037+
10051038
cactus-plugin-ledger-connector-polkadot:
10061039
continue-on-error: false
10071040
env:

packages/cactus-plugin-ledger-connector-besu/package.json

+5
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
"dist/*"
4444
],
4545
"scripts": {
46+
"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",
4647
"codegen": "run-p 'codegen:*'",
4748
"codegen:openapi": "npm run generate-sdk",
4849
"generate-sdk": "run-p 'generate-sdk:*'",
@@ -77,13 +78,17 @@
7778
"devDependencies": {
7879
"@hyperledger/cactus-plugin-keychain-memory": "2.0.0-alpha.2",
7980
"@hyperledger/cactus-test-tooling": "2.0.0-alpha.2",
81+
"@types/benchmark": "2.1.5",
8082
"@types/body-parser": "1.19.4",
8183
"@types/express": "4.17.19",
8284
"@types/http-errors": "2.0.4",
8385
"@types/uuid": "9.0.8",
86+
"benchmark": "2.1.4",
8487
"body-parser": "1.20.2",
8588
"key-encoder": "2.0.3",
89+
"protobufjs": "7.2.5",
8690
"socket.io": "4.5.4",
91+
"tsx": "4.7.0",
8792
"uuid": "9.0.1",
8893
"web3-core": "1.6.1",
8994
"web3-eth": "1.6.1"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
import path from "path";
2+
import { EOL } from "os";
3+
import * as Benchmark from "benchmark";
4+
5+
import { v4 as uuidv4 } from "uuid";
6+
import { Server as SocketIoServer } from "socket.io";
7+
import fse from "fs-extra";
8+
import KeyEncoder from "key-encoder";
9+
import express from "express";
10+
import bodyParser from "body-parser";
11+
import http from "http";
12+
import { AddressInfo } from "net";
13+
14+
import {
15+
PluginLedgerConnectorBesu,
16+
BesuApiClient,
17+
IPluginLedgerConnectorBesuOptions,
18+
} from "../../../main/typescript/public-api";
19+
import HelloWorldContractJson from "../../solidity/hello-world-contract/HelloWorld.json";
20+
import { BesuApiClientOptions } from "../../../main/typescript/api-client/besu-api-client";
21+
import OAS from "../../../main/json/openapi.json";
22+
23+
import {
24+
IListenOptions,
25+
KeyFormat,
26+
LogLevelDesc,
27+
Logger,
28+
LoggerProvider,
29+
Secp256k1Keys,
30+
Servers,
31+
} from "@hyperledger/cactus-common";
32+
import { Constants } from "@hyperledger/cactus-core-api";
33+
import { PluginRegistry } from "@hyperledger/cactus-core";
34+
import { installOpenapiValidationMiddleware } from "@hyperledger/cactus-core";
35+
import { PluginKeychainMemory } from "@hyperledger/cactus-plugin-keychain-memory";
36+
import { BesuTestLedger } from "@hyperledger/cactus-test-tooling";
37+
38+
const LOG_TAG =
39+
"[packages/cactus-plugin-ledger-connector-besu/src/test/typescript/benchmark/run-plugin-ledger-connector-besu-benchmark.ts]";
40+
41+
const createTestInfrastructure = async (opts: {
42+
readonly logLevel: LogLevelDesc;
43+
}) => {
44+
const logLevel = opts.logLevel || "DEBUG";
45+
const keyEncoder: KeyEncoder = new KeyEncoder("secp256k1");
46+
const keychainIdForSigned = uuidv4();
47+
const keychainIdForUnsigned = uuidv4();
48+
const keychainRefForSigned = uuidv4();
49+
const keychainRefForUnsigned = uuidv4();
50+
51+
const besuTestLedger = new BesuTestLedger();
52+
try {
53+
await besuTestLedger.start();
54+
const rpcApiHttpHost = await besuTestLedger.getRpcApiHttpHost();
55+
const rpcApiWsHost = await besuTestLedger.getRpcApiWsHost();
56+
57+
const testEthAccount1 = await besuTestLedger.createEthTestAccount();
58+
59+
// keychainPlugin for signed transactions
60+
const { privateKey } = Secp256k1Keys.generateKeyPairsBuffer();
61+
const keyHex = privateKey.toString("hex");
62+
const pem = keyEncoder.encodePrivate(keyHex, KeyFormat.Raw, KeyFormat.PEM);
63+
const signedKeychainPlugin = new PluginKeychainMemory({
64+
instanceId: uuidv4(),
65+
keychainId: keychainIdForSigned,
66+
backend: new Map([[keychainRefForSigned, pem]]),
67+
logLevel,
68+
});
69+
70+
// keychainPlugin for unsigned transactions
71+
const keychainEntryValue = testEthAccount1.privateKey;
72+
const unsignedKeychainPlugin = new PluginKeychainMemory({
73+
instanceId: uuidv4(),
74+
keychainId: keychainIdForUnsigned,
75+
backend: new Map([[keychainRefForUnsigned, keychainEntryValue]]),
76+
logLevel,
77+
});
78+
unsignedKeychainPlugin.set(
79+
HelloWorldContractJson.contractName,
80+
JSON.stringify(HelloWorldContractJson),
81+
);
82+
83+
const pluginRegistry = new PluginRegistry({
84+
plugins: [signedKeychainPlugin, unsignedKeychainPlugin],
85+
});
86+
87+
const options: IPluginLedgerConnectorBesuOptions = {
88+
instanceId: uuidv4(),
89+
rpcApiHttpHost,
90+
rpcApiWsHost,
91+
pluginRegistry,
92+
logLevel,
93+
};
94+
const connector = new PluginLedgerConnectorBesu(options);
95+
pluginRegistry.add(connector);
96+
97+
const expressApp = express();
98+
expressApp.use(bodyParser.json({ limit: "250mb" }));
99+
const server = http.createServer(expressApp);
100+
101+
const wsApi = new SocketIoServer(server, {
102+
path: Constants.SocketIoConnectionPathV1,
103+
});
104+
105+
const listenOptions: IListenOptions = {
106+
hostname: "127.0.0.1",
107+
port: 0,
108+
server,
109+
};
110+
const addressInfo = (await Servers.listen(listenOptions)) as AddressInfo;
111+
const { address, port } = addressInfo;
112+
const apiHost = `http://${address}:${port}`;
113+
114+
const besuApiClientOptions = new BesuApiClientOptions({
115+
basePath: apiHost,
116+
});
117+
const apiClient = new BesuApiClient(besuApiClientOptions);
118+
119+
await installOpenapiValidationMiddleware({
120+
logLevel,
121+
app: expressApp,
122+
apiSpec: OAS,
123+
});
124+
125+
await connector.getOrCreateWebServices();
126+
await connector.registerWebServices(expressApp, wsApi);
127+
128+
return {
129+
httpApi: apiClient,
130+
apiServer: connector,
131+
};
132+
} finally {
133+
await besuTestLedger.stop();
134+
await besuTestLedger.destroy();
135+
}
136+
};
137+
138+
const main = async (opts: { readonly argv: Readonly<Array<string>> }) => {
139+
const logLevel: LogLevelDesc = "INFO";
140+
141+
const { apiServer, httpApi } = await createTestInfrastructure({ logLevel });
142+
143+
const level = apiServer.options.logLevel || "INFO";
144+
const label = apiServer.className;
145+
const log: Logger = LoggerProvider.getOrCreate({ level, label });
146+
147+
const gitRootPath = path.join(
148+
__dirname,
149+
"../../../../../../", // walk back up to the project root
150+
);
151+
152+
log.info("%s gitRootPath=%s", LOG_TAG, gitRootPath);
153+
154+
const DEFAULT_OUTPUT_FILE_RELATIVE_PATH =
155+
".tmp/benchmark-results/plugin-ledger-connector-besu/run-plugin-ledger-connector-besu-benchmark.ts.log";
156+
157+
const relativeOutputFilePath =
158+
opts.argv[2] === undefined
159+
? DEFAULT_OUTPUT_FILE_RELATIVE_PATH
160+
: opts.argv[2];
161+
162+
log.info(
163+
"%s DEFAULT_OUTPUT_FILE_RELATIVE_PATH=%s",
164+
LOG_TAG,
165+
DEFAULT_OUTPUT_FILE_RELATIVE_PATH,
166+
);
167+
168+
log.info("%s opts.argv[2]=%s", LOG_TAG, opts.argv[2]);
169+
170+
log.info("%s relativeOutputFilePath=%s", LOG_TAG, relativeOutputFilePath);
171+
172+
const absoluteOutputFilePath = path.join(gitRootPath, relativeOutputFilePath);
173+
174+
log.info("%s absoluteOutputFilePath=%s", LOG_TAG, absoluteOutputFilePath);
175+
176+
const absoluteOutputDirPath = path.dirname(absoluteOutputFilePath);
177+
log.info("%s absoluteOutputDirPath=%s", LOG_TAG, absoluteOutputDirPath);
178+
179+
await fse.mkdirp(absoluteOutputDirPath);
180+
log.info("%s mkdir -p OK: %s", LOG_TAG, absoluteOutputDirPath);
181+
182+
const minSamples = 100;
183+
const suite = new Benchmark.Suite({});
184+
185+
const cycles: string[] = [];
186+
187+
await new Promise((resolve, reject) => {
188+
suite
189+
.add("plugin-ledger-connector-besu_HTTP_GET_getOpenApiSpecV1", {
190+
defer: true,
191+
minSamples,
192+
fn: async function (deferred: Benchmark.Deferred) {
193+
await httpApi.getOpenApiSpecV1();
194+
deferred.resolve();
195+
},
196+
})
197+
.on("cycle", (event: { target: unknown }) => {
198+
// Output benchmark result by converting benchmark result to string
199+
// Example line on stdout:
200+
// plugin-ledger-connector-besu_HTTP_GET_getOpenApiSpecV1 x 1,020 ops/sec ±2.25% (177 runs sampled)
201+
const cycle = String(event.target);
202+
log.info("%s Benchmark.js CYCLE: %s", LOG_TAG, cycle);
203+
cycles.push(cycle);
204+
})
205+
.on("complete", function () {
206+
log.info("%s Benchmark.js COMPLETE.", LOG_TAG);
207+
resolve(suite);
208+
})
209+
.on("error", async (ex: unknown) => {
210+
log.info("%s Benchmark.js ERROR: %o", LOG_TAG, ex);
211+
reject(ex);
212+
})
213+
.run();
214+
});
215+
216+
const data = cycles.join(EOL);
217+
log.info("%s Writing results...", LOG_TAG);
218+
await fse.writeFile(absoluteOutputFilePath, data, { encoding: "utf-8" });
219+
log.info("%s Wrote results to %s", LOG_TAG, absoluteOutputFilePath);
220+
221+
await apiServer.shutdown();
222+
log.info("%s Shut down API server OK", LOG_TAG);
223+
};
224+
225+
main({ argv: process.argv })
226+
.then(() => {
227+
console.log("%s Script execution completed successfully", LOG_TAG);
228+
process.exit(1);
229+
})
230+
.catch((ex) => {
231+
console.error("%s process crashed with:", LOG_TAG, ex);
232+
process.exit(1);
233+
});

yarn.lock

+4
Original file line numberDiff line numberDiff line change
@@ -8379,22 +8379,26 @@ __metadata:
83798379
"@hyperledger/cactus-core-api": "npm:2.0.0-alpha.2"
83808380
"@hyperledger/cactus-plugin-keychain-memory": "npm:2.0.0-alpha.2"
83818381
"@hyperledger/cactus-test-tooling": "npm:2.0.0-alpha.2"
8382+
"@types/benchmark": "npm:2.1.5"
83828383
"@types/body-parser": "npm:1.19.4"
83838384
"@types/express": "npm:4.17.19"
83848385
"@types/http-errors": "npm:2.0.4"
83858386
"@types/uuid": "npm:9.0.8"
83868387
axios: "npm:1.6.0"
8388+
benchmark: "npm:2.1.4"
83878389
body-parser: "npm:1.20.2"
83888390
express: "npm:4.19.2"
83898391
http-errors: "npm:2.0.0"
83908392
joi: "npm:17.9.1"
83918393
key-encoder: "npm:2.0.3"
83928394
openapi-types: "npm:12.1.3"
83938395
prom-client: "npm:13.2.0"
8396+
protobufjs: "npm:7.2.5"
83948397
run-time-error-cjs: "npm:1.4.0"
83958398
rxjs: "npm:7.8.1"
83968399
socket.io: "npm:4.5.4"
83978400
socket.io-client-fixed-types: "npm:4.5.4"
8401+
tsx: "npm:4.7.0"
83988402
typescript-optional: "npm:2.0.1"
83998403
uuid: "npm:9.0.1"
84008404
web3: "npm:1.6.1"

0 commit comments

Comments
 (0)