Skip to content

Commit 1e20447

Browse files
authored
Merge branch 'main' into jl/bump-multichain-api-client-0.2.0
2 parents b16e3fd + 108289a commit 1e20447

File tree

5 files changed

+177
-28
lines changed

5 files changed

+177
-28
lines changed

.github/workflows/publish-prerelease.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,6 @@ jobs:
6464
MERGE_BASE_COMMIT_HASH: ${{ steps.get-merge-base.outputs.MERGE_BASE }}
6565
CIRCLE_BUILD_NUM: ${{ steps.get-circleci-job-details.outputs.CIRCLE_BUILD_NUM }}
6666
CIRCLE_WORKFLOW_JOB_ID: ${{ steps.get-circleci-job-details.outputs.CIRCLE_WORKFLOW_JOB_ID }}
67+
CLOUDFRONT_REPO_URL: ${{ vars.AWS_CLOUDFRONT_URL }}/${{ github.event.repository.name }}
6768
HOST_URL: ${{ vars.AWS_CLOUDFRONT_URL }}/${{ github.event.repository.name }}/${{ github.run_id }}
6869
run: yarn tsx ./development/metamaskbot-build-announce.ts

development/metamaskbot-build-announce.ts

Lines changed: 106 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,17 @@ import { version as VERSION } from '../package.json';
33

44
start().catch(console.error);
55

