Better logging for hanging benchmarks

This commit is contained in:
Fedor Indutny 2023-03-13 16:41:47 -07:00 committed by GitHub
parent adf2957537
commit 51c2029b5c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 446 additions and 458 deletions

View file

@ -291,7 +291,7 @@
"npm-run-all": "4.1.5", "npm-run-all": "4.1.5",
"nyc": "11.4.1", "nyc": "11.4.1",
"patch-package": "6.4.7", "patch-package": "6.4.7",
"playwright": "1.30.0", "playwright": "1.31.2",
"prettier": "2.8.0", "prettier": "2.8.0",
"sass": "1.49.7", "sass": "1.49.7",
"sass-loader": "10.2.0", "sass-loader": "10.2.0",

View file

@ -5,103 +5,87 @@
import assert from 'assert'; import assert from 'assert';
import type { PrimaryDevice } from '@signalapp/mock-server'; import type { PrimaryDevice } from '@signalapp/mock-server';
import type { App } from './fixtures';
import { Bootstrap, debug, stats, RUN_COUNT, DISCARD_COUNT } from './fixtures'; import { Bootstrap, debug, stats, RUN_COUNT, DISCARD_COUNT } from './fixtures';
const CONVERSATION_SIZE = 1000; // messages const CONVERSATION_SIZE = 1000; // messages
const DELAY = 50; // milliseconds const DELAY = 50; // milliseconds
void (async () => { Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise<void> => {
const bootstrap = new Bootstrap({ const app = await bootstrap.link();
benchmark: true, const { server, contacts, phone, desktop } = bootstrap;
});
await bootstrap.init(); const [first, second] = contacts;
let app: App | undefined; const messages = new Array<Buffer>();
try { debug('encrypting');
app = await bootstrap.link(); // Send messages from just two contacts
const { server, contacts, phone, desktop } = bootstrap; for (const contact of [second, first]) {
for (let i = 0; i < CONVERSATION_SIZE; i += 1) {
const messageTimestamp = bootstrap.getTimestamp();
messages.push(
await contact.encryptText(
desktop,
`hello from: ${contact.profileName}`,
{
timestamp: messageTimestamp,
sealed: true,
}
)
);
const [first, second] = contacts; messages.push(
await phone.encryptSyncRead(desktop, {
const messages = new Array<Buffer>(); timestamp: bootstrap.getTimestamp(),
debug('encrypting'); messages: [
// Send messages from just two contacts
for (const contact of [second, first]) {
for (let i = 0; i < CONVERSATION_SIZE; i += 1) {
const messageTimestamp = bootstrap.getTimestamp();
messages.push(
await contact.encryptText(
desktop,
`hello from: ${contact.profileName}`,
{ {
senderUUID: contact.device.uuid,
timestamp: messageTimestamp, timestamp: messageTimestamp,
sealed: true, },
} ],
) })
); );
}
}
messages.push( const sendQueue = async (): Promise<void> => {
await phone.encryptSyncRead(desktop, { await Promise.all(messages.map(message => server.send(desktop, message)));
timestamp: bootstrap.getTimestamp(), };
messages: [
{ const measure = async (): Promise<void> => {
senderUUID: contact.device.uuid, assert(app);
timestamp: messageTimestamp, const window = await app.getWindow();
},
], const leftPane = window.locator('.left-pane-wrapper');
})
); const openConvo = async (contact: PrimaryDevice): Promise<void> => {
debug('opening conversation', contact.profileName);
const item = leftPane.locator(
`[data-testid="${contact.toContact().uuid}"]`
);
await item.click();
};
const deltaList = new Array<number>();
for (let runId = 0; runId < RUN_COUNT + DISCARD_COUNT; runId += 1) {
await openConvo(runId % 2 === 0 ? first : second);
debug('waiting for timing from the app');
const { delta } = await app.waitForConversationOpen();
// Let render complete
await new Promise(resolve => setTimeout(resolve, DELAY));
if (runId >= DISCARD_COUNT) {
deltaList.push(delta);
console.log('run=%d info=%j', runId - DISCARD_COUNT, { delta });
} else {
console.log('discarded=%d info=%j', runId, { delta });
} }
} }
const sendQueue = async (): Promise<void> => { console.log('stats info=%j', { delta: stats(deltaList, [99, 99.8]) });
await Promise.all(messages.map(message => server.send(desktop, message))); };
};
const measure = async (): Promise<void> => { await Promise.all([sendQueue(), measure()]);
assert(app); });
const window = await app.getWindow();
const leftPane = window.locator('.left-pane-wrapper');
const openConvo = async (contact: PrimaryDevice): Promise<void> => {
debug('opening conversation', contact.profileName);
const item = leftPane.locator(
`[data-testid="${contact.toContact().uuid}"]`
);
await item.click();
};
const deltaList = new Array<number>();
for (let runId = 0; runId < RUN_COUNT + DISCARD_COUNT; runId += 1) {
await openConvo(runId % 2 === 0 ? first : second);
debug('waiting for timing from the app');
const { delta } = await app.waitForConversationOpen();
// Let render complete
await new Promise(resolve => setTimeout(resolve, DELAY));
if (runId >= DISCARD_COUNT) {
deltaList.push(delta);
console.log('run=%d info=%j', runId - DISCARD_COUNT, { delta });
} else {
console.log('discarded=%d info=%j', runId, { delta });
}
}
console.log('stats info=%j', { delta: stats(deltaList, [99, 99.8]) });
};
await Promise.all([sendQueue(), measure()]);
} catch (error) {
await bootstrap.saveLogs(app);
throw error;
} finally {
await app?.close();
await bootstrap.teardown();
}
})();

View file

@ -10,7 +10,6 @@ import {
ReceiptType, ReceiptType,
} from '@signalapp/mock-server'; } from '@signalapp/mock-server';
import type { App } from './fixtures';
import { import {
Bootstrap, Bootstrap,
debug, debug,
@ -23,13 +22,7 @@ import {
const CONVERSATION_SIZE = 500; // messages const CONVERSATION_SIZE = 500; // messages
const LAST_MESSAGE = 'start sending messages now'; const LAST_MESSAGE = 'start sending messages now';
void (async () => { Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise<void> => {
const bootstrap = new Bootstrap({
benchmark: true,
});
await bootstrap.init();
const { contacts, phone } = bootstrap; const { contacts, phone } = bootstrap;
const members = [...contacts].slice(0, GROUP_SIZE); const members = [...contacts].slice(0, GROUP_SIZE);
@ -45,145 +38,131 @@ void (async () => {
.pinGroup(group) .pinGroup(group)
); );
let app: App | undefined; const app = await bootstrap.link();
try { const { server, desktop } = bootstrap;
app = await bootstrap.link(); const [first] = members;
const { server, desktop } = bootstrap; const messages = new Array<Buffer>();
const [first] = members; debug('encrypting');
// Fill left pane
for (const contact of members.slice().reverse()) {
const messageTimestamp = bootstrap.getTimestamp();
const messages = new Array<Buffer>(); messages.push(
debug('encrypting'); await contact.encryptText(desktop, `hello from: ${contact.profileName}`, {
// Fill left pane timestamp: messageTimestamp,
for (const contact of members.slice().reverse()) { sealed: true,
const messageTimestamp = bootstrap.getTimestamp(); })
);
messages.push( messages.push(
await contact.encryptText( await phone.encryptSyncRead(desktop, {
desktop, timestamp: bootstrap.getTimestamp(),
`hello from: ${contact.profileName}`, messages: [
{ {
senderUUID: contact.device.uuid,
timestamp: messageTimestamp, timestamp: messageTimestamp,
sealed: true, },
} ],
) })
); );
messages.push( }
await phone.encryptSyncRead(desktop, {
timestamp: bootstrap.getTimestamp(),
messages: [
{
senderUUID: contact.device.uuid,
timestamp: messageTimestamp,
},
],
})
);
}
// Fill group // Fill group
for (let i = 0; i < CONVERSATION_SIZE; i += 1) { for (let i = 0; i < CONVERSATION_SIZE; i += 1) {
const contact = members[i % members.length]; const contact = members[i % members.length];
const messageTimestamp = bootstrap.getTimestamp(); const messageTimestamp = bootstrap.getTimestamp();
const isLast = i === CONVERSATION_SIZE - 1; const isLast = i === CONVERSATION_SIZE - 1;
messages.push( messages.push(
await contact.encryptText( await contact.encryptText(
desktop, desktop,
isLast ? LAST_MESSAGE : `#${i} from: ${contact.profileName}`, isLast ? LAST_MESSAGE : `#${i} from: ${contact.profileName}`,
{
timestamp: messageTimestamp,
sealed: true,
group,
}
)
);
messages.push(
await phone.encryptSyncRead(desktop, {
timestamp: bootstrap.getTimestamp(),
messages: [
{ {
senderUUID: contact.device.uuid,
timestamp: messageTimestamp, timestamp: messageTimestamp,
sealed: true, },
group, ],
} })
) );
); }
messages.push( debug('encrypted');
await phone.encryptSyncRead(desktop, {
timestamp: bootstrap.getTimestamp(),
messages: [
{
senderUUID: contact.device.uuid,
timestamp: messageTimestamp,
},
],
})
);
}
debug('encrypted');
await Promise.all(messages.map(message => server.send(desktop, message))); await Promise.all(messages.map(message => server.send(desktop, message)));
const window = await app.getWindow(); const window = await app.getWindow();
debug('opening conversation'); debug('opening conversation');
{ {
const leftPane = window.locator('.left-pane-wrapper'); const leftPane = window.locator('.left-pane-wrapper');
const item = leftPane const item = leftPane
.locator( .locator(
'.module-conversation-list__item--contact-or-conversation' + '.module-conversation-list__item--contact-or-conversation' +
`>> text=${LAST_MESSAGE}` `>> text=${LAST_MESSAGE}`
) )
.first(); .first();
await item.click(); await item.click();
} }
const timeline = window.locator( const timeline = window.locator(
'.timeline-wrapper, .conversation .ConversationView' '.timeline-wrapper, .conversation .ConversationView'
);
const deltaList = new Array<number>();
for (let runId = 0; runId < RUN_COUNT + DISCARD_COUNT; runId += 1) {
debug('finding composition input and clicking it');
const composeArea = window.locator(
'.composition-area-wrapper, .conversation .ConversationView'
); );
const deltaList = new Array<number>(); const input = composeArea.locator('[data-testid=CompositionInput]');
for (let runId = 0; runId < RUN_COUNT + DISCARD_COUNT; runId += 1) {
debug('finding composition input and clicking it');
const composeArea = window.locator(
'.composition-area-wrapper, .conversation .ConversationView'
);
const input = composeArea.locator('[data-testid=CompositionInput]'); debug('entering message text');
await input.type(`my message ${runId}`);
await input.press('Enter');
debug('entering message text'); debug('waiting for message on server side');
await input.type(`my message ${runId}`); const { body, source, envelopeType } = await first.waitForMessage();
await input.press('Enter'); assert.strictEqual(body, `my message ${runId}`);
assert.strictEqual(source, desktop);
assert.strictEqual(envelopeType, EnvelopeType.SenderKey);
debug('waiting for message on server side'); debug('waiting for timing from the app');
const { body, source, envelopeType } = await first.waitForMessage(); const { timestamp, delta } = await app.waitForMessageSend();
assert.strictEqual(body, `my message ${runId}`);
assert.strictEqual(source, desktop);
assert.strictEqual(envelopeType, EnvelopeType.SenderKey);
debug('waiting for timing from the app'); debug('sending delivery receipts');
const { timestamp, delta } = await app.waitForMessageSend(); const delivery = await first.encryptReceipt(desktop, {
timestamp: timestamp + 1,
messageTimestamps: [timestamp],
type: ReceiptType.Delivery,
});
debug('sending delivery receipts'); await server.send(desktop, delivery);
const delivery = await first.encryptReceipt(desktop, {
timestamp: timestamp + 1,
messageTimestamps: [timestamp],
type: ReceiptType.Delivery,
});
await server.send(desktop, delivery); debug('waiting for message state change');
const message = timeline.locator(`[data-testid="${timestamp}"]`);
await message.waitFor();
debug('waiting for message state change'); if (runId >= DISCARD_COUNT) {
const message = timeline.locator(`[data-testid="${timestamp}"]`); deltaList.push(delta);
await message.waitFor(); console.log('run=%d info=%j', runId - DISCARD_COUNT, { delta });
} else {
if (runId >= DISCARD_COUNT) { console.log('discarded=%d info=%j', runId, { delta });
deltaList.push(delta);
console.log('run=%d info=%j', runId - DISCARD_COUNT, { delta });
} else {
console.log('discarded=%d info=%j', runId, { delta });
}
} }
console.log('stats info=%j', { delta: stats(deltaList, [99, 99.8]) });
} catch (error) {
await bootstrap.saveLogs(app);
throw error;
} finally {
await app?.close();
await bootstrap.teardown();
} }
})();
console.log('stats info=%j', { delta: stats(deltaList, [99, 99.8]) });
});

View file

@ -6,131 +6,115 @@ import assert from 'assert';
import { ReceiptType } from '@signalapp/mock-server'; import { ReceiptType } from '@signalapp/mock-server';
import type { App } from './fixtures';
import { Bootstrap, debug, stats, RUN_COUNT, DISCARD_COUNT } from './fixtures'; import { Bootstrap, debug, stats, RUN_COUNT, DISCARD_COUNT } from './fixtures';
const CONVERSATION_SIZE = 500; // messages const CONVERSATION_SIZE = 500; // messages
const LAST_MESSAGE = 'start sending messages now'; const LAST_MESSAGE = 'start sending messages now';
void (async () => { Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise<void> => {
const bootstrap = new Bootstrap({ const app = await bootstrap.link();
benchmark: true,
});
await bootstrap.init(); const { server, contacts, phone, desktop } = bootstrap;
let app: App | undefined;
try { const [first] = contacts;
app = await bootstrap.link();
const { server, contacts, phone, desktop } = bootstrap; const messages = new Array<Buffer>();
debug('encrypting');
// Note: make it so that we receive the latest message from the first
// contact.
for (const contact of contacts.slice().reverse()) {
let count = 1;
if (contact === first) {
count = CONVERSATION_SIZE;
}
const [first] = contacts; for (let i = 0; i < count; i += 1) {
const messageTimestamp = bootstrap.getTimestamp();
const messages = new Array<Buffer>(); const isLast = i === count - 1;
debug('encrypting');
// Note: make it so that we receive the latest message from the first
// contact.
for (const contact of contacts.slice().reverse()) {
let count = 1;
if (contact === first) {
count = CONVERSATION_SIZE;
}
for (let i = 0; i < count; i += 1) { messages.push(
const messageTimestamp = bootstrap.getTimestamp(); await contact.encryptText(
desktop,
const isLast = i === count - 1; isLast ? LAST_MESSAGE : `#${i} from: ${contact.profileName}`,
{
messages.push( timestamp: messageTimestamp,
await contact.encryptText( sealed: true,
desktop, }
isLast ? LAST_MESSAGE : `#${i} from: ${contact.profileName}`, )
);
messages.push(
await phone.encryptSyncRead(desktop, {
timestamp: bootstrap.getTimestamp(),
messages: [
{ {
senderUUID: contact.device.uuid,
timestamp: messageTimestamp, timestamp: messageTimestamp,
sealed: true, },
} ],
) })
);
messages.push(
await phone.encryptSyncRead(desktop, {
timestamp: bootstrap.getTimestamp(),
messages: [
{
senderUUID: contact.device.uuid,
timestamp: messageTimestamp,
},
],
})
);
}
}
await Promise.all(messages.map(message => server.send(desktop, message)));
const window = await app.getWindow();
debug('opening conversation');
{
const leftPane = window.locator('.left-pane-wrapper');
const item = leftPane.locator(
`[data-testid="${first.toContact().uuid}"] >> text=${LAST_MESSAGE}`
); );
await item.click();
} }
const timeline = window.locator(
'.timeline-wrapper, .conversation .ConversationView'
);
const deltaList = new Array<number>();
for (let runId = 0; runId < RUN_COUNT + DISCARD_COUNT; runId += 1) {
debug('finding composition input and clicking it');
const composeArea = window.locator(
'.composition-area-wrapper, .conversation .ConversationView'
);
const input = composeArea.locator('[data-testid=CompositionInput]');
debug('entering message text');
await input.type(`my message ${runId}`);
await input.press('Enter');
debug('waiting for message on server side');
const { body, source } = await first.waitForMessage();
assert.strictEqual(body, `my message ${runId}`);
assert.strictEqual(source, desktop);
debug('waiting for timing from the app');
const { timestamp, delta } = await app.waitForMessageSend();
debug('sending delivery receipt');
const delivery = await first.encryptReceipt(desktop, {
timestamp: timestamp + 1,
messageTimestamps: [timestamp],
type: ReceiptType.Delivery,
});
await server.send(desktop, delivery);
debug('waiting for message state change');
const message = timeline.locator(`[data-testid="${timestamp}"]`);
await message.waitFor();
if (runId >= DISCARD_COUNT) {
deltaList.push(delta);
console.log('run=%d info=%j', runId - DISCARD_COUNT, { delta });
} else {
console.log('discarded=%d info=%j', runId, { delta });
}
}
console.log('stats info=%j', { delta: stats(deltaList, [99, 99.8]) });
} catch (error) {
await bootstrap.saveLogs(app);
throw error;
} finally {
await app?.close();
await bootstrap.teardown();
} }
})();
await Promise.all(messages.map(message => server.send(desktop, message)));
const window = await app.getWindow();
debug('opening conversation');
{
const leftPane = window.locator('.left-pane-wrapper');
const item = leftPane.locator(
`[data-testid="${first.toContact().uuid}"] >> text=${LAST_MESSAGE}`
);
await item.click();
}
const timeline = window.locator(
'.timeline-wrapper, .conversation .ConversationView'
);
const deltaList = new Array<number>();
for (let runId = 0; runId < RUN_COUNT + DISCARD_COUNT; runId += 1) {
debug('finding composition input and clicking it');
const composeArea = window.locator(
'.composition-area-wrapper, .conversation .ConversationView'
);
const input = composeArea.locator('[data-testid=CompositionInput]');
debug('entering message text');
await input.type(`my message ${runId}`);
await input.press('Enter');
debug('waiting for message on server side');
const { body, source } = await first.waitForMessage();
assert.strictEqual(body, `my message ${runId}`);
assert.strictEqual(source, desktop);
debug('waiting for timing from the app');
const { timestamp, delta } = await app.waitForMessageSend();
debug('sending delivery receipt');
const delivery = await first.encryptReceipt(desktop, {
timestamp: timestamp + 1,
messageTimestamps: [timestamp],
type: ReceiptType.Delivery,
});
await server.send(desktop, delivery);
debug('waiting for message state change');
const message = timeline.locator(`[data-testid="${timestamp}"]`);
await message.waitFor();
if (runId >= DISCARD_COUNT) {
deltaList.push(delta);
console.log('run=%d info=%j', runId - DISCARD_COUNT, { delta });
} else {
console.log('discarded=%d info=%j', runId, { delta });
}
}
console.log('stats info=%j', { delta: stats(deltaList, [99, 99.8]) });
});

View file

@ -10,127 +10,115 @@ const MESSAGE_BATCH_SIZE = 1000; // messages
const ENABLE_RECEIPTS = Boolean(process.env.ENABLE_RECEIPTS); const ENABLE_RECEIPTS = Boolean(process.env.ENABLE_RECEIPTS);
void (async () => { Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise<void> => {
const bootstrap = new Bootstrap({
benchmark: true,
});
await bootstrap.init();
await bootstrap.linkAndClose(); await bootstrap.linkAndClose();
try { const { server, contacts, phone, desktop } = bootstrap;
const { server, contacts, phone, desktop } = bootstrap;
const messagesPerSec = new Array<number>(); const messagesPerSec = new Array<number>();
for (let runId = 0; runId < RUN_COUNT; runId += 1) { for (let runId = 0; runId < RUN_COUNT; runId += 1) {
// Generate messages // Generate messages
const messagePromises = new Array<Promise<Buffer>>(); const messagePromises = new Array<Promise<Buffer>>();
debug('started generating messages'); debug('started generating messages');
for (let i = 0; i < MESSAGE_BATCH_SIZE; i += 1) { for (let i = 0; i < MESSAGE_BATCH_SIZE; i += 1) {
const contact = contacts[Math.floor(i / 2) % contacts.length]; const contact = contacts[Math.floor(i / 2) % contacts.length];
const direction = i % 2 ? 'message' : 'reply'; const direction = i % 2 ? 'message' : 'reply';
const messageTimestamp = bootstrap.getTimestamp(); const messageTimestamp = bootstrap.getTimestamp();
if (direction === 'message') {
messagePromises.push(
contact.encryptText(
desktop,
`Ping from mock server ${i + 1} / ${MESSAGE_BATCH_SIZE}`,
{
timestamp: messageTimestamp,
sealed: true,
}
)
);
if (ENABLE_RECEIPTS) {
messagePromises.push(
phone.encryptSyncRead(desktop, {
timestamp: bootstrap.getTimestamp(),
messages: [
{
senderUUID: contact.device.uuid,
timestamp: messageTimestamp,
},
],
})
);
}
continue;
}
if (direction === 'message') {
messagePromises.push( messagePromises.push(
phone.encryptSyncSent( contact.encryptText(
desktop, desktop,
`Pong from mock server ${i + 1} / ${MESSAGE_BATCH_SIZE}`, `Ping from mock server ${i + 1} / ${MESSAGE_BATCH_SIZE}`,
{ {
timestamp: messageTimestamp, timestamp: messageTimestamp,
destinationUUID: contact.device.uuid, sealed: true,
} }
) )
); );
if (ENABLE_RECEIPTS) { if (ENABLE_RECEIPTS) {
messagePromises.push( messagePromises.push(
contact.encryptReceipt(desktop, { phone.encryptSyncRead(desktop, {
timestamp: bootstrap.getTimestamp(), timestamp: bootstrap.getTimestamp(),
messageTimestamps: [messageTimestamp], messages: [
type: ReceiptType.Delivery, {
}) senderUUID: contact.device.uuid,
); timestamp: messageTimestamp,
messagePromises.push( },
contact.encryptReceipt(desktop, { ],
timestamp: bootstrap.getTimestamp(),
messageTimestamps: [messageTimestamp],
type: ReceiptType.Read,
}) })
); );
} }
continue;
} }
debug('ended generating messages'); messagePromises.push(
phone.encryptSyncSent(
desktop,
`Pong from mock server ${i + 1} / ${MESSAGE_BATCH_SIZE}`,
{
timestamp: messageTimestamp,
destinationUUID: contact.device.uuid,
}
)
);
const messages = await Promise.all(messagePromises); if (ENABLE_RECEIPTS) {
messagePromises.push(
// Open the flood gates contact.encryptReceipt(desktop, {
{ timestamp: bootstrap.getTimestamp(),
debug('got synced, sending messages'); messageTimestamps: [messageTimestamp],
type: ReceiptType.Delivery,
// Queue all messages })
const queue = async (): Promise<void> => { );
await Promise.all( messagePromises.push(
messages.map(message => { contact.encryptReceipt(desktop, {
return server.send(desktop, message); timestamp: bootstrap.getTimestamp(),
}) messageTimestamps: [messageTimestamp],
); type: ReceiptType.Read,
}; })
);
const run = async (): Promise<void> => {
const app = await bootstrap.startApp();
const appLoadedInfo = await app.waitUntilLoaded();
console.log('run=%d info=%j', runId, appLoadedInfo);
messagesPerSec.push(appLoadedInfo.messagesPerSec);
await app.close();
};
await Promise.all([queue(), run()]);
} }
} }
// Compute human-readable statistics debug('ended generating messages');
if (messagesPerSec.length !== 0) {
console.log('stats info=%j', { messagesPerSec: stats(messagesPerSec) }); const messages = await Promise.all(messagePromises);
// Open the flood gates
{
debug('got synced, sending messages');
// Queue all messages
const queue = async (): Promise<void> => {
await Promise.all(
messages.map(message => {
return server.send(desktop, message);
})
);
};
const run = async (): Promise<void> => {
const app = await bootstrap.startApp();
const appLoadedInfo = await app.waitUntilLoaded();
console.log('run=%d info=%j', runId, appLoadedInfo);
messagesPerSec.push(appLoadedInfo.messagesPerSec);
await app.close();
};
await Promise.all([queue(), run()]);
} }
} catch (error) {
await bootstrap.saveLogs();
throw error;
} finally {
await bootstrap.teardown();
} }
})();
// Compute human-readable statistics
if (messagesPerSec.length !== 0) {
console.log('stats info=%j', { messagesPerSec: stats(messagesPerSec) });
}
});

