This is an absolutely must read resource: [https://www.moritzjung.dev/obsidian-collection/plugin-dev/debuggingandprofiling/](https://www.moritzjung.dev/obsidian-collection/plugin-dev/debuggingandprofiling/ "https://www.moritzjung.dev/obsidian-collection/plugin-dev/debuggingandprofiling/") Plus this [article](https://alan.norbauer.com/articles/browser-debugging-tricks)by [Fevol](https://github.com/Fevol) On top of those above, I've implemented the following in the Excalidraw plugin: In my main Plugin class I overrode registerEvent to monitor duration of events: ```typescript export default class ExcalidrawPlugin extends Plugin { //.... public registerEvent(event: any) { if(!isDebugMode) { super.registerEvent(event); return; } const originalHandler = event.fn; // Wrap the original event handler const wrappedHandler = async (...args: any[]) => { const startTime = performance.now(); // Get start time // Invoke the original event handler const result = await originalHandler(...args); const endTime = performance.now(); // Get end time const executionTime = endTime - startTime; if(executionTime > durationTreshold) { console.log(`Excalidraw Event '${event.name}' took ${executionTime}ms to execute`); } return result; }; // Replace the original event handler with the wrapped one event.fn = wrappedHandler; // Register the modified event super.registerEvent(event); } ``` And I created a DebugHelper.ts that includes a wrapper for MutationObservers - I have couple of those in the code - and those can become resource-intensive. ```typescript export const isDebugMode = true; export const durationTreshold = 2; //ms export class CustomMutationObserver { private originalCallback: MutationCallback; private observer: MutationObserver | null; private name: string; constructor(callback: MutationCallback, name: string) { this.originalCallback = callback; this.observer = null; this.name = name; } observe(target: Node, options: MutationObserverInit) { const wrappedCallback: MutationCallback = async (mutationsList, observer) => { const startTime = performance.now(); // Get start time await this.originalCallback(mutationsList, observer); // Invoke the original callback const endTime = performance.now(); // Get end time const executionTime = endTime - startTime; if (executionTime > durationTreshold) { console.log(`Excalidraw ${this.name} MutationObserver callback took ${executionTime}ms to execute`); } }; this.observer = new MutationObserver(wrappedCallback); // Start observing with the modified callback this.observer.observe(target, options); } disconnect() { if (this.observer) { this.observer.disconnect(); this.observer = null; } } } ``` And here's how I modified my mutation observers' declarations: ```typescript export const observer = isDebugMode ? new CustomMutationObserver(legacyExcalidrawPopoverObserverFn, "legacyExcalidrawPopoverObserverFn") : new MutationObserver(legacyExcalidrawPopoverObserverFn); ```