(
entries: { logsDir: string; dirEntries: fs.Dirent[] }[],
totalDirs: number,
ctx: ParseContext,
onProgress?: ProgressCallback,
isColdParse = true,
)
| 348 | } |
| 349 | |
| 350 | async function processWorkspaces( |
| 351 | entries: { logsDir: string; dirEntries: fs.Dirent[] }[], |
| 352 | totalDirs: number, |
| 353 | ctx: ParseContext, |
| 354 | onProgress?: ProgressCallback, |
| 355 | isColdParse = true, |
| 356 | ): Promise<void> { |
| 357 | const effectiveMaxPrefetch = isColdParse |
| 358 | ? Math.min(COLD_PARSE_MAX_PREFETCH_FILES, MAX_PREFETCH_FILES) |
| 359 | : MAX_PREFETCH_FILES; |
| 360 | const effectiveMaxPrefetchBytes = isColdParse ? COLD_PARSE_MAX_PREFETCH_BYTES : MAX_PREFETCH_BYTES; |
| 361 | const tWorkList = Date.now(); |
| 362 | const work = await buildWorkspaceWorkList(entries); |
| 363 | const workListMs = Date.now() - tWorkList; |
| 364 | const planItems = buildWorkspacePlan(work); |
| 365 | |
| 366 | // Phase-2 gap attribution (issue #106 follow-up). The cold-parse-breakdown showed the actual |
| 367 | // file parsing is a small fraction of phase 2; these accumulators reveal where the rest of the |
| 368 | // wall-clock goes (workspace listing vs prefetch I/O wait vs per-workspace processing). |
| 369 | let prefetchWaitMs = 0; |
| 370 | let entryWallMs = 0; |
| 371 | |
| 372 | // Build the workspace-level loading plan in processing order. |
| 373 | if (onProgress && planItems.length > 0) { |
| 374 | onProgress({ |
| 375 | phase: 2, |
| 376 | detail: `Scanning ${totalDirs} workspace folders for sessions`, |
| 377 | pct: pct(2, 0), |
| 378 | sessions: ctx.sessions.length, |
| 379 | workspacePlan: planItems, |
| 380 | }); |
| 381 | await yieldToLoop(); |
| 382 | } |
| 383 | |
| 384 | let processed = 0; |
| 385 | let lastLocIndex = 0; |
| 386 | let strippedUpTo = 0; |
| 387 | const running = createRunningTotals(); |
| 388 | |
| 389 | function foldNewSessions(): void { |
| 390 | for (; lastLocIndex < ctx.sessions.length; lastLocIndex++) running.add(ctx.sessions[lastLocIndex]); |
| 391 | } |
| 392 | |
| 393 | try { |
| 394 | for (let i = 0; i < work.length; i += BATCH_SIZE) { |
| 395 | const batch = work.slice(i, i + BATCH_SIZE); |
| 396 | const nextBatch = work.slice(i + BATCH_SIZE, i + BATCH_SIZE * 2); |
| 397 | |
| 398 | if (i === 0) { |
| 399 | const tPre = Date.now(); |
| 400 | await prefetchBatch(batch, effectiveMaxPrefetch, effectiveMaxPrefetchBytes); |
| 401 | prefetchWaitMs += Date.now() - tPre; |
| 402 | } |
| 403 | |
| 404 | const nextPrefetch = nextBatch.length > 0 ? prefetchBatch(nextBatch, effectiveMaxPrefetch, effectiveMaxPrefetchBytes) : Promise.resolve(); |
| 405 | |
| 406 | let lastWsName = ''; |
| 407 | for (const { logsDir, wsId, harness, workspaceKey } of batch) { |
no test coverage detected