Profiling and Timing Investigation¶
This guide documents the internal profiling helpers currently available for export and save flows in the Electron desktop app.
These flags are intended for development and debugging. They are not part of the end-user UI.
Prerequisites¶
Run the desktop app:
make run-app
Open the project in Electron and open DevTools in the renderer process.
ELPX Export Timing Profiler¶
Use this profiler when investigating delays during:
- Export to
.elpx - "Download project" in offline/Electron mode
- Delays before the native save dialog appears
Enable¶
window.eXeLearning.config.debugElpxExport = true;
window.eXeLearning.config.debugElpxExportIncludeCaller = true;
Run¶
Trigger the normal .elpx export from the UI.
Results¶
After the export finishes or is cancelled:
window.__lastElpxExportSummary
window.__lastElpxExportTimeline
Important summary fields¶
totalElapsedMs: end-to-end export timezipGenerateMs: ZIP creation timeelectronSaveMs: full Electron save phaseelectronPromptMs: native save dialog phaseelectronNormalizeMs: renderer payload ->Buffernormalization in Electron mainelectronWriteMs: final disk write timedeflatedFiles/storedFiles: how many files were compressed vs stored as-isdeflatedBytes/storedBytes: byte totals for each group
Useful timeline phases¶
bridge:exporter:run:start/endexporter:preprocess-pages:start/endexporter:asset-export-map:start/endexporter:assets-to-zip:start/endexporter:zip-generate:start/endbridge:electron:dialog:start/endbridge:electron:buffer-normalize:start/endbridge:electron:write:start/end
Quick inspection snippets¶
Largest phases:
window.__lastElpxExportTimeline
.slice()
.sort((a, b) => b.elapsedMs - a.elapsedMs);
Only ZIP and Electron save phases:
window.__lastElpxExportTimeline.filter(entry =>
entry.phase.includes('zip-generate') ||
entry.phase.includes('bridge:electron:')
);
How to read the data¶
- If
zipGenerateMsis dominant, the bottleneck is archive generation/compression. - If
electronPromptMsis dominant, the native dialog is the slow phase. - If
electronNormalizeMsis dominant, the main-process payload conversion is expensive. - If
electronWriteMsis dominant, the bottleneck is disk I/O.
Save Memory Profiler¶
Use this profiler when investigating high RAM usage or long save times in the Yjs/Electron save flow.
Enable¶
window.eXeLearning.config.debugSaveMemory = true;
Optional experiment flags:
window.eXeLearning.config.saveMemoryExperiment = 'auto';
Supported values:
'auto': current default behavior'baseline': restore pre-optimization Electron small-asset batching'small-session-batches': lower session byte cap'legacy-batches': force legacy sequential batches'yjs-only': skip asset upload'assets-only': skip Yjs upload
Optional batch-size overrides:
window.eXeLearning.config.saveMemorySessionBatchBytes = 5 * 1024 * 1024;
window.eXeLearning.config.saveMemoryBatchBytes = 5 * 1024 * 1024;
Run¶
Trigger a normal save from the UI.
Results¶
window.__lastSaveMemorySummary
window.__lastSaveMemoryTimeline
Important memory fields¶
rssheapUsedheapTotalexternalarrayBuffersrendererWorkingSetSizerendererPeakWorkingSetSizerendererPrivateBytes
Important save phases¶
save:startyjs:serialize:start/endyjs:upload:start/endassets:metadata:start/endbatch:blob-load:start/endbatch:formdata:start/endbatch:request:start/endbatch:mark-uploaded:start/endsave:endsave:delayed+3000ms
How to read the data¶
- Peak before upload starts usually points to Yjs serialization.
- Peak during blob-load points to asset loading pressure.
- Peak during
formdataorrequestusually points to multipart/request buffering. - High delayed samples suggest retained references after save completion.
Other Useful Helpers¶
Keep the logs clean¶
Reset debug flags after a run:
delete window.eXeLearning.config.debugElpxExport;
delete window.eXeLearning.config.debugElpxExportIncludeCaller;
delete window.eXeLearning.config.debugSaveMemory;
delete window.eXeLearning.config.saveMemoryExperiment;
delete window.eXeLearning.config.saveMemorySessionBatchBytes;
delete window.eXeLearning.config.saveMemoryBatchBytes;
Compare two runs manually¶
Capture a copy before changing flags:
const run1 = structuredClone(window.__lastElpxExportSummary);
const run2 = structuredClone(window.__lastSaveMemorySummary);
Inspect the last 20 export phases¶
window.__lastElpxExportTimeline.slice(-20);
Inspect the highest memory samples¶
window.__lastSaveMemoryTimeline
.slice()
.sort((a, b) => (b.rss || 0) - (a.rss || 0))
.slice(0, 10);