Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Loadtest E2Es and monitor memory for leak detection #611

Draft
wants to merge 60 commits into
base: main
Choose a base branch
from
Draft

Conversation

enisdenjo
Copy link
Member

@enisdenjo enisdenjo commented Feb 6, 2025

Completes GW-154

TODO

  • Memory increase slope for leak detection
  • Idle memory check after loadtest for leak detection
  • Add more tests
  • Reliably test in CI (making sure memory is not throttled when running concurrently)
  • Fix getting memory stats for Bun

@enisdenjo enisdenjo marked this pull request as draft February 6, 2025 19:39
.github/workflows/memtest.yml Fixed Show fixed Hide fixed
.github/workflows/memtest.yml Fixed Show fixed Hide fixed
@theguild-bot
Copy link
Collaborator

theguild-bot commented Feb 6, 2025

🚀 Snapshot Release (alpha)

The latest changes of this PR are available as alpha on npm (based on the declared changesets):

Package Version Info
@graphql-hive/gateway 1.10.3-alpha-3ceab6d536ae4f3cf122e89c4534ae4552bcdbe9 npm ↗︎ unpkg ↗︎

Copy link

coderabbitai bot commented Feb 6, 2025

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Launched a dedicated workflow for memory testing on code pushes and pull requests.
    • Introduced load testing utilities with performance measurement and visual memory trend generation.
  • Tests

    • Expanded test coverage with enhanced memory performance validations and observability for federated services and telemetry integrations.
    • Added a new script command to run memory tests.
  • Chores

    • Streamlined CI configurations and job naming.
    • Enhanced process management for more reliable testing and stability.

Walkthrough

This pull request refines GitHub Actions workflows by updating the benchmark job and introducing a new memtest workflow. It adds new testing files and utilities for load and memory testing, including regression analysis to monitor memory trends. Several internal modules have been updated to improve server and container management, and TypeScript configurations and package scripts have been extended to support the new memtest functionality.

Changes

File(s) Change Summary
.github/workflows/bench.yml, .github/workflows/memtest.yml Bench workflow updated: removed CI environment variable and simplified job name; new memtest workflow added with concurrency, environment variables, and matrix strategy.
e2e/.../federation-example.memtest.ts Added a new memory test file for the federation example setup.
internal/e2e/src/tenv.ts, internal/testing/src/env.ts Updated server and container interfaces with waitForExit and imported Server; modified isDebug signature to optionally accept a module name.
internal/examples/src/convert.ts Refined file filtering logic by excluding files containing .memtest..
internal/perf/package.json, internal/perf/src/index.ts, internal/perf/src/loadtest-script.ts, internal/perf/src/loadtest.ts, internal/perf/src/memtest.ts Introduced the performance testing module with new interfaces (LoadtestOptions, MemtestOptions), load test and memtest functions, regression slope calculation, and export adjustments.
internal/proc/src/index.ts Revised Proc interface to include waitForExit and added a new Server interface extending Proc.
package.json, tsconfig.json, vitest.projects.ts Added new npm script (test:mem), updated TS path mappings for internal perf modules, and configured a new Vitest project for memtests.
.gitignore Added entry to ignore load test memory snapshot SVG files.

Possibly related PRs

  • Add Node 23 to CI #601: The changes in the main PR, which involve modifications to the .github/workflows/bench.yml file, are related to the changes in the retrieved PR that also modifies the .github/workflows/bench.yml file by updating how the Node.js version is specified. Both PRs involve direct alterations to the same workflow file.

Suggested reviewers

  • dotansimha
  • ardatan

Poem

I'm a bunny, quick and spry,
Leaping through code beneath the sky,
Workflows and tests all in a row,
Memory checks that truly glow.
With hops of joy and lines so neat,
Our PR makes the project complete!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@theguild-bot
Copy link
Collaborator

theguild-bot commented Feb 6, 2025

🚀 Snapshot Release (Binary for Linux-ARM64)

The latest changes of this PR are available for download (based on the declared changesets).

Download

@theguild-bot
Copy link
Collaborator

theguild-bot commented Feb 6, 2025

🚀 Snapshot Release (Binary for Linux-X64)

The latest changes of this PR are available for download (based on the declared changesets).

Download

@theguild-bot
Copy link
Collaborator

theguild-bot commented Feb 6, 2025

🚀 Snapshot Release (Binary for macOS-ARM64)

The latest changes of this PR are available for download (based on the declared changesets).

Download

@theguild-bot
Copy link
Collaborator

theguild-bot commented Feb 6, 2025

🚀 Snapshot Release (Node Docker Image)

The latest changes of this PR are available as image on GitHub Container Registry (based on the declared changesets):

ghcr.io/graphql-hive/gateway:1.10.3-alpha-3ceab6d536ae4f3cf122e89c4534ae4552bcdbe9

@theguild-bot
Copy link
Collaborator

theguild-bot commented Feb 6, 2025

🚀 Snapshot Release (Bun Docker Image)

The latest changes of this PR are available as image on GitHub Container Registry (based on the declared changesets):

ghcr.io/graphql-hive/gateway:1.10.3-alpha-3ceab6d536ae4f3cf122e89c4534ae4552bcdbe9-bun

@theguild-bot
Copy link
Collaborator

theguild-bot commented Feb 6, 2025

🚀 Snapshot Release (Binary for macOS-X64)

The latest changes of this PR are available for download (based on the declared changesets).

Download

@theguild-bot
Copy link
Collaborator

theguild-bot commented Feb 6, 2025

🚀 Snapshot Release (Binary for Windows-X64)

The latest changes of this PR are available for download (based on the declared changesets).

Download

@@ -11,7 +11,6 @@ concurrency:

env:
NODE_NO_WARNINGS: 1
CI: true
Copy link
Member Author

Choose a reason for hiding this comment

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

@@ -22,7 +21,7 @@ jobs:
- 10
- 100
- 1000
name: Benchmark / ${{matrix.e2e_runner}} / ${{matrix.products_size}} items
Copy link
Member Author

Choose a reason for hiding this comment

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

Looks nicer in the checks overview.

- Bench / Benchmark / node / 10 items
+ Bench / node / 10 items

@enisdenjo enisdenjo changed the title Memtest by check memory increase slope during load testing Stress-test E2Es and monitor memory for leak detection Feb 6, 2025
@enisdenjo enisdenjo changed the title Stress-test E2Es and monitor memory for leak detection Loadtest E2Es and monitor memory for leak detection Feb 6, 2025
Copy link

@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: 10

🔭 Outside diff range comments (1)
internal/perf/src/loadtest.ts (1)

26-103: Potential concurrency considerations.

This loadtest functionality is useful, but if you plan to run multiple load tests in parallel with a shared cwd or server instance, carefully handle concurrency to avoid port conflicts or shared resource issues.

♻️ Duplicate comments (1)
.github/workflows/memtest.yml (1)

33-36: Action Version Pinning Advisory

The workflow uses the-guild-org/shared-config/setup@v1 for environment setup. For enhanced reproducibility and security, consider pinning this action to a specific commit hash rather than using a mutable version alias.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d72209a and 776529c.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (15)
  • .github/workflows/bench.yml (1 hunks)
  • .github/workflows/memtest.yml (1 hunks)
  • e2e/federation-example/federation-example.memtest.ts (1 hunks)
  • internal/e2e/src/tenv.ts (3 hunks)
  • internal/examples/src/convert.ts (1 hunks)
  • internal/perf/package.json (1 hunks)
  • internal/perf/src/index.ts (1 hunks)
  • internal/perf/src/loadtest-script.ts (1 hunks)
  • internal/perf/src/loadtest.ts (1 hunks)
  • internal/perf/src/memtest.ts (1 hunks)
  • internal/proc/src/index.ts (3 hunks)
  • internal/testing/src/env.ts (1 hunks)
  • package.json (1 hunks)
  • tsconfig.json (1 hunks)
  • vitest.projects.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
`**`: For all PRs, we would like to verify that a Linear iss...

**: For all PRs, we would like to verify that a Linear issue is linked. Check if there's a mention for "GW-*" in the PR description, and the us know when it's missing.

In most cases, when a PR adds a new functionality, we would like to make sure it has documentation in the corresponding console project.
Use the global knowledge feature to search for PRs in graphql-hive/console repository that adds documentation. Suggest to create a follow up issue to add the missing documentation.

  • internal/perf/src/index.ts
  • internal/testing/src/env.ts
  • tsconfig.json
  • internal/perf/package.json
  • vitest.projects.ts
  • e2e/federation-example/federation-example.memtest.ts
  • package.json
  • internal/perf/src/loadtest-script.ts
  • internal/examples/src/convert.ts
  • internal/perf/src/memtest.ts
  • internal/e2e/src/tenv.ts
  • internal/proc/src/index.ts
  • internal/perf/src/loadtest.ts
`e2e/**`: This directory includes end-to-end tests for the g...

