From 332fb7fbe3105eb52c03636b752c57b01e0768b7 Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 9 Sep 2024 15:45:18 +0200 Subject: [PATCH 1/4] Add optional module for performance measurements - Introduce an optional performance module that add performance measurements to core GLSP concepts. - Performance is simply measured using console.time which also allows inspection of the tracked events in the dev tools trace viewer - The module is opt-in and disabled by default. To enable it the &performance=true query parameter has to be added to the url --- integration/standalone/src/app.ts | 4 +++- integration/standalone/src/di.config.ts | 4 +++- integration/viewer/src/app.ts | 4 +++- integration/viewer/src/di.config.ts | 3 +++ packages/editor/src/index.ts | 2 +- .../src/performance/perf-action-dispatcher.ts | 23 +++++++++++++++++++ .../src/performance/perf-diagram-loader.ts | 23 +++++++++++++++++++ .../editor/src/performance/perf-viewer.ts | 14 +++++++++++ .../src/performance/performance-module.ts | 18 +++++++++++++++ 9 files changed, 91 insertions(+), 4 deletions(-) create mode 100644 packages/editor/src/performance/perf-action-dispatcher.ts create mode 100644 packages/editor/src/performance/perf-diagram-loader.ts create mode 100644 packages/editor/src/performance/perf-viewer.ts create mode 100644 packages/editor/src/performance/performance-module.ts diff --git a/integration/standalone/src/app.ts b/integration/standalone/src/app.ts index b6909c2d0..4f456b3c5 100644 --- a/integration/standalone/src/app.ts +++ b/integration/standalone/src/app.ts @@ -23,6 +23,7 @@ const sourceUri = parameters.get('file') ?? ''; const select = parameters.get('select'); const theme = (parameters.get('theme') as ThemeMode) ?? SwitchThemeActionHandler.prefsColorScheme(); const debug = parameters.has('debug', 'true'); +const measurePerformance = parameters.has('performance', 'true'); const id = 'ivy-glsp-process-editor'; const diagramType = 'ivy-glsp-process'; @@ -51,7 +52,8 @@ async function initialize(connectionProvider: MessageConnection, isReconnecting app, pmv, server: webSocketBase - } + }, + measurePerformance }); const diagramLoader = container.get(DiagramLoader); diff --git a/integration/standalone/src/di.config.ts b/integration/standalone/src/di.config.ts index c0ae0628c..e668022de 100644 --- a/integration/standalone/src/di.config.ts +++ b/integration/standalone/src/di.config.ts @@ -1,4 +1,4 @@ -import { createIvyDiagramContainer, ivyThemeModule } from '@axonivy/process-editor'; +import { createIvyDiagramContainer, createPerformanceModule, ivyThemeModule } from '@axonivy/process-editor'; import { ivyInscriptionModule } from '@axonivy/process-editor-inscription'; import type { IDiagramOptions } from '@eclipse-glsp/client'; import { createDiagramOptionsModule, standaloneExportModule, standaloneSelectModule, undoRedoModule } from '@eclipse-glsp/client'; @@ -15,12 +15,14 @@ export interface IvyDiagramOptions extends IDiagramOptions { select: string | null; theme: ThemeMode; inscriptionContext: InscriptionContext & { server: string }; + measurePerformance?: boolean; } export default function createContainer(options: IvyDiagramOptions): Container { const container = createIvyDiagramContainer( 'sprotty', createDiagramOptionsModule(options), + createPerformanceModule(options.measurePerformance), // standalone modules standaloneSelectModule, standaloneExportModule, diff --git a/integration/viewer/src/app.ts b/integration/viewer/src/app.ts index ed174130a..80d070567 100644 --- a/integration/viewer/src/app.ts +++ b/integration/viewer/src/app.ts @@ -23,6 +23,7 @@ const highlight = parameters.get('highlight') ?? ''; const select = parameters.get('select'); const zoom = parameters.get('zoom') ?? ''; const theme = (parameters.get('theme') as ThemeMode) ?? SwitchThemeActionHandler.prefsColorScheme(); +const measurePerformance = parameters.has('performance', 'true'); const id = 'ivy-glsp-process-viewer'; const diagramType = 'ivy-glsp-process'; @@ -47,7 +48,8 @@ async function initialize(connectionProvider: MessageConnection, isReconnecting highlight, select, zoom, - theme + theme, + measurePerformance }); const diagramLoader = container.get(DiagramLoader); diff --git a/integration/viewer/src/di.config.ts b/integration/viewer/src/di.config.ts index 895150926..f0e51970b 100644 --- a/integration/viewer/src/di.config.ts +++ b/integration/viewer/src/di.config.ts @@ -1,5 +1,6 @@ import { createIvyDiagramContainer, + createPerformanceModule, ivyChangeBoundsToolModule, ivyConnectorModule, ivyKeyListenerModule, @@ -26,6 +27,7 @@ export interface IvyDiagramOptions extends IDiagramOptions { select: string | null; zoom: string; theme: ThemeMode; + measurePerformance?: boolean; } export default function createContainer(options: IvyDiagramOptions): Container { @@ -33,6 +35,7 @@ export default function createContainer(options: IvyDiagramOptions): Container { const container = createIvyDiagramContainer( 'sprotty', createDiagramOptionsModule(options), + createPerformanceModule(options.measurePerformance), ivyThemeModule, ivyNavigationModule, ivyStartupDiagramModule, diff --git a/packages/editor/src/index.ts b/packages/editor/src/index.ts index de13b466d..911e3e2a8 100644 --- a/packages/editor/src/index.ts +++ b/packages/editor/src/index.ts @@ -42,5 +42,5 @@ export * from './ui-tools/viewport/viewport-bar'; export * from './key-listener/jump-out'; export * from './key-listener/quick-actions'; export * from './start-action/actions'; - +export * from './performance/performance-module'; export * from './ivy-glsp-jsonrpc-client'; diff --git a/packages/editor/src/performance/perf-action-dispatcher.ts b/packages/editor/src/performance/perf-action-dispatcher.ts new file mode 100644 index 000000000..e9ba0658b --- /dev/null +++ b/packages/editor/src/performance/perf-action-dispatcher.ts @@ -0,0 +1,23 @@ +import { Action, GLSPActionDispatcher, RequestAction, ResponseAction } from '@eclipse-glsp/client'; +import { injectable } from 'inversify'; + +@injectable() +export class PerfActionDispatcher extends GLSPActionDispatcher { + protected counter = 0; + + override request(action: RequestAction): Promise { + const counter = ++this.counter; + console.time(`request-${action.kind}-${counter}`); + const result = super.request(action); + console.timeEnd(`request-${action.kind}-${counter}`); + this.counter++; + return result; + } + + override async dispatch(action: Action): Promise { + const counter = ++this.counter; + console.time(`dispatch-${action.kind}-${counter}`); + await super.dispatch(action); + console.timeEnd(`dispatch-${action.kind}-${counter}`); + } +} diff --git a/packages/editor/src/performance/perf-diagram-loader.ts b/packages/editor/src/performance/perf-diagram-loader.ts new file mode 100644 index 000000000..5d944efcd --- /dev/null +++ b/packages/editor/src/performance/perf-diagram-loader.ts @@ -0,0 +1,23 @@ +import { DiagramLoader, IDiagramStartup, ResolvedDiagramLoadingOptions } from '@eclipse-glsp/client'; +import { injectable } from 'inversify'; + +@injectable() +export class PerfDiagramLoader extends DiagramLoader { + protected override async invokeStartupHook(hook: keyof Omit): Promise { + console.time('invokeStartupHook-' + hook); + await super.invokeStartupHook(hook); + console.timeEnd('invokeStartupHook-' + hook); + } + + protected override async initialize(options: ResolvedDiagramLoadingOptions): Promise { + console.time('DiagramLoader.initialize'); + await super.initialize(options); + console.timeEnd('DiagramLoader.initialize'); + } + + protected override async requestModel(options: ResolvedDiagramLoadingOptions): Promise { + console.time('DiagramLoader.requestModel'); + await super.requestModel(options); + console.timeEnd('DiagramLoader.requestModel'); + } +} diff --git a/packages/editor/src/performance/perf-viewer.ts b/packages/editor/src/performance/perf-viewer.ts new file mode 100644 index 000000000..a6e6163d3 --- /dev/null +++ b/packages/editor/src/performance/perf-viewer.ts @@ -0,0 +1,14 @@ +import { Action, GModelRoot, ModelViewer } from '@eclipse-glsp/client'; +import { injectable } from 'inversify'; + +@injectable() +export class PerfModelViewer extends ModelViewer { + protected counter = 0; + + update(model: Readonly, cause?: Action): void { + const counter = ++this.counter; + console.time('Viewer update ' + counter); + super.update(model, cause); + console.timeEnd('Viewer update ' + counter); + } +} diff --git a/packages/editor/src/performance/performance-module.ts b/packages/editor/src/performance/performance-module.ts new file mode 100644 index 000000000..d5eebe0b1 --- /dev/null +++ b/packages/editor/src/performance/performance-module.ts @@ -0,0 +1,18 @@ +import { DiagramLoader, FeatureModule, GLSPActionDispatcher, TYPES } from '@eclipse-glsp/client'; +import { PerfDiagramLoader } from './perf-diagram-loader'; +import { PerfActionDispatcher } from './perf-action-dispatcher'; +import { PerfModelViewer } from './perf-viewer'; + +export function createPerformanceModule(enabled?: boolean): FeatureModule { + return new FeatureModule( + (bind, unbind, isBound, rebind) => { + if (!enabled) { + return; + } + rebind(DiagramLoader).to(PerfDiagramLoader).inSingletonScope(); + rebind(GLSPActionDispatcher).to(PerfActionDispatcher).inSingletonScope(); + rebind(TYPES.ModelViewer).to(PerfModelViewer).inSingletonScope(); + }, + { featureId: Symbol('performance') } + ); +} From 8dab2add311d951264f0d58ba186e6532995bcdd Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 1 Oct 2024 11:37:49 +0200 Subject: [PATCH 2/4] Improve performance tracking Measure actions when they are actually handled instead of on dispatch (avoids inconsistencies due to action queue blocking) Add in depth logging for set-model-command and model update --- .../src/performance/per-set-model-command.ts | 21 +++++++ .../src/performance/perf-action-dispatcher.ts | 10 ++-- .../src/performance/perf-diagram-loader.ts | 35 ++++++++++- .../editor/src/performance/perf-viewer.ts | 14 ----- .../editor/src/performance/perf-viewer.tsx | 59 +++++++++++++++++++ .../src/performance/performance-module.ts | 4 +- 6 files changed, 123 insertions(+), 20 deletions(-) create mode 100644 packages/editor/src/performance/per-set-model-command.ts delete mode 100644 packages/editor/src/performance/perf-viewer.ts create mode 100644 packages/editor/src/performance/perf-viewer.tsx diff --git a/packages/editor/src/performance/per-set-model-command.ts b/packages/editor/src/performance/per-set-model-command.ts new file mode 100644 index 000000000..99969ee04 --- /dev/null +++ b/packages/editor/src/performance/per-set-model-command.ts @@ -0,0 +1,21 @@ +import { CommandExecutionContext, GModelRoot, SetModelCommand } from '@eclipse-glsp/client'; +import { injectable } from 'inversify'; + +@injectable() +export class PerfSetModelCommand extends SetModelCommand { + protected counter = 0; + + execute(context: CommandExecutionContext): GModelRoot { + const counter = ++this.counter; + console.time(`execute SetModelCommand (sc) ${counter}`); + console.time(`sc-createOldRoot (${counter})`); + this.oldRoot = context.modelFactory.createRoot(context.root); + console.timeEnd(`sc-createOldRoot (${counter})`); + console.time(`sc-createNewRoot (${counter})`); + this.newRoot = context.modelFactory.createRoot(this.action.newRoot); + console.timeEnd(`sc-createNewRoot (${counter})`); + console.timeEnd(`execute SetModelCommand (sc) ${counter}`); + + return this.newRoot; + } +} diff --git a/packages/editor/src/performance/perf-action-dispatcher.ts b/packages/editor/src/performance/perf-action-dispatcher.ts index e9ba0658b..1747c1040 100644 --- a/packages/editor/src/performance/perf-action-dispatcher.ts +++ b/packages/editor/src/performance/perf-action-dispatcher.ts @@ -14,10 +14,12 @@ export class PerfActionDispatcher extends GLSPActionDispatcher { return result; } - override async dispatch(action: Action): Promise { + protected override async handleAction(action: Action): Promise { const counter = ++this.counter; - console.time(`dispatch-${action.kind}-${counter}`); - await super.dispatch(action); - console.timeEnd(`dispatch-${action.kind}-${counter}`); + console.time(`handleAction-${action.kind}-${counter}`); + console.log(`handleAction-${action.kind}-${counter}`, action); + const result = await super.handleAction(action); + console.timeEnd(`handleAction-${action.kind}-${counter}`); + return result; } } diff --git a/packages/editor/src/performance/perf-diagram-loader.ts b/packages/editor/src/performance/perf-diagram-loader.ts index 5d944efcd..dbe07f541 100644 --- a/packages/editor/src/performance/perf-diagram-loader.ts +++ b/packages/editor/src/performance/perf-diagram-loader.ts @@ -1,4 +1,12 @@ -import { DiagramLoader, IDiagramStartup, ResolvedDiagramLoadingOptions } from '@eclipse-glsp/client'; +import { + ApplicationIdProvider, + DiagramLoader, + DiagramLoadingOptions, + GLSPClient, + IDiagramStartup, + Ranked, + ResolvedDiagramLoadingOptions +} from '@eclipse-glsp/client'; import { injectable } from 'inversify'; @injectable() @@ -20,4 +28,29 @@ export class PerfDiagramLoader extends DiagramLoader { await super.requestModel(options); console.timeEnd('DiagramLoader.requestModel'); } + + override async load(options: DiagramLoadingOptions = {}): Promise { + this.diagramStartups.sort(Ranked.sort); + await this.invokeStartupHook('preLoadDiagram'); + const resolvedOptions: ResolvedDiagramLoadingOptions = { + requestModelOptions: { + sourceUri: this.options.sourceUri ?? '', + diagramType: this.options.diagramType, + ...options.requestModelOptions + }, + initializeParameters: { + applicationId: ApplicationIdProvider.get(), + protocolVersion: GLSPClient.protocolVersion, + ...options.initializeParameters + }, + enableNotifications: options.enableNotifications ?? true + }; + await this.actionDispatcher.initialize(); + await this.invokeStartupHook('preInitialize'); + await this.initialize(resolvedOptions); + await this.invokeStartupHook('preRequestModel'); + await this.requestModel(resolvedOptions); + await this.invokeStartupHook('postRequestModel'); + this.modelInitializationConstraint.onInitialized(() => this.invokeStartupHook('postModelInitialization')); + } } diff --git a/packages/editor/src/performance/perf-viewer.ts b/packages/editor/src/performance/perf-viewer.ts deleted file mode 100644 index a6e6163d3..000000000 --- a/packages/editor/src/performance/perf-viewer.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Action, GModelRoot, ModelViewer } from '@eclipse-glsp/client'; -import { injectable } from 'inversify'; - -@injectable() -export class PerfModelViewer extends ModelViewer { - protected counter = 0; - - update(model: Readonly, cause?: Action): void { - const counter = ++this.counter; - console.time('Viewer update ' + counter); - super.update(model, cause); - console.timeEnd('Viewer update ' + counter); - } -} diff --git a/packages/editor/src/performance/perf-viewer.tsx b/packages/editor/src/performance/perf-viewer.tsx new file mode 100644 index 000000000..1cbd430a7 --- /dev/null +++ b/packages/editor/src/performance/perf-viewer.tsx @@ -0,0 +1,59 @@ +/** @jsx html */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { Action, GModelRoot, ModelViewer, copyClassesFromElement, copyClassesFromVNode, html, setClass } from '@eclipse-glsp/client'; +import { injectable } from 'inversify'; + +@injectable() +export class PerfModelViewer extends ModelViewer { + protected counter = 0; + update(model: Readonly, cause?: Action): void { + const counter = ++this.counter; + console.time(`Viewer update (vu) (${counter})`); + this.logger.log(this, 'rendering', model); + console.time(`vu- renderElement (${counter})`); + const newVDOM =
{this.renderer.renderElement(model)}
; + console.timeEnd(`vu- renderElement (${counter})`); + if (this.lastVDOM !== undefined) { + const hadFocus = this.hasFocus(); + console.time(`vu- copyClassesFromVNode (${counter})`); + copyClassesFromVNode(this.lastVDOM, newVDOM); + console.timeEnd(`vu- copyClassesFromVNode (${counter})`); + console.time(`vu- patch vdom (${counter})`); + this.lastVDOM = this.patcher.call(this, this.lastVDOM, newVDOM); + console.timeEnd(`vu- patch vdom (${counter})`); + console.time(`vu- restoreFocus (${counter})`); + this.restoreFocus(hadFocus); + console.timeEnd(`vu- restoreFocus (${counter})`); + } else if (typeof document !== 'undefined') { + let placeholder = null; + if (this.options.shadowRoot) { + const shadowRoot = document.getElementById(this.options.shadowRoot)?.shadowRoot; + if (shadowRoot) { + placeholder = shadowRoot.getElementById(this.options.baseDiv); + } + } else { + placeholder = document.getElementById(this.options.baseDiv); + } + if (placeholder !== null) { + if (typeof window !== 'undefined') { + window.addEventListener('resize', () => { + this.onWindowResize(newVDOM); + }); + } + console.time(`vu- copyClassesFromElement (${counter})`); + copyClassesFromElement(placeholder, newVDOM); + setClass(newVDOM, this.options.baseClass, true); + console.timeEnd(`vu- copyClassesFromElement (${counter})`); + console.time(`vu- patch vdom (${counter})`); + this.lastVDOM = this.patcher.call(this, placeholder, newVDOM); + console.timeEnd(`vu- patch vdom (${counter})`); + } else { + this.logger.error(this, 'element not in DOM:', this.options.baseDiv); + } + } + console.time(`vu- postUpdate (${counter})`); + this.renderer.postUpdate(cause); + console.timeEnd(`vu- postUpdate (${counter})`); + console.timeEnd(`Viewer update (vu) (${counter})`); + } +} diff --git a/packages/editor/src/performance/performance-module.ts b/packages/editor/src/performance/performance-module.ts index d5eebe0b1..b072aa22b 100644 --- a/packages/editor/src/performance/performance-module.ts +++ b/packages/editor/src/performance/performance-module.ts @@ -1,7 +1,8 @@ -import { DiagramLoader, FeatureModule, GLSPActionDispatcher, TYPES } from '@eclipse-glsp/client'; +import { DiagramLoader, FeatureModule, GLSPActionDispatcher, SetModelCommand, TYPES } from '@eclipse-glsp/client'; import { PerfDiagramLoader } from './perf-diagram-loader'; import { PerfActionDispatcher } from './perf-action-dispatcher'; import { PerfModelViewer } from './perf-viewer'; +import { PerfSetModelCommand } from './per-set-model-command'; export function createPerformanceModule(enabled?: boolean): FeatureModule { return new FeatureModule( @@ -12,6 +13,7 @@ export function createPerformanceModule(enabled?: boolean): FeatureModule { rebind(DiagramLoader).to(PerfDiagramLoader).inSingletonScope(); rebind(GLSPActionDispatcher).to(PerfActionDispatcher).inSingletonScope(); rebind(TYPES.ModelViewer).to(PerfModelViewer).inSingletonScope(); + rebind(SetModelCommand).to(PerfSetModelCommand); }, { featureId: Symbol('performance') } ); From a460093f396238903184c559ee9dec8a4585333e Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Tue, 26 Nov 2024 08:49:15 +0100 Subject: [PATCH 3/4] Rebase and improver performance logging --- packages/editor/src/di.config.ts | 3 +- .../src/performance/per-set-model-command.ts | 21 ----------- .../src/performance/perf-command-stack.ts | 19 ++++++++++ .../src/performance/perf-diagram-loader.ts | 35 +++---------------- .../editor/src/performance/perf-viewer.tsx | 14 -------- .../src/performance/performance-module.ts | 10 +++--- 6 files changed, 29 insertions(+), 73 deletions(-) delete mode 100644 packages/editor/src/performance/per-set-model-command.ts create mode 100644 packages/editor/src/performance/perf-command-stack.ts diff --git a/packages/editor/src/di.config.ts b/packages/editor/src/di.config.ts index d9d0b9b28..9a3ff8900 100644 --- a/packages/editor/src/di.config.ts +++ b/packages/editor/src/di.config.ts @@ -25,7 +25,6 @@ import ivyDiagramModule from './diagram/di.config'; import { LaneNode } from './diagram/model'; import { ivyLabelEditModule, ivyLabelEditUiModule } from './edit-label/di.config'; import ivyExecutionModule from './execution/di.config'; -import { IvyGLSPCommandStack } from './ivy-command-stack'; import ivyJumpModule from './jump/di.config'; import ivyKeyListenerModule from './key-listener/di.config'; import ivyLaneModule from './lanes/di.config'; @@ -93,7 +92,7 @@ export default function createContainer(widgetId: string, ...containerConfigurat bindOrRebind(container, MarqueeUtil).to(IvyMarqueeUtil).inSingletonScope(); bindOrRebind(container, TYPES.IMarqueeBehavior).toConstantValue({ entireEdge: true, entireElement: true }); - bindOrRebind(container, TYPES.ICommandStack).to(IvyGLSPCommandStack).inSingletonScope(); + // bindOrRebind(container, TYPES.ICommandStack).to(IvyGLSPCommandStack).inSingletonScope(); bindOrRebind(container, TYPES.ILogger).to(ConsoleLogger).inSingletonScope(); bindOrRebind(container, TYPES.LogLevel).toConstantValue(LogLevel.warn); bindOrRebind(container, TYPES.ISnapper).to(GLSPCenterGridSnapper); diff --git a/packages/editor/src/performance/per-set-model-command.ts b/packages/editor/src/performance/per-set-model-command.ts deleted file mode 100644 index 99969ee04..000000000 --- a/packages/editor/src/performance/per-set-model-command.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { CommandExecutionContext, GModelRoot, SetModelCommand } from '@eclipse-glsp/client'; -import { injectable } from 'inversify'; - -@injectable() -export class PerfSetModelCommand extends SetModelCommand { - protected counter = 0; - - execute(context: CommandExecutionContext): GModelRoot { - const counter = ++this.counter; - console.time(`execute SetModelCommand (sc) ${counter}`); - console.time(`sc-createOldRoot (${counter})`); - this.oldRoot = context.modelFactory.createRoot(context.root); - console.timeEnd(`sc-createOldRoot (${counter})`); - console.time(`sc-createNewRoot (${counter})`); - this.newRoot = context.modelFactory.createRoot(this.action.newRoot); - console.timeEnd(`sc-createNewRoot (${counter})`); - console.timeEnd(`execute SetModelCommand (sc) ${counter}`); - - return this.newRoot; - } -} diff --git a/packages/editor/src/performance/perf-command-stack.ts b/packages/editor/src/performance/perf-command-stack.ts new file mode 100644 index 000000000..905454bef --- /dev/null +++ b/packages/editor/src/performance/perf-command-stack.ts @@ -0,0 +1,19 @@ +import { GModelRoot, ICommand } from '@eclipse-glsp/client'; +import { injectable } from 'inversify'; +import { IvyGLSPCommandStack } from '../ivy-command-stack'; + +@injectable() +export class PerfCommandStack extends IvyGLSPCommandStack { + protected counter = 0; + + override async execute(command: ICommand): Promise { + const counter = ++this.counter; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const kind = (command as any).action?.kind; + const perfmessage = kind ? `executeCommand-${kind}-${counter}` : `executeCommand-${counter}`; + console.time(perfmessage); + const result = await super.execute(command); + console.timeEnd(perfmessage); + return result; + } +} diff --git a/packages/editor/src/performance/perf-diagram-loader.ts b/packages/editor/src/performance/perf-diagram-loader.ts index dbe07f541..4154b0453 100644 --- a/packages/editor/src/performance/perf-diagram-loader.ts +++ b/packages/editor/src/performance/perf-diagram-loader.ts @@ -1,12 +1,4 @@ -import { - ApplicationIdProvider, - DiagramLoader, - DiagramLoadingOptions, - GLSPClient, - IDiagramStartup, - Ranked, - ResolvedDiagramLoadingOptions -} from '@eclipse-glsp/client'; +import { DiagramLoader, DiagramLoadingOptions, IDiagramStartup, ResolvedDiagramLoadingOptions } from '@eclipse-glsp/client'; import { injectable } from 'inversify'; @injectable() @@ -30,27 +22,8 @@ export class PerfDiagramLoader extends DiagramLoader { } override async load(options: DiagramLoadingOptions = {}): Promise { - this.diagramStartups.sort(Ranked.sort); - await this.invokeStartupHook('preLoadDiagram'); - const resolvedOptions: ResolvedDiagramLoadingOptions = { - requestModelOptions: { - sourceUri: this.options.sourceUri ?? '', - diagramType: this.options.diagramType, - ...options.requestModelOptions - }, - initializeParameters: { - applicationId: ApplicationIdProvider.get(), - protocolVersion: GLSPClient.protocolVersion, - ...options.initializeParameters - }, - enableNotifications: options.enableNotifications ?? true - }; - await this.actionDispatcher.initialize(); - await this.invokeStartupHook('preInitialize'); - await this.initialize(resolvedOptions); - await this.invokeStartupHook('preRequestModel'); - await this.requestModel(resolvedOptions); - await this.invokeStartupHook('postRequestModel'); - this.modelInitializationConstraint.onInitialized(() => this.invokeStartupHook('postModelInitialization')); + console.time('DiagramLoader.load'); + await super.load(options); + console.timeEnd('DiagramLoader.load'); } } diff --git a/packages/editor/src/performance/perf-viewer.tsx b/packages/editor/src/performance/perf-viewer.tsx index 1cbd430a7..df5f1b5da 100644 --- a/packages/editor/src/performance/perf-viewer.tsx +++ b/packages/editor/src/performance/perf-viewer.tsx @@ -10,20 +10,12 @@ export class PerfModelViewer extends ModelViewer { const counter = ++this.counter; console.time(`Viewer update (vu) (${counter})`); this.logger.log(this, 'rendering', model); - console.time(`vu- renderElement (${counter})`); const newVDOM =
{this.renderer.renderElement(model)}
; - console.timeEnd(`vu- renderElement (${counter})`); if (this.lastVDOM !== undefined) { const hadFocus = this.hasFocus(); - console.time(`vu- copyClassesFromVNode (${counter})`); copyClassesFromVNode(this.lastVDOM, newVDOM); - console.timeEnd(`vu- copyClassesFromVNode (${counter})`); - console.time(`vu- patch vdom (${counter})`); this.lastVDOM = this.patcher.call(this, this.lastVDOM, newVDOM); - console.timeEnd(`vu- patch vdom (${counter})`); - console.time(`vu- restoreFocus (${counter})`); this.restoreFocus(hadFocus); - console.timeEnd(`vu- restoreFocus (${counter})`); } else if (typeof document !== 'undefined') { let placeholder = null; if (this.options.shadowRoot) { @@ -40,20 +32,14 @@ export class PerfModelViewer extends ModelViewer { this.onWindowResize(newVDOM); }); } - console.time(`vu- copyClassesFromElement (${counter})`); copyClassesFromElement(placeholder, newVDOM); setClass(newVDOM, this.options.baseClass, true); - console.timeEnd(`vu- copyClassesFromElement (${counter})`); - console.time(`vu- patch vdom (${counter})`); this.lastVDOM = this.patcher.call(this, placeholder, newVDOM); - console.timeEnd(`vu- patch vdom (${counter})`); } else { this.logger.error(this, 'element not in DOM:', this.options.baseDiv); } } - console.time(`vu- postUpdate (${counter})`); this.renderer.postUpdate(cause); - console.timeEnd(`vu- postUpdate (${counter})`); console.timeEnd(`Viewer update (vu) (${counter})`); } } diff --git a/packages/editor/src/performance/performance-module.ts b/packages/editor/src/performance/performance-module.ts index b072aa22b..eafceb3a8 100644 --- a/packages/editor/src/performance/performance-module.ts +++ b/packages/editor/src/performance/performance-module.ts @@ -1,8 +1,8 @@ -import { DiagramLoader, FeatureModule, GLSPActionDispatcher, SetModelCommand, TYPES } from '@eclipse-glsp/client'; -import { PerfDiagramLoader } from './perf-diagram-loader'; +import { DiagramLoader, FeatureModule, GLSPActionDispatcher, ModelViewer, TYPES } from '@eclipse-glsp/client'; import { PerfActionDispatcher } from './perf-action-dispatcher'; +import { PerfCommandStack } from './perf-command-stack'; +import { PerfDiagramLoader } from './perf-diagram-loader'; import { PerfModelViewer } from './perf-viewer'; -import { PerfSetModelCommand } from './per-set-model-command'; export function createPerformanceModule(enabled?: boolean): FeatureModule { return new FeatureModule( @@ -12,8 +12,8 @@ export function createPerformanceModule(enabled?: boolean): FeatureModule { } rebind(DiagramLoader).to(PerfDiagramLoader).inSingletonScope(); rebind(GLSPActionDispatcher).to(PerfActionDispatcher).inSingletonScope(); - rebind(TYPES.ModelViewer).to(PerfModelViewer).inSingletonScope(); - rebind(SetModelCommand).to(PerfSetModelCommand); + rebind(ModelViewer).to(PerfModelViewer).inSingletonScope(); + rebind(TYPES.ICommandStack).to(PerfCommandStack).inSingletonScope(); }, { featureId: Symbol('performance') } ); From 59982d178fc1a6510f713c98658d529fe646d29d Mon Sep 17 00:00:00 2001 From: Tobias Ortmayr Date: Mon, 2 Dec 2024 15:59:06 +0100 Subject: [PATCH 4/4] Add detailed diagram loader initialize measurements --- .../src/performance/perf-diagram-loader.ts | 33 ++++++++++++++++--- .../src/performance/perf-model-source.ts | 32 ++++++++++++++++++ .../src/performance/performance-module.ts | 4 ++- 3 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 packages/editor/src/performance/perf-model-source.ts diff --git a/packages/editor/src/performance/perf-diagram-loader.ts b/packages/editor/src/performance/perf-diagram-loader.ts index 4154b0453..875666935 100644 --- a/packages/editor/src/performance/perf-diagram-loader.ts +++ b/packages/editor/src/performance/perf-diagram-loader.ts @@ -1,4 +1,4 @@ -import { DiagramLoader, DiagramLoadingOptions, IDiagramStartup, ResolvedDiagramLoadingOptions } from '@eclipse-glsp/client'; +import { DiagramLoader, DiagramLoadingOptions, IDiagramStartup, ResolvedDiagramLoadingOptions, StatusAction } from '@eclipse-glsp/client'; import { injectable } from 'inversify'; @injectable() @@ -10,9 +10,34 @@ export class PerfDiagramLoader extends DiagramLoader { } protected override async initialize(options: ResolvedDiagramLoadingOptions): Promise { - console.time('DiagramLoader.initialize'); - await super.initialize(options); - console.timeEnd('DiagramLoader.initialize'); + console.time('DiagramLoader.initialize (DI)'); + if (options.enableNotifications) { + console.time('dispatchStatus - (DI)'); + await this.actionDispatcher.dispatch(StatusAction.create('Initializing...', { severity: 'INFO' })); + console.timeEnd('dispatchStatus - (DI)'); + } + + console.time('getClient - (DI)'); + const glspClient = await this.options.glspClientProvider(); + console.timeEnd('getClient - (DI)'); + console.time('startClient - (DI)'); + await glspClient.start(); + console.timeEnd('startClient - (DI)'); + if (!glspClient.initializeResult) { + console.time('initializeServer - (DI)'); + await glspClient.initializeServer(options.initializeParameters); + console.timeEnd('initializeServer - (DI)'); + } + console.time('configureModelSource - (DI)'); + this.modelSource.configure(glspClient); + console.timeEnd('configureModelSource - (DI)'); + + if (options.enableNotifications) { + console.time('clearStatus - (DI)'); + this.actionDispatcher.dispatch(StatusAction.create('', { severity: 'NONE' })); + console.timeEnd('clearStatus - (DI)'); + } + console.timeEnd('DiagramLoader.initialize (DI)'); } protected override async requestModel(options: ResolvedDiagramLoadingOptions): Promise { diff --git a/packages/editor/src/performance/perf-model-source.ts b/packages/editor/src/performance/perf-model-source.ts new file mode 100644 index 000000000..4c55c9fc1 --- /dev/null +++ b/packages/editor/src/performance/perf-model-source.ts @@ -0,0 +1,32 @@ +import { Disposable, GLSPClient, GLSPModelSource } from '@eclipse-glsp/client'; +import { injectable } from 'inversify'; + +@injectable() +export class PerfGLSPModelSource extends GLSPModelSource { + async configure(glspClient: GLSPClient): Promise { + console.time('GLSPModelSource.configure (MS)'); + this.glspClient = glspClient; + if (!glspClient.initializeResult) { + throw new Error('Could not configure model source. The GLSP client is not initialized yet!'); + } + + console.time('createInitializeClientSessionParameters (MS)'); + const initializeParams = this.createInitializeClientSessionParameters(glspClient.initializeResult); + console.timeEnd('createInitializeClientSessionParameters (MS)'); + + console.time('configureServerActions (MS)'); + this.configureServeActions(glspClient.initializeResult); + console.timeEnd('configureServerActions (MS)'); + + this.toDispose.push( + glspClient.onActionMessage(message => this.messageReceived(message), this.clientId), + Disposable.create(() => glspClient.disposeClientSession(this.createDisposeClientSessionParameters())) + ); + + console.time('initializeClientSession (MS)'); + const result = await glspClient!.initializeClientSession(initializeParams); + console.timeEnd('initializeClientSession (MS)'); + console.timeEnd('GLSPModelSource.configure (MS)'); + return result; + } +} diff --git a/packages/editor/src/performance/performance-module.ts b/packages/editor/src/performance/performance-module.ts index eafceb3a8..745bc57cc 100644 --- a/packages/editor/src/performance/performance-module.ts +++ b/packages/editor/src/performance/performance-module.ts @@ -1,8 +1,9 @@ -import { DiagramLoader, FeatureModule, GLSPActionDispatcher, ModelViewer, TYPES } from '@eclipse-glsp/client'; +import { DiagramLoader, FeatureModule, GLSPActionDispatcher, GLSPModelSource, ModelViewer, TYPES } from '@eclipse-glsp/client'; import { PerfActionDispatcher } from './perf-action-dispatcher'; import { PerfCommandStack } from './perf-command-stack'; import { PerfDiagramLoader } from './perf-diagram-loader'; import { PerfModelViewer } from './perf-viewer'; +import { PerfGLSPModelSource } from './perf-model-source'; export function createPerformanceModule(enabled?: boolean): FeatureModule { return new FeatureModule( @@ -14,6 +15,7 @@ export function createPerformanceModule(enabled?: boolean): FeatureModule { rebind(GLSPActionDispatcher).to(PerfActionDispatcher).inSingletonScope(); rebind(ModelViewer).to(PerfModelViewer).inSingletonScope(); rebind(TYPES.ICommandStack).to(PerfCommandStack).inSingletonScope(); + rebind(GLSPModelSource).to(PerfGLSPModelSource).inSingletonScope(); }, { featureId: Symbol('performance') } );