Skip to content

perf(coverage): speed up report generation (bounded-memory merge + precompiled globs)#10506

Open
toxik wants to merge 4 commits into
vitest-dev:mainfrom
toxik:perf/coverage-generation
Open

perf(coverage): speed up report generation (bounded-memory merge + precompiled globs)#10506
toxik wants to merge 4 commits into
vitest-dev:mainfrom
toxik:perf/coverage-generation

Conversation

@toxik
Copy link
Copy Markdown

@toxik toxik commented Jun 2, 2026

Description

Two independent, self-contained performance fixes to coverage report generation (generateCoverage / BaseCoverageProvider), measured on a real 1124-test-file app (~39 MB of raw V8 coverage across the run). Numbers below are best-of-3 runs, baseline vs this PR on the same machine:

Phase Before After
read + merge ~20.8s ~0.30s
convert (incl. isIncluded filtering) ~22.2s ~2.6s
total generateCoverage ~43s ~2.9s

Peak heap during coverage generation drops from ~2.5 GB → ~1.15 GB, and resident raw coverage is now bounded to ≤16 MB regardless of suite size.

isIncluded runs per covered file inside convertCoverage, so the glob fix's savings land in the convert row above; the pure astV8ToIstanbul transform (~2.6s) is itself unchanged.

Tests

No behavior change. Both fixes are verified against the existing test/coverage-test suites (v8 + istanbul), which assert coverage output and the set of included files. pnpm typecheck and pnpm lint pass. A backport to the v4 release line will follow.

🤖 Generated with Claude Code

@github-actions github-actions Bot added the maybe automated User is likely an AI agent, or the content was generated by an AI assistant without user control label Jun 2, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 2, 2026

Hello @toxik. Your PR has been labeled maybe automated because it appears to have been fully generated by AI with no human involvement.

To keep your PR open, please follow these steps:

  • Confirm that you are a real human. If you are an automated agent, disclose that
  • Confirm you've read, reviewed and stand behind its content
  • Confirm you've read the full issue along with all of its comments, as well as any linked issues and their comments
  • Make sure it follows our contribution guidelines and uses the correct GitHub template
  • Disclose any AI tools you used (e.g. Claude, Copilot, Codex)

Please, do not generate or format the response with AI. If you do not speak English, reply in your native language or use translation software like Google Translate or Deepl. If the response is generated, the PR will be closed automatically.

These measures help us reduce maintenance burden and keep the team's work efficient. See our AI contributions policy for more context.

@netlify
Copy link
Copy Markdown

netlify Bot commented Jun 2, 2026

Deploy Preview for vitest-dev ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit 65dd2ad
🔍 Latest deploy log https://app.netlify.com/projects/vitest-dev/deploys/6a1fe1045c43a30008811159
😎 Deploy Preview https://deploy-preview-10506--vitest-dev.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

@toxik
Copy link
Copy Markdown
Author

toxik commented Jun 2, 2026

Hello @toxik. Your PR has been labeled maybe automated because it appears to have been fully generated by AI with no human involvement.

To keep your PR open, please follow these steps:

  • Confirm that you are a real human. If you are an automated agent, disclose that
  • Confirm you've read, reviewed and stand behind its content
  • Confirm you've read the full issue along with all of its comments, as well as any linked issues and their comments
  • Make sure it follows our contribution guidelines and uses the correct GitHub template
  • Disclose any AI tools you used (e.g. Claude, Copilot, Codex)

Please, do not generate or format the response with AI. If you do not speak English, reply in your native language or use translation software like Google Translate or Deepl. If the response is generated, the PR will be closed automatically.

These measures help us reduce maintenance burden and keep the team's work efficient. See our AI contributions policy for more context.

Hello,

I am indeed a human and there was indeed coding agent help with this PR from Claude Code.
I've reviewed and tested the code prior to opening the PR, as well as tested it in a large project to confirm the numbers.

@sheremet-va sheremet-va removed the maybe automated User is likely an AI agent, or the content was generated by an AI assistant without user control label Jun 2, 2026
@vitest-dev vitest-dev deleted a comment from vitest-ecosystem-ci Bot Jun 3, 2026
@vitest-dev vitest-dev deleted a comment from vitest-ecosystem-ci Bot Jun 3, 2026
@vitest-ecosystem-ci
Copy link
Copy Markdown

vitest-ecosystem-ci Bot commented Jun 3, 2026

📝 Ran ecosystem CI: Open

suite result
vitest-coverage-large ✅ success

Copy link
Copy Markdown
Member

@AriPerkkio AriPerkkio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The early coverage map merging is there intentionally. We cannot hold all coverage results in memory at once, as it would lead to out-of-memory crashes. That's the reason why we do file-system round-trip in the first place, instead of just holding everything in memory.

Related issue:

Comment thread packages/coverage-v8/src/provider.ts Outdated

// mergeProcessCovs sometimes loses autoAttachSubprocess
const fromExtendedContext = autoAttachSubprocess ? coverage.result.filter(r => r.isExtendedContext) : []
coverages.push(coverage)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We cannot hold these in-memory as it would cause out-of-memory issues in large projects.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review!
I'll look into what can be done to reduce the memory footprint.
In the meantime, would it make sense to open a separate PR for the regex optimization?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup sounds good! Also if it makes sense to optimize the startOffset and autoAttachSubprocess handling separately, feel free to include them there.

Copy link
Copy Markdown
Author

@toxik toxik Jun 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left things in the same PR though 😅
batched the files into ~16MB chunks to still keep O(n) instead of O(n^2)
01c2c27

Comment thread packages/vitest/src/node/coverage.ts Outdated

this._globMatchers = {
matchExclude: exclude.length ? pm(exclude, { dot: true }) : () => false,
matchInclude: pm(include, { dot: true, ignore: exclude }),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could probably also check this.options.include, and default to () => true when it's falsy. 🤔

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

toxik and others added 3 commits June 3, 2026 11:03
generateCoverage() folded mergeProcessCovs([merged, coverage]) once per coverage
file and re-scanned the growing merged.result on every file to repair
startOffset/isExtendedContext, making the read phase O(n^2) in the number of
coverage files.

Collect the raw coverages and merge them in a single pass, restoring the dropped
fields via lookup maps built while reading.

Profiled on a real 1124-test-file app: the read+merge phase dropped from ~24.5s
to ~0.3s (single merge ~95ms). Together with the isIncluded fix, total coverage
generation went from 52.8s to 7.1s.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
isIncluded() called picomatch.isMatch(file, patterns, options) per file, which
recompiles every glob into a regex on each call. The globCache does not help
during coverage filtering because each result entry has a unique filename, so
every call paid the full compile cost.

Compile the include/exclude matchers once and reuse them.

Profiled on a real 1124-test-file app: the include/exclude filtering step dropped
from ~23.3s to ~0.38s (~61x), with identical results. Together with the
single-pass merge, total coverage generation went from 52.8s to 7.1s.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@toxik toxik force-pushed the perf/coverage-generation branch from 14a9977 to 65dd2ad Compare June 3, 2026 08:08
Co-authored-by: Ari Perkkiö <ari.perkkio@gmail.com>
@toxik toxik force-pushed the perf/coverage-generation branch from 65dd2ad to 43c3903 Compare June 3, 2026 08:10
@toxik toxik changed the title perf(coverage): speed up report generation (single-pass merge + precompiled globs) perf(coverage): speed up report generation (bounded-memory merge + precompiled globs) Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants