Skip to content

Comments

test(message-parser): add benchmark suite for parser performance baseline#38905

Open
smirk-dev wants to merge 1 commit intoRocketChat:developfrom
smirk-dev:test/message-parser-benchmark-suite
Open

test(message-parser): add benchmark suite for parser performance baseline#38905
smirk-dev wants to merge 1 commit intoRocketChat:developfrom
smirk-dev:test/message-parser-benchmark-suite

Conversation

@smirk-dev
Copy link
Contributor

@smirk-dev smirk-dev commented Feb 22, 2026

Summary

Adds a dedicated benchmark suite for @rocket.chat/message-parser to measure parser throughput across representative message patterns.

What changed

  • Adds benchmarks/parser.bench.test.ts using tinybench
  • Covers plain text, emphasis, links, emoji, mentions, code blocks, spoilers, and mixed messages
  • Adds benchmark scripts in packages/message-parser/package.json

Why

This establishes a measurable baseline for the High-Performance Message Parser Rewrite project and enables before/after performance comparisons.

Testing

  • Ran benchmark suite locally via Jest bench entry

Summary by CodeRabbit

  • Tests

    • Added comprehensive performance benchmark suite for the message parser covering multiple parsing categories
  • Chores

    • Added bench and bench:size npm scripts for running performance benchmarks and checking bundle size
    • Added tinybench and ts-node to development dependencies

Add a comprehensive performance benchmark suite for @rocket.chat/message-parser
using tinybench. This establishes baseline performance metrics across 11
categories of message parsing, which can be used to measure improvements
from the High-Performance Message Parser Rewrite GSoC project.

Categories benchmarked:
- Plain text (short/medium/long)
- Emphasis/formatting (bold, italic, strike, nested, multiple)
- URLs and auto-linking
- Emoji (shortcode, unicode, BigEmoji)
- Mentions (user, channel)
- Code (inline, block)
- Structured blocks (lists, tasks, blockquotes, headings, spoilers)
- KaTeX math expressions
- Adversarial / stress test inputs
- Real-world mixed messages
- Timestamps

Also adds a bench:size script to report bundle size metrics.

Run with: yarn bench (in packages/message-parser/)
@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Feb 22, 2026

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label
  • This PR is missing the required milestone or project

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@changeset-bot
Copy link

changeset-bot bot commented Feb 22, 2026

⚠️ No Changeset found

Latest commit: 3fc2ae9

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 22, 2026

Walkthrough

A comprehensive performance benchmarking suite is added to the message-parser package. The suite includes a new Jest-based benchmark test file using TinyBench to measure parsing performance across 14+ categories, along with new npm scripts and dependencies to execute and analyze the results.

Changes

Cohort / File(s) Summary
Benchmark Suite
packages/message-parser/benchmarks/parser.bench.test.ts
New comprehensive performance benchmark test measuring parsing throughput across 14+ categories including plain text, formatting, URLs, emoji, mentions, code blocks, lists, blockquotes, KaTeX, adversarial inputs, real-world messages, and timestamps using TinyBench.
Package Configuration
packages/message-parser/package.json
Added benchmark scripts (bench, bench:size) and new dev dependencies (tinybench, ts-node) for running performance measurements.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 Hop, hop, let's measure the speed!
Benchmarks are planted like clover seed,
Fourteen categories, fixtures so grand,
TinyBench runs them across the land!
Parse it quick, parse it true—
Performance metrics, now we have the clue!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and directly describes the main change: adding a benchmark suite for parser performance in the message-parser package.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/message-parser/benchmarks/parser.bench.test.ts (1)

38-38: bench script should use --runInBand to avoid process-level interference with benchmark timing.

When Jest spawns worker processes (the default), the OS scheduler and inter-process communication can introduce jitter that inflates variance in the timing measurements. For a baseline benchmark, a single-process run produces more consistent results.