e2e/**: This directory includes end-to-end tests for the gateway.
The examples directory is generated based on the code in this directory.

  • e2e/federation-example/federation-example.memtest.ts
🪛 Biome (1.9.4)
internal/perf/src/loadtest.ts

[error] 1-1: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 2-2: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)

⏰ Context from checks skipped due to timeout of 90000ms (31)
  • GitHub Check: E2E / Bun Docker on Ubuntu
  • GitHub Check: E2E / Bun on Ubuntu
  • GitHub Check: Unit / Bun
  • GitHub Check: E2E / Node Binary on Windows
  • GitHub Check: E2E / Node Binary on Ubuntu
  • GitHub Check: Unit / Node v23
  • GitHub Check: E2E / Node Docker on Ubuntu
  • GitHub Check: Leaks / Node v23
  • GitHub Check: Unit / Node v22
  • GitHub Check: E2E / Node 23 on Ubuntu
  • GitHub Check: Leaks / Node v22
  • GitHub Check: Unit / Node v20
  • GitHub Check: Snapshot / snapshot
  • GitHub Check: E2E / Node 22 on Ubuntu
  • GitHub Check: Build
  • GitHub Check: Leaks / Node v20
  • GitHub Check: bun / 1000 items
  • GitHub Check: Unit / Node v18
  • GitHub Check: bun / 100 items
  • GitHub Check: E2E / Node 20 on Ubuntu
  • GitHub Check: bun / 10 items
  • GitHub Check: node / 1000 items
  • GitHub Check: Leaks / Node v18
  • GitHub Check: bun
  • GitHub Check: Bundle
  • GitHub Check: node / 100 items
  • GitHub Check: E2E / Node 18 on Ubuntu
  • GitHub Check: node
  • GitHub Check: node / 10 items
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (actions)
🔇 Additional comments (21)
internal/proc/src/index.ts (2)

11-12: LGTM! Good addition for process lifecycle management.

The waitForExit property provides a clean way to track process termination.


22-25: LGTM! Well-structured server interface.

The Server interface provides a clean abstraction for server-specific processes, which is essential for load testing.

internal/e2e/src/tenv.ts (2)

709-714: LGTM! Clean implementation of the Server interface.

The service object correctly implements the Server interface with all required properties.


873-873: LGTM! Good use of Docker's wait() method.

The waitForExit property is correctly implemented using the container's native wait() method.

internal/perf/src/memtest.ts (2)

6-21: Interface design looks solid!

The MemtestOptions interface is clearly defined, with sensible default values and documentation.


56-72: Verify zero slope handling.

Throwing an error when the slope is zero might be too strict if a truly stable (flat) memory usage is a valid outcome. Confirm the desired behavior for a perfectly flat slope scenario.

internal/perf/src/loadtest.ts (1)

6-24: Clean interface definition.

The LoadtestOptions interface is well-structured, providing clear defaults and documentation.

e2e/federation-example/federation-example.memtest.ts (1)

1-8: Federation setup looks good!

Establishing the gateway and supergraph within this test is logical and easy to follow.

internal/perf/src/loadtest-script.ts (1)

1-3: LGTM! Imports are appropriate for k6 load testing.

The imports from k6 are correctly specified for load testing requirements.

internal/testing/src/env.ts (1)

3-10: LGTM! Documentation is clear and includes examples.

The JSDoc comments effectively explain the function's purpose and provide a helpful example.

internal/examples/src/convert.ts (2)

183-184: LGTM! Appropriate exclusion of memtest files.

The addition of .memtest. to the exclusion list is correct and consistent with the handling of other test files.


1-1: Documentation update may be required.

The PR implements memory leak detection functionality, but no corresponding documentation PR was found in the graphql-hive/console repository. Consider creating a follow-up issue to add documentation for this feature.

✅ Verification successful

🌐 Web query:

Search for documentation PRs in graphql-hive/console repository related to memory leak detection or load testing

💡 Result:

Based on the search results provided, there are no documentation PRs (Pull Requests) mentioned in the graphql-hive/console repository related to memory leak detection or load testing. Here's what we found:

  1. graphql-hive/console issues ([7]):

    • Open issues include security vulnerability alerts, schema validation improvements, and GitHub integration requests.
    • No issues explicitly mention memory leaks or load testing documentation.
  2. Other relevant results:

    • Memory leak detection guidance exists for ProxySQL ([1]) and Java applications ([4]), but these are unrelated to GraphQL Hive.
    • Load testing advice appears for Apollo Federation ([8]), but this isn’t part of Hive’s documentation.

For direct insights into Hive’s memory management or load-testing features:

  • Review Hive’s official development docs ([10]) for performance-related configurations.
  • Check Hive’s GitHub repository for newer PRs beyond the indexed search results.

Citations:


Documentation update required

The web search confirms that there are no documentation PRs in the graphql-hive/console repository related to memory leak detection or load testing. Since the PR implements memory leak detection functionality without corresponding documentation, please consider creating a follow-up issue to add the necessary documentation.

  • Memory leak detection functionality introduced in the PR is undocumented.
  • No related documentation PR was found in graphql-hive/console.
internal/perf/src/index.ts (1)

1-1: Re-export Loadtest Module

The new export statement cleanly re-exports all public members from the loadtest module. This facilitates easier imports for consumers of the internal/perf module—an appropriate design choice for consolidating related testing utilities.

.github/workflows/bench.yml (3)

12-14: Global Environment Variable Cleanup

The removal of the CI environment variable from the global settings is appropriate since GitHub Actions sets CI=true automatically. This streamlines the configuration.


24-24: Enhanced Job Naming

The updated job name now dynamically reflects the matrix parameters (${{matrix.e2e_runner}} / ${{matrix.products_size}} items), providing clear context in the workflow logs.


33-37: Conditional Command Execution in Benchmark Step

Using an inline conditional (${{matrix.e2e_runner == 'bun' && 'yarn bun' || 'yarn'}}) to choose the appropriate command based on the runner is a neat tactic. Ensure that both commands are tested in your CI environment.

.github/workflows/memtest.yml (2)

1-8: New Memtest Workflow Introduction

The new workflow named Memtest is set up correctly to trigger on pushes and pull requests to main. Its concurrency settings will help avoid overlapping runs. This workflow lays the foundation for systematic memory leak detection.


24-31: Robust k6 Installation Step

The k6 installation step downloads and extracts the specified version (v0.56.0) to a local bin directory and correctly appends its path to $GITHUB_PATH. This ensures that the k6 tool is available for the memory testing step.

tsconfig.json (2)

17-18: Updated Path Mappings for Internal Perf

The new path mappings for @internal/perf and @internal/perf/memtest correctly point to their respective source files. This update streamlines module resolution for the new performance and memory testing functionality. Please verify these mappings work as expected across all development and CI environments.


1-87: Overall TSConfig Consistency

Beyond the new mappings, the TSConfig maintains strict type-checking and modern module settings. Confirm that these additions integrate well with your existing path aliases and that all newly added modules are appropriately referenced in your codebase.

package.json (1)

26-26: Review of the Existing Leak Detection Script ("test:leaks")

The existing "test:leaks" script is configured properly using Jest with the --detectOpenHandles and --detectLeaks flags, ensuring that any resource leakage during tests is caught. This aligns well with the leak detection objectives of the PR.

Comment on lines 23 to 145
export function memtest(opts: MemtestOptions, setup: () => Promise<Server>) {
const { memoryThresholdInMB = 5, duration = 60_000, ...loadtestOpts } = opts;
it(
'should not have a memory increase trend',
{
timeout: duration + 10_000, // allow 10s for the test teardown
},
async ({ expect }) => {
const server = await setup();

const { memoryInMBSnapshots } = await loadtest({
...loadtestOpts,
duration,
server,
});

const slope = calculateRegressionSlope(memoryInMBSnapshots);

expect(
slope,
`Memory increase trend detected with slope of ${slope}MB (exceding threshold of ${memoryThresholdInMB}MB)`,
).toBeLessThan(memoryThresholdInMB);
},
);
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Consider ensuring the server is properly shut down after the test completes.

Currently, there's no explicit teardown step to stop the server once the memory test finishes. Leaving an ephemeral server running can consume resources unnecessarily, especially for local development or CI pipelines.

 it(
   'should not have a memory increase trend',
   {
     timeout: duration + 10_000, // allow 10s for the test teardown
   },
   async ({ expect }) => {
     const server = await setup();
     try {
       const { memoryInMBSnapshots } = await loadtest({
         ...loadtestOpts,
         duration,
         server,
       });

       const slope = calculateRegressionSlope(memoryInMBSnapshots);

       expect(
         slope,
         `Memory increase trend detected with slope of ${slope}MB (exceding threshold of ${memoryThresholdInMB}MB)`,
       ).toBeLessThan(memoryThresholdInMB);
     } finally {
+      await server.stop?.();
     }
   },
 );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function memtest(opts: MemtestOptions, setup: () => Promise<Server>) {
const { memoryThresholdInMB = 5, duration = 60_000, ...loadtestOpts } = opts;
it(
'should not have a memory increase trend',
{
timeout: duration + 10_000, // allow 10s for the test teardown
},
async ({ expect }) => {
const server = await setup();
const { memoryInMBSnapshots } = await loadtest({
...loadtestOpts,
duration,
server,
});
const slope = calculateRegressionSlope(memoryInMBSnapshots);
expect(
slope,
`Memory increase trend detected with slope of ${slope}MB (exceding threshold of ${memoryThresholdInMB}MB)`,
).toBeLessThan(memoryThresholdInMB);
},
);
}
export function memtest(opts: MemtestOptions, setup: () => Promise<Server>) {
const { memoryThresholdInMB = 5, duration = 60_000, ...loadtestOpts } = opts;
it(
'should not have a memory increase trend',
{
timeout: duration + 10_000, // allow 10s for the test teardown
},
async ({ expect }) => {
const server = await setup();
try {
const { memoryInMBSnapshots } = await loadtest({
...loadtestOpts,
duration,
server,
});
const slope = calculateRegressionSlope(memoryInMBSnapshots);
expect(
slope,
`Memory increase trend detected with slope of ${slope}MB (exceding threshold of ${memoryThresholdInMB}MB)`,
).toBeLessThan(memoryThresholdInMB);
} finally {
await server.stop?.();
}
},
);
}

Comment on lines +1 to +2
import path from 'path';
import { setTimeout } from 'timers/promises';
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Adopt Node.js import protocol for built-in modules.

According to static analysis hints, use node: prefix for built-in modules to be more explicit.

-import path from 'path';
-import { setTimeout } from 'timers/promises';
+import path from 'node:path';
+import { setTimeout } from 'node:timers/promises';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import path from 'path';
import { setTimeout } from 'timers/promises';
import path from 'node:path';
import { setTimeout } from 'node:timers/promises';
🧰 Tools
🪛 Biome (1.9.4)

[error] 1-1: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 2-2: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)

Comment on lines +9 to +18
memtest(
{
cwd,
query,
},
async () =>
gateway({
supergraph: await supergraph(),
}),
);
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Add idle memory verification or documentation.

Your PR objectives mention an idle memory check after load testing and possible docs in console. Consider incorporating a post-loadtest idle check or opening a follow-up issue to add relevant documentation in graphql-hive/console.

Comment on lines +6 to +14
const url = __ENV['URL'];
if (!url) {
return test.abort('Environment variable "URL" not provided');
}

const query = __ENV['QUERY'];
if (!query) {
return test.abort('Environment variable "QUERY" not provided');
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Consider adding type validation for environment variables.

While the existence check is good, consider validating the URL format and query structure.

 const url = __ENV['URL'];
 if (!url) {
   return test.abort('Environment variable "URL" not provided');
 }
+try {
+  new URL(url); // Validate URL format
+} catch {
+  return test.abort('Environment variable "URL" is not a valid URL');
+}

 const query = __ENV['QUERY'];
 if (!query) {
   return test.abort('Environment variable "QUERY" not provided');
 }
+if (!query.trim().startsWith('{') || !query.trim().endsWith('}')) {
+  return test.abort('Environment variable "QUERY" is not a valid GraphQL query');
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const url = __ENV['URL'];
if (!url) {
return test.abort('Environment variable "URL" not provided');
}
const query = __ENV['QUERY'];
if (!query) {
return test.abort('Environment variable "QUERY" not provided');
}
const url = __ENV['URL'];
if (!url) {
return test.abort('Environment variable "URL" not provided');
}
try {
new URL(url); // Validate URL format
} catch {
return test.abort('Environment variable "URL" is not a valid URL');
}
const query = __ENV['QUERY'];
if (!query) {
return test.abort('Environment variable "QUERY" not provided');
}
if (!query.trim().startsWith('{') || !query.trim().endsWith('}')) {
return test.abort('Environment variable "QUERY" is not a valid GraphQL query');
}

Comment on lines +16 to +21
const res = http.post(url, { query });

check(res, {
'status is 200': (res) => res.status === 200,
'body contains data': (res) => !!res.body?.toString().includes('"data":{'),
});
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Consider enhancing response validation.

The current response validation is basic. Consider adding more comprehensive checks:

 const res = http.post(url, { query });

 check(res, {
   'status is 200': (res) => res.status === 200,
-  'body contains data': (res) => !!res.body?.toString().includes('"data":{'),
+  'body contains data': (res) => {
+    const body = res.body?.toString();
+    return body && JSON.parse(body).data !== undefined;
+  },
+  'response time under 2s': (res) => res.timings.duration < 2000,
+  'no errors in response': (res) => {
+    const body = res.body?.toString();
+    return body && !JSON.parse(body).errors;
+  },
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const res = http.post(url, { query });
check(res, {
'status is 200': (res) => res.status === 200,
'body contains data': (res) => !!res.body?.toString().includes('"data":{'),
});
const res = http.post(url, { query });
check(res, {
'status is 200': (res) => res.status === 200,
'body contains data': (res) => {
const body = res.body?.toString();
return body && JSON.parse(body).data !== undefined;
},
'response time under 2s': (res) => res.timings.duration < 2000,
'no errors in response': (res) => {
const body = res.body?.toString();
return body && !JSON.parse(body).errors;
},
});

Comment on lines +47 to +55
{
extends: './vitest.config.ts',
test: {
name: 'memtest',
include: ['**/*.memtest.ts'],
hookTimeout: testTimeout,
testTimeout,
},
},
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

LGTM! The memtest configuration is properly structured.

The new memtest configuration correctly extends the base configuration and maintains consistency with other test suites by using the same timeout settings.

However, consider documenting the purpose of this test suite:

 {
   extends: './vitest.config.ts',
   test: {
+    // Suite for memory leak detection tests
     name: 'memtest',
     include: ['**/*.memtest.ts'],
     hookTimeout: testTimeout,
     testTimeout,
   },
 },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{
extends: './vitest.config.ts',
test: {
name: 'memtest',
include: ['**/*.memtest.ts'],
hookTimeout: testTimeout,
testTimeout,
},
},
{
extends: './vitest.config.ts',
test: {
// Suite for memory leak detection tests
name: 'memtest',
include: ['**/*.memtest.ts'],
hookTimeout: testTimeout,
testTimeout,
},
},

