Modernize Benchmarks CI

This commit is contained in:
Fedor Indutny 2021-08-11 12:29:07 -07:00 committed by GitHub
parent 12c78c742f
commit 61b7eebfcf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 149 additions and 58 deletions

View file

@ -23,6 +23,7 @@ jobs:
with: with:
repository: 'signalapp/Mock-Signal-Server-Private' repository: 'signalapp/Mock-Signal-Server-Private'
path: 'Mock-Server' path: 'Mock-Server'
ref: 'alpha'
token: ${{ secrets.AUTOMATED_GITHUB_PAT }} token: ${{ secrets.AUTOMATED_GITHUB_PAT }}
- name: Setup node.js - name: Setup node.js
@ -56,21 +57,40 @@ jobs:
run: yarn build:webpack run: yarn build:webpack
- name: Copy CI configuration - name: Copy CI configuration
run: cp -rf ./Mock-Server/config/local-development.json ./config/ run: |
cp -rf ./Mock-Server/config/local-development.json \
./config/local-development.json
cp -rf ./config/local-development.json ./config/local-production.json
- name: Setup hosts - name: Setup hosts
run: sudo echo "127.0.0.1 mock.signal.org" | sudo tee -a /etc/hosts run: sudo echo "127.0.0.1 mock.signal.org" | sudo tee -a /etc/hosts
- name: Run benchmarks - name: Run startup benchmarks
run: | run: |
set -o pipefail
rm -rf /tmp/mock
xvfb-run --auto-servernum \ xvfb-run --auto-servernum \
ts-node Mock-Server/scripts/load-test.ts ./node_modules/.bin/electron . | tee benchmark.log || \ ts-node Mock-Server/scripts/load-test.ts ./node_modules/.bin/electron . | tee benchmark.log || \
(cat /home/runner/.config/Signal-mock/logs/{app,main}.log && exit 1) (cat /tmp/mock/logs/{app,main}.log && exit 1)
timeout-minutes: 10 timeout-minutes: 10
env: env:
NODE_ENV: production
RUN_COUNT: 10 RUN_COUNT: 10
ELECTRON_ENABLE_STACK_DUMPING: on ELECTRON_ENABLE_STACK_DUMPING: on
- name: Run send benchmarks
run: |
set -o pipefail
rm -rf /tmp/mock
xvfb-run --auto-servernum \
ts-node Mock-Server/scripts/send-test.ts ./node_modules/.bin/electron . | tee send-benchmark.log || \
(cat /tmp/mock/logs/{app,main}.log && exit 1)
timeout-minutes: 10
env:
NODE_ENV: production
RUN_COUNT: 100
ELECTRON_ENABLE_STACK_DUMPING: on
- name: Clone benchmark branch - name: Clone benchmark branch
uses: actions/checkout@v2 uses: actions/checkout@v2
with: with:
@ -83,6 +103,7 @@ jobs:
run: | run: |
npm ci npm ci
node ./bin/collect.js ../benchmark.log data.json node ./bin/collect.js ../benchmark.log data.json
node ./bin/collect.js ../send-benchmark.log send-data.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"
git config --global user.name "Signal Bot" git config --global user.name "Signal Bot"

View file

@ -7,8 +7,10 @@ import { app } from 'electron';
import { start } from './base_config'; import { start } from './base_config';
import config from './config'; import config from './config';
// Use separate data directory for development // Use separate data directory for benchmarks & development
if (config.has('storageProfile')) { if (config.has('storagePath')) {
app.setPath('userData', String(config.get('storagePath')));
} else if (config.has('storageProfile')) {
const userData = join( const userData = join(
app.getPath('appData'), app.getPath('appData'),
`Signal-${config.get('storageProfile')}` `Signal-${config.get('storageProfile')}`

15
main.js
View file

@ -1172,19 +1172,14 @@ app.on('ready', async () => {
console.log('Processed count:', processedCount); console.log('Processed count:', processedCount);
console.log('Messages per second:', messagesPerSec); console.log('Messages per second:', messagesPerSec);
if (enableCI) { event.sender.send('ci:event', 'app-loaded', {
console._log(
'ci: app_loaded=%s',
JSON.stringify({
loadTime, loadTime,
sqlInitTime, sqlInitTime,
preloadTime, preloadTime,
connectTime, connectTime,
processedCount, processedCount,
messagesPerSec, messagesPerSec,
}) });
);
}
}); });
const userDataPath = await getRealPath(app.getPath('userData')); const userDataPath = await getRealPath(app.getPath('userData'));
@ -1537,12 +1532,6 @@ app.on('will-finish-launching', () => {
}); });
}); });
if (enableCI) {
ipc.on('set-provisioning-url', (event, provisioningURL) => {
console._log('ci: provisioning_url=%j', provisioningURL);
});
}
ipc.on('set-badge-count', (event, count) => { ipc.on('set-badge-count', (event, count) => {
app.badgeCount = count; app.badgeCount = count;
}); });