View file

@ -5,22 +5,16 @@
import type { PrimaryDevice } from '@signalapp/mock-server'; import type { PrimaryDevice } from '@signalapp/mock-server';
import { StorageState } from '@signalapp/mock-server'; import { StorageState } from '@signalapp/mock-server';
import type { App } from './fixtures';
import { Bootstrap } from './fixtures'; import { Bootstrap } from './fixtures';
const CONTACT_COUNT = 1000; const CONTACT_COUNT = 1000;
void (async () => { Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise<void> => {
const contactNames = new Array<string>(); const contactNames = new Array<string>();
for (let i = 0; i < CONTACT_COUNT; i += 1) { for (let i = 0; i < CONTACT_COUNT; i += 1) {
contactNames.push(`Contact ${i}`); contactNames.push(`Contact ${i}`);
} }
const bootstrap = new Bootstrap({
benchmark: true,
});
await bootstrap.init();
const { phone, server } = bootstrap; const { phone, server } = bootstrap;
let state = StorageState.getEmpty(); let state = StorageState.getEmpty();
@ -50,25 +44,16 @@ void (async () => {
await phone.setStorageState(state); await phone.setStorageState(state);
const start = Date.now(); const start = Date.now();
let app: App | undefined; const app = await bootstrap.link();
try { const window = await app.getWindow();
app = await bootstrap.link();
const window = await app.getWindow();
const leftPane = window.locator('.left-pane-wrapper'); const leftPane = window.locator('.left-pane-wrapper');
const item = leftPane.locator( const item = leftPane.locator(
`[data-testid="${lastContact?.toContact().uuid}"]` `[data-testid="${lastContact?.toContact().uuid}"]`
); );
await item.waitFor(); await item.waitFor();
const duration = Date.now() - start; const duration = Date.now() - start;
console.log(`Took: ${(duration / 1000).toFixed(2)} seconds`); console.log(`Took: ${(duration / 1000).toFixed(2)} seconds`);
} catch (error) { });
await bootstrap.saveLogs(app);
throw error;
} finally {
await app?.close();
await bootstrap.teardown();
}
})();

View file

@ -6,11 +6,13 @@ import fs from 'fs/promises';
import path from 'path'; import path from 'path';
import os from 'os'; import os from 'os';
import createDebug from 'debug'; import createDebug from 'debug';
import pTimeout from 'p-timeout';
import type { Device, PrimaryDevice } from '@signalapp/mock-server'; import type { Device, PrimaryDevice } from '@signalapp/mock-server';
import { Server, UUIDKind, loadCertificates } from '@signalapp/mock-server'; import { Server, UUIDKind, loadCertificates } from '@signalapp/mock-server';
import { MAX_READ_KEYS as MAX_STORAGE_READ_KEYS } from '../services/storageConstants'; import { MAX_READ_KEYS as MAX_STORAGE_READ_KEYS } from '../services/storageConstants';
import * as durations from '../util/durations'; import * as durations from '../util/durations';
import { drop } from '../util/drop';
import { App } from './playwright'; import { App } from './playwright';
export { App }; export { App };
@ -111,6 +113,7 @@ export class Bootstrap {
private privDesktop?: Device; private privDesktop?: Device;
private storagePath?: string; private storagePath?: string;
private timestamp: number = Date.now() - durations.WEEK; private timestamp: number = Date.now() - durations.WEEK;
private lastApp?: App;
constructor(options: BootstrapOptions = {}) { constructor(options: BootstrapOptions = {}) {
this.server = new Server({ this.server = new Server({
@ -178,6 +181,13 @@ export class Bootstrap {
debug('setting storage path=%j', this.storagePath); debug('setting storage path=%j', this.storagePath);
} }
public static benchmark(
fn: (bootstrap: Bootstrap) => Promise<void>,
timeout = 5 * durations.MINUTE
): void {
drop(Bootstrap.runBenchmark(fn, timeout));
}
public get logsDir(): string { public get logsDir(): string {
assert( assert(
this.storagePath !== undefined, this.storagePath !== undefined,
@ -191,10 +201,13 @@ export class Bootstrap {
debug('tearing down'); debug('tearing down');
await Promise.race([ await Promise.race([
this.storagePath Promise.all([
? fs.rm(this.storagePath, { recursive: true }) this.storagePath
: Promise.resolve(), ? fs.rm(this.storagePath, { recursive: true })
this.server.close(), : Promise.resolve(),
this.server.close(),
this.lastApp?.close(),
]),
new Promise(resolve => setTimeout(resolve, CLOSE_TIMEOUT).unref()), new Promise(resolve => setTimeout(resolve, CLOSE_TIMEOUT).unref()),
]); ]);
} }
@ -260,6 +273,13 @@ export class Bootstrap {
await app.start(); await app.start();
this.lastApp = app;
app.on('close', () => {
if (this.lastApp === app) {
this.lastApp = undefined;
}
});
return app; return app;
} }
@ -269,7 +289,7 @@ export class Bootstrap {
return result; return result;
} }
public async saveLogs(app?: App): Promise<void> { public async saveLogs(app: App | undefined = this.lastApp): Promise<void> {
const { ARTIFACTS_DIR } = process.env; const { ARTIFACTS_DIR } = process.env;
if (!ARTIFACTS_DIR) { if (!ARTIFACTS_DIR) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -334,6 +354,26 @@ export class Bootstrap {
// Private // Private
// //
private static async runBenchmark(
fn: (bootstrap: Bootstrap) => Promise<void>,
timeout: number
): Promise<void> {
const bootstrap = new Bootstrap({
benchmark: true,
});
await bootstrap.init();
try {
await pTimeout(fn(bootstrap), timeout);
} catch (error) {
await bootstrap.saveLogs();
throw error;
} finally {
await bootstrap.teardown();
}
}
private async generateConfig(port: number): Promise<string> { private async generateConfig(port: number): Promise<string> {
const url = `https://127.0.0.1:${port}`; const url = `https://127.0.0.1:${port}`;
return JSON.stringify({ return JSON.stringify({

View file

@ -3,6 +3,7 @@
import type { ElectronApplication, Page } from 'playwright'; import type { ElectronApplication, Page } from 'playwright';
import { _electron as electron } from 'playwright'; import { _electron as electron } from 'playwright';
import { EventEmitter } from 'events';
import type { import type {
IPCRequest as ChallengeRequestType, IPCRequest as ChallengeRequestType,
@ -39,10 +40,12 @@ export type AppOptionsType = Readonly<{
config: string; config: string;
}>; }>;
export class App { export class App extends EventEmitter {
private privApp: ElectronApplication | undefined; private privApp: ElectronApplication | undefined;
constructor(private readonly options: AppOptionsType) {} constructor(private readonly options: AppOptionsType) {
super();
}
public async start(): Promise<void> { public async start(): Promise<void> {
this.privApp = await electron.launch({ this.privApp = await electron.launch({
@ -54,6 +57,8 @@ export class App {
}, },
locale: 'en', locale: 'en',
}); });
this.privApp.on('close', () => this.emit('close'));
} }
public async waitForProvisionURL(): Promise<string> { public async waitForProvisionURL(): Promise<string> {
@ -111,6 +116,29 @@ export class App {
return this.app.firstWindow(); return this.app.firstWindow();
} }
// EventEmitter types
public override on(type: 'close', callback: () => void): this;
public override on(
type: string | symbol,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
listener: (...args: Array<any>) => void
): this {
return super.on(type, listener);
}
public override emit(type: 'close'): boolean;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public override emit(type: string | symbol, ...args: Array<any>): boolean {
return super.emit(type, ...args);
}
//
// Private
//
private async waitForEvent<T>(event: string): Promise<T> { private async waitForEvent<T>(event: string): Promise<T> {
const window = await this.getWindow(); const window = await this.getWindow();

View file

@ -14546,17 +14546,17 @@ pkg-dir@^5.0.0:
dependencies: dependencies:
find-up "^5.0.0" find-up "^5.0.0"
playwright-core@1.30.0: playwright-core@1.31.2:
version "1.30.0" version "1.31.2"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.30.0.tgz#de987cea2e86669e3b85732d230c277771873285" resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.31.2.tgz#debf4b215d14cb619adb7e511c164d068075b2ed"
integrity sha512-7AnRmTCf+GVYhHbLJsGUtskWTE33SwMZkybJ0v6rqR1boxq2x36U7p1vDRV7HO2IwTZgmycracLxPEJI49wu4g== integrity sha512-a1dFgCNQw4vCsG7bnojZjDnPewZcw7tZUNFN0ZkcLYKj+mPmXvg4MpaaKZ5SgqPsOmqIf2YsVRkgqiRDxD+fDQ==
playwright@1.30.0: playwright@1.31.2:
version "1.30.0" version "1.31.2"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.30.0.tgz#b1d7be2d45d97fbb59f829f36f521f12010fe072" resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.31.2.tgz#4252280586c596746122cd1fdf9f8ff6a63fa852"
integrity sha512-ENbW5o75HYB3YhnMTKJLTErIBExrSlX2ZZ1C/FzmHjUYIfxj/UnI+DWpQr992m+OQVSg0rCExAOlRwB+x+yyIg== integrity sha512-jpC47n2PKQNtzB7clmBuWh6ftBRS/Bt5EGLigJ9k2QAKcNeYXZkEaDH5gmvb6+AbcE0DO6GnXdbl9ogG6Eh+og==
dependencies: dependencies:
playwright-core "1.30.0" playwright-core "1.31.2"
plist@^3.0.1, plist@^3.0.4: plist@^3.0.1, plist@^3.0.4:
version "3.0.5" version "3.0.5"