6+
const benchmarkPlatforms = ['chrome', 'firefox'];
7+
const buildTypes = ['browserify', 'webpack'];
8+
9+
type BenchmarkResults = Record<
10+
(typeof benchmarkPlatforms)[number],
11+
Record<
12+
(typeof buildTypes)[number],
13+
Record<string, Record<string, Record<string, string>>>
14+
>
15+
>;
16+
617
function getHumanReadableSize(bytes: number): string {
718
if (!bytes) {
819
return '0 Bytes';
@@ -165,17 +176,6 @@ async function start(): Promise<void> {
165176
const exposedContent = `Builds ready [${SHORT_SHA1}]`;
166177
const artifactsBody = `<details><summary>${exposedContent}</summary>${hiddenContent}</details>\n\n`;
167178

168-
const benchmarkPlatforms = ['chrome', 'firefox'];
169-
const buildTypes = ['browserify', 'webpack'];
170-
171-
type BenchmarkResults = Record<
172-
(typeof benchmarkPlatforms)[number],
173-
Record<
174-
(typeof buildTypes)[number],
175-
Record<string, Record<string, Record<string, string>>>
176-
>
177-
>;
178-
179179
const benchmarkResults: BenchmarkResults = {};
180180
for (const platform of benchmarkPlatforms) {
181181
benchmarkResults[platform] = {};
@@ -294,7 +294,9 @@ async function start(): Promise<void> {
294294
.join('')}</tr></thead>`;
295295
const benchmarkTableBody = `<tbody>${tableRows.join('')}</tbody>`;
296296
const benchmarkTable = `<table>${benchmarkTableHeader}${benchmarkTableBody}</table>`;
297-
const benchmarkBody = `<details><summary>${benchmarkSummary}</summary>${benchmarkTable}</details>\n\n`;
297+
const benchmarkWarnings = await runBenchmarkGate(benchmarkResults);
298+
const benchmarkBody = `<details><summary>${benchmarkSummary}</summary>${benchmarkTable}${benchmarkWarnings}</details>\n\n`;
299+
298300
commentBody += `${benchmarkBody}`;
299301
} catch (error) {
300302
console.error(`Error constructing benchmark results: '${error}'`);
@@ -373,17 +375,97 @@ async function start(): Promise<void> {
373375
const JSON_PAYLOAD = JSON.stringify({ body: commentBody });
374376
const POST_COMMENT_URI = `https://api.github.com/repos/metamask/metamask-extension/issues/${PR_NUMBER}/comments`;
375377
console.log(`Announcement:\n${commentBody}`);
376-
console.log(`Posting to: ${POST_COMMENT_URI}`);
377-
378-
const response = await fetch(POST_COMMENT_URI, {
379-
method: 'POST',
380-
body: JSON_PAYLOAD,
381-
headers: {
382-
'User-Agent': 'metamaskbot',
383-
Authorization: `token ${PR_COMMENT_TOKEN}`,
384-
},
385-
});
386-
if (!response.ok) {
387-
throw new Error(`Post comment failed with status '${response.statusText}'`);
378+
379+
if (PR_COMMENT_TOKEN) {
380+
console.log(`Posting to: ${POST_COMMENT_URI}`);
381+
382+
const response = await fetch(POST_COMMENT_URI, {
383+
method: 'POST',
384+
body: JSON_PAYLOAD,
385+
headers: {
386+
'User-Agent': 'metamaskbot',
387+
Authorization: `token ${PR_COMMENT_TOKEN}`,
388+
},
389+
});
390+
if (!response.ok) {
391+
throw new Error(
392+
`Post comment failed with status '${response.statusText}'`,
393+
);
394+
}
395+
}
396+
}
397+
398+
async function runBenchmarkGate(
399+
benchmarkResults: BenchmarkResults,
400+
): Promise<string> {
401+
const benchmarkGateUrl = `${process.env.CLOUDFRONT_REPO_URL}/benchmark-gate/benchmark-gate.json`;
402+
const exceededSums = { mean: 0, p95: 0 };
403+
let benchmarkGateBody = '';
404+
405+
console.log(`Fetching benchmark gate from ${benchmarkGateUrl}`);
406+
try {
407+
const benchmarkResponse = await fetch(benchmarkGateUrl);
408+
if (!benchmarkResponse.ok) {
409+
throw new Error(
410+
`Failed to fetch benchmark gate data, status ${benchmarkResponse.statusText}`,
411+
);
412+
}
413+
414+
const { gates, pingThresholds } = await benchmarkResponse.json();
415+
416+
// Compare benchmarkResults with benchmark-gate.json
417+
for (const platform of Object.keys(gates)) {
418+
for (const buildType of Object.keys(gates[platform])) {
419+
for (const page of Object.keys(gates[platform][buildType])) {
420+
for (const measure of Object.keys(gates[platform][buildType][page])) {
421+
for (const metric of Object.keys(
422+
gates[platform][buildType][page][measure],
423+
)) {
424+
const benchmarkValue =
425+
benchmarkResults[platform][buildType][page][measure][metric];
426+
427+
const gateValue =
428+
gates[platform][buildType][page][measure][metric];
429+
430+
if (benchmarkValue > gateValue) {
431+
const ceiledValue = Math.ceil(parseFloat(benchmarkValue));
432+
433+
if (measure === 'mean') {
434+
exceededSums.mean += ceiledValue - gateValue;
435+
} else if (measure === 'p95') {
436+
exceededSums.p95 += ceiledValue - gateValue;
437+
}
438+
439+
benchmarkGateBody += `Benchmark value ${ceiledValue} exceeds gate value ${gateValue} for ${platform} ${buildType} ${page} ${measure} ${metric}<br>\n`;
440+
}
441+
}
442+
}
443+
}
444+
}
445+
}
446+
447+
if (benchmarkGateBody) {
448+
benchmarkGateBody += `<b>Sum of mean exceeds: ${
449+
exceededSums.mean
450+
}ms | Sum of p95 exceeds: ${
451+
exceededSums.p95
452+
}ms<br>\nSum of all benchmark exceeds: ${
453+
exceededSums.mean + exceededSums.p95
454+
}ms</b><br>\n`;
455+
456+
if (
457+
exceededSums.mean > pingThresholds.mean ||
458+
exceededSums.p95 > pingThresholds.p95 ||
459+
exceededSums.mean + exceededSums.p95 >
460+
pingThresholds.mean + pingThresholds.p95
461+
) {
462+
// Soft gate, just pings @HowardBraham
463+
benchmarkGateBody = `cc: @HowardBraham<br>\n${benchmarkGateBody}`;
464+
}
465+
}
466+
} catch (error) {
467+
console.error(`Error encountered fetching benchmark gate data: '${error}'`);
388468
}
469+
470+
return benchmarkGateBody;
389471
}

test/e2e/page-objects/pages/bridge/quote-page.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ export type BridgeQuote = {
1212
class BridgeQuotePage {
1313
protected driver: Driver;
1414

15-
private sourceAssetPickerButton = '[data-testid="bridge-source-button"]';
15+
public sourceAssetPickerButton = '[data-testid="bridge-source-button"]';
1616

1717
private destinationAssetPickerButton =
1818
'[data-testid="bridge-destination-button"]';
1919

20-
private assetPrickerSearchInput =
20+
public assetPrickerSearchInput =
2121
'[data-testid="asset-picker-modal-search-input"]';
2222

2323
private sourceAmount = '[data-testid="from-amount"]';
@@ -26,7 +26,7 @@ class BridgeQuotePage {
2626

2727
private lineaNetwork = '[data-testid="Linea"]';
2828

29-
private tokenButton = '[data-testid="multichain-token-list-button"]';
29+
public tokenButton = '[data-testid="multichain-token-list-button"]';
3030

3131
private submitButton = { text: 'Submit', tag: 'button' };
3232

test/e2e/tests/bridge/bridge-test-utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export class BridgePage {
8282
};
8383
}
8484

85-
async function mockFeatureFlag(
85+
export async function mockFeatureFlag(
8686
mockServer: Mockttp,
8787
featureFlagOverrides: Partial<FeatureFlagResponse>,
8888
) {

test/e2e/user-actions-benchmark.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ import yargs from 'yargs/yargs';
55
import { exitWithError } from '../../development/lib/exit-with-error';
66
import { getFirstParentDirectoryThatExists, isWritable } from '../helpers/file';
77
import FixtureBuilder from './fixture-builder';
8+
import { Mockttp } from 'mockttp';
9+
import HomePage from './page-objects/pages/home/homepage';
10+
import { mockFeatureFlag } from './tests/bridge/bridge-test-utils';
11+
import BridgeQuotePage from './page-objects/pages/bridge/quote-page';
12+
import { DEFAULT_FEATURE_FLAGS_RESPONSE } from './tests/bridge/constants';
813
import {
914
logInWithBalanceValidation,
1015
openActionMenuAndStartSendFlow,
@@ -96,6 +101,65 @@ async function confirmTx(): Promise<number> {
96101
return loadingTimes;
97102
}
98103

104+
async function bridgeUserActions(): Promise<any> {
105+
let loadPage: number = 0;
106+
let loadAssetPicker: number = 0;
107+
let searchToken: number = 0;
108+
109+
const fixtureBuilder = new FixtureBuilder().withNetworkControllerOnMainnet();
110+
111+
await withFixtures(
112+
{
113+
fixtures: fixtureBuilder.build(),
114+
disableServerMochaToBackground: true,
115+
localNodeOptions: 'ganache',
116+
title: 'benchmark-userActions-bridgeUserActions',
117+
testSpecificMock: async (mockServer: Mockttp) => [
118+
await mockFeatureFlag(mockServer, {
119+
'extension-config': {
120+
...DEFAULT_FEATURE_FLAGS_RESPONSE['extension-config'],
121+
support: true,
122+
},
123+
}),
124+
],
125+
},
126+
async ({ driver }: { driver: Driver }) => {
127+
await logInWithBalanceValidation(driver);
128+
const homePage = new HomePage(driver);
129+
const quotePage = new BridgeQuotePage(driver);
130+
131+
const timestampBeforeLoadPage = new Date();
132+
await homePage.startBridgeFlow();
133+
const timestampAfterLoadPage = new Date();
134+
135+
loadPage =
136+
timestampAfterLoadPage.getTime() - timestampBeforeLoadPage.getTime();
137+
138+
const timestampBeforeClickAssetPicker = new Date();
139+
await driver.clickElement(quotePage.sourceAssetPickerButton);
140+
const timestampAfterClickAssetPicker = new Date();
141+
142+
loadAssetPicker =
143+
timestampAfterClickAssetPicker.getTime() -
144+
timestampBeforeClickAssetPicker.getTime();
145+
146+
const tokenToSearch = 'FXS';
147+
const timestampBeforeTokenSearch = new Date();
148+
await driver.fill(quotePage.assetPrickerSearchInput, tokenToSearch);
149+
await driver.waitForSelector({
150+
text: tokenToSearch,
151+
css: quotePage.tokenButton,
152+
});
153+
const timestampAferTokenSearch = new Date();
154+
155+
searchToken =
156+
timestampAferTokenSearch.getTime() -
157+
timestampBeforeTokenSearch.getTime();
158+
},
159+
);
160+
return { loadPage, loadAssetPicker, searchToken };
161+
}
162+
99163
async function main(): Promise<void> {
100164
const { argv } = yargs(hideBin(process.argv)).usage(
101165
'$0 [options]',
@@ -112,6 +176,8 @@ async function main(): Promise<void> {
112176
const results: Record<string, number> = {};
113177
results.loadNewAccount = await loadNewAccount();
114178
results.confirmTx = await confirmTx();
179+
const bridgeResults = await bridgeUserActions();
180+
results.bridge = bridgeResults;
115181
const { out } = argv as { out?: string };
116182

117183
if (out) {

0 commit comments

Comments
 (0)