Add large group send benchmark

This commit is contained in:
trevor-signal 2023-12-13 14:47:51 -05:00 committed by GitHub
parent e14356f580
commit 4dfbb25c71
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 151 additions and 37 deletions

View file

@ -89,9 +89,29 @@ jobs:
env: env:
NODE_ENV: production NODE_ENV: production
RUN_COUNT: 100 RUN_COUNT: 100
CONVERSATION_SIZE: 500
ELECTRON_ENABLE_STACK_DUMPING: on ELECTRON_ENABLE_STACK_DUMPING: on
ARTIFACTS_DIR: artifacts/group-send ARTIFACTS_DIR: artifacts/group-send
- name: Run large group send benchmarks with delivery receipts
run: |
set -o pipefail
rm -rf /tmp/mock
xvfb-run --auto-servernum node \
ts/test-mock/benchmarks/group_send_bench.js | \
tee benchmark-large-group-send.log
timeout-minutes: 10
env:
NODE_ENV: production
GROUP_SIZE: 500
CONTACT_COUNT: 500
GROUP_DELIVERY_RECEIPTS: 500
DISCARD_COUNT: 2
RUN_COUNT: 20
CONVERSATION_SIZE: 50
ELECTRON_ENABLE_STACK_DUMPING: on
ARTIFACTS_DIR: artifacts/large-group-send
- name: Run conversation open benchmarks - name: Run conversation open benchmarks
run: | run: |
set -o pipefail set -o pipefail
@ -131,6 +151,7 @@ jobs:
node ./bin/collect.js ../benchmark-startup.log data/startup.json node ./bin/collect.js ../benchmark-startup.log data/startup.json
node ./bin/collect.js ../benchmark-send.log data/send.json node ./bin/collect.js ../benchmark-send.log data/send.json
node ./bin/collect.js ../benchmark-group-send.log data/group-send.json node ./bin/collect.js ../benchmark-group-send.log data/group-send.json
node ./bin/collect.js ../benchmark-large-group-send.log data/large-group-send.json
node ./bin/collect.js ../benchmark-convo-open.log data/convo-open.json node ./bin/collect.js ../benchmark-convo-open.log data/convo-open.json
npm run build npm run build
git config --global user.email "no-reply@signal.org" git config --global user.email "no-reply@signal.org"

View file

@ -19,6 +19,18 @@ export const GROUP_SIZE = process.env.GROUP_SIZE
? parseInt(process.env.GROUP_SIZE, 10) ? parseInt(process.env.GROUP_SIZE, 10)
: 8; : 8;
export const CONTACT_COUNT = process.env.CONTACT_COUNT
? parseInt(process.env.CONTACT_COUNT, 10)
: 10;
export const CONVERSATION_SIZE = process.env.CONVERSATION_SIZE
? parseInt(process.env.CONVERSATION_SIZE, 10)
: 10;
export const GROUP_DELIVERY_RECEIPTS = process.env.GROUP_DELIVERY_RECEIPTS
? parseInt(process.env.GROUP_DELIVERY_RECEIPTS, 10)
: 1;
export const DISCARD_COUNT = process.env.DISCARD_COUNT export const DISCARD_COUNT = process.env.DISCARD_COUNT
? parseInt(process.env.DISCARD_COUNT, 10) ? parseInt(process.env.DISCARD_COUNT, 10)
: 5; : 5;

View file