Comment on lines 11 to 19
export function isDebug(module?: string) {
if (module) {
return boolEnv(`DEBUG_${module}`);
}
return boolEnv('DEBUG');
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Consider adding input validation for the module parameter.

The function could benefit from validating the module name format.

 export function isDebug(module?: string) {
+  if (module && !/^[a-z0-9-_]+$/i.test(module)) {
+    throw new Error('Module name must contain only alphanumeric characters, hyphens, and underscores');
+  }
   if (module) {
     return boolEnv(`DEBUG_${module}`);
   }
   return boolEnv('DEBUG');
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function isDebug(module?: string) {
if (module) {
return boolEnv(`DEBUG_${module}`);
}
return boolEnv('DEBUG');
}
export function isDebug(module?: string) {
if (module && !/^[a-z0-9-_]+$/i.test(module)) {
throw new Error('Module name must contain only alphanumeric characters, hyphens, and underscores');
}
if (module) {
return boolEnv(`DEBUG_${module}`);
}
return boolEnv('DEBUG');
}

Comment on lines 1 to 22
{
"name": "@internal/perf",
"type": "module",
"private": true,
"main": "./src/index.ts",
"dependencies": {
"parse-duration": "^2.0.0",
"regression": "^2.0.1"
},
"devDependencies": {
"@types/k6": "^0.54.2",
"@types/regression": "^2"
}
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Module Configuration & Dependency Integration

The configuration for the new @internal/perf module is well-structured:

  • The module is correctly marked as an ES module and set to private.
  • The "main" entry point points to ./src/index.ts, ensuring proper module resolution.
  • Dependencies (parse-duration, regression) and development dependencies (type definitions) are versioned appropriately.

Additional Note: The enriched summary indicated a new script "test:mem": "vitest --project memtest". Please confirm this script is part of your final package configuration. Also, remember to update documentation (e.g., in the console project) describing the memory testing functionality introduced here.

Comment on lines 36 to 42
- name: Test
run: yarn test:mem
env:
E2E_GATEWAY_RUNNER: ${{matrix.e2e_runner}}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Memory Test Execution

The final step executing yarn test:mem correctly triggers the memory test suite. Ensure that the corresponding script in package.json and the configuration in vitest.projects.ts align with this command.

Copy link

@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: 4

🔭 Outside diff range comments (1)
internal/perf/src/memtest.ts (1)

77-77: Document the new memtest feature in the console project.

This PR references "GW-154," indicating a linked Linear issue. However, there's no mention of corresponding documentation in the graphql-hive/console repository. Consider creating a follow-up issue in the console project to document the new memtest functionality.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 776529c and 146e196.

📒 Files selected for processing (3)
  • e2e/opentelemetry/opentelemetry.memtest.ts (1 hunks)
  • internal/perf/src/memtest.ts (1 hunks)
  • internal/testing/src/env.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
`**`: For all PRs, we would like to verify that a Linear iss...

**: For all PRs, we would like to verify that a Linear issue is linked. Check if there's a mention for "GW-*" in the PR description, and the us know when it's missing.

In most cases, when a PR adds a new functionality, we would like to make sure it has documentation in the corresponding console project.
Use the global knowledge feature to search for PRs in graphql-hive/console repository that adds documentation. Suggest to create a follow up issue to add the missing documentation.

  • internal/testing/src/env.ts
  • e2e/opentelemetry/opentelemetry.memtest.ts
  • internal/perf/src/memtest.ts
`e2e/**`: This directory includes end-to-end tests for the g...

e2e/**: This directory includes end-to-end tests for the gateway.
The examples directory is generated based on the code in this directory.

  • e2e/opentelemetry/opentelemetry.memtest.ts
⏰ Context from checks skipped due to timeout of 90000ms (31)
  • GitHub Check: E2E / Bun Docker on Ubuntu
  • GitHub Check: E2E / Bun on Ubuntu
  • GitHub Check: E2E / Node Binary on Windows
  • GitHub Check: E2E / Node Binary on Ubuntu
  • GitHub Check: E2E / Node Docker on Ubuntu
  • GitHub Check: Unit / Bun
  • GitHub Check: E2E / Node 23 on Ubuntu
  • GitHub Check: Unit / Node v23
  • GitHub Check: E2E / Node 22 on Ubuntu
  • GitHub Check: Unit / Node v22
  • GitHub Check: Leaks / Node v23
  • GitHub Check: E2E / Node 20 on Ubuntu
  • GitHub Check: Leaks / Node v22
  • GitHub Check: bun / 1000 items
  • GitHub Check: Snapshot / snapshot
  • GitHub Check: Unit / Node v20
  • GitHub Check: bun / 100 items
  • GitHub Check: Missing peer deps
  • GitHub Check: E2E / Node 18 on Ubuntu
  • GitHub Check: bun / 10 items
  • GitHub Check: Leaks / Node v20
  • GitHub Check: node / 1000 items
  • GitHub Check: Bundle
  • GitHub Check: node / 100 items
  • GitHub Check: Unit / Node v18
  • GitHub Check: node / 10 items
  • GitHub Check: bun
  • GitHub Check: Leaks / Node v18
  • GitHub Check: Build
  • GitHub Check: node
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
internal/perf/src/memtest.ts (2)

32-49: Ensure the server is properly shut down after the test completes.

This code starts the server but never calls server.stop(). Leaving the server running can needlessly consume resources in CI or local development.

  it(
    'should not have a memory increase trend',
    {
      timeout: duration + 10_000, // allow 10s for the test teardown
    },
    async ({ expect }) => {
      const server = await setup();

      const { memoryInMBSnapshots } = await loadtest({
        ...loadtestOpts,
        duration,
        server,
      });

+     // Teardown
+     await server.stop?.();

      const slope = calculateRegressionSlope(memoryInMBSnapshots);
      expect(
        slope,
        `Memory increase trend detected with slope of ${slope}MB (exceding threshold of ${memoryThresholdInMB}MB)`,
      ).toBeLessThan(memoryThresholdInMB);
    },
  );

60-73: Confirm whether a zero slope should truly trigger an error.

Returning an error for a zero slope implies no memory increase is unacceptable. Verify whether negative or zero slope scenarios should instead pass successfully.


expect(
slope,
`Memory increase trend detected with slope of ${slope}MB (exceding threshold of ${memoryThresholdInMB}MB)`,
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Fix the typographical error in the error message.

Use "exceeding" instead of "exceding".

-  `Memory increase trend detected with slope of ${slope}MB (exceding threshold of ${memoryThresholdInMB}MB)`,
+  `Memory increase trend detected with slope of ${slope}MB (exceeding threshold of ${memoryThresholdInMB}MB)`,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
`Memory increase trend detected with slope of ${slope}MB (exceding threshold of ${memoryThresholdInMB}MB)`,
`Memory increase trend detected with slope of ${slope}MB (exceeding threshold of ${memoryThresholdInMB}MB)`,

Comment on lines +10 to +50
(['grpc', 'http'] as const).forEach((OTLP_EXPORTER_TYPE) => {
describe(`OpenTelemetry ${OTLP_EXPORTER_TYPE} exporter`, () => {
let jaeger: Container;
beforeAll(async () => {
jaeger = await container({
name: `jaeger-${OTLP_EXPORTER_TYPE}`,
image: 'jaegertracing/all-in-one:1.56',
env: {
COLLECTOR_OTLP_ENABLED: 'true',
},
containerPort: 4318,
additionalContainerPorts: [16686, 4317],
healthcheck: ['CMD-SHELL', 'wget --spider http://0.0.0.0:14269'],
});
});
const jaegerUrls = {
get http() {
return `http://0.0.0.0:${jaeger.port}/v1/traces`;
},
get grpc() {
return `http://0.0.0.0:${jaeger.additionalPorts[4317]}`;
},
};

memtest(
{
cwd,
query,
},
async () =>
gateway({
supergraph: await supergraph(),
env: {
OTLP_EXPORTER_TYPE,
OTLP_EXPORTER_URL: jaegerUrls[OTLP_EXPORTER_TYPE],
OTLP_SERVICE_NAME: `memtest-${OTLP_EXPORTER_TYPE}`,
},
}),
);
});
});
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Consider adding a container teardown step.

The container is launched but never explicitly stopped. This can introduce resource leaks, especially for CI runs or local environments.

 describe(`OpenTelemetry ${OTLP_EXPORTER_TYPE} exporter`, () => {
   let jaeger: Container;

   beforeAll(async () => {
     jaeger = await container({
       // ...
     });
   });

+  afterAll(async () => {
+    await jaeger.stop?.();
+  });

   memtest(
     {
       cwd,
       query,
     },
     async () =>
       gateway({
         // ...
       }),
   );
 });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
(['grpc', 'http'] as const).forEach((OTLP_EXPORTER_TYPE) => {
describe(`OpenTelemetry ${OTLP_EXPORTER_TYPE} exporter`, () => {
let jaeger: Container;
beforeAll(async () => {
jaeger = await container({
name: `jaeger-${OTLP_EXPORTER_TYPE}`,
image: 'jaegertracing/all-in-one:1.56',
env: {
COLLECTOR_OTLP_ENABLED: 'true',
},
containerPort: 4318,
additionalContainerPorts: [16686, 4317],
healthcheck: ['CMD-SHELL', 'wget --spider http://0.0.0.0:14269'],
});
});
const jaegerUrls = {
get http() {
return `http://0.0.0.0:${jaeger.port}/v1/traces`;
},
get grpc() {
return `http://0.0.0.0:${jaeger.additionalPorts[4317]}`;
},
};
memtest(
{
cwd,
query,
},
async () =>
gateway({
supergraph: await supergraph(),
env: {
OTLP_EXPORTER_TYPE,
OTLP_EXPORTER_URL: jaegerUrls[OTLP_EXPORTER_TYPE],
OTLP_SERVICE_NAME: `memtest-${OTLP_EXPORTER_TYPE}`,
},
}),
);
});
});
(['grpc', 'http'] as const).forEach((OTLP_EXPORTER_TYPE) => {
describe(`OpenTelemetry ${OTLP_EXPORTER_TYPE} exporter`, () => {
let jaeger: Container;
beforeAll(async () => {
jaeger = await container({
name: `jaeger-${OTLP_EXPORTER_TYPE}`,
image: 'jaegertracing/all-in-one:1.56',
env: {
COLLECTOR_OTLP_ENABLED: 'true',
},
containerPort: 4318,
additionalContainerPorts: [16686, 4317],
healthcheck: ['CMD-SHELL', 'wget --spider http://0.0.0.0:14269'],
});
});
afterAll(async () => {
await jaeger.stop?.();
});
const jaegerUrls = {
get http() {
return `http://0.0.0.0:${jaeger.port}/v1/traces`;
},
get grpc() {
return `http://0.0.0.0:${jaeger.additionalPorts[4317]}`;
},
};
memtest(
{
cwd,
query,
},
async () =>
gateway({
supergraph: await supergraph(),
env: {
OTLP_EXPORTER_TYPE,
OTLP_EXPORTER_URL: jaegerUrls[OTLP_EXPORTER_TYPE],
OTLP_SERVICE_NAME: `memtest-${OTLP_EXPORTER_TYPE}`,
},
}),
);
});
});

* be activated for the specific module in code.
*
* For example, `isDebug('loadtest')` will only activate when the
* variable `loadtest` is listed in the `DEBUG` enviorment variable.
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Fix the typo in the JSDoc.

Use "environment" instead of "enviorment".

- * variable `loadtest` is listed in the `DEBUG` enviorment variable.
+ * variable `loadtest` is listed in the `DEBUG` environment variable.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* variable `loadtest` is listed in the `DEBUG` enviorment variable.
* variable `loadtest` is listed in the `DEBUG` environment variable.

Comment on lines +11 to +17
export function isDebug(module?: string) {
if (module) {
const modules = String(process.env['DEBUG'])
.split(',')
.map((m) => m.trim());
return modules.includes(module);
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Handle unset or empty DEBUG environment variable more robustly.

When process.env['DEBUG'] is not defined or empty, String(process.env['DEBUG']) becomes "undefined" or "". Consider a fallback to an empty array to avoid unintended matches.

export function isDebug(module?: string) {
  if (module) {
-    const modules = String(process.env['DEBUG'])
+    const debugStr = process.env['DEBUG'] ?? '';
+    if (!debugStr) return false;
+    const modules = debugStr
       .split(',')
       .map((m) => m.trim());
    return modules.includes(module);
  }
  return boolEnv('DEBUG');
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function isDebug(module?: string) {
if (module) {
const modules = String(process.env['DEBUG'])
.split(',')
.map((m) => m.trim());
return modules.includes(module);
}
export function isDebug(module?: string) {
if (module) {
const debugStr = process.env['DEBUG'] ?? '';
if (!debugStr) return false;
const modules = debugStr
.split(',')
.map((m) => m.trim());
return modules.includes(module);
}
return boolEnv('DEBUG');
}

Copy link

@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: 5

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 146e196 and 3074f4d.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (4)
  • internal/perf/package.json (1 hunks)
  • internal/perf/src/chart.ts (1 hunks)
  • internal/perf/src/loadtest.ts (1 hunks)
  • internal/perf/src/memtest.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**`: For all PRs, we would like to verify that a Linear iss...

**: For all PRs, we would like to verify that a Linear issue is linked. Check if there's a mention for "GW-*" in the PR description, and the us know when it's missing.

In most cases, when a PR adds a new functionality, we would like to make sure it has documentation in the corresponding console project.
Use the global knowledge feature to search for PRs in graphql-hive/console repository that adds documentation. Suggest to create a follow up issue to add the missing documentation.

  • internal/perf/package.json
  • internal/perf/src/loadtest.ts
  • internal/perf/src/chart.ts
  • internal/perf/src/memtest.ts
🪛 Biome (1.9.4)
internal/perf/src/loadtest.ts

[error] 1-1: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 2-2: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 3-3: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)

⏰ Context from checks skipped due to timeout of 90000ms (30)
  • GitHub Check: Leaks / Node v23
  • GitHub Check: E2E / Bun Docker on Ubuntu
  • GitHub Check: Leaks / Node v22
  • GitHub Check: E2E / Bun on Ubuntu
  • GitHub Check: Leaks / Node v20
  • GitHub Check: E2E / Node Binary on Windows
  • GitHub Check: Leaks / Node v18
  • GitHub Check: E2E / Node Binary on Ubuntu
  • GitHub Check: E2E / Node Docker on Ubuntu
  • GitHub Check: E2E / Node 23 on Ubuntu
  • GitHub Check: E2E / Node 22 on Ubuntu
  • GitHub Check: E2E / Node 20 on Ubuntu
  • GitHub Check: E2E / Node 18 on Ubuntu
  • GitHub Check: Snapshot / snapshot
  • GitHub Check: bun / 1000 items
  • GitHub Check: Unit / Node v23
  • GitHub Check: bun / 100 items
  • GitHub Check: Unit / Node v22
  • GitHub Check: bun / 10 items
  • GitHub Check: Unit / Bun
  • GitHub Check: node / 1000 items
  • GitHub Check: Unit / Node v20
  • GitHub Check: node / 100 items
  • GitHub Check: Unit / Node v18
  • GitHub Check: Bundle
  • GitHub Check: bun
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: node / 10 items
  • GitHub Check: Build
  • GitHub Check: node
🔇 Additional comments (4)
internal/perf/src/memtest.ts (1)

36-98: Ensure the server is properly shut down after the test completes.

This code doesn't explicitly handle a teardown step to stop the server once the memory test finishes. Leaving the server running might consume resources unnecessarily, especially in local or CI environments.

internal/perf/package.json (3)

1-5: Module Configuration and Metadata

The module is correctly set up as an ES module and marked as private. The main entry point ("./src/index.ts") is defined appropriately, ensuring proper module resolution.


6-12: Dependencies Setup

The dependencies—including canvas, chart.js, chartjs-plugin-trendline, parse-duration, and regression—are declared with clear version constraints. These libraries appear well-chosen for performance and data analysis tasks. Just ensure they have been validated against your overall performance and memory testing requirements.


13-16: Development Dependencies

The development dependencies for @types/k6 and @types/regression are appropriately specified for improved type safety and tooling support. This aligns with the module’s focus on performance and memory testing.

loadtestSlopeThreshold?: number;
}

export function memtest(opts: MemtestOptions, setup: () => Promise<Server>) {
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Add documentation in the console project for this new functionality.

This function introduces a new memory testing feature. Consider creating or updating documentation in the graphql-hive/console repository to inform users about the usage and configuration of memtest.

Do you want me to open a follow-up issue in the graphql-hive/console repository to ensure the feature is well documented?

Comment on lines 28 to 73
const canvas = createCanvas(800, 400, 'svg');

const chartConfig: ChartConfiguration = {
type: 'line',
data: {
labels,
datasets: datasets.map(({ label, data }) => ({
label,
data,
trendlineLinear: {
width: 1,
lineStyle: 'dashed',
},
})),
},
options: {
responsive: false, // because we're rendering the chart statically
scales: {
y: {
ticks: {
callback: options.yTicksCallback,
},
},
},
},
plugins: [
chartTrendline,
{
id: 'set-white-background',
beforeDraw: (chart) => {
chart.ctx.fillStyle = 'white';
chart.ctx.fillRect(0, 0, chart.width, chart.height);
chart.ctx.restore();
},
},
],
};

new Chart(
// @ts-expect-error canvas types are of a different instance, but they fit
canvas.getContext('2d'),
chartConfig,
);

return canvas;
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Allow optional chart dimensions via parameters.

Consider introducing parameters for the chart's width and height (currently hardcoded to 800×400), allowing for flexibility in various test cases or custom outputs.

-export function createLineChart(
-  labels: (number | string | null)[],
-  datasets: LineChartDataset[],
-  options: LineChartOptions = {},
-): Canvas {
-  const canvas = createCanvas(800, 400, 'svg');
+export function createLineChart(
+  labels: (number | string | null)[],
+  datasets: LineChartDataset[],
+  options: LineChartOptions = {},
+  canvasWidth = 800,
+  canvasHeight = 400
+): Canvas {
+  const canvas = createCanvas(canvasWidth, canvasHeight, 'svg');
   ...
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const canvas = createCanvas(800, 400, 'svg');
const chartConfig: ChartConfiguration = {
type: 'line',
data: {
labels,
datasets: datasets.map(({ label, data }) => ({
label,
data,
trendlineLinear: {
width: 1,
lineStyle: 'dashed',
},
})),
},
options: {
responsive: false, // because we're rendering the chart statically
scales: {
y: {
ticks: {
callback: options.yTicksCallback,
},
},
},
},
plugins: [
chartTrendline,
{
id: 'set-white-background',
beforeDraw: (chart) => {
chart.ctx.fillStyle = 'white';
chart.ctx.fillRect(0, 0, chart.width, chart.height);
chart.ctx.restore();
},
},
],
};
new Chart(
// @ts-expect-error canvas types are of a different instance, but they fit
canvas.getContext('2d'),
chartConfig,
);
return canvas;
}
export function createLineChart(
labels: (number | string | null)[],
datasets: LineChartDataset[],
options: LineChartOptions = {},
canvasWidth = 800,
canvasHeight = 400
): Canvas {
const canvas = createCanvas(canvasWidth, canvasHeight, 'svg');
const chartConfig: ChartConfiguration = {
type: 'line',
data: {
labels,
datasets: datasets.map(({ label, data }) => ({
label,
data,
trendlineLinear: {
width: 1,
lineStyle: 'dashed',
},
})),
},
options: {
responsive: false, // because we're rendering the chart statically
scales: {
y: {
ticks: {
callback: options.yTicksCallback,
},
},
},
},
plugins: [
chartTrendline,
{
id: 'set-white-background',
beforeDraw: (chart) => {
chart.ctx.fillStyle = 'white';
chart.ctx.fillRect(0, 0, chart.width, chart.height);
chart.ctx.restore();
},
},
],
};
new Chart(
// @ts-expect-error canvas types are of a different instance, but they fit
canvas.getContext('2d'),
chartConfig,
);
return canvas;
}

Comment on lines 93 to 135
debugLog(`Loadtesting...`);
state = 'loadtest';
const [, waitForExit] = await spawn(
{
cwd,
...procOptions,
signal,
},
'k6',
'run',
`--vus=${vus}`,
`--duration=${duration}ms`,
`--env=URL=${server.protocol}://localhost:${server.port}/graphql`,
`--env=QUERY=${query}`,
path.join(__dirname, 'loadtest-script.ts'),
);
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Consider clarifying behavior if k6 isn’t installed.

If k6 is not available on the environment’s PATH, this call fails with a cryptic error. A more user-friendly error or a pre-flight check might help.

Comment on lines 1 to 2
import fs from 'fs/promises';
import path from 'path';
import { setTimeout } from 'timers/promises';
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use the Node.js builtin “node:” import protocol for clarity.

Static analysis recommends using the node: prefix for the built-in modules to avoid ambiguity and align with best practices.

-import fs from 'fs/promises';
-import path from 'path';
-import { setTimeout } from 'timers/promises';
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import { setTimeout } from 'node:timers/promises';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import fs from 'fs/promises';
import path from 'path';
import { setTimeout } from 'timers/promises';
import fs from 'node:fs/promises';
import path from 'node:path';
import { setTimeout } from 'node:timers/promises';
🧰 Tools
🪛 Biome (1.9.4)

[error] 1-1: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 2-2: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 3-3: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)

Comment on lines 1 to 22
{
"name": "@internal/perf",
"type": "module",
"private": true,
"main": "./src/index.ts",
"dependencies": {
"canvas": "^3.1.0",
"chart.js": "^4.4.7",
"chartjs-plugin-trendline": "^2.1.6",
"parse-duration": "^2.0.0",
"regression": "^2.0.1"
},
"devDependencies": {
"@types/k6": "^0.54.2",
"@types/regression": "^2"
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Missing Test Script and Documentation Update

The enriched summary mentioned a new script ("test:mem": "vitest --project memtest") to support memory testing locally, but it is currently absent from this configuration. Consider adding an appropriate "scripts" section with the test:mem command to facilitate local testing of the memtest functionality. Additionally, as this module implements new memory leak detection features, please update the corresponding documentation (for example, in the related console project) so that users have clear guidance on how to use these new capabilities.

[assertive]

Copy link

@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: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3074f4d and 27b5f62.

📒 Files selected for processing (1)
  • internal/perf/src/memtest.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**`: For all PRs, we would like to verify that a Linear iss...

**: For all PRs, we would like to verify that a Linear issue is linked. Check if there's a mention for "GW-*" in the PR description, and the us know when it's missing.

In most cases, when a PR adds a new functionality, we would like to make sure it has documentation in the corresponding console project.
Use the global knowledge feature to search for PRs in graphql-hive/console repository that adds documentation. Suggest to create a follow up issue to add the missing documentation.

  • internal/perf/src/memtest.ts
🪛 GitHub Check: bun
internal/perf/src/memtest.ts

[failure] 95-95: e2e/federation-example/federation-example.memtest.ts > should not have a memory increase trend
AssertionError: No memory decrease detected during calmdown: expected 0 to be less than or equal to -10
❯ internal/perf/src/memtest.ts:95:10

🪛 GitHub Actions: Memtest
internal/perf/src/memtest.ts

[error] 95-95: AssertionError: No memory decrease detected during calmdown: expected 0 to be less than or equal to -10

⏰ Context from checks skipped due to timeout of 90000ms (25)
  • GitHub Check: Examples / Convert operation-field-permissions
  • GitHub Check: Examples / Convert type-merging-batching
  • GitHub Check: Examples / Convert openapi-subscriptions
  • GitHub Check: Examples / Convert openapi-javascript-wiki
  • GitHub Check: Examples / Convert interface-additional-resolvers
  • GitHub Check: Examples / Convert federation-mixed
  • GitHub Check: Binary built on windows-latest
  • GitHub Check: Binary built on macos-14
  • GitHub Check: Bun Docker image
  • GitHub Check: Binary built on macos-13
  • GitHub Check: Node Docker image
  • GitHub Check: Binary built on ubuntu-latest
  • GitHub Check: E2E / Bun Docker on Ubuntu
  • GitHub Check: E2E / Node Binary on Windows
  • GitHub Check: Leaks / Node v23
  • GitHub Check: E2E / Node Binary on Ubuntu
  • GitHub Check: Leaks / Node v22
  • GitHub Check: E2E / Node Docker on Ubuntu
  • GitHub Check: Leaks / Node v20
  • GitHub Check: E2E / Node 23 on Ubuntu
  • GitHub Check: Leaks / Node v18
  • GitHub Check: E2E / Node 22 on Ubuntu
  • GitHub Check: E2E / Node 20 on Ubuntu
  • GitHub Check: E2E / Node 18 on Ubuntu
  • GitHub Check: bun / 10 items
🔇 Additional comments (2)
internal/perf/src/memtest.ts (2)

36-36: Add documentation for the new functionality in the console project.

Since memtest is a new core feature, ensure that documentation is added or updated in the graphql-hive/console repository to guide users on setup, configuration, and usage.


50-96: Gracefully stop the server after the test.

As noted in a previous review, consider wrapping the loadtest call in a try/finally block to stop the created server instance even if the test fails. This helps avoid leaving resources open in local or CI environments.

🧰 Tools
🪛 GitHub Check: bun

[failure] 95-95: e2e/federation-example/federation-example.memtest.ts > should not have a memory increase trend
AssertionError: No memory decrease detected during calmdown: expected 0 to be less than or equal to -10
❯ internal/perf/src/memtest.ts:95:10

🪛 GitHub Actions: Memtest

[error] 95-95: AssertionError: No memory decrease detected during calmdown: expected 0 to be less than or equal to -10

Comment on lines 94 to 142
.soft(calmdownSlope, `No memory decrease detected during calmdown`)
.toBeLessThanOrEqual(-10);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Pipeline fails due to strict memory decrease threshold.

The memtest pipeline is failing because .toBeLessThanOrEqual(-10) expects a slope of -10 or lower, yet the actual slope is 0. Confirm whether this indicates a genuine memory leak or if the threshold is simply too strict. Consider reducing the threshold to a more reasonable value if you’re not expecting such a large drop in memory usage:

- .toBeLessThanOrEqual(-10);
+ .toBeLessThanOrEqual(0);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.soft(calmdownSlope, `No memory decrease detected during calmdown`)
.toBeLessThanOrEqual(-10);
.soft(calmdownSlope, `No memory decrease detected during calmdown`)
.toBeLessThanOrEqual(0);
🧰 Tools
🪛 GitHub Check: bun

[failure] 95-95: e2e/federation-example/federation-example.memtest.ts > should not have a memory increase trend
AssertionError: No memory decrease detected during calmdown: expected 0 to be less than or equal to -10
❯ internal/perf/src/memtest.ts:95:10

🪛 GitHub Actions: Memtest

[error] 95-95: AssertionError: No memory decrease detected during calmdown: expected 0 to be less than or equal to -10

Copy link

@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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 37a6f8a and 9867bb9.

📒 Files selected for processing (1)
  • internal/perf/src/loadtest.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**`: For all PRs, we would like to verify that a Linear iss...

**: For all PRs, we would like to verify that a Linear issue is linked. Check if there's a mention for "GW-*" in the PR description, and the us know when it's missing.

In most cases, when a PR adds a new functionality, we would like to make sure it has documentation in the corresponding console project.
Use the global knowledge feature to search for PRs in graphql-hive/console repository that adds documentation. Suggest to create a follow up issue to add the missing documentation.

  • internal/perf/src/loadtest.ts
🪛 Biome (1.9.4)
internal/perf/src/loadtest.ts

[error] 1-1: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 2-2: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 3-3: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)

⏰ Context from checks skipped due to timeout of 90000ms (23)
  • GitHub Check: E2E / Bun Docker on Ubuntu
  • GitHub Check: E2E / Bun on Ubuntu
  • GitHub Check: E2E / Node Binary on Windows
  • GitHub Check: E2E / Node Binary on Ubuntu
  • GitHub Check: E2E / Node Docker on Ubuntu
  • GitHub Check: E2E / Node 23 on Ubuntu
  • GitHub Check: E2E / Node 22 on Ubuntu
  • GitHub Check: E2E / Node 20 on Ubuntu
  • GitHub Check: Leaks / Node v23
  • GitHub Check: Unit / Bun
  • GitHub Check: Bundle
  • GitHub Check: Leaks / Node v22
  • GitHub Check: bun / 1000 items
  • GitHub Check: bun / 100 items
  • GitHub Check: E2E / Node 18 on Ubuntu
  • GitHub Check: bun / 10 items
  • GitHub Check: Leaks / Node v20
  • GitHub Check: node / 1000 items
  • GitHub Check: Leaks / Node v18
  • GitHub Check: node / 100 items
  • GitHub Check: node / 10 items
  • GitHub Check: bun
  • GitHub Check: node
🔇 Additional comments (3)
internal/perf/src/loadtest.ts (3)

1-3: Use the Node.js built-in modules with the node: prefix.

This aligns with the best practice recommended by the static analysis tool and was previously flagged in the past review comments.

-import fs from 'fs/promises';
-import path from 'path';
-import { setTimeout } from 'timers/promises';
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import { setTimeout } from 'node:timers/promises';
🧰 Tools
🪛 Biome (1.9.4)

[error] 1-1: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 2-2: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 3-3: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


93-113: Consider a user-friendly check if k6 is not installed.

Spawning k6 without verifying its presence may cause a cryptic error if k6 is missing from the environment. As noted in a previous review, adding a pre-flight check helps catch this scenario early and improve the developer experience.


49-54: Confirm Node.js version support for AbortSignal.timeout and [Symbol.dispose].

Using AbortSignal.timeout() and using { [Symbol.dispose]() } is only supported in newer Node.js versions (≥17 for AbortSignal.timeout(), ≥20 for [Symbol.dispose]). Ensure the CI environment or deployment target meets these requirements.

Please run a web query for the official Node.js documentation to confirm these are fully supported in your environment:

✅ Verification successful

🌐 Web query:

Node.js versions that support `[Symbol.dispose]` and `AbortSignal.timeout`

💡 Result:

Node.js support for Symbol.dispose and AbortSignal.timeout() can be summarized as follows:

Symbol.dispose Support

  • Native implementation: Available since Node.js v18.18 and v20.4 as built-in polyfills while awaiting full V8 engine integration[1][7].
  • Full support: Added via TypeScript 5.2 compatibility with runtime polyfills[4][9].
  • Key use cases:
    • Automatically disposing resources like file handles or timers using using declarations[7][10].
    • Simplifying cleanup logic for objects like Timeout (e.g., using setInterval(...))[7][10].

AbortSignal.timeout() Support

  • Introduced natively in Node.js v18+ via updates to the timers/promises API[2][8].
  • Enables direct timeout configuration for operations like HTTP requests:
await fetch(url, { signal: AbortSignal.timeout(3000) });
  • Throws a TimeoutError DOMException on expiration[5][8].

Version Compatibility Table

Feature Supported Node.js Versions Notes
Symbol.dispose ≥v18.18 / ≥v20.4 Polyfilled; requires TypeScript ≥5.2[1][4][9].
AbortSignal.timeout ≥v18 Integrated with fetch/timeout APIs[2][5][8].

For production use:

  • Ensure TypeScript polyfills (Symbol.dispose/Symbol.asyncDispose) are included if targeting older runtimes[1][9].
  • Prefer native implementations in newer LTS releases (e.g., ≥v20)[10].

Citations:


Review Verification: Node.js Versions Confirmed

The web query confirms that:

  • AbortSignal.timeout() is available in Node.js v18 and later.
  • [Symbol.dispose] is supported via built-in polyfills from Node.js v18.18 and has full native support in Node.js v20.4.

Ensure your CI and deployment environments meet these version requirements.

Comment on lines 120 to 154
if (isDebug('loadtest')) {
const chart = createLineChart(
memoryInMBSnapshots.total.map(
(_, i) => `${i + memorySnapshotWindow / 1000}. sec`,
),
[
{
label: 'Idle',
data: memoryInMBSnapshots.idle,
},
{
label: 'Loadtest',
data: [
...memoryInMBSnapshots.idle.map(() => null), // skip idle data
...memoryInMBSnapshots.loadtest,
],
},
{
label: 'Calmdown',
data: [
...memoryInMBSnapshots.idle.map(() => null), // skip idle data
...memoryInMBSnapshots.loadtest.map(() => null), // skip loadtest data
...memoryInMBSnapshots.calmdown,
],
},
],
{
yTicksCallback: (tickValue) => `${tickValue} MB`,
},
);
await fs.writeFile(
path.join(cwd, 'loadtest-memory-snapshots.svg'),
chart.toBuffer(),
);
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Document this new loadtesting feature in the console project.

Given that this is a new piece of functionality, it’s helpful for teams or end-users to have accompanying documentation. Consider opening a follow-up issue in the graphql-hive/console repository to add relevant usage instructions or references so that it’s discoverable in your docs.

Shall I open the issue on your behalf?

Comment on lines 159 to 148
function debugLog(msg: string) {
if (isDebug('loadtest')) {
console.log(`[loadtest] ${msg}`);
}
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Add tests or validation for debug logging.

Currently, there’s no test coverage verifying the contents or behavior of the debugLog function. Consider adding at least minimal tests to ensure that debug output is only produced under isDebug('loadtest') contexts.

Comment on lines 18 to 42
strategy:
matrix:
e2e_runner:
- node
# - bun TODO: fix getting memory snaps for bun
name: ${{matrix.e2e_runner}}
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install k6
run: |
mkdir -p "$HOME/.local/bin"
cd "$HOME/.local/bin"
curl https://github.com/grafana/k6/releases/download/${{ env.K6_VERSION }}/k6-${{ env.K6_VERSION }}-linux-amd64.tar.gz -L | tar xvz --strip-components 1
echo "$PWD" >> $GITHUB_PATH
- name: Set up env
uses: the-guild-org/shared-config/setup@v1
with:
node-version-file: .node-version
- name: Test
run: yarn test:mem
env:
E2E_GATEWAY_RUNNER: ${{matrix.e2e_runner}}

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions Job or Workflow does not set permissions
Copy link

@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: 1

🔭 Outside diff range comments (1)
.github/workflows/memtest.yml (1)

16-42: Security: Missing 'permissions' Declaration.
The workflow currently lacks an explicit permissions block. For improved security, it is recommended to restrict token scopes by adding a top-level permissions section. For example:

+permissions:
+  contents: read

This change minimizes the access token's privileges and aligns with security best practices.

🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 18-41: Workflow does not contain permissions
Actions Job or Workflow does not set permissions

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0427cae and 94b96ba.

📒 Files selected for processing (1)
  • .github/workflows/memtest.yml (1 hunks)
🧰 Additional context used
🪛 GitHub Check: CodeQL
.github/workflows/memtest.yml

[warning] 18-41: Workflow does not contain permissions
Actions Job or Workflow does not set permissions

⏰ Context from checks skipped due to timeout of 90000ms (44)
  • GitHub Check: Examples / Convert operation-field-permissions
  • GitHub Check: Examples / Convert type-merging-batching
  • GitHub Check: Examples / Convert subscriptions-with-transforms
  • GitHub Check: Examples / Convert programmatic-batching
  • GitHub Check: Examples / Convert openapi-subscriptions
  • GitHub Check: Examples / Convert openapi-javascript-wiki
  • GitHub Check: Examples / Convert openapi-arg-rename
  • GitHub Check: Examples / Convert openapi-additional-resolvers
  • GitHub Check: Examples / Convert json-schema-subscriptions
  • GitHub Check: Examples / Convert interface-additional-resolvers
  • GitHub Check: Examples / Convert hmac-auth-https
  • GitHub Check: Examples / Convert federation-subscriptions-passthrough
  • GitHub Check: Examples / Convert apq-subgraphs
  • GitHub Check: Examples / Convert file-upload
  • GitHub Check: Examples / Convert federation-mixed
  • GitHub Check: Examples / Convert federation-example
  • GitHub Check: Examples / Convert extra-fields
  • GitHub Check: Binary built on ubuntu-24.04-arm
  • GitHub Check: Binary built on windows-latest
  • GitHub Check: Binary built on macos-14
  • GitHub Check: Binary built on macos-13
  • GitHub Check: Binary built on ubuntu-latest
  • GitHub Check: Bun Docker image
  • GitHub Check: Node Docker image
  • GitHub Check: E2E / Bun Docker on Ubuntu
  • GitHub Check: E2E / Bun on Ubuntu
  • GitHub Check: E2E / Node Binary on Windows
  • GitHub Check: E2E / Node Binary on Ubuntu
  • GitHub Check: E2E / Node Docker on Ubuntu
  • GitHub Check: Leaks / Node v23
  • GitHub Check: E2E / Node 23 on Ubuntu
  • GitHub Check: Leaks / Node v22
  • GitHub Check: E2E / Node 22 on Ubuntu
  • GitHub Check: bun / 1000 items
  • GitHub Check: bun / 100 items
  • GitHub Check: Leaks / Node v20
  • GitHub Check: bun / 10 items
  • GitHub Check: E2E / Node 20 on Ubuntu
  • GitHub Check: node / 1000 items
  • GitHub Check: Leaks / Node v18
  • GitHub Check: node / 100 items
  • GitHub Check: E2E / Node 18 on Ubuntu
  • GitHub Check: node / 10 items
  • GitHub Check: node
🔇 Additional comments (6)
.github/workflows/memtest.yml (6)

1-7: Workflow Trigger Configuration is Clear.
The workflow name and event triggers (push on the main branch and pull requests) are well defined and easy to follow.


8-10: Concurrency Settings are Well Configured.
The concurrency block uses a dynamic group key (${{ github.workflow }}-${{ github.ref }}) with cancel-in-progress: true to ensure that only the latest run per branch is maintained.


12-15: Environment Variables are Appropriately Configured.
Defining NODE_NO_WARNINGS and pinning the K6_VERSION (v0.56.0) helps ensure consistent behavior during runtime and tool installation.


26-27: Checkout Step is Properly Configured.
The repository checkout using actions/checkout@v4 is standard and correctly implemented.


34-37: Action Version Pinning.
The Set up env step uses the-guild-org/shared-config/setup@v1, which is not pinned to a specific commit hash. For enhanced security and reproducibility, consider pinning this action to an exact commit. For example:

-uses: the-guild-org/shared-config/setup@v1
+uses: the-guild-org/shared-config/setup@<commit-hash>

38-41: Test Step is Clear.
The final step invokes yarn test:mem to run the memory test suite, using the matrix-defined runner (E2E_GATEWAY_RUNNER). Ensure that the associated test script exists and is correctly configured.

🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 18-41: Workflow does not contain permissions
Actions Job or Workflow does not set permissions

Comment on lines +28 to +33
- name: Install k6
run: |
mkdir -p "$HOME/.local/bin"
cd "$HOME/.local/bin"
curl https://github.com/grafana/k6/releases/download/${{ env.K6_VERSION }}/k6-${{ env.K6_VERSION }}-linux-amd64.tar.gz -L | tar xvz --strip-components 1
echo "$PWD" >> $GITHUB_PATH
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Install k6: Consider Enhanced Error Handling.
The script for installing k6 correctly creates the necessary directory, downloads, and extracts the binary. To further improve robustness, consider adding error handling by enabling immediate failure on errors (using set -e) and employing curl -f to fail if the download does not succeed. For example:

-run: |
-  mkdir -p "$HOME/.local/bin"
-  cd "$HOME/.local/bin"
-  curl https://github.com/grafana/k6/releases/download/${{ env.K6_VERSION }}/k6-${{ env.K6_VERSION }}-linux-amd64.tar.gz -L | tar xvz --strip-components 1
-  echo "$PWD" >> $GITHUB_PATH
+run: |
+  set -e
+  mkdir -p "$HOME/.local/bin"
+  cd "$HOME/.local/bin"
+  curl -f https://github.com/grafana/k6/releases/download/${{ env.K6_VERSION }}/k6-${{ env.K6_VERSION }}-linux-amd64.tar.gz -L | tar xvz --strip-components 1
+  echo "$PWD" >> $GITHUB_PATH
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Install k6
run: |
mkdir -p "$HOME/.local/bin"
cd "$HOME/.local/bin"
curl https://github.com/grafana/k6/releases/download/${{ env.K6_VERSION }}/k6-${{ env.K6_VERSION }}-linux-amd64.tar.gz -L | tar xvz --strip-components 1
echo "$PWD" >> $GITHUB_PATH
- name: Install k6
run: |
set -e
mkdir -p "$HOME/.local/bin"
cd "$HOME/.local/bin"
curl -f https://github.com/grafana/k6/releases/download/${{ env.K6_VERSION }}/k6-${{ env.K6_VERSION }}-linux-amd64.tar.gz -L | tar xvz --strip-components 1
echo "$PWD" >> $GITHUB_PATH

curl https://github.com/grafana/k6/releases/download/${{ env.K6_VERSION }}/k6-${{ env.K6_VERSION }}-linux-amd64.tar.gz -L | tar xvz --strip-components 1
echo "$PWD" >> $GITHUB_PATH
- name: Set up env
uses: the-guild-org/shared-config/setup@v1

Check warning

Code scanning / CodeQL

Unpinned tag for a non-immutable Action in workflow Medium

Unpinned 3rd party Action 'Memtest' step
Uses Step
uses 'the-guild-org/shared-config/setup' with ref 'v1', not a pinned commit hash
Copy link

@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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 94b96ba and a625f8c.

📒 Files selected for processing (2)
  • .github/workflows/memtest.yml (1 hunks)
  • internal/perf/src/loadtest.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
`**`: For all PRs, we would like to verify that a Linear iss...

**: For all PRs, we would like to verify that a Linear issue is linked. Check if there's a mention for "GW-*" in the PR description, and the us know when it's missing.

In most cases, when a PR adds a new functionality, we would like to make sure it has documentation in the corresponding console project.
Use the global knowledge feature to search for PRs in graphql-hive/console repository that adds documentation. Suggest to create a follow up issue to add the missing documentation.

  • internal/perf/src/loadtest.ts
🪛 GitHub Check: CodeQL
.github/workflows/memtest.yml

[warning] 18-42: Workflow does not contain permissions
Actions Job or Workflow does not set permissions


[warning] 35-35: Unpinned tag for a non-immutable Action in workflow
Unpinned 3rd party Action 'Memtest' step Uses Step uses 'the-guild-org/shared-config/setup' with ref 'v1', not a pinned commit hash

🪛 Biome (1.9.4)
internal/perf/src/loadtest.ts

[error] 1-1: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 2-2: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 3-3: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)

🔇 Additional comments (5)
internal/perf/src/loadtest.ts (4)

1-3: Use the node: prefix for built-in modules.

This new file still imports Node.js built-ins without the node: protocol. It's recommended to adopt the explicit protocol for clarity.

-import fs from 'fs/promises';
-import path from 'path';
-import { setTimeout } from 'timers/promises';
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import { setTimeout } from 'node:timers/promises';
🧰 Tools
🪛 Biome (1.9.4)

[error] 1-1: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 2-2: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 3-3: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


32-63: Document the new loadtesting feature in the console project.

Since this is new functionality, consider adding or updating documentation in the console repo to help users discover and leverage the loadtest utility.


93-113: Clarify behavior if k6 is not installed.

The spawn call could fail silently if k6 isn’t on the PATH. Adding a check or a more descriptive error message would help.


159-163: Add tests for debug logging.

Currently, there is no coverage verifying correct debug output. Consider adding minimal tests to ensure logs are generated only under isDebug('loadtest').

.github/workflows/memtest.yml (1)

28-33: Enhance error handling when installing k6.

Use a fail-fast approach to better handle unsuccessful downloads:

-run: |
+run: |
+  set -e
   mkdir -p "$HOME/.local/bin"
   cd "$HOME/.local/bin"
-  curl https://github.com/grafana/k6/releases/download/${{ env.K6_VERSION }}/k6-${{ env.K6_VERSION }}-linux-amd64.tar.gz -L | tar xvz --strip-components 1
+  curl -f https://github.com/grafana/k6/releases/download/${{ env.K6_VERSION }}/k6-${{ env.K6_VERSION }}-linux-amd64.tar.gz -L | tar xvz --strip-components 1
   echo "$PWD" >> $GITHUB_PATH

NODE_NO_WARNINGS: 1
K6_VERSION: v0.56.0

jobs:
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Explicitly set workflow permissions.

Security best practices recommend specifying permissions even if using none or read-only. Consider adding a block like:

permissions:
  contents: read

curl https://github.com/grafana/k6/releases/download/${{ env.K6_VERSION }}/k6-${{ env.K6_VERSION }}-linux-amd64.tar.gz -L | tar xvz --strip-components 1
echo "$PWD" >> $GITHUB_PATH
- name: Set up env
uses: the-guild-org/shared-config/setup@v1
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Pin the Action version to a commit SHA.

Using an unpinned reference (@v1) can lead to unexpected behavior if the action changes. Pinning the version helps ensure reproducible workflows.

🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 35-35: Unpinned tag for a non-immutable Action in workflow
Unpinned 3rd party Action 'Memtest' step Uses Step uses 'the-guild-org/shared-config/setup' with ref 'v1', not a pinned commit hash

Copy link

@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: 6

♻️ Duplicate comments (9)
e2e/opentelemetry/opentelemetry.memtest.ts (1)

10-24: ⚠️ Potential issue

Add container teardown step.

The Jaeger container is launched but never explicitly stopped. This can lead to resource leaks, especially during CI runs or local testing.

Apply this diff to add the cleanup:

   describe(`OpenTelemetry ${OTLP_EXPORTER_TYPE} exporter`, () => {
     let jaeger: Container;
     beforeAll(async () => {
       jaeger = await container({
         // ...
       });
     });
+    afterAll(async () => {
+      await jaeger?.stop();
+    });
internal/perf/src/chart.ts (1)

28-28: 🧹 Nitpick (assertive)

Parameterize the canvas dimensions.

This was suggested in a previous review but hasn’t been addressed. Allowing custom width/height parameters would improve flexibility.

-  const canvas = createCanvas(1366, 768, 'svg');
+export function createLineChart(
+  labels: (number | string | null)[],
+  datasets: LineChartDataset[],
+  options: LineChartOptions = {},
+  width = 1366,
+  height = 768
+): Canvas {
+  const canvas = createCanvas(width, height, 'svg');
   ...
}
internal/perf/src/loadtest.ts (2)

118-125: 🧹 Nitpick (assertive)

Check if k6 is installed or provide a user-friendly error message.

A previous comment suggested verifying the presence of k6. If k6 is missing, an explicit warning or pre-flight check could be more helpful than a cryptic spawn error.

Would you like assistance in adding a pre-flight check to handle missing k6?


1-2: 🧹 Nitpick (assertive)

Use the Node.js "node:" import protocol.

Static analysis recommends adding the node: prefix to built-in modules for clarity.

-import path from 'path';
-import { setTimeout } from 'timers/promises';
+import path from 'node:path';
+import { setTimeout } from 'node:timers/promises';
🧰 Tools
🪛 Biome (1.9.4)

[error] 1-1: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 2-2: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)

internal/perf/src/memtest.ts (1)

41-41: 🧹 Nitpick (assertive)

Document this new memtest feature in the console project.

Following the guidelines, new functionalities typically need documentation there. You could open a follow-up issue in graphql-hive/console to make it discoverable.

internal/perf/package.json (1)

1-17: ⚠️ Potential issue

Add missing test:mem script.

The workflow uses yarn test:mem but the script is missing from package.json.

 {
   "name": "@internal/perf",
   "type": "module",
   "private": true,
   "main": "./src/index.ts",
+  "scripts": {
+    "test:mem": "vitest --project memtest"
+  },
   "dependencies": {
.github/workflows/memtest.yml (3)

1-11: 🛠️ Refactor suggestion

Add permissions block for security.

Following security best practices, explicitly set permissions for the workflow.

 name: Memtest
 on:
   push:
     branches:
       - main
   pull_request:
 
+permissions:
+  contents: read
+
 concurrency:
   group: ${{ github.workflow }}-${{ github.ref }}
   cancel-in-progress: true

28-33: 🧹 Nitpick (assertive)

Enhance k6 installation reliability.

Add error handling to ensure the installation fails fast on errors.

       run: |
+        set -e
         mkdir -p "$HOME/.local/bin"
         cd "$HOME/.local/bin"
-        curl https://github.com/grafana/k6/releases/download/${{ env.K6_VERSION }}/k6-${{ env.K6_VERSION }}-linux-amd64.tar.gz -L | tar xvz --strip-components 1
+        curl -f https://github.com/grafana/k6/releases/download/${{ env.K6_VERSION }}/k6-${{ env.K6_VERSION }}-linux-amd64.tar.gz -L | tar xvz --strip-components 1
         echo "$PWD" >> $GITHUB_PATH

35-37: 🛠️ Refactor suggestion

Pin the action version to a commit SHA.

Using an unpinned reference (@v1) can lead to unexpected behavior if the action changes.

       - name: Set up env
-        uses: the-guild-org/shared-config/setup@v1
+        uses: the-guild-org/shared-config/setup@5c5a9a9
         with:
           node-version-file: .node-version
🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 35-35: Unpinned tag for a non-immutable Action in workflow
Unpinned 3rd party Action 'Memtest' step Uses Step uses 'the-guild-org/shared-config/setup' with ref 'v1', not a pinned commit hash

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 507cbb8 and df19a2e.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (18)
  • .github/workflows/bench.yml (1 hunks)
  • .github/workflows/memtest.yml (1 hunks)
  • .gitignore (1 hunks)
  • e2e/federation-example/federation-example.memtest.ts (1 hunks)
  • e2e/opentelemetry/opentelemetry.memtest.ts (1 hunks)
  • internal/e2e/src/tenv.ts (3 hunks)
  • internal/examples/src/convert.ts (1 hunks)
  • internal/perf/package.json (1 hunks)
  • internal/perf/src/chart.ts (1 hunks)
  • internal/perf/src/index.ts (1 hunks)
  • internal/perf/src/loadtest-script.ts (1 hunks)
  • internal/perf/src/loadtest.ts (1 hunks)
  • internal/perf/src/memtest.ts (1 hunks)
  • internal/proc/src/index.ts (3 hunks)
  • internal/testing/src/env.ts (1 hunks)
  • package.json (1 hunks)
  • tsconfig.json (1 hunks)
  • vitest.projects.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
`**`: For all PRs, we would like to verify that a Linear iss...

**: For all PRs, we would like to verify that a Linear issue is linked. Check if there's a mention for "GW-*" in the PR description, and the us know when it's missing.

In most cases, when a PR adds a new functionality, we would like to make sure it has documentation in the corresponding console project.
Use the global knowledge feature to search for PRs in graphql-hive/console repository that adds documentation. Suggest to create a follow up issue to add the missing documentation.

  • internal/perf/src/index.ts
  • tsconfig.json
  • vitest.projects.ts
  • e2e/federation-example/federation-example.memtest.ts
  • e2e/opentelemetry/opentelemetry.memtest.ts
  • internal/perf/src/loadtest-script.ts
  • internal/perf/package.json
  • internal/proc/src/index.ts
  • internal/testing/src/env.ts
  • internal/e2e/src/tenv.ts
  • package.json
  • internal/examples/src/convert.ts
  • internal/perf/src/chart.ts
  • internal/perf/src/memtest.ts
  • internal/perf/src/loadtest.ts
`e2e/**`: This directory includes end-to-end tests for the g...

e2e/**: This directory includes end-to-end tests for the gateway.
The examples directory is generated based on the code in this directory.

  • e2e/federation-example/federation-example.memtest.ts
  • e2e/opentelemetry/opentelemetry.memtest.ts
🪛 GitHub Check: CodeQL
.github/workflows/memtest.yml

[warning] 18-42: Workflow does not contain permissions
Actions Job or Workflow does not set permissions


[warning] 35-35: Unpinned tag for a non-immutable Action in workflow
Unpinned 3rd party Action 'Memtest' step Uses Step uses 'the-guild-org/shared-config/setup' with ref 'v1', not a pinned commit hash

🪛 Biome (1.9.4)
internal/perf/src/memtest.ts

[error] 1-1: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 2-2: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)

internal/perf/src/loadtest.ts

[error] 1-1: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 2-2: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)

⏰ Context from checks skipped due to timeout of 90000ms (31)
  • GitHub Check: E2E / Bun Docker on Ubuntu
  • GitHub Check: E2E / Bun on Ubuntu
  • GitHub Check: E2E / Node Binary on Windows
  • GitHub Check: E2E / Node Binary on Ubuntu
  • GitHub Check: E2E / Node Docker on Ubuntu
  • GitHub Check: Leaks / Node v23
  • GitHub Check: E2E / Node 23 on Ubuntu
  • GitHub Check: Leaks / Node v22
  • GitHub Check: E2E / Node 22 on Ubuntu
  • GitHub Check: Leaks / Node v20
  • GitHub Check: Leaks / Node v18
  • GitHub Check: E2E / Node 20 on Ubuntu
  • GitHub Check: E2E / Node 18 on Ubuntu
  • GitHub Check: bun / 1000 items
  • GitHub Check: Snapshot / snapshot
  • GitHub Check: Unit / Bun
  • GitHub Check: Bundle
  • GitHub Check: bun / 100 items
  • GitHub Check: Unit / Node v23
  • GitHub Check: Unit / Node v22
  • GitHub Check: bun / 10 items
  • GitHub Check: Build
  • GitHub Check: node / 1000 items
  • GitHub Check: Types
  • GitHub Check: Unit / Node v20
  • GitHub Check: node / 100 items
  • GitHub Check: Unit / Node v18
  • GitHub Check: Format
  • GitHub Check: node / 10 items
  • GitHub Check: node
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (20)
e2e/opentelemetry/opentelemetry.memtest.ts (2)

1-9: LGTM! Clean and well-structured setup.

The imports and initial setup follow the established e2e testing patterns and include all necessary utilities.


1-1: Add documentation in the console repository.

Since this PR adds new functionality for memory leak detection, corresponding documentation should be added in the graphql-hive/console repository.

❌ Verification inconclusive

🏁 Script executed:

#!/bin/bash
# Description: Check for documentation PRs in the console repository

gh search prs \
  --repo graphql-hive/console \
  --json title,url \
  --jq '.[] | select(.title | contains("memory") or contains("leak") or contains("GW-154"))' \
  --state all

Length of output: 3480


Documentation missing in the console repository

Our verification confirms that no related documentation PR has been found for the memory leak detection functionality in the graphql-hive/console repository. Please ensure that corresponding documentation is added to properly support this new functionality.

  • No open or closed documentation PRs matching keywords (“memory”, “leak”, “GW-154”) exist in graphql-hive/console.
internal/perf/src/chart.ts (1)

1-21: Interfaces look good.

The definitions for LineChartDataset and LineChartOptions are straightforward and well-documented.

internal/perf/src/memtest.ts (1)

137-138: Confirm the strict memory decrease threshold.

Your test requires the calmdown slope ≤ -10. If the pipeline fails because the slope is not that negative, you may need to adjust it or verify the expected behavior.

-        .toBeLessThanOrEqual(-10);
+        .toBeLessThanOrEqual(0);
internal/perf/src/index.ts (1)

1-1: LGTM! Clean and concise export statement.

The export statement follows TypeScript best practices for re-exporting all entities from a module.

e2e/federation-example/federation-example.memtest.ts (1)

9-18: Add idle memory verification.

Your PR objectives mention an idle memory check after load testing, but it's not implemented in this test.

internal/perf/src/loadtest-script.ts (2)

6-14: Consider adding type validation for environment variables.

While the existence check is good, consider validating the URL format and query structure.


16-21: Consider enhancing response validation.

The current response validation is basic. Consider adding more comprehensive checks.

vitest.projects.ts (1)

47-55: LGTM! The memtest configuration is properly structured.

The new memtest configuration correctly extends the base configuration and maintains consistency with other test suites by using the same timeout settings.

However, consider documenting the purpose of this test suite.

internal/testing/src/env.ts (3)

9-9: Fix the typo in the JSDoc.

Use "environment" instead of "enviorment".

- * variable `loadtest` is listed in the `DEBUG` enviorment variable.
+ * variable `loadtest` is listed in the `DEBUG` environment variable.

11-17: Consider adding input validation for the module parameter.

The function could benefit from validating the module name format.

 export function isDebug(module?: string) {
+  if (module && !/^[a-z0-9-_]+$/i.test(module)) {
+    throw new Error('Module name must contain only alphanumeric characters, hyphens, and underscores');
+  }
   if (module) {
     return boolEnv(`DEBUG_${module}`);
   }
   return boolEnv('DEBUG');
 }

11-17: Handle unset or empty DEBUG environment variable more robustly.

When process.env['DEBUG'] is not defined or empty, String(process.env['DEBUG']) becomes "undefined" or "". Consider a fallback to an empty array to avoid unintended matches.

export function isDebug(module?: string) {
  if (module) {
-    const modules = String(process.env['DEBUG'])
+    const debugStr = process.env['DEBUG'] ?? '';
+    if (!debugStr) return false;
+    const modules = debugStr
       .split(',')
       .map((m) => m.trim());
    return modules.includes(module);
  }
  return boolEnv('DEBUG');
}
internal/proc/src/index.ts (2)

11-13: LGTM! Good addition of process lifecycle management.

The waitForExit property enhances the Proc interface by providing a Promise-based way to track process completion, which is a clean and idiomatic approach in async JavaScript/TypeScript.


22-25: LGTM! Well-structured server interface.

The Server interface is a clean extension of Proc that properly encapsulates server-specific properties. The interface follows the Interface Segregation Principle by adding only the essential properties needed for server processes.

internal/e2e/src/tenv.ts (2)

709-714: LGTM! Clean Service interface implementation.

The implementation correctly spreads the Proc properties and adds the required Service-specific properties in a concise manner.


873-873: LGTM! Proper container lifecycle management.

The addition of waitForExit using ctr.wait() provides proper container lifecycle management, aligning with the Proc interface changes.

internal/examples/src/convert.ts (1)

183-184: LGTM! Consistent file exclusion pattern.

The addition of .memtest. file exclusion follows the existing pattern and properly supports the new memory testing functionality.

.gitignore (1)

15-15: LGTM!

The new ignore pattern correctly excludes memory test snapshot SVGs generated during testing.

.github/workflows/bench.yml (1)

24-24: LGTM!

The simplified job name improves readability in the GitHub Actions UI.

tsconfig.json (1)

17-18: LGTM!

The path mappings are correctly configured for the new performance testing module.

Comment on lines +25 to +32
const jaegerUrls = {
get http() {
return `http://0.0.0.0:${jaeger.port}/v1/traces`;
},
get grpc() {
return `http://0.0.0.0:${jaeger.additionalPorts[4317]}`;
},
};
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Add URL validation for Jaeger endpoints.

The URLs are constructed without validation. Consider adding checks to ensure the ports are available and the URLs are properly formatted.

     const jaegerUrls = {
       get http() {
+        const url = `http://0.0.0.0:${jaeger.port}/v1/traces`;
+        if (!jaeger.port) throw new Error('HTTP port not available');
-        return `http://0.0.0.0:${jaeger.port}/v1/traces`;
+        return url;
       },
       get grpc() {
+        const port = jaeger.additionalPorts[4317];
+        if (!port) throw new Error('gRPC port not available');
+        const url = `http://0.0.0.0:${port}`;
-        return `http://0.0.0.0:${jaeger.additionalPorts[4317]}`;
+        return url;
       },
     };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const jaegerUrls = {
get http() {
return `http://0.0.0.0:${jaeger.port}/v1/traces`;
},
get grpc() {
return `http://0.0.0.0:${jaeger.additionalPorts[4317]}`;
},
};
const jaegerUrls = {
get http() {
const url = `http://0.0.0.0:${jaeger.port}/v1/traces`;
if (!jaeger.port) throw new Error('HTTP port not available');
return url;
},
get grpc() {
const port = jaeger.additionalPorts[4317];
if (!port) throw new Error('gRPC port not available');
const url = `http://0.0.0.0:${port}`;
return url;
},
};

beforeAll(async () => {
jaeger = await container({
name: `jaeger-${OTLP_EXPORTER_TYPE}`,
image: 'jaegertracing/all-in-one:1.56',
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify Jaeger image version.

The Jaeger image version is hardcoded to '1.56'. Consider implementing version verification to ensure compatibility and security updates.


🏁 Script executed:

#!/bin/bash
# Description: Check for latest Jaeger image version and security advisories

# Check DockerHub for latest versions
curl -s https://hub.docker.com/v2/repositories/jaegertracing/all-in-one/tags\?page_size=5 | jq -r '.results[].name'

# Check for security advisories
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: CONTAINER, package: "jaegertracing/all-in-one") {
    nodes {
      advisory {
        summary
        severity
        publishedAt
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'

Length of output: 953


Jaeger Image Version Outdated – Action Needed

  • Current Issue: The file e2e/opentelemetry/opentelemetry.memtest.ts is using the hardcoded image tag jaegertracing/all-in-one:1.56, which is older than the versions currently available on DockerHub (e.g., 1.66.0, 1.65.0, etc.).
  • Recommendation: Update the image version or implement a dynamic version verification mechanism to ensure compatibility with the latest releases and security updates.
  • Additional Note: The security advisories check failed due to an invalid API parameter (ecosystem set to CONTAINER). This query needs correction if security vulnerability checks are to be integrated.

Comment on lines +34 to +48
memtest(
{
cwd,
query,
},
async () =>
gateway({
supergraph: await supergraph(),
env: {
OTLP_EXPORTER_TYPE,
OTLP_EXPORTER_URL: jaegerUrls[OTLP_EXPORTER_TYPE],
OTLP_SERVICE_NAME: `memtest-${OTLP_EXPORTER_TYPE}`,
},
}),
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Document memory testing strategy and thresholds.

The memory test configuration lacks documentation about the testing strategy and explicit memory leak detection criteria. Based on the PR objectives, this should include memory increase slope and idle memory checks.

Add JSDoc comments to document the testing strategy:

+    /**
+     * Memory test configuration for OpenTelemetry exporters
+     * 
+     * Strategy:
+     * 1. Monitor memory increase slope during load testing
+     * 2. Check idle memory after load testing
+     * 3. Fail if memory increase exceeds threshold or doesn't stabilize
+     * 
+     * @threshold Maximum allowed memory increase rate (MB/min)
+     * @idleTime Time to wait for memory stabilization (ms)
+     * @idleThreshold Maximum allowed memory fluctuation during idle (MB)
+     */
     memtest(
       {
         cwd,
         query,
+        threshold: 5, // MB/min
+        idleTime: 30000, // 30 seconds
+        idleThreshold: 1, // 1 MB
       },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
memtest(
{
cwd,
query,
},
async () =>
gateway({
supergraph: await supergraph(),
env: {
OTLP_EXPORTER_TYPE,
OTLP_EXPORTER_URL: jaegerUrls[OTLP_EXPORTER_TYPE],
OTLP_SERVICE_NAME: `memtest-${OTLP_EXPORTER_TYPE}`,
},
}),
);
/**
* Memory test configuration for OpenTelemetry exporters
*
* Strategy:
* 1. Monitor memory increase slope during load testing
* 2. Check idle memory after load testing
* 3. Fail if memory increase exceeds threshold or doesn't stabilize
*
* @threshold Maximum allowed memory increase rate (MB/min)
* @idleTime Time to wait for memory stabilization (ms)
* @idleThreshold Maximum allowed memory fluctuation during idle (MB)
*/
memtest(
{
cwd,
query,
threshold: 5, // MB/min
idleTime: 30000, // 30 seconds
idleThreshold: 1, // 1 MB
},
async () =>
gateway({
supergraph: await supergraph(),
env: {
OTLP_EXPORTER_TYPE,
OTLP_EXPORTER_URL: jaegerUrls[OTLP_EXPORTER_TYPE],
OTLP_SERVICE_NAME: `memtest-${OTLP_EXPORTER_TYPE}`,
},
}),
);

Comment on lines 6 to 16
"dependencies": {
"canvas": "^3.1.0",
"chart.js": "^4.4.7",
"chartjs-plugin-trendline": "^2.1.6",
"parse-duration": "^2.0.0",
"regression": "^2.0.1"
},
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Dependencies look good but consider pinning versions.

The dependencies align well with the memory testing requirements. However, consider pinning the versions for better reproducibility.

   "dependencies": {
-    "canvas": "^3.1.0",
-    "chart.js": "^4.4.7",
-    "chartjs-plugin-trendline": "^2.1.6",
-    "parse-duration": "^2.0.0",
-    "regression": "^2.0.1"
+    "canvas": "3.1.0",
+    "chart.js": "4.4.7",
+    "chartjs-plugin-trendline": "2.1.6",
+    "parse-duration": "2.0.0",
+    "regression": "2.0.1"
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"dependencies": {
"canvas": "^3.1.0",
"chart.js": "^4.4.7",
"chartjs-plugin-trendline": "^2.1.6",
"parse-duration": "^2.0.0",
"regression": "^2.0.1"
},
"dependencies": {
- "canvas": "^3.1.0",
- "chart.js": "^4.4.7",
- "chartjs-plugin-trendline": "^2.1.6",
- "parse-duration": "^2.0.0",
- "regression": "^2.0.1"
+ "canvas": "3.1.0",
+ "chart.js": "4.4.7",
+ "chartjs-plugin-trendline": "2.1.6",
+ "parse-duration": "2.0.0",
+ "regression": "2.0.1"
}

Copy link

@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: 11

♻️ Duplicate comments (11)
internal/perf/src/chart.ts (2)

22-73: 🧹 Nitpick (assertive)

Add corresponding console documentation for new charting functionality
This new charting utility is useful, but may not be sufficiently discoverable across teams.

(Same suggestion as a past comment: consider adding or updating documentation in the graphql-hive/console repository so that teams know how to generate and use these charts.)


1-5: 🧹 Nitpick (assertive)

Consider making canvas dimensions configurable
Currently, the canvas is fixed at 1366 × 768. For flexibility in different environments, you could add parameters to set custom canvas width and height.

-  const canvas = createCanvas(1366, 768, 'svg');
+  export function createLineChart(
+    labels: (number | string | null)[],
+    datasets: LineChartDataset[],
+    options: LineChartOptions = {},
+    canvasWidth = 1366,
+    canvasHeight = 768
+  ): Canvas {
+    const canvas = createCanvas(canvasWidth, canvasHeight, 'svg');
     ...
  }
internal/perf/src/inspector.ts (1)

1-2: 🧹 Nitpick (assertive)

Use the Node.js import protocol
Static analysis notes that Node.js built-in imports should use the node: prefix for clarity.

-import fs from 'fs/promises';
-import { setTimeout } from 'timers/promises';
+import fs from 'node:fs/promises';
+import { setTimeout } from 'node:timers/promises';
🧰 Tools
🪛 Biome (1.9.4)

[error] 1-1: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 2-2: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)

internal/perf/src/loadtest.ts (3)

128-135: 🧹 Nitpick (assertive)

Check for the presence of k6
If k6 isn’t installed, the load test fails in unclear ways.

(Same suggestion noted before: add a pre-flight check or handle an error gracefully if k6 isn’t found on the PATH.)


44-148: 🧹 Nitpick (assertive)

Add documentation for new loadtesting feature
This is a significant new feature that might need a usage explanation in your docs or graphql-hive/console.

(Same suggestion from previous reviews: track or open a follow-up issue to document these capabilities.)


1-2: 🧹 Nitpick (assertive)

Adopt node: prefix for built-in modules
Again, static analysis recommends using the node: protocol for clarity with Node.js built-in modules.

-import path from 'path';
-import { setTimeout } from 'timers/promises';
+import path from 'node:path';
+import { setTimeout } from 'node:timers/promises';
🧰 Tools
🪛 Biome (1.9.4)

[error] 1-1: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 2-2: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)

internal/perf/package.json (1)

1-22: 🧹 Nitpick (assertive)

Add test:mem script and consider pinning dependency versions.

The dependencies align well with the memory testing requirements. However, consider pinning the versions for better reproducibility and adding the test script.

Apply this diff to pin versions and add the test script:

 {
   "name": "@internal/perf",
   "type": "module",
   "private": true,
   "main": "./src/index.ts",
+  "scripts": {
+    "test:mem": "vitest --project memtest"
+  },
   "dependencies": {
     "@graphql-tools/delegate": "workspace:^",
     "@memlab/core": "patch:@memlab/core@npm%3A1.1.39#~/.yarn/patches/@memlab-core-npm-1.1.39-34d29dabbd.patch",
-    "canvas": "^3.1.0",
-    "chart.js": "^4.4.7",
-    "chartjs-plugin-trendline": "^2.1.6",
-    "memlab": "^1.1.56",
-    "parse-duration": "^2.0.0",
-    "regression": "^2.0.1",
-    "ws": "^8.18.0"
+    "canvas": "3.1.0",
+    "chart.js": "4.4.7",
+    "chartjs-plugin-trendline": "2.1.6",
+    "memlab": "1.1.56",
+    "parse-duration": "2.0.0",
+    "regression": "2.0.1",
+    "ws": "8.18.0"
   },
   "devDependencies": {
-    "@types/k6": "^0.54.2",
-    "@types/regression": "^2",
-    "@types/ws": "^8.5.12"
+    "@types/k6": "0.54.2",
+    "@types/regression": "2.0.0",
+    "@types/ws": "8.5.12"
   }
 }
.github/workflows/memtest.yml (4)

35-35: 🛠️ Refactor suggestion

Pin the Shared Config Action Version

The Set up env step uses an unpinned reference (@v1) for the-guild-org/shared-config/setup. Pinning to a specific commit hash ensures reproducibility and avoids unexpected changes.

Example suggestion:

-uses: the-guild-org/shared-config/setup@v1
+uses: the-guild-org/shared-config/setup@<commit-hash>
🧰 Tools
🪛 GitHub Check: CodeQL

[warning] 35-35: Unpinned tag for a non-immutable Action in workflow
Unpinned 3rd party Action 'Memtest' step Uses Step uses 'the-guild-org/shared-config/setup' with ref 'v1', not a pinned commit hash


1-7: 🧹 Nitpick (assertive)

⚠️ Potential issue

Add Explicit Workflow Permissions

Security best practices recommend explicitly setting workflow permissions. Consider adding a permissions block (e.g., contents: read) at the top of the workflow to avoid potential security risks.

Suggested diff:

+permissions:
+  contents: read

Place this block near the top (after the on: section) of the workflow file.


29-33: 🧹 Nitpick (assertive)

Enhance k6 Installation Resilience

The Install k6 step currently lacks error handling. To improve resilience, add set -e at the beginning and use curl -f to ensure the command fails if the download is unsuccessful.

Suggested diff:

-run: |
-  mkdir -p "$HOME/.local/bin"
-  cd "$HOME/.local/bin"
-  curl https://github.com/grafana/k6/releases/download/${{ env.K6_VERSION }}/k6-${{ env.K6_VERSION }}-linux-amd64.tar.gz -L | tar xvz --strip-components 1
-  echo "$PWD" >> $GITHUB_PATH
+run: |
+  set -e
+  mkdir -p "$HOME/.local/bin"
+  cd "$HOME/.local/bin"
+  curl -f https://github.com/grafana/k6/releases/download/${{ env.K6_VERSION }}/k6-${{ env.K6_VERSION }}-linux-amd64.tar.gz -L | tar xvz --strip-components 1
+  echo "$PWD" >> $GITHUB_PATH

24-25: 🧹 Nitpick (assertive)

Enforce a Job-level Timeout

To avoid potential hangs or overly long test executions, consider adding a job-level timeout. For example, adding timeout-minutes: 30 immediately after runs-on: ubuntu-latest can help prevent runaway jobs.

Example placement:

 runs-on: ubuntu-latest
+timeout-minutes: 30
 steps:
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0fada78 and 3ceab6d.

⛔ Files ignored due to path filters (2)
  • .yarn/patches/@memlab-core-npm-1.1.39-34d29dabbd.patch is excluded by !**/.yarn/**
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (21)
  • .github/workflows/bench.yml (1 hunks)
  • .github/workflows/memtest.yml (1 hunks)
  • .gitignore (1 hunks)
  • e2e/cloudflare-workers/cloudflare-workers.e2e.ts (1 hunks)
  • e2e/federation-example/federation-example.memtest.ts (1 hunks)
  • e2e/opentelemetry/opentelemetry.memtest.ts (1 hunks)
  • internal/e2e/src/tenv.ts (9 hunks)
  • internal/examples/src/convert.ts (1 hunks)
  • internal/perf/package.json (1 hunks)
  • internal/perf/src/chart.ts (1 hunks)
  • internal/perf/src/heap.ts (1 hunks)
  • internal/perf/src/index.ts (1 hunks)
  • internal/perf/src/inspector.ts (1 hunks)
  • internal/perf/src/loadtest-script.ts (1 hunks)
  • internal/perf/src/loadtest.ts (1 hunks)
  • internal/perf/src/memtest.ts (1 hunks)
  • internal/proc/src/index.ts (3 hunks)
  • internal/testing/src/env.ts (1 hunks)
  • package.json (1 hunks)
  • tsconfig.json (1 hunks)
  • vitest.projects.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
`**`: For all PRs, we would like to verify that a Linear iss...

**: For all PRs, we would like to verify that a Linear issue is linked. Check if there's a mention for "GW-*" in the PR description, and the us know when it's missing.

In most cases, when a PR adds a new functionality, we would like to make sure it has documentation in the corresponding console project.
Use the global knowledge feature to search for PRs in graphql-hive/console repository that adds documentation. Suggest to create a follow up issue to add the missing documentation.

  • internal/perf/src/heap.ts
  • internal/examples/src/convert.ts
  • e2e/federation-example/federation-example.memtest.ts
  • internal/testing/src/env.ts
  • internal/perf/src/index.ts
  • vitest.projects.ts
  • package.json
  • e2e/opentelemetry/opentelemetry.memtest.ts
  • tsconfig.json
  • e2e/cloudflare-workers/cloudflare-workers.e2e.ts
  • internal/perf/src/loadtest-script.ts
  • internal/perf/package.json
  • internal/proc/src/index.ts
  • internal/perf/src/chart.ts
  • internal/perf/src/loadtest.ts
  • internal/perf/src/memtest.ts
  • internal/perf/src/inspector.ts
  • internal/e2e/src/tenv.ts
`e2e/**`: This directory includes end-to-end tests for the g...

e2e/**: This directory includes end-to-end tests for the gateway.
The examples directory is generated based on the code in this directory.

  • e2e/federation-example/federation-example.memtest.ts
  • e2e/opentelemetry/opentelemetry.memtest.ts
  • e2e/cloudflare-workers/cloudflare-workers.e2e.ts
🪛 Biome (1.9.4)
e2e/cloudflare-workers/cloudflare-workers.e2e.ts

[error] 88-88: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)


[error] 90-90: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)

internal/perf/src/loadtest.ts

[error] 1-1: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 2-2: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)

internal/perf/src/memtest.ts

[error] 1-1: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 2-2: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)

internal/perf/src/inspector.ts

[error] 1-1: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 2-2: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)

🪛 GitHub Check: CodeQL
.github/workflows/memtest.yml

[warning] 18-42: Workflow does not contain permissions
Actions Job or Workflow does not set permissions


[warning] 35-35: Unpinned tag for a non-immutable Action in workflow
Unpinned 3rd party Action 'Memtest' step Uses Step uses 'the-guild-org/shared-config/setup' with ref 'v1', not a pinned commit hash

⏰ Context from checks skipped due to timeout of 90000ms (29)
  • GitHub Check: Unit / Node v23
  • GitHub Check: Unit / Node v22
  • GitHub Check: Unit / Bun
  • GitHub Check: E2E / Bun Docker on Ubuntu
  • GitHub Check: Unit / Node v20
  • GitHub Check: E2E / Bun on Ubuntu
  • GitHub Check: Unit / Node v18
  • GitHub Check: E2E / Node Binary on Windows
  • GitHub Check: Leaks / Node v23
  • GitHub Check: E2E / Node Binary on Ubuntu
  • GitHub Check: Leaks / Node v22
  • GitHub Check: Snapshot / snapshot
  • GitHub Check: E2E / Node Docker on Ubuntu
  • GitHub Check: Build
  • GitHub Check: Leaks / Node v20
  • GitHub Check: bun / 1000 items
  • GitHub Check: bun / 100 items
  • GitHub Check: E2E / Node 23 on Ubuntu
  • GitHub Check: bun / 10 items
  • GitHub Check: Leaks / Node v18
  • GitHub Check: node / 1000 items
  • GitHub Check: E2E / Node 22 on Ubuntu
  • GitHub Check: Bundle
  • GitHub Check: E2E / Node 20 on Ubuntu
  • GitHub Check: node / 100 items
  • GitHub Check: E2E / Node 18 on Ubuntu
  • GitHub Check: node
  • GitHub Check: node / 10 items
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (25)
internal/perf/src/inspector.ts (1)

39-81: Ensure robust error handling for heap snapshot writing
If the HeapProfiler.takeHeapSnapshot call fails or returns an invalid response, the code might remain in a waiting state. Consider adding additional checks for response integrity and possible errors.

Would you like a script to search the codebase for other usage or error handling for takeHeapSnapshot to confirm coverage?

internal/perf/src/loadtest.ts (2)

60-62: Approved: Minimal duration check
It’s good practice to disallow very short durations to prevent meaningless load tests. This check is straightforward and clear.


71-71:

✅ Verification successful

Verify TS version support for using statement
TypeScript’s using syntax is stable in recent releases. Ensure your environment targets a TS version >= 5.2.


🌐 Web query:

Is the `using` keyword stable in TypeScript 5.2 or later?

💡 Result:

The using keyword was officially introduced as a stable feature in TypeScript 5.2, released in August 2023[4][9]. It enables explicit resource management by automatically disposing of objects with a Symbol.dispose method at the end of their scope, simplifying cleanup tasks like closing file handles or database connections[1][4][5].

Key points:

  • Stability: The feature is part of TypeScript 5.2's stable release[4][9], not experimental.
  • Implementation: Based on the TC39 Explicit Resource Management proposal (Stage 3)[5], making it likely to become a future ECMAScript standard.
  • Syntax:
    using resource = new TempFile(".tmp");
    // Automatically calls resource[Symbol.dispose]() when out of scope
  • Behavior: Works with both synchronous (Symbol.dispose) and asynchronous (Symbol.asyncDispose) cleanup[2][4].
  • Adoption: Already supported in tooling like Prettier as of July 2023[10].

The feature simplifies code by eliminating manual try/finally blocks for resource cleanup[1][4]. For example:

// Before
let file;
try {
  file = openFile();
} finally {
  file.close();
}

// After TypeScript 5.2
using file = openFile();
// Automatic disposal

This functionality is now considered stable and production-ready in TypeScript 5.2 and later versions[4][9].

Citations:


TS 5.2 Using Statement Confirmed

The using keyword is officially stable as of TypeScript 5.2. The code on line 71 in internal/perf/src/loadtest.ts is valid, provided that your project’s configuration targets TypeScript 5.2 or later. No further changes are needed in this area—just ensure your environment's tsconfig.json (or equivalent settings) specifies a compatible TS version.

internal/perf/src/memtest.ts (2)

138-142: Verify the strictness of the calmdown slope threshold
A slope threshold of -10 might cause false positives if memory usage doesn't decrease enough. Consider adjusting the threshold or documenting why -10 is appropriate.


147-167: Linear regression slope calculation is well-implemented
The usage of the regression library and returning result.equation[0] is correct and clear.

internal/e2e/src/tenv.ts (5)

17-17: Importing Server from @internal/proc promotes consistency
Centralizing the server definition in a single module helps maintain a clean architecture.


343-343: Spawn command parsing looks correct
Splitting the command string or array is straightforward. No issues detected here.


526-526: Use of --inspect-port=0 is beneficial
Allocating the next available port prevents conflicts in shared environments. Good approach.


717-722: New Service object structure is consistent
Merging proc with additional properties keeps the code DRY and clear.


881-885: Reinforce error message for container kill
This was previously flagged. Consider providing actionable guidance such as 'Use container.stop()' to explain alternative approaches.

 kill() {
-  throw new Error('Cannot send signals to containers.');
+  throw new Error('Cannot send signals to containers. Use container.stop() instead.');
 }
internal/perf/src/index.ts (1)

1-1: Re-exporting loadtest utilities is clear and concise
This simple export helps consolidate the module's public API.

e2e/federation-example/federation-example.memtest.ts (1)

9-18: Add idle memory verification or documentation.

Your PR objectives mention an idle memory check after load testing and possible docs in console. Consider incorporating a post-loadtest idle check or opening a follow-up issue to add relevant documentation in graphql-hive/console.

internal/perf/src/loadtest-script.ts (2)

6-14: Consider adding type validation for environment variables.

While the existence check is good, consider validating the URL format and query structure.


16-21: Consider enhancing response validation.

The current response validation is basic. Consider adding more comprehensive checks.

vitest.projects.ts (1)

47-55: LGTM! The memtest configuration is properly structured.

The new memtest configuration correctly extends the base configuration and maintains consistency with other test suites by using the same timeout settings.

However, consider documenting the purpose of this test suite.

e2e/opentelemetry/opentelemetry.memtest.ts (3)

13-24: Add container cleanup to prevent resource leaks.

The container is launched but never explicitly stopped. This can lead to resource leaks, especially in CI environments.

Apply this diff:

   beforeAll(async () => {
     jaeger = await container({
       name: `jaeger-${OTLP_EXPORTER_TYPE}`,
       image: 'jaegertracing/all-in-one:1.56',
       env: {
         COLLECTOR_OTLP_ENABLED: 'true',
       },
       containerPort: 4318,
       additionalContainerPorts: [16686, 4317],
       healthcheck: ['CMD-SHELL', 'wget --spider http://0.0.0.0:14269'],
     });
   });
+  afterAll(async () => {
+    await jaeger.stop?.();
+  });

25-32: Add URL validation for Jaeger endpoints.

The URLs are constructed without validation. Consider adding checks to ensure the ports are available and the URLs are properly formatted.

Apply this diff:

   const jaegerUrls = {
     get http() {
+      const url = `http://0.0.0.0:${jaeger.port}/v1/traces`;
+      if (!jaeger.port) throw new Error('HTTP port not available');
-      return `http://0.0.0.0:${jaeger.port}/v1/traces`;
+      return url;
     },
     get grpc() {
+      const port = jaeger.additionalPorts[4317];
+      if (!port) throw new Error('gRPC port not available');
+      const url = `http://0.0.0.0:${port}`;
-      return `http://0.0.0.0:${jaeger.additionalPorts[4317]}`;
+      return url;
     },
   };

34-48: Document memory testing strategy and thresholds.

Based on the PR objectives, this test should include memory increase slope and idle memory checks. The configuration lacks documentation about the testing strategy and explicit memory leak detection criteria.

Add JSDoc comments to document the testing strategy:

+    /**
+     * Memory test configuration for OpenTelemetry exporters
+     * 
+     * Strategy:
+     * 1. Monitor memory increase slope during load testing
+     * 2. Check idle memory after load testing
+     * 3. Fail if memory increase exceeds threshold or doesn't stabilize
+     * 
+     * @threshold Maximum allowed memory increase rate (MB/min)
+     * @idleTime Time to wait for memory stabilization (ms)
+     * @idleThreshold Maximum allowed memory fluctuation during idle (MB)
+     */
     memtest(
       {
         cwd,
         query,
+        threshold: 5, // MB/min
+        idleTime: 30000, // 30 seconds
+        idleThreshold: 1, // 1 MB
       },
internal/testing/src/env.ts (2)

9-9: Fix the typo in the JSDoc.

The word "enviorment" is misspelled.

Apply this diff:

- * variable `loadtest` is listed in the `DEBUG` enviorment variable.
+ * variable `loadtest` is listed in the `DEBUG` environment variable.

11-17: Improve input validation and string handling.

The function needs better input validation and more robust string handling.

Apply this diff:

 export function isDebug(module?: string) {
+  if (module && !/^[a-z0-9-_]+$/i.test(module)) {
+    throw new Error('Module name must contain only alphanumeric characters, hyphens, and underscores');
+  }
   if (module) {
-    const modules = String(process.env['DEBUG'])
+    const debugStr = process.env['DEBUG'] ?? '';
+    if (!debugStr) return false;
+    const modules = debugStr
       .split(',')
       .map((m) => m.trim());
     return modules.includes(module);
   }
   return boolEnv('DEBUG');
 }
internal/proc/src/index.ts (1)

89-91: Add error handling for kill signal.

The kill method should handle invalid signals and process states.

Apply this diff:

     kill(signal) {
+      if (signal && !['SIGTERM', 'SIGKILL', 'SIGINT'].includes(signal)) {
+        throw new Error(`Invalid signal: ${signal}`);
+      }
+      if (!child.pid) {
+        throw new Error('Process is not running');
+      }
       child.kill(signal);
     },
e2e/cloudflare-workers/cloudflare-workers.e2e.ts (1)

81-92: Use template literals for string concatenation.

The string concatenation can be improved using template literals.

Apply this diff:

     await spawn([
       'yarn',
       'wrangler',
       'dev',
       '--port',
       port.toString(),
       '--var',
-      'OTLP_EXPORTER_URL:' + env.OTLP_EXPORTER_URL,
+      `OTLP_EXPORTER_URL:${env.OTLP_EXPORTER_URL}`,
       '--var',
-      'OTLP_SERVICE_NAME:' + env.OTLP_SERVICE_NAME,
+      `OTLP_SERVICE_NAME:${env.OTLP_SERVICE_NAME}`,
       ...(isDebug() ? ['--var', 'DEBUG:1'] : []),
     ]);
🧰 Tools
🪛 Biome (1.9.4)

[error] 88-88: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)


[error] 90-90: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)

internal/examples/src/convert.ts (1)

183-184: LGTM! Exclusion of memtest files is correctly implemented.

The addition of the .memtest. exclusion condition aligns with the PR's memory leak detection objectives and follows the established pattern for file exclusions.

.gitignore (1)

15-16: LGTM! Memory test artifacts are correctly ignored.

The new entries appropriately ignore memory test related files:

  • Memory snapshot SVG files
  • Heap snapshot files
.github/workflows/bench.yml (1)

24-24: LGTM! Workflow improvements enhance readability.

The job name simplification improves readability in the checks overview, and the removal of redundant CI variable (as noted in past reviews) is correct since GitHub Actions sets it by default.

Comment on lines +13 to +20
export interface LineChartOptions {
/**
* The tick label callbacks of {@link LineChartDataset.data y} entries.
*
* TODO: separate the tick callback for each data.
*/
yTicksCallback?: (tickValue: number | string) => string;
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Remember the TODO
The TODO for separating the tick callback per dataset indicates a planned feature. If each dataset needs distinct formatting for the y-axis labels, consider adding a property like datasets[i].tickCallback.

Would you like me to propose a code snippet for multiple tick callbacks?

Comment on lines +15 to +24
let debuggerUrl = '';
while (!debuggerUrl) {
await setTimeout(100);
for (const line of proc.getStd('err').split('\n')) {
debuggerUrl = line.split('Debugger listening on ')?.[1] || '';
if (debuggerUrl) {
break;
}
}
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Prevent infinite loop if debuggerUrl is never found
If the process fails to print the debugger URL, the while (!debuggerUrl) loop could run indefinitely. Consider adding a maximum retry count or timeout to avoid hanging.

 let attempts = 0;
 while (!debuggerUrl) {
+  if (attempts++ > 1000) {
+    throw new Error("Debugger URL not found within reasonable time");
+  }
   await setTimeout(100);
   ...
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
let debuggerUrl = '';
while (!debuggerUrl) {
await setTimeout(100);
for (const line of proc.getStd('err').split('\n')) {
debuggerUrl = line.split('Debugger listening on ')?.[1] || '';
if (debuggerUrl) {
break;
}
}
}
let debuggerUrl = '';
let attempts = 0;
while (!debuggerUrl) {
if (attempts++ > 1000) {
throw new Error("Debugger URL not found within reasonable time");
}
await setTimeout(100);
for (const line of proc.getStd('err').split('\n')) {
debuggerUrl = line.split('Debugger listening on ')?.[1] || '';
if (debuggerUrl) {
break;
}
}
}

Comment on lines +41 to +145
duration = 180_000,
calmdown = 30_000,
onMemorySnapshot,
...loadtestOpts
} = opts;
it(
'should have stable memory usage',
{
timeout: idle + duration + calmdown + 30_000, // allow 30s for the test teardown (compensate for heap snapshots)
},
async ({ expect }) => {
const server = await setup();

const startTime = new Date()
.toISOString()
// replace time colons with dashes to make it a valid filename
.replaceAll(':', '-')
// remove milliseconds
.split('.')[0];

const snapshots = await loadtest({
...loadtestOpts,
cwd,
memorySnapshotWindow,
idle,
duration,
calmdown,
server,
async onMemorySnapshot(memoryUsageInMB, phase, snapshots) {
if (isDebug('memtest')) {
const chart = createLineChart(
snapshots.total.map(
(_, i) => `${i + memorySnapshotWindow / 1000}. sec`,
),
[
{
label: 'Idle',
data: snapshots.idle,
},
...(snapshots.loadtest.length
? [
{
label: 'Loadtest',
data: [
...snapshots.idle.map((val, i, arr) =>
i === arr.length - 1 ? val : null,
), // skip idle data except for the last point to make a connection in the chart
...snapshots.loadtest,
],
},
]
: []),
...(snapshots.calmdown.length
? [
{
label: 'Calmdown',
data: [
...snapshots.idle.map(() => null), // skip idle data
...snapshots.loadtest.map((val, i, arr) =>
i === arr.length - 1 ? val : null,
), // skip loadtest data except for the last point to make a connection in the chart
...snapshots.calmdown,
],
},
]
: []),
],
{
yTicksCallback: (tickValue) => `${tickValue} MB`,
},
);
await fs.writeFile(
path.join(cwd, `memtest-memory-snapshots_${startTime}.svg`),
chart.toBuffer(),
);
}
return onMemorySnapshot?.(memoryUsageInMB, phase, snapshots);
},
});

const idleSlope = calculateRegressionSlope(snapshots.idle);
debugLog(`server memory idle regression slope: ${idleSlope}`);
expect
.soft(idleSlope, 'Memory increase detected while idling')
.toBeLessThanOrEqual(0);

const loadtestSlope = calculateRegressionSlope(snapshots.loadtest);
debugLog(`server memory loadtest regression slope: ${loadtestSlope}`);
expect
.soft(loadtestSlope, 'Memory never stopped growing during loadtest')
.toBeLessThanOrEqual(1);

const calmdownSlope = calculateRegressionSlope(snapshots.calmdown);
debugLog(`server memory calmdown regression slope: ${calmdownSlope}`);
expect
.soft(calmdownSlope, 'No memory decrease detected during calmdown')
.toBeLessThanOrEqual(-10);
},
);
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Consider splitting the primary test into smaller blocks
The logic within this single test function is substantial, which can impact readability and maintenance. Splitting it into smaller, descriptive test blocks can help manage complexity.

Comment on lines +1 to +2
import fs from 'fs/promises';
import path from 'path';
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enforce "node:" import protocol for built-in modules
Use the node: prefix for built-in modules to align with recommended Node.js import practices.

- import fs from 'fs/promises';
- import path from 'path';
+ import fs from 'node:fs/promises';
+ import path from 'node:path';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import fs from 'fs/promises';
import path from 'path';
import fs from 'node:fs/promises';
import path from 'node:path';
🧰 Tools
🪛 Biome (1.9.4)

[error] 1-1: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)


[error] 2-2: A Node.js builtin module should be imported with the node: protocol.

Using the node: protocol is more explicit and signals that the imported module belongs to Node.js.
Unsafe fix: Add the node: protocol.

(lint/style/useNodejsImportProtocol)

export interface GatewayOptions extends ProcOptions {
port?: number;
/** Extra args to pass to the process. */
args?: (string | number | boolean)[];
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Flexible args property introduced
Allowing (string | number | boolean)[] is broad. If possible, consider narrowing it or introducing more specific argument handling.

@@ -163,6 +162,8 @@
}

export interface ComposeOptions extends ProcOptions {
/** Extra args to pass to the process. */
args?: (string | number | boolean)[];
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Broad argument type for ComposeOptions
Consistency with other interfaces is good. Opportunities remain for typed or validated arguments if your use cases require more precision.

Comment on lines +3 to +6
export async function parseHeap(file: string) {
await HeapParser.parse(file);
// TODO: implement and use
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Implement heap parsing functionality.

The function is exported but not fully implemented. The TODO comment suggests that additional functionality is needed.

Would you like me to help implement the heap parsing functionality? I can:

  1. Parse the heap data into a structured format
  2. Add memory usage analysis
  3. Generate a report with memory trends

Comment on lines +1 to +22
{
"name": "@internal/perf",
"type": "module",
"private": true,
"main": "./src/index.ts",
"dependencies": {
"@graphql-tools/delegate": "workspace:^",
"@memlab/core": "patch:@memlab/core@npm%3A1.1.39#~/.yarn/patches/@memlab-core-npm-1.1.39-34d29dabbd.patch",
"canvas": "^3.1.0",
"chart.js": "^4.4.7",
"chartjs-plugin-trendline": "^2.1.6",
"memlab": "^1.1.56",
"parse-duration": "^2.0.0",
"regression": "^2.0.1",
"ws": "^8.18.0"
},
"devDependencies": {
"@types/k6": "^0.54.2",
"@types/regression": "^2",
"@types/ws": "^8.5.12"
}
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

💡 Verification agent

🧩 Analysis chain

Documentation needed for memory leak detection feature.

Since this PR (GW-154) introduces memory leak detection functionality, documentation should be added to help users understand and utilize this feature.

Let's check if documentation exists in the console project:

Consider creating documentation that covers:

  • How to use the memory leak detection feature
  • Understanding memory snapshots and heap dumps
  • Interpreting test results and visualizations

🏁 Script executed:

#!/bin/bash
# Description: Search for documentation related to memory leak detection in the console project.

gh api graphql -f query='
query {
  search(query:"repo:graphql-hive/console memory leak detection", type:ISSUE, first:5) {
    nodes {
      ... on PullRequest {
        title
        url
        state
      }
    }
  }
}'

Length of output: 917


Action Required: Add Documentation for Memory Leak Detection Feature

The search query in the console project did not return any documentation related to memory leak detection. Since GW-154 introduces this new functionality, please add user-facing documentation that covers the following aspects:

  • Usage: Steps for enabling and using the memory leak detection feature.
  • Technical Details: Explanation of memory snapshots, heap dumps, and how the underlying mechanism works.
  • Result Interpretation: Guidance on how to interpret test results and visualizations.

Ensure the documentation is added in the appropriate location to help users take full advantage of the new feature.

Comment on lines +17 to +18
"@internal/perf": ["./internal/perf/src/index.ts"],
"@internal/perf/memtest": ["./internal/perf/src/memtest.ts"],
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Confirm New Path Mappings for Memory Test Integration

The new path mappings for @internal/perf and @internal/perf/memtest are correctly defined to support the memtest functionality. Ensure that any related documentation (e.g., in the console project) is updated to reflect these additions.

Comment on lines +26 to +27
"test:leaks": "cross-env \"LEAK_TEST=1\" jest --detectOpenHandles --detectLeaks",
"test:mem": "vitest --project memtest"
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick (assertive)

Document the New Memory Testing Script

A new script "test:mem": "vitest --project memtest" has been added to expand testing capabilities. Please ensure that this new memory testing functionality is documented in the console project so that users understand its purpose and usage.

It might be helpful to open a follow-up issue or PR to update the documentation accordingly.

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.

2 participants