Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/memtest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jobs:
- opentelemetry
- programmatic-batching
- logger
- execution-cancellation
e2e_runner:
- node
# - bun TODO: get memory snaps and heap sampling for bun. is it even necessary?
Expand Down
56 changes: 56 additions & 0 deletions e2e/execution-cancellation/execution-cancellation.memtest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import path from 'path';
import { createExampleSetup, createTenv } from '@internal/e2e';
import { memtest } from '@internal/perf/memtest';
import { spawn, waitForPort } from '@internal/proc';
import { getAvailablePort } from '@internal/testing';
import { describe } from 'vitest';

const cwd = __dirname;

const { gateway } = createTenv(cwd);
const { supergraph, query } = createExampleSetup(cwd);

describe('Hive Gateway', () => {
memtest(
{
cwd,
query,
},
async () =>
gateway({
supergraph: await supergraph(),
}),
);
});

describe('Yoga', () => {
memtest(
{
cwd,
query: '{hello}',
},
async () => {
const port = await getAvailablePort();
const [proc] = await spawn(
{ cwd, env: { PORT: port } },
'node',
'--inspect-port=0', // necessary for perf inspector
'--import',
'tsx',
path.join(cwd, 'yoga-server.ts'),
);

await waitForPort({
port,
protocol: 'http',
signal: new AbortController().signal,
});

return {
...proc,
port,
protocol: 'http',
};
},
);
});
5 changes: 5 additions & 0 deletions e2e/execution-cancellation/gateway.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { defineConfig } from '@graphql-hive/gateway';

export const gatewayConfig = defineConfig({
executionCancellation: true,
});
4 changes: 4 additions & 0 deletions e2e/execution-cancellation/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "@e2e/execution-cancellation",
"private": true
}
33 changes: 33 additions & 0 deletions e2e/execution-cancellation/yoga-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { createServer } from 'node:http';
import {
createSchema,
createYoga,
useExecutionCancellation,
} from 'graphql-yoga';

export const schema = createSchema({
typeDefs: /* GraphQL */ `
type Query {
hello: String
}
`,
resolvers: {
Query: {
hello: () => 'world',
},
},
});

// Create a Yoga instance with a GraphQL schema.
const yoga = createYoga({
schema,
plugins: [useExecutionCancellation()],
});

// Pass it into a server to hook into request handlers.
const server = createServer(yoga);

// Start the server and you're done!
server.listen(parseInt(process.env['PORT']!), () => {
console.info('Server is running on http://localhost:4000/graphql');
});
4 changes: 3 additions & 1 deletion internal/perf/src/graphql-loadtest-script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ export default function () {
return test.abort('Environment variable "QUERY" not provided');
}

const res = http.post(url, { query });
const res = http.post(url, JSON.stringify({ query }), {
headers: { 'content-type': 'application/json' },
});

if (__ENV['ALLOW_FAILING_REQUESTS']) {
check(res, {
Expand Down
7 changes: 6 additions & 1 deletion internal/perf/src/loadtest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import { connectInspector, Inspector } from './inspector';

export interface LoadtestOptions extends ProcOptions {
cwd: string;
/**
* The ID of the loadtest, when running with test, prefer using the task id.
* @default Math.random().toString(36).slice(2, 6)
*/
id?: string;
/** @default 100 */
vus?: number;
/** Idling duration before loadtest in milliseconds. */
Expand Down Expand Up @@ -139,7 +144,7 @@ export async function loadtest(opts: LoadtestOptions): Promise<{

// we create a random id to make sure the heapsnapshot files are unique and easily distinguishable in the filesystem
// when running multiple loadtests in parallel. see e2e/opentelemetry memtest as an example
const id = Math.random().toString(36).slice(2, 6);
const id = opts.id || Math.random().toString(36).slice(2, 6);

// make sure the endpoint works before starting the loadtests
// the request here matches the request done in loadtest-script.ts or http-loadtest-script.ts
Expand Down
26 changes: 18 additions & 8 deletions internal/perf/src/memtest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ const __project = path.resolve(__dirname, '..', '..', '..');

const supportedFlags = [
'short' as const,
'cleanheapsnaps' as const,
'clean' as const,
'keepheapsnaps' as const,
'noheapsnaps' as const,
'moreruns' as const,
'chart' as const,
Expand All @@ -28,7 +29,8 @@ const supportedFlags = [
*
* {@link supportedFlags Supported flags} are:
* - `short` Runs the loadtest for `30s` and the calmdown for `10s` instead of the defaults.
* - `cleanheapsnaps` Remove any existing heap snapshot (`*.heapsnapshot`) files before the test.
* - `clean` Remove any existing heap snapshot (`*.heapsnapshot`), allocation profiles (`*.heapprofile`) and charts (`.svg`) files before the test.
* - `keepheapsnaps` Keeps the heap snapshots (`*.heapsnapshot`) even if there are no leaks detected.
* - `noheapsnaps` Disable taking heap snapshots.
* - `moreruns` Does `10` runs instead of the defaults.
* - `chart` Writes the memory consumption chart.
Expand Down Expand Up @@ -144,10 +146,15 @@ export function memtest(opts: MemtestOptions, setup: () => Promise<Server>) {
runs,
},
async ({ expect, task }) => {
if (flags.includes('cleanheapsnaps')) {
if (flags.includes('clean')) {
const filesInCwd = await fs.readdir(cwd, { withFileTypes: true });
for (const file of filesInCwd) {
if (file.isFile() && file.name.endsWith('.heapsnapshot')) {
if (
file.isFile() &&
(file.name.endsWith('.heapsnapshot') ||
file.name.endsWith('.heapprofile') ||
file.name.endsWith('.svg'))
) {
await fs.unlink(path.join(cwd, file.name));
}
}
Expand All @@ -164,6 +171,7 @@ export function memtest(opts: MemtestOptions, setup: () => Promise<Server>) {

const loadtestResult = await loadtest({
...loadtestOpts,
id: task.id,
cwd,
memorySnapshotWindow,
takeHeapSnapshots,
Expand Down Expand Up @@ -250,10 +258,12 @@ ${loadtestResult.heapSnapshots.map(({ file }, index) => `\t${index + 1}. ${path.
expect.fail('Expected to diff heap snapshots, but none were taken.');
}

// no leak, remove the heap snapshots
await Promise.all(
loadtestResult.heapSnapshots.map(({ file }) => fs.unlink(file)),
);
if (!flags.includes('keepheapsnaps')) {
// no leak, remove the heap snapshots
await Promise.all(
loadtestResult.heapSnapshots.map(({ file }) => fs.unlink(file)),
);
}
},
);
}
6 changes: 6 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2990,6 +2990,12 @@ __metadata:
languageName: unknown
linkType: soft

"@e2e/execution-cancellation@workspace:e2e/execution-cancellation":
version: 0.0.0-use.local
resolution: "@e2e/execution-cancellation@workspace:e2e/execution-cancellation"
languageName: unknown
linkType: soft

"@e2e/extra-fields@workspace:e2e/extra-fields":
version: 0.0.0-use.local
resolution: "@e2e/extra-fields@workspace:e2e/extra-fields"
Expand Down
Loading