♻️ Proposed change
- "bench": "jest --testPathPatterns='parser.bench' --no-coverage --verbose",
+ "bench": "jest --testPathPatterns='parser.bench' --no-coverage --verbose --runInBand",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/message-parser/benchmarks/parser.bench.test.ts` at line 38, Update
the project's Jest benchmark invocation to run in a single process by adding the
--runInBand flag to the "bench" script so Jest does not spawn worker processes;
locate the npm script named "bench" (in the package.json scripts section) and
append --runInBand to the jest command invocation to reduce OS
scheduler/inter-process jitter during parser benchmarks.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3145c41 and 3fc2ae9.

📒 Files selected for processing (2)
  • packages/message-parser/benchmarks/parser.bench.test.ts
  • packages/message-parser/package.json
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • packages/message-parser/benchmarks/parser.bench.test.ts
🧠 Learnings (2)
📓 Common learnings
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 37419
File: packages/i18n/src/locales/en.i18n.json:918-921
Timestamp: 2025-11-19T18:20:07.720Z
Learning: Repo: RocketChat/Rocket.Chat — i18n/formatting
Learning: This repository uses a custom message formatting parser in UI blocks/messages; do not assume standard Markdown rules. For keys like Call_ended_bold, Call_not_answered_bold, Call_failed_bold, and Call_transferred_bold in packages/i18n/src/locales/en.i18n.json, retain the existing single-asterisk emphasis unless maintainers request otherwise.
📚 Learning: 2025-12-10T21:00:54.909Z
Learnt from: KevLehman
Repo: RocketChat/Rocket.Chat PR: 37091
File: ee/packages/abac/jest.config.ts:4-7
Timestamp: 2025-12-10T21:00:54.909Z
Learning: Rocket.Chat monorepo: Jest testMatch pattern '<rootDir>/src/**/*.spec.(ts|js|mjs)' is valid in this repo and used across multiple packages (e.g., packages/tools, ee/packages/omnichannel-services). Do not flag it as invalid in future reviews.

Applied to files:

  • packages/message-parser/benchmarks/parser.bench.test.ts
🧬 Code graph analysis (1)
packages/message-parser/benchmarks/parser.bench.test.ts (1)
packages/message-parser/src/utils.ts (1)
  • task (53-57)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: 📦 Build Packages
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: CodeQL-Build
🔇 Additional comments (2)
packages/message-parser/package.json (2)

38-38: --testPathPatterns (plural) is correct for Jest 30 — LGTM.

The --testPathPattern flag was renamed to --testPathPatterns in Jest 30, so the bench script is correctly using the updated flag.


64-64: No changes needed—ts-node is actually required.

Jest 30.2.0 requires ts-node to load TypeScript configuration files (jest.config.ts) by default. When the bench script runs jest, Jest must read and parse the jest.config.ts file, which depends on ts-node since no alternate loader (like esbuild-register) is specified via a docblock pragma in the config. This is a transitive dependency, not a direct script invocation, which is why it's necessary to keep.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/message-parser/benchmarks/parser.bench.test.ts`:
- Around line 99-101: The fixture named spoilerWithFormatting is defined but
never used; either remove the unused fixture from the fixtures object or add it
to the benchmark group blockBench so it is exercised. To fix, locate the
fixtures definition (symbol: spoilerWithFormatting) and either delete that
entry, or add a corresponding .add(...) call to blockBench (the benchmark group
symbol: blockBench) using fixtures.spoilerWithFormatting so the fixture is
included alongside fixtures.spoiler.
- Around line 237-244: Enable sample retention by passing retainSamples: true to
every Bench constructor (the Bench(...) calls) so task.result?.samples will be
populated; replace unsafe non-null assertions like task.result! with optional
chaining and sensible fallbacks in the reporting code (e.g., use task.result?.hz
?? 0, task.result?.mean ?? 0, and task.result?.samples.length ?? 0) when
building the table and summary so an errored task or missing result cannot crash
the report; update the summary sorting/slicing usages to rely on these safe
fallbacks as well.

---

Nitpick comments:
In `@packages/message-parser/benchmarks/parser.bench.test.ts`:
- Line 38: Update the project's Jest benchmark invocation to run in a single
process by adding the --runInBand flag to the "bench" script so Jest does not
spawn worker processes; locate the npm script named "bench" (in the package.json
scripts section) and append --runInBand to the jest command invocation to reduce
OS scheduler/inter-process jitter during parser benchmarks.

Comment on lines +99 to +101
// Category 13: Spoiler
spoiler: '||This is a spoiler||',
spoilerWithFormatting: '||**bold** and _italic_ spoiler||',
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

spoilerWithFormatting fixture is defined but never added to any benchmark group.

The fixture at line 101 is never passed to any .add() call — blockBench only uses fixtures.spoiler. This is dead code.

🛠️ Options

Either remove the fixture, or add it to blockBench:

  blockBench
    .add('block: ordered list', () => parse(fixtures.orderedList))
    .add('block: unordered list', () => parse(fixtures.unorderedList))
    .add('block: task list', () => parse(fixtures.taskList))
    .add('block: blockquote', () => parse(fixtures.blockquote))
    .add('block: heading', () => parse(fixtures.heading))
    .add('block: heading multi-level', () => parse(fixtures.headingMultiLevel))
