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

@ -5,103 +5,87 @@
import assert from 'assert';
import type { PrimaryDevice } from '@signalapp/mock-server';
import type { App } from './fixtures';
import { Bootstrap, debug, stats, RUN_COUNT, DISCARD_COUNT } from './fixtures';
const CONVERSATION_SIZE = 1000; // messages
const DELAY = 50; // milliseconds
void (async () => {
const bootstrap = new Bootstrap({
benchmark: true,
});
Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise<void> => {
const app = await bootstrap.link();
const { server, contacts, phone, desktop } = bootstrap;
await bootstrap.init();
const [first, second] = contacts;
let app: App | undefined;
try {
app = await bootstrap.link();
const { server, contacts, phone, desktop } = bootstrap;
const messages = new Array<Buffer>();
debug('encrypting');
// 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}`,
{
timestamp: messageTimestamp,
sealed: true,
}
)
);
const [first, second] = contacts;
const messages = new Array<Buffer>();
debug('encrypting');
// 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}`,
messages.push(
await phone.encryptSyncRead(desktop, {
timestamp: bootstrap.getTimestamp(),
messages: [
{
senderUUID: contact.device.uuid,
timestamp: messageTimestamp,
sealed: true,
}
)
);
},
],
})
);
}
}
messages.push(
await phone.encryptSyncRead(desktop, {
timestamp: bootstrap.getTimestamp(),
messages: [
{
senderUUID: contact.device.uuid,
timestamp: messageTimestamp,
},
],
})
);
const sendQueue = async (): Promise<void> => {
await Promise.all(messages.map(message => server.send(desktop, message)));
};
const measure = async (): Promise<void> => {
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 });
}
}
const sendQueue = async (): Promise<void> => {
await Promise.all(messages.map(message => server.send(desktop, message)));
};
console.log('stats info=%j', { delta: stats(deltaList, [99, 99.8]) });
};
const measure = async (): Promise<void> => {
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();
}
})();
await Promise.all([sendQueue(), measure()]);
});

View file

