From 079639ec735c86623c0b19fd17bda2b2b191120b Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Mon, 12 Jun 2023 16:20:05 +0200 Subject: [PATCH 1/3] implement NEXT_CPU_PROF option --- packages/next/src/server/lib/cpu-profile.ts | 30 +++++++++++ packages/next/src/server/lib/render-server.ts | 1 + .../next/src/server/lib/server-ipc/index.ts | 9 ++-- packages/next/src/server/lib/start-server.ts | 15 +++--- packages/next/src/server/lib/utils.ts | 12 +++-- packages/next/src/server/lib/worker-utils.ts | 52 +++++++++++-------- 6 files changed, 82 insertions(+), 37 deletions(-) create mode 100644 packages/next/src/server/lib/cpu-profile.ts diff --git a/packages/next/src/server/lib/cpu-profile.ts b/packages/next/src/server/lib/cpu-profile.ts new file mode 100644 index 000000000000..a0a5b9ef14bc --- /dev/null +++ b/packages/next/src/server/lib/cpu-profile.ts @@ -0,0 +1,30 @@ +import * as Log from '../../build/output/log' + +if (process.env.__NEXT_PRIVATE_CPU_PROFILE) { + const { Session } = require('inspector') as typeof import('inspector') + const fs = require('fs') + + const session = new Session() + session.connect() + + session.post('Profiler.enable') + session.post('Profiler.start') + + function saveProfile() { + session.post('Profiler.stop', (error, param) => { + if (error) { + console.error('Cannot generate CPU profiling:', error) + return + } + + // Write profile to disk + const filename = `${ + process.env.__NEXT_PRIVATE_CPU_PROFILE + }.${Date.now()}.cpuprofile` + fs.writeFileSync(`./${filename}`, JSON.stringify(param.profile)) + process.exit(0) + }) + } + process.on('SIGINT', saveProfile) + process.on('SIGTERM', saveProfile) +} diff --git a/packages/next/src/server/lib/render-server.ts b/packages/next/src/server/lib/render-server.ts index 9e8484f200da..0fe872014c76 100644 --- a/packages/next/src/server/lib/render-server.ts +++ b/packages/next/src/server/lib/render-server.ts @@ -1,5 +1,6 @@ import type { RequestHandler } from '../next' +import './cpu-profile' import v8 from 'v8' import http from 'http' import { isIPv6 } from 'net' diff --git a/packages/next/src/server/lib/server-ipc/index.ts b/packages/next/src/server/lib/server-ipc/index.ts index cc6ff4c8ea34..04f030b46dc8 100644 --- a/packages/next/src/server/lib/server-ipc/index.ts +++ b/packages/next/src/server/lib/server-ipc/index.ts @@ -4,7 +4,7 @@ import { getNodeOptionsWithoutInspect } from '../utils' import { deserializeErr, errorToJSON } from '../../render' import crypto from 'crypto' import isError from '../../../lib/is-error' -import { genRenderExecArgv, getFreePort } from '../worker-utils' +import { genRenderExecArgv } from '../worker-utils' // we can't use process.send as jest-worker relies on // it already and can cause unexpected message errors @@ -115,10 +115,11 @@ export const createWorker = async ( : 'next', } : {}), + ...(process.env.NEXT_CPU_PROF + ? { __NEXT_PRIVATE_CPU_PROFILE: `CPU.${type}-renderer` } + : {}), }, - execArgv: isNodeDebugging - ? genRenderExecArgv(await getFreePort(), type) - : undefined, + execArgv: await genRenderExecArgv(isNodeDebugging, type), }, exposedMethods: [ 'initialize', diff --git a/packages/next/src/server/lib/start-server.ts b/packages/next/src/server/lib/start-server.ts index 7897bd8d8412..2e7b95d018bc 100644 --- a/packages/next/src/server/lib/start-server.ts +++ b/packages/next/src/server/lib/start-server.ts @@ -7,8 +7,7 @@ import { isIPv6 } from 'net' import * as Log from '../../build/output/log' import { normalizeRepeatedSlashes } from '../../shared/lib/utils' import { initialEnv } from '@next/env' -import { genExecArgv, getNodeOptionsWithoutInspect } from './utils' -import { getFreePort } from './worker-utils' +import { genRouterWorkerExecArgv, getNodeOptionsWithoutInspect } from './utils' export interface StartServerOptions { dir: string @@ -187,17 +186,17 @@ export async function startServer({ // TODO: do we want to allow more than 10 OOM restarts? maxRetries: 10, forkOptions: { - execArgv: isNodeDebugging - ? genExecArgv( - isNodeDebugging === undefined ? false : isNodeDebugging, - await getFreePort() - ) - : undefined, + execArgv: await genRouterWorkerExecArgv( + isNodeDebugging === undefined ? false : isNodeDebugging + ), env: { FORCE_COLOR: '1', ...((initialEnv || process.env) as typeof process.env), PORT: port + '', NODE_OPTIONS: getNodeOptionsWithoutInspect(), + ...(process.env.NEXT_CPU_PROF + ? { __NEXT_PRIVATE_CPU_PROFILE: `CPU.router` } + : {}), }, }, exposedMethods: ['initialize'], diff --git a/packages/next/src/server/lib/utils.ts b/packages/next/src/server/lib/utils.ts index afb62663ec4c..fbb7d1666ff4 100644 --- a/packages/next/src/server/lib/utils.ts +++ b/packages/next/src/server/lib/utils.ts @@ -1,4 +1,5 @@ import type arg from 'next/dist/compiled/arg/index.js' +import { getFreePort } from './worker-utils' export function printAndExit(message: string, code = 1) { if (code === 0) { @@ -10,24 +11,27 @@ export function printAndExit(message: string, code = 1) { process.exit(code) } -export const genExecArgv = (enabled: boolean | 'brk', debugPort: number) => { +export const genRouterWorkerExecArgv = async ( + isNodeDebugging: boolean | 'brk' +) => { const execArgv = process.execArgv.filter((localArg) => { return ( !localArg.startsWith('--inspect') && !localArg.startsWith('--inspect-brk') ) }) - if (enabled) { + if (isNodeDebugging) { + const debugPort = await getFreePort() execArgv.push( - `--inspect${enabled === 'brk' ? '-brk' : ''}=${debugPort + 1}` + `--inspect${isNodeDebugging === 'brk' ? '-brk' : ''}=${debugPort + 1}` ) } return execArgv } +const NODE_INSPECT_RE = /--inspect(-brk)?(=\S+)?( |$)/ export function getNodeOptionsWithoutInspect() { - const NODE_INSPECT_RE = /--inspect(-brk)?(=\S+)?( |$)/ return (process.env.NODE_OPTIONS || '').replace(NODE_INSPECT_RE, '') } diff --git a/packages/next/src/server/lib/worker-utils.ts b/packages/next/src/server/lib/worker-utils.ts index 76f1a5c114bb..5db44a965e88 100644 --- a/packages/next/src/server/lib/worker-utils.ts +++ b/packages/next/src/server/lib/worker-utils.ts @@ -17,32 +17,42 @@ export const getFreePort = async (): Promise => { }) } -export const genRenderExecArgv = (debugPort: number, type: 'pages' | 'app') => { - const isDebugging = - process.execArgv.some((localArg) => localArg.startsWith('--inspect')) || - process.env.NODE_OPTIONS?.match?.(/--inspect(=\S+)?( |$)/) - - const isDebuggingWithBrk = - process.execArgv.some((localArg) => localArg.startsWith('--inspect-brk')) || - process.env.NODE_OPTIONS?.match?.(/--inspect-brk(=\S+)?( |$)/) - - if (isDebugging || isDebuggingWithBrk) { - Log.info( - `the --inspect${ - isDebuggingWithBrk ? '-brk' : '' - } option was detected, the Next.js server${ - type === 'pages' ? ' for pages' : type === 'app' ? ' for app' : '' - } should be inspected at port ${debugPort}.` - ) - } +export const genRenderExecArgv = async ( + isNodeDebugging: string | boolean | undefined, + type: 'pages' | 'app' +) => { const execArgv = process.execArgv.filter((localArg) => { return ( - !localArg.startsWith('--inspect') && !localArg.startsWith('--inspect-brk') + !localArg.startsWith('--inspect') && + !localArg.startsWith('--inspect-brk') && + !localArg.startsWith('--cpu-prof') ) }) - if (isDebugging || isDebuggingWithBrk) { - execArgv.push(`--inspect${isDebuggingWithBrk ? '-brk' : ''}=${debugPort}`) + if (isNodeDebugging) { + const debugPort = await getFreePort() + const isDebugging = + process.execArgv.some((localArg) => localArg.startsWith('--inspect')) || + process.env.NODE_OPTIONS?.match?.(/--inspect(=\S+)?( |$)/) + + const isDebuggingWithBrk = + process.execArgv.some((localArg) => + localArg.startsWith('--inspect-brk') + ) || process.env.NODE_OPTIONS?.match?.(/--inspect-brk(=\S+)?( |$)/) + + if (isDebugging || isDebuggingWithBrk) { + Log.info( + `the --inspect${ + isDebuggingWithBrk ? '-brk' : '' + } option was detected, the Next.js server${ + type === 'pages' ? ' for pages' : type === 'app' ? ' for app' : '' + } should be inspected at port ${debugPort}.` + ) + } + + if (isDebugging || isDebuggingWithBrk) { + execArgv.push(`--inspect${isDebuggingWithBrk ? '-brk' : ''}=${debugPort}`) + } } return execArgv From f9fa11678ab0af34d9b6f525dd695f7135b243fa Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Mon, 12 Jun 2023 16:20:19 +0200 Subject: [PATCH 2/3] remove unused line --- packages/next/src/server/lib/cpu-profile.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/next/src/server/lib/cpu-profile.ts b/packages/next/src/server/lib/cpu-profile.ts index a0a5b9ef14bc..2310513024a3 100644 --- a/packages/next/src/server/lib/cpu-profile.ts +++ b/packages/next/src/server/lib/cpu-profile.ts @@ -1,5 +1,3 @@ -import * as Log from '../../build/output/log' - if (process.env.__NEXT_PRIVATE_CPU_PROFILE) { const { Session } = require('inspector') as typeof import('inspector') const fs = require('fs') From 07a4cb9bba6e9e2293c4db4a9dd634970ff058c2 Mon Sep 17 00:00:00 2001 From: Shu Ding Date: Mon, 12 Jun 2023 16:21:55 +0200 Subject: [PATCH 3/3] remove unused line --- packages/next/src/server/lib/worker-utils.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/next/src/server/lib/worker-utils.ts b/packages/next/src/server/lib/worker-utils.ts index 5db44a965e88..40dd01477abf 100644 --- a/packages/next/src/server/lib/worker-utils.ts +++ b/packages/next/src/server/lib/worker-utils.ts @@ -23,9 +23,7 @@ export const genRenderExecArgv = async ( ) => { const execArgv = process.execArgv.filter((localArg) => { return ( - !localArg.startsWith('--inspect') && - !localArg.startsWith('--inspect-brk') && - !localArg.startsWith('--cpu-prof') + !localArg.startsWith('--inspect') && !localArg.startsWith('--inspect-brk') ) })