-   .add('block: spoiler', () => parse(fixtures.spoiler));
+   .add('block: spoiler', () => parse(fixtures.spoiler))
+   .add('block: spoiler with formatting', () => parse(fixtures.spoilerWithFormatting));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/message-parser/benchmarks/parser.bench.test.ts` around lines 99 -
101, The fixture named spoilerWithFormatting is defined but never used; either
remove the unused fixture from the fixtures object or add it to the benchmark
group blockBench so it is exercised. To fix, locate the fixtures definition
(symbol: spoilerWithFormatting) and either delete that entry, or add a
corresponding .add(...) call to blockBench (the benchmark group symbol:
blockBench) using fixtures.spoilerWithFormatting so the fixture is included
alongside fixtures.spoiler.

Comment on lines +237 to +244
'ops/sec': Math.round(task.result!.hz).toLocaleString(),
'Avg (ms)': (task.result!.mean * 1000).toFixed(4),
'Min (ms)': (task.result!.min * 1000).toFixed(4),
'Max (ms)': (task.result!.max * 1000).toFixed(4),
'P99 (ms)': (task.result!.p99 * 1000).toFixed(4),
'Samples': task.result!.samples.length,
})),
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

task.result!.samples.length will always report 0retainSamples must be enabled.

By default, tinybench does not keep the samples for latency and throughput to minimize memory usage. To enable sample retention you need to set the retainSamples option to true when creating a Bench instance. Without it, task.result!.samples is always an empty array, so the Samples column is permanently 0 across every group, making that column meaningless.

Additionally, the bare ! non-null assertion on task.result is unsafe: if a task errors during bench.run() (which the adversarial inputs could trigger against the PEG parser), result may be undefined, causing a crash at the reporting step. Prefer optional chaining with a fallback.

🔧 Proposed fix
- const plainTextBench = new Bench({ time: 1000, warmupTime: 200 });
+ const plainTextBench = new Bench({ time: 1000, warmupTime: 200, retainSamples: true });

Apply retainSamples: true to all Bench constructors, and replace non-null assertions with optional chaining in the table map:

  bench.tasks.map((task) => ({
-   'Task': task.name,
-   'ops/sec': Math.round(task.result!.hz).toLocaleString(),
-   'Avg (ms)': (task.result!.mean * 1000).toFixed(4),
-   'Min (ms)': (task.result!.min * 1000).toFixed(4),
-   'Max (ms)': (task.result!.max * 1000).toFixed(4),
-   'P99 (ms)': (task.result!.p99 * 1000).toFixed(4),
-   'Samples': task.result!.samples.length,
+   'Task': task.name,
+   'ops/sec': Math.round(task.result?.hz ?? 0).toLocaleString(),
+   'Avg (ms)': ((task.result?.mean ?? 0) * 1000).toFixed(4),
+   'Min (ms)': ((task.result?.min ?? 0) * 1000).toFixed(4),
+   'Max (ms)': ((task.result?.max ?? 0) * 1000).toFixed(4),
+   'P99 (ms)': ((task.result?.p99 ?? 0) * 1000).toFixed(4),
+   'Samples': task.result?.samples.length ?? 0,
  })),

And in the summary sort/slice:

- const sorted = [...allTasks].sort((a, b) => a.result!.hz - b.result!.hz);
+ const sorted = [...allTasks].sort((a, b) => (a.result?.hz ?? 0) - (b.result?.hz ?? 0));

- console.log(`    ${Math.round(task.result!.hz).toLocaleString().padStart(12)} ops/sec  │  ${task.name}`);
+ console.log(`    ${Math.round(task.result?.hz ?? 0).toLocaleString().padStart(12)} ops/sec  │  ${task.name}`);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/message-parser/benchmarks/parser.bench.test.ts` around lines 237 -
244, Enable sample retention by passing retainSamples: true to every Bench
constructor (the Bench(...) calls) so task.result?.samples will be populated;
replace unsafe non-null assertions like task.result! with optional chaining and
sensible fallbacks in the reporting code (e.g., use task.result?.hz ?? 0,
task.result?.mean ?? 0, and task.result?.samples.length ?? 0) when building the
table and summary so an errored task or missing result cannot crash the report;
update the summary sorting/slicing usages to rely on these safe fallbacks as
well.

@codecov
Copy link

codecov bot commented Feb 22, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 70.55%. Comparing base (3145c41) to head (3fc2ae9).

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff            @@
##           develop   #38905   +/-   ##
========================================
  Coverage    70.55%   70.55%           
========================================
  Files         3189     3189           
  Lines       112703   112703           
  Branches     20429    20429           
========================================
  Hits         79519    79519           
  Misses       31123    31123           
  Partials      2061     2061           
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 2 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/message-parser/benchmarks/parser.bench.test.ts">

<violation number="1" location="packages/message-parser/benchmarks/parser.bench.test.ts:270">
P2: Benchmark file with `.test.ts` extension will be executed during standard test runs, unnecessarily slowing down CI. Consider excluding benchmark files from default test discovery via `testPathIgnorePatterns` or renaming to not match test patterns.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@@ -0,0 +1,276 @@
/**
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 22, 2026

Choose a reason for hiding this comment

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

P2: Benchmark file with .test.ts extension will be executed during standard test runs, unnecessarily slowing down CI. Consider excluding benchmark files from default test discovery via testPathIgnorePatterns or renaming to not match test patterns.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/message-parser/benchmarks/parser.bench.test.ts, line 270:

<comment>Benchmark file with `.test.ts` extension will be executed during standard test runs, unnecessarily slowing down CI. Consider excluding benchmark files from default test discovery via `testPathIgnorePatterns` or renaming to not match test patterns.</comment>

<file context>
@@ -0,0 +1,276 @@
+	console.log();
+}
+
+it(
+	'benchmark: message-parser performance',
+	async () => {
</file context>
Fix with Cubic

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.

1 participant