@ -15,11 +15,14 @@ import {
debug, debug,
RUN_COUNT, RUN_COUNT,
GROUP_SIZE, GROUP_SIZE,
CONVERSATION_SIZE,
DISCARD_COUNT, DISCARD_COUNT,
GROUP_DELIVERY_RECEIPTS,
} from './fixtures'; } from './fixtures';
import { stats } from '../../util/benchmark/stats'; import { stats } from '../../util/benchmark/stats';
import { sleep } from '../../util/sleep';
import { MINUTE } from '../../util/durations';
const CONVERSATION_SIZE = 500; // messages
const LAST_MESSAGE = 'start sending messages now'; const LAST_MESSAGE = 'start sending messages now';
Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise<void> => { Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise<void> => {
@ -46,7 +49,7 @@ Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise<void> => {
const messages = new Array<Buffer>(); const messages = new Array<Buffer>();
debug('encrypting'); debug('encrypting');
// Fill left pane // Fill left pane
for (const contact of members.slice().reverse()) { for (const contact of members.slice(0, CONVERSATION_SIZE).reverse()) {
const messageTimestamp = bootstrap.getTimestamp(); const messageTimestamp = bootstrap.getTimestamp();
messages.push( messages.push(
@ -114,17 +117,58 @@ Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise<void> => {
`>> text=${LAST_MESSAGE}` `>> text=${LAST_MESSAGE}`
) )
.first(); .first();
await item.click(); await item.click({ timeout: 2 * MINUTE });
} }
const timeline = window.locator(
'.timeline-wrapper, .Inbox__conversation .ConversationView'
);
const deltaList = new Array<number>(); const deltaList = new Array<number>();
const input = await app.waitForEnabledComposer();
function sendReceiptsInBatches({
receipts,
batchSize,
nextBatchSize,
runId,
delay,
}: {
receipts: Array<Buffer>;
batchSize: number;
nextBatchSize: number;
runId: number;
delay: number;
}) {
const receiptsToSend = receipts.splice(0, batchSize);
debug(`sending ${receiptsToSend.length} receipts for runId ${runId}`);
receiptsToSend.forEach(delivery => server.send(desktop, delivery));
if (receipts.length) {
setTimeout(
() =>
sendReceiptsInBatches({
receipts,
batchSize: nextBatchSize,
nextBatchSize,
runId,
delay,
}),
delay
);
}
}
let receiptsFromPreviousMessage: Array<Buffer> = [];
for (let runId = 0; runId < RUN_COUNT + DISCARD_COUNT; runId += 1) { for (let runId = 0; runId < RUN_COUNT + DISCARD_COUNT; runId += 1) {
debug('finding composition input and clicking it'); debug(`sending previous ${receiptsFromPreviousMessage.length} receipts`);
const input = await app.waitForEnabledComposer();
// deliver up to 256 receipts at once (max that server will send) and then in chunks
// of 30 every 200ms to approximate real behavior as we acknowledge each batch
sendReceiptsInBatches({
receipts: receiptsFromPreviousMessage,
batchSize: 256,
nextBatchSize: 30,
delay: 100,
runId,
});
debug('entering message text'); debug('entering message text');
await input.type(`my message ${runId}`); await input.type(`my message ${runId}`);
@ -139,18 +183,19 @@ Bootstrap.benchmark(async (bootstrap: Bootstrap): Promise<void> => {
debug('waiting for timing from the app'); debug('waiting for timing from the app');
const { timestamp, delta } = await app.waitForMessageSend(); const { timestamp, delta } = await app.waitForMessageSend();
// Sleep to allow any receipts from previous rounds to be processed
await sleep(1000);
debug('sending delivery receipts'); debug('sending delivery receipts');
const delivery = await first.encryptReceipt(desktop, { receiptsFromPreviousMessage = await Promise.all(
timestamp: timestamp + 1, members.slice(0, GROUP_DELIVERY_RECEIPTS).map(member =>
messageTimestamps: [timestamp], member.encryptReceipt(desktop, {
type: ReceiptType.Delivery, 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) { if (runId >= DISCARD_COUNT) {
deltaList.push(delta); deltaList.push(delta);

View file

@ -19,6 +19,7 @@ import { MAX_READ_KEYS as MAX_STORAGE_READ_KEYS } from '../services/storageConst
import * as durations from '../util/durations'; import * as durations from '../util/durations';
import { drop } from '../util/drop'; import { drop } from '../util/drop';
import { App } from './playwright'; import { App } from './playwright';
import { CONTACT_COUNT } from './benchmarks/fixtures';
export { App }; export { App };
@ -40,6 +41,10 @@ const CONTACT_FIRST_NAMES = [
'Alice', 'Alice',
'Bob', 'Bob',
'Charlie', 'Charlie',
'Danielle',
'Elaine',
'Frankie',
'Grandma',
'Paul', 'Paul',
'Steve', 'Steve',
'William', 'William',
@ -51,7 +56,23 @@ const CONTACT_LAST_NAMES = [
'Miller', 'Miller',
'Davis', 'Davis',
'Lopez', 'Lopez',
'Gonazales', 'Gonzales',
'Singh',
'Baker',
'Farmer',
];
const CONTACT_SUFFIXES = [
'Sr.',
'Jr.',
'the 3rd',
'the 4th',
'the 5th',
'the 6th',
'the 7th',
'the 8th',
'the 9th',
'the 10th',
]; ];
const CONTACT_NAMES = new Array<string>(); const CONTACT_NAMES = new Array<string>();
@ -61,6 +82,14 @@ for (const firstName of CONTACT_FIRST_NAMES) {
} }
} }
for (const suffix of CONTACT_SUFFIXES) {
for (const firstName of CONTACT_FIRST_NAMES) {
for (const lastName of CONTACT_LAST_NAMES) {
CONTACT_NAMES.push(`${firstName} ${lastName}, ${suffix}`);
}
}
}
const MAX_CONTACTS = CONTACT_NAMES.length; const MAX_CONTACTS = CONTACT_NAMES.length;
export type BootstrapOptions = Readonly<{ export type BootstrapOptions = Readonly<{
@ -131,7 +160,7 @@ export class Bootstrap {
this.options = { this.options = {
linkedDevices: 5, linkedDevices: 5,
contactCount: MAX_CONTACTS, contactCount: CONTACT_COUNT,
contactsWithoutProfileKey: 0, contactsWithoutProfileKey: 0,
unknownContactCount: 0, unknownContactCount: 0,
contactNames: CONTACT_NAMES, contactNames: CONTACT_NAMES,
@ -140,12 +169,12 @@ export class Bootstrap {
...options, ...options,
}; };
assert( const totalContactCount =
this.options.contactCount + this.options.contactCount +
this.options.contactsWithoutProfileKey + this.options.contactsWithoutProfileKey +
this.options.unknownContactCount <= this.options.unknownContactCount;
this.options.contactNames.length assert(totalContactCount <= this.options.contactNames.length);
); assert(totalContactCount <= MAX_CONTACTS);
} }
public async init(): Promise<void> { public async init(): Promise<void> {
@ -156,19 +185,26 @@ export class Bootstrap {
const { port } = this.server.address(); const { port } = this.server.address();
debug('started server on port=%d', port); debug('started server on port=%d', port);
const totalContactCount =
this.options.contactCount +
this.options.contactsWithoutProfileKey +
this.options.unknownContactCount;
const allContacts = await Promise.all( const allContacts = await Promise.all(
this.options.contactNames.map(async profileName => { this.options.contactNames
const primary = await this.server.createPrimaryDevice({ .slice(0, totalContactCount)
profileName, .map(async profileName => {
}); const primary = await this.server.createPrimaryDevice({
profileName,
});
for (let i = 0; i < this.options.linkedDevices; i += 1) { for (let i = 0; i < this.options.linkedDevices; i += 1) {
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
await this.server.createSecondaryDevice(primary); await this.server.createSecondaryDevice(primary);
} }
return primary; return primary;
}) })
); );
this.privContacts = allContacts.splice(0, this.options.contactCount); this.privContacts = allContacts.splice(0, this.options.contactCount);