View file

@ -582,12 +582,12 @@ try {
getRegionCode: () => window.storage.get('regionCode'), getRegionCode: () => window.storage.get('regionCode'),
logger: window.log, logger: window.log,
}); });
window.CI = config.enableCI
? { if (config.enableCI) {
setProvisioningURL: url => ipc.send('set-provisioning-url', url), const { CI, electronRequire } = require('./ts/CI');
deviceName: title, window.CI = new CI(title);
window.electronRequire = electronRequire;
} }
: undefined;
// these need access to window.Signal: // these need access to window.Signal:
require('./ts/models/messages'); require('./ts/models/messages');
@ -616,7 +616,7 @@ try {
}); });
if (config.environment === 'test') { if (config.environment === 'test') {
require('./preload_test.js'); require('./preload_test');
} }
} catch (error) { } catch (error) {
/* eslint-disable no-console */ /* eslint-disable no-console */

77
ts/CI.ts Normal file
View file

@ -0,0 +1,77 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { ipcRenderer } from 'electron';
import { explodePromise } from './util/explodePromise';
type ResolveType = (data: unknown) => void;
export const electronRequire = require;
export class CI {
private readonly eventListeners = new Map<string, Array<ResolveType>>();
private readonly completedEvents = new Map<string, Array<unknown>>();
constructor(public readonly deviceName: string) {
ipcRenderer.on('ci:event', (_, event, data) => {
this.handleEvent(event, data);
});
}
public async waitForEvent(event: string): Promise<unknown> {
const pendingCompleted = this.completedEvents.get(event) || [];
const pending = pendingCompleted.shift();
if (pending) {
window.log.info(`CI: resolving pending result for ${event}`, pending);
if (pendingCompleted.length === 0) {
this.completedEvents.delete(event);
}
return pending;
}
window.log.info(`CI: waiting for event ${event}`);
const { resolve, promise } = explodePromise();
let list = this.eventListeners.get(event);
if (!list) {
list = [];
this.eventListeners.set(event, list);
}
list.push(resolve);
return promise;
}
public setProvisioningURL(url: string): void {
this.handleEvent('provisioning-url', url);
}
public handleEvent(event: string, data: unknown): void {
const list = this.eventListeners.get(event) || [];
const resolve = list.shift();
if (resolve) {
if (list.length === 0) {
this.eventListeners.delete(event);
}
window.log.info(`CI: got event ${event} with data`, data);
resolve(data);
return;
}
window.log.info(`CI: postponing event ${event}`);
let resultList = this.completedEvents.get(event);
if (!resultList) {
resultList = [];
this.completedEvents.set(event, resultList);
}
resultList.push(data);
}
}

View file

@ -86,7 +86,7 @@ export type Props = {
const MAX_LENGTH = 64 * 1024; const MAX_LENGTH = 64 * 1024;
const BASE_CLASS_NAME = 'module-composition-input'; const BASE_CLASS_NAME = 'module-composition-input';
export const CompositionInput: React.ComponentType<Props> = props => { export function CompositionInput(props: Props): React.ReactElement {
const { const {
i18n, i18n,
disabled, disabled,
@ -644,4 +644,4 @@ export const CompositionInput: React.ComponentType<Props> = props => {
</Reference> </Reference>
</Manager> </Manager>
); );
}; }

View file

@ -478,6 +478,10 @@ export class Message extends React.PureComponent<Props, State> {
status === 'viewed') status === 'viewed')
) { ) {
const delta = Date.now() - timestamp; const delta = Date.now() - timestamp;
window.CI?.handleEvent('message:send-complete', {
timestamp,
delta,
});
window.log.info( window.log.info(
`Message.tsx: Rendered 'send complete' for message ${timestamp}; took ${delta}ms` `Message.tsx: Rendered 'send complete' for message ${timestamp}; took ${delta}ms`
); );

View file

@ -54,7 +54,7 @@ type PropsType = {
>; >;
export const BaseConversationListItem: FunctionComponent<PropsType> = React.memo( export const BaseConversationListItem: FunctionComponent<PropsType> = React.memo(
({ function BaseConversationListItem({
acceptedMessageRequest, acceptedMessageRequest,
avatarPath, avatarPath,
checked, checked,
@ -79,7 +79,7 @@ export const BaseConversationListItem: FunctionComponent<PropsType> = React.memo
title, title,
unblurredAvatarPath, unblurredAvatarPath,
unreadCount, unreadCount,
}) => { }) {
const isUnread = isConversationUnread({ markedUnread, unreadCount }); const isUnread = isConversationUnread({ markedUnread, unreadCount });
const isAvatarNoteToSelf = isBoolean(isNoteToSelf) const isAvatarNoteToSelf = isBoolean(isNoteToSelf)

View file

@ -47,7 +47,7 @@ type PropsHousekeepingType = {
type PropsType = PropsDataType & PropsHousekeepingType; type PropsType = PropsDataType & PropsHousekeepingType;
export const ContactCheckbox: FunctionComponent<PropsType> = React.memo( export const ContactCheckbox: FunctionComponent<PropsType> = React.memo(
({ function ContactCheckbox({
about, about,
acceptedMessageRequest, acceptedMessageRequest,
avatarPath, avatarPath,
@ -65,7 +65,7 @@ export const ContactCheckbox: FunctionComponent<PropsType> = React.memo(
title, title,
type, type,
unblurredAvatarPath, unblurredAvatarPath,
}) => { }) {
const disabled = Boolean(disabledReason); const disabled = Boolean(disabledReason);
const headerName = isMe ? ( const headerName = isMe ? (

View file

@ -34,7 +34,7 @@ type PropsHousekeepingType = {
type PropsType = PropsDataType & PropsHousekeepingType; type PropsType = PropsDataType & PropsHousekeepingType;
export const ContactListItem: FunctionComponent<PropsType> = React.memo( export const ContactListItem: FunctionComponent<PropsType> = React.memo(
({ function ContactListItem({
about, about,
acceptedMessageRequest, acceptedMessageRequest,
avatarPath, avatarPath,
@ -50,7 +50,7 @@ export const ContactListItem: FunctionComponent<PropsType> = React.memo(
title, title,
type, type,
unblurredAvatarPath, unblurredAvatarPath,
}) => { }) {
const headerName = isMe ? ( const headerName = isMe ? (
i18n('noteToSelf') i18n('noteToSelf')
) : ( ) : (

View file

@ -63,7 +63,7 @@ type PropsHousekeeping = {
export type Props = PropsData & PropsHousekeeping; export type Props = PropsData & PropsHousekeeping;
export const ConversationListItem: FunctionComponent<Props> = React.memo( export const ConversationListItem: FunctionComponent<Props> = React.memo(
({ function ConversationListItem({
acceptedMessageRequest, acceptedMessageRequest,
avatarPath, avatarPath,
color, color,
@ -87,7 +87,7 @@ export const ConversationListItem: FunctionComponent<Props> = React.memo(
typingContact, typingContact,
unblurredAvatarPath, unblurredAvatarPath,
unreadCount, unreadCount,
}) => { }) {
const headerName = isMe ? ( const headerName = isMe ? (
i18n('noteToSelf') i18n('noteToSelf')
) : ( ) : (

View file

@ -12,7 +12,7 @@ type PropsType = {
}; };
export const CreateNewGroupButton: FunctionComponent<PropsType> = React.memo( export const CreateNewGroupButton: FunctionComponent<PropsType> = React.memo(
({ i18n, onClick }) => { function CreateNewGroupButton({ i18n, onClick }) {
const title = i18n('createNewGroupButton'); const title = i18n('createNewGroupButton');
return ( return (

View file

@ -143,7 +143,7 @@ function getFilteredBodyRanges(
} }
export const MessageSearchResult: FunctionComponent<PropsType> = React.memo( export const MessageSearchResult: FunctionComponent<PropsType> = React.memo(
({ function MessageSearchResult({
body, body,
bodyRanges, bodyRanges,
conversationId, conversationId,
@ -154,7 +154,7 @@ export const MessageSearchResult: FunctionComponent<PropsType> = React.memo(
sentAt, sentAt,
snippet, snippet,
to, to,
}) => { }) {
const onClickItem = useCallback(() => { const onClickItem = useCallback(() => {
openConversationInternal({ conversationId, messageId: id }); openConversationInternal({ conversationId, messageId: id });
}, [openConversationInternal, conversationId, id]); }, [openConversationInternal, conversationId, id]);

View file

@ -25,7 +25,7 @@ type PropsHousekeeping = {
export type Props = PropsData & PropsHousekeeping; export type Props = PropsData & PropsHousekeeping;
export const StartNewConversation: FunctionComponent<Props> = React.memo( export const StartNewConversation: FunctionComponent<Props> = React.memo(
({ i18n, onClick, phoneNumber }) => { function StartNewConversation({ i18n, onClick, phoneNumber }) {
const messageText = ( const messageText = (
<div className={TEXT_CLASS_NAME}>{i18n('startConversation')}</div> <div className={TEXT_CLASS_NAME}>{i18n('startConversation')}</div>
); );

View file

@ -222,9 +222,7 @@ export default class AccountManager extends EventTarget {
} }
const url = getProvisioningUrl(uuid, pubKey); const url = getProvisioningUrl(uuid, pubKey);
if (window.CI) { window.CI?.setProvisioningURL(url);
window.CI.setProvisioningURL(url);
}
setProvisioningUrl(url); setProvisioningUrl(url);
request.respond(200, 'OK'); request.respond(200, 'OK');

8
ts/window.d.ts vendored
View file

@ -117,6 +117,7 @@ import { MessageController } from './util/MessageController';
import { isValidGuid } from './util/isValidGuid'; import { isValidGuid } from './util/isValidGuid';
import { StateType } from './state/reducer'; import { StateType } from './state/reducer';
import { SystemTraySetting } from './types/SystemTraySetting'; import { SystemTraySetting } from './types/SystemTraySetting';
import { CI } from './CI';
export { Long } from 'long'; export { Long } from 'long';
@ -277,12 +278,7 @@ declare global {
}; };
Backbone: typeof Backbone; Backbone: typeof Backbone;
CI: CI?: CI;
| {
setProvisioningURL: (url: string) => void;
deviceName: string;
}
| undefined;
Accessibility: { Accessibility: {
reducedMotionSetting: boolean; reducedMotionSetting: boolean;
}; };

View file

@ -31,7 +31,10 @@ const EXTERNAL_MODULE = new Set([
'zkgroup', 'zkgroup',
// Uses fast-glob and dynamic requires // Uses fast-glob and dynamic requires
'./preload_test.js', './preload_test',
// Needs to exports `electronRequire`
'./ts/CI',
]); ]);
const preloadConfig: Configuration = { const preloadConfig: Configuration = {
@ -43,11 +46,12 @@ const preloadConfig: Configuration = {
optimization: { optimization: {
minimizer: [ minimizer: [
new TerserPlugin({ new TerserPlugin({
parallel: true,
terserOptions: { terserOptions: {
mangle: { mangle: false,
keep_classnames: true,
keep_fnames: true, keep_fnames: true,
}, },
},
}), }),
], ],
}, },