@ -10,7 +10,6 @@ import {
ReceiptType,
} from '@signalapp/mock-server';
import type { App } from './fixtures';
import {
Bootstrap,
debug,
@ -23,13 +22,7 @@ import {
const CONVERSATION_SIZE = 500; // messages
const LAST_MESSAGE = 'start sending messages now';
void (async () => {
const bootstrap = new Bootstrap({
benchmark: true,
});
await bootstrap.init();
Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise<void> => {
const { contacts, phone } = bootstrap;
const members = [...contacts].slice(0, GROUP_SIZE);
@ -45,145 +38,131 @@ void (async () => {
.pinGroup(group)
);
let app: App | undefined;
const app = await bootstrap.link();
try {
app = await bootstrap.link();
const { server, desktop } = bootstrap;
const [first] = members;
const { server, desktop } = bootstrap;
const [first] = members;
const messages = new Array<Buffer>();
debug('encrypting');
// Fill left pane
for (const contact of members.slice().reverse()) {
const messageTimestamp = bootstrap.getTimestamp();
const messages = new Array<Buffer>();
debug('encrypting');
// Fill left pane
for (const contact of members.slice().reverse()) {
const messageTimestamp = bootstrap.getTimestamp();
messages.push(
await contact.encryptText(
desktop,
`hello from: ${contact.profileName}`,
messages.push(
await contact.encryptText(desktop, `hello from: ${contact.profileName}`, {
timestamp: messageTimestamp,
sealed: true,
})
);
messages.push(
await phone.encryptSyncRead(desktop, {
timestamp: bootstrap.getTimestamp(),
messages: [
{
senderUUID: contact.device.uuid,
timestamp: messageTimestamp,
sealed: true,
}
)
);
messages.push(
await phone.encryptSyncRead(desktop, {
timestamp: bootstrap.getTimestamp(),
messages: [
{
senderUUID: contact.device.uuid,
timestamp: messageTimestamp,
},
],
})
);
}
},
],
})
);
}
// Fill group
for (let i = 0; i < CONVERSATION_SIZE; i += 1) {
const contact = members[i % members.length];
const messageTimestamp = bootstrap.getTimestamp();
// Fill group
for (let i = 0; i < CONVERSATION_SIZE; i += 1) {
const contact = members[i % members.length];
const messageTimestamp = bootstrap.getTimestamp();
const isLast = i === CONVERSATION_SIZE - 1;
const isLast = i === CONVERSATION_SIZE - 1;
messages.push(
await contact.encryptText(
desktop,
isLast ? LAST_MESSAGE : `#${i} from: ${contact.profileName}`,
messages.push(
await contact.encryptText(
desktop,
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,
sealed: true,
group,
}
)
);
messages.push(
await phone.encryptSyncRead(desktop, {
timestamp: bootstrap.getTimestamp(),
messages: [
{
senderUUID: contact.device.uuid,
timestamp: messageTimestamp,
},
],
})
);
}
debug('encrypted');
},
],
})
);
}
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');
{
const leftPane = window.locator('.left-pane-wrapper');
debug('opening conversation');
{
const leftPane = window.locator('.left-pane-wrapper');
const item = leftPane
.locator(
'.module-conversation-list__item--contact-or-conversation' +
`>> text=${LAST_MESSAGE}`
)
.first();
await item.click();
}
const item = leftPane
.locator(
'.module-conversation-list__item--contact-or-conversation' +
`>> text=${LAST_MESSAGE}`
)
.first();
await item.click();
}
const timeline = window.locator(
'.timeline-wrapper, .conversation .ConversationView'
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 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]');
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');
await input.type(`my message ${runId}`);
await input.press('Enter');
debug('waiting for message on server side');
const { body, source, envelopeType } = await first.waitForMessage();
assert.strictEqual(body, `my message ${runId}`);
assert.strictEqual(source, desktop);
assert.strictEqual(envelopeType, EnvelopeType.SenderKey);
debug('waiting for message on server side');
const { body, source, envelopeType } = await first.waitForMessage();
assert.strictEqual(body, `my message ${runId}`);
assert.strictEqual(source, desktop);
assert.strictEqual(envelopeType, EnvelopeType.SenderKey);
debug('waiting for timing from the app');
const { timestamp, delta } = await app.waitForMessageSend();
debug('waiting for timing from the app');
const { timestamp, delta } = await app.waitForMessageSend();
debug('sending delivery receipts');
const delivery = await first.encryptReceipt(desktop, {
timestamp: timestamp + 1,
messageTimestamps: [timestamp],
type: ReceiptType.Delivery,
});
debug('sending delivery receipts');
const delivery = await first.encryptReceipt(desktop, {
timestamp: timestamp + 1,
messageTimestamps: [timestamp],
type: ReceiptType.Delivery,
});
await server.send(desktop, 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');
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 });
}
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();
}
})();
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 type { App } from './fixtures';
import { Bootstrap, debug, stats, RUN_COUNT, DISCARD_COUNT } from './fixtures';
const CONVERSATION_SIZE = 500; // messages
const LAST_MESSAGE = 'start sending messages now';
void (async () => {
const bootstrap = new Bootstrap({
benchmark: true,
});
Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise<void> => {
const app = await bootstrap.link();
await bootstrap.init();
let app: App | undefined;
const { server, contacts, phone, desktop } = bootstrap;
try {
app = await bootstrap.link();
const [first] = contacts;
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>();
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 isLast = i === count - 1;
for (let i = 0; i < count; i += 1) {
const messageTimestamp = bootstrap.getTimestamp();
const isLast = i === count - 1;
messages.push(
await contact.encryptText(
desktop,
isLast ? LAST_MESSAGE : `#${i} from: ${contact.profileName}`,
messages.push(
await contact.encryptText(
desktop,
isLast ? LAST_MESSAGE : `#${i} from: ${contact.profileName}`,
{
timestamp: messageTimestamp,
sealed: true,
}
)
);
messages.push(
await phone.encryptSyncRead(desktop, {
timestamp: bootstrap.getTimestamp(),
messages: [
{
senderUUID: contact.device.uuid,
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);
void (async () => {
const bootstrap = new Bootstrap({
benchmark: true,
});
await bootstrap.init();
Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise<void> => {
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) {
// Generate messages
const messagePromises = new Array<Promise<Buffer>>();
debug('started generating messages');
for (let runId = 0; runId < RUN_COUNT; runId += 1) {
// Generate messages
const messagePromises = new Array<Promise<Buffer>>();
debug('started generating messages');
for (let i = 0; i < MESSAGE_BATCH_SIZE; i += 1) {
const contact = contacts[Math.floor(i / 2) % contacts.length];
const direction = i % 2 ? 'message' : 'reply';
for (let i = 0; i < MESSAGE_BATCH_SIZE; i += 1) {
const contact = contacts[Math.floor(i / 2) % contacts.length];
const direction = i % 2 ? 'message' : 'reply';
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;
}
const messageTimestamp = bootstrap.getTimestamp();
if (direction === 'message') {
messagePromises.push(
phone.encryptSyncSent(
contact.encryptText(
desktop,
`Pong from mock server ${i + 1} / ${MESSAGE_BATCH_SIZE}`,
`Ping from mock server ${i + 1} / ${MESSAGE_BATCH_SIZE}`,
{
timestamp: messageTimestamp,
destinationUUID: contact.device.uuid,
sealed: true,
}
)
);
if (ENABLE_RECEIPTS) {
messagePromises.push(
contact.encryptReceipt(desktop, {
phone.encryptSyncRead(desktop, {
timestamp: bootstrap.getTimestamp(),
messageTimestamps: [messageTimestamp],
type: ReceiptType.Delivery,
})
);
messagePromises.push(
contact.encryptReceipt(desktop, {
timestamp: bootstrap.getTimestamp(),
messageTimestamps: [messageTimestamp],
type: ReceiptType.Read,
messages: [
{
senderUUID: contact.device.uuid,
timestamp: messageTimestamp,
},
],
})
);
}
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);
// 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()]);
if (ENABLE_RECEIPTS) {
messagePromises.push(
contact.encryptReceipt(desktop, {
timestamp: bootstrap.getTimestamp(),
messageTimestamps: [messageTimestamp],
type: ReceiptType.Delivery,
})
);
messagePromises.push(
contact.encryptReceipt(desktop, {
timestamp: bootstrap.getTimestamp(),
messageTimestamps: [messageTimestamp],
type: ReceiptType.Read,
})
);
}
}
// Compute human-readable statistics
if (messagesPerSec.length !== 0) {
console.log('stats info=%j', { messagesPerSec: stats(messagesPerSec) });
debug('ended generating messages');
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 { StorageState } from '@signalapp/mock-server';
import type { App } from './fixtures';
import { Bootstrap } from './fixtures';
const CONTACT_COUNT = 1000;
void (async () => {
Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise<void> => {
const contactNames = new Array<string>();
for (let i = 0; i < CONTACT_COUNT; i += 1) {
contactNames.push(`Contact ${i}`);
}
const bootstrap = new Bootstrap({
benchmark: true,
});
await bootstrap.init();
const { phone, server } = bootstrap;
let state = StorageState.getEmpty();
@ -50,25 +44,16 @@ void (async () => {
await phone.setStorageState(state);
const start = Date.now();
let app: App | undefined;
try {
app = await bootstrap.link();
const window = await app.getWindow();
const 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(
`[data-testid="${lastContact?.toContact().uuid}"]`
);
await item.waitFor();
const item = leftPane.locator(
`[data-testid="${lastContact?.toContact().uuid}"]`
);
await item.waitFor();
const duration = Date.now() - start;
console.log(`Took: ${(duration / 1000).toFixed(2)} seconds`);
} catch (error) {
await bootstrap.saveLogs(app);
throw error;
} finally {
await app?.close();
await bootstrap.teardown();
}
})();
const duration = Date.now() - start;
console.log(`Took: ${(duration / 1000).toFixed(2)} seconds`);
});