From 2fd26add1a985a08a109cf047cff8490b8bec3a2 Mon Sep 17 00:00:00 2001 From: Administrator Date: Sun, 21 Jun 2026 04:10:43 +0200 Subject: [PATCH] perf(main): bump UV_THREADPOOL_SIZE to 64 + instrument event-loop delay MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The user runs 50+ concurrent uploads (parallel counts raised deliberately). Every async uploader feeds undici from fs.createReadStream and resolves DNS via getaddrinfo — both go through the libuv threadpool, whose default size is 4. At 50 concurrent uploads, file reads and lookups serialize 4-at-a-time: a hard cliff at a small connection count that matches the 'lags from X connections onward' symptom. Raise the cap to 64 as the first statement (before require('electron'), so libuv reads it when it lazily inits the pool; an explicit env override still wins). Threads are created on demand, so a higher max costs nothing when unused — reversible, zero upload-core change. Also enable perf_hooks.monitorEventLoopDelay and log mean/p99/max/stddev every ~5s while uploading. This is the ground-truth instrument that splits the two competing explanations for the lag: a high event-loop delay means the main thread is CPU-blocked (TLS/crypto) and only workers or a concurrency cap will help; a low delay while uploads stall means the work is IO-bound and the threadpool/socket config is the lever, not workers. The numbers are pure metrics — they never touch the credential-redaction path. Co-Authored-By: Claude Opus 4.8 (1M context) --- main.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/main.js b/main.js index 7c7c938..3a22ca7 100644 --- a/main.js +++ b/main.js @@ -1,3 +1,5 @@ +process.env.UV_THREADPOOL_SIZE = process.env.UV_THREADPOOL_SIZE || '64'; +const { monitorEventLoopDelay } = require('perf_hooks'); const { app, BrowserWindow, ipcMain, dialog, clipboard, nativeTheme, Tray, Menu } = require('electron'); nativeTheme.themeSource = 'dark'; const path = require('path'); @@ -25,6 +27,10 @@ const stats = require('./lib/stats'); const { createCollectors } = require('./lib/diagnostics-collectors'); const { createAgent } = require('./lib/diagnostics-agent'); +const _eventLoopDelay = monitorEventLoopDelay({ resolution: 10 }); +_eventLoopDelay.enable(); +let _eldLastLog = 0; + let mainWindow; let _lastImportPath = null; let dropTargetWindow = null; @@ -181,6 +187,21 @@ function logMarker(label, fields) { debugLog(`────── ${label}${extra} ──────`); } +function _maybeLogEventLoopDelay(activeJobs) { + const now = Date.now(); + if (now - _eldLastLog < 5000) return; + _eldLastLog = now; + try { + const ns = 1e6; + const mean = (_eventLoopDelay.mean / ns).toFixed(1); + const max = (_eventLoopDelay.max / ns).toFixed(1); + const p99 = (_eventLoopDelay.percentile(99) / ns).toFixed(1); + const stddev = (_eventLoopDelay.stddev / ns).toFixed(1); + logInfo('perf', `eventloop-delay active=${activeJobs} mean=${mean}ms p99=${p99}ms max=${max}ms stddev=${stddev}ms threadpool=${process.env.UV_THREADPOOL_SIZE}`); + _eventLoopDelay.reset(); + } catch {} +} + // Dedicated account-rotation log so users can trace fallback decisions // without wading through general debug output. Writes to account-rotation.log // in the same directory as fileuploader.log (honors user's configured path). @@ -1705,6 +1726,7 @@ ipcMain.handle('start-upload', (_event, payload) => { if (data.state === 'uploading' && data.activeJobs > 0) { const speedMb = ((Number(data.globalSpeedKbs) || 0) / 1024).toFixed(1); updateTrayTooltip(`Upload: ${data.activeJobs} aktiv - ${speedMb} MB/s`); + _maybeLogEventLoopDelay(data.activeJobs); } else { updateTrayTooltip('Multi-Hoster-Upload'); }