Add large group send benchmark
This commit is contained in:
		
					parent
					
						
							
								e14356f580
							
						
					
				
			
			
				commit
				
					
						4dfbb25c71
					
				
			
		
					 4 changed files with 151 additions and 37 deletions
				
			
		
							
								
								
									
										21
									
								
								.github/workflows/benchmark.yml
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								.github/workflows/benchmark.yml
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -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"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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,18 +117,59 @@ 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>();
 | 
				
			||||||
  for (let runId = 0; runId < RUN_COUNT + DISCARD_COUNT; runId += 1) {
 | 
					 | 
				
			||||||
    debug('finding composition input and clicking it');
 | 
					 | 
				
			||||||
  const input = await app.waitForEnabledComposer();
 | 
					  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) {
 | 
				
			||||||
 | 
					    debug(`sending previous ${receiptsFromPreviousMessage.length} receipts`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 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}`);
 | 
				
			||||||
    await input.press('Enter');
 | 
					    await input.press('Enter');
 | 
				
			||||||
| 
						 | 
					@ -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(
 | 
				
			||||||
 | 
					      members.slice(0, GROUP_DELIVERY_RECEIPTS).map(member =>
 | 
				
			||||||
 | 
					        member.encryptReceipt(desktop, {
 | 
				
			||||||
          timestamp: timestamp + 1,
 | 
					          timestamp: timestamp + 1,
 | 
				
			||||||
          messageTimestamps: [timestamp],
 | 
					          messageTimestamps: [timestamp],
 | 
				
			||||||
          type: ReceiptType.Delivery,
 | 
					          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);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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,8 +185,15 @@ 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
 | 
				
			||||||
 | 
					        .slice(0, totalContactCount)
 | 
				
			||||||
 | 
					        .map(async profileName => {
 | 
				
			||||||
          const primary = await this.server.createPrimaryDevice({
 | 
					          const primary = await this.server.createPrimaryDevice({
 | 
				
			||||||
            profileName,
 | 
					            profileName,
 | 
				
			||||||
          });
 | 
					          });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue