From 96696c2d3bf24b181d24ecc57506e66de3c5a44b Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Mon, 31 Oct 2022 12:19:22 -0700 Subject: [PATCH] Add event for dev process stop (#42255) x-ref: [slack thread](https://vercel.slack.com/archives/C02HEJASXGD/p1667173179573409?thread_ts=1667165920.338789&channel=C02HEJASXGD&message_ts=1667173179.573409) ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] Make sure the linting passes by running `pnpm build && pnpm lint` - [ ] The "examples guidelines" are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing/examples/adding-examples.md) --- packages/next/bin/next.ts | 2 +- packages/next/cli/next-dev.ts | 43 +++++++++++++- .../next/telemetry/events/session-stopped.ts | 31 ++++++++++ test/integration/telemetry/test/index.test.js | 56 +++++++++++++++++++ 4 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 packages/next/telemetry/events/session-stopped.ts diff --git a/packages/next/bin/next.ts b/packages/next/bin/next.ts index 534b9875a8640..aa1b4797adb3d 100755 --- a/packages/next/bin/next.ts +++ b/packages/next/bin/next.ts @@ -113,7 +113,7 @@ if (process.versions.pnp === '3') { // Make sure commands gracefully respect termination signals (e.g. from Docker) // Allow the graceful termination to be manually configurable -if (!process.env.NEXT_MANUAL_SIG_HANDLE) { +if (!process.env.NEXT_MANUAL_SIG_HANDLE && command !== 'dev') { process.on('SIGTERM', () => process.exit(0)) process.on('SIGINT', () => process.exit(0)) } diff --git a/packages/next/cli/next-dev.ts b/packages/next/cli/next-dev.ts index a26ea893914c9..602196d034f31 100755 --- a/packages/next/cli/next-dev.ts +++ b/packages/next/cli/next-dev.ts @@ -11,9 +11,39 @@ import { getProjectDir } from '../lib/get-project-dir' import { CONFIG_FILES } from '../shared/lib/constants' import path from 'path' import type { NextConfig } from '../types' -import { interopDefault } from '../lib/interop-default' -import { Telemetry } from '../telemetry/storage' -import { NextConfigComplete } from '../server/config-shared' +import type { NextConfigComplete } from '../server/config-shared' +import { traceGlobals } from '../trace/shared' + +let isTurboSession = false +let sessionStopHandled = false +let sessionStarted = Date.now() + +const handleSessionStop = async () => { + if (sessionStopHandled) return + sessionStopHandled = true + + const { eventCliSession } = + require('../telemetry/events/session-stopped') as typeof import('../telemetry/events/session-stopped') + const telemetry = traceGlobals.get('telemetry') as InstanceType< + typeof import('../telemetry/storage').Telemetry + > + if (!telemetry) { + process.exit(0) + } + + telemetry.record( + eventCliSession({ + cliCommand: 'dev', + turboFlag: isTurboSession, + durationMilliseconds: Date.now() - sessionStarted, + }) + ) + await telemetry.flush() + process.exit(0) +} + +process.on('SIGINT', handleSessionStop) +process.on('SIGTERM', handleSessionStop) const nextDev: cliCommand = (argv) => { const validArgs: arg.Spec = { @@ -105,6 +135,7 @@ const nextDev: cliCommand = (argv) => { } if (args['--turbo']) { + isTurboSession = true // check for postcss, babelrc, swc plugins return new Promise(async (resolve) => { const { findConfigPath } = @@ -127,6 +158,11 @@ const nextDev: cliCommand = (argv) => { require('../telemetry/events/version') as typeof import('../telemetry/events/version') const { findPagesDir } = require('../lib/find-pages-dir') as typeof import('../lib/find-pages-dir') + const { interopDefault } = + require('../lib/interop-default') as typeof import('../lib/interop-default') + const { setGlobal } = require('../trace') as typeof import('../trace') + const { Telemetry } = + require('../telemetry/storage') as typeof import('../telemetry/storage') // To regenerate the TURBOPACK gradient require('gradient-string')('blue', 'red')('>>> TURBOPACK') const isTTY = process.stdout.isTTY @@ -293,6 +329,7 @@ If you cannot make the changes above, but still want to try out\nNext.js v13 wit const telemetry = new Telemetry({ distDir, }) + setGlobal('telemetry', telemetry) telemetry.record( eventCliSession(distDir, rawNextConfig as NextConfigComplete, { diff --git a/packages/next/telemetry/events/session-stopped.ts b/packages/next/telemetry/events/session-stopped.ts new file mode 100644 index 0000000000000..2c00233ead091 --- /dev/null +++ b/packages/next/telemetry/events/session-stopped.ts @@ -0,0 +1,31 @@ +const EVENT_VERSION = 'NEXT_CLI_SESSION_STOPPED' + +export type EventCliSessionStopped = { + cliCommand: string + nextVersion: string + nodeVersion: string + turboFlag?: boolean | null + durationMilliseconds?: number | null +} + +export function eventCliSession( + event: Omit +): { eventName: string; payload: EventCliSessionStopped }[] { + // This should be an invariant, if it fails our build tooling is broken. + if (typeof process.env.__NEXT_VERSION !== 'string') { + return [] + } + + const payload: EventCliSessionStopped = { + nextVersion: process.env.__NEXT_VERSION, + nodeVersion: process.version, + cliCommand: event.cliCommand, + durationMilliseconds: event.durationMilliseconds, + ...(typeof event.turboFlag !== 'undefined' + ? { + turboFlag: !!event.turboFlag, + } + : {}), + } + return [{ eventName: EVENT_VERSION, payload }] +} diff --git a/test/integration/telemetry/test/index.test.js b/test/integration/telemetry/test/index.test.js index 930ec2af18fd4..7c92a6248073b 100644 --- a/test/integration/telemetry/test/index.test.js +++ b/test/integration/telemetry/test/index.test.js @@ -10,6 +10,7 @@ import { waitFor, nextBuild, nextLint, + check, } from 'next-test-utils' const appDir = path.join(__dirname, '..') @@ -394,6 +395,61 @@ describe('Telemetry CLI', () => { expect(event1).toMatch(/"turboFlag": true/) }) + it('detects --turbo correctly for `next dev` stopped', async () => { + let port = await findPort() + let stderr = '' + + const handleStderr = (msg) => { + stderr += msg + } + let app = await launchApp(appDir, port, { + onStderr: handleStderr, + env: { + NEXT_TELEMETRY_DEBUG: 1, + }, + turbo: true, + }) + + if (app) { + await killApp(app) + } + await check(() => stderr, /NEXT_CLI_SESSION_STOPPED/) + + const event1 = /NEXT_CLI_SESSION_STOPPED[\s\S]+?{([\s\S]+?)}/ + .exec(stderr) + .pop() + + expect(event1).toMatch(/"turboFlag": true/) + }) + + it('detects correctly for `next dev` stopped (no turbo)', async () => { + let port = await findPort() + let stderr = '' + + const handleStderr = (msg) => { + stderr += msg + } + let app = await launchApp(appDir, port, { + onStderr: handleStderr, + env: { + NEXT_TELEMETRY_DEBUG: 1, + }, + }) + + await check(() => stderr, /NEXT_CLI_SESSION_STARTED/) + + if (app) { + await killApp(app) + } + await check(() => stderr, /NEXT_CLI_SESSION_STOPPED/) + + const event1 = /NEXT_CLI_SESSION_STOPPED[\s\S]+?{([\s\S]+?)}/ + .exec(stderr) + .pop() + + expect(event1).toMatch(/"turboFlag": false/) + }) + it('detect reportWebVitals correctly for `next build`', async () => { // Case 1: When _app.js does not exist. let build = await nextBuild(appDir, [], {