From 74690757c5fb3be4a5a9330ae77e91e7b569781d Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 20 Feb 2024 10:27:47 -0800 Subject: [PATCH 1/3] Add otel span for client component loading --- .../next/src/server/app-render/app-render.tsx | 60 ++++++++++++++++++- .../next/src/server/lib/trace/constants.ts | 3 + packages/next/src/server/lib/trace/tracer.ts | 1 + 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index 2d667ca730484..be64e2166c7d8 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -51,7 +51,7 @@ import { getRedirectStatusCodeFromError, } from '../../client/components/redirect' import { addImplicitTags } from '../lib/patch-fetch' -import { AppRenderSpan } from '../lib/trace/constants' +import { AppRenderSpan, NextNodeServerSpan } from '../lib/trace/constants' import { getTracer } from '../lib/trace/tracer' import { FlightRenderResult } from './flight-render-result' import { @@ -615,15 +615,69 @@ async function renderToHTMLOrFlightImpl( enableTainting, } = renderOpts + // Combined load times for loading client components + let clientComponentLoadStart = 0 + let clientComponentLoadTimes = 0 + let clientComponentLoadCount = 0 + // We need to expose the bundled `require` API globally for // react-server-dom-webpack. This is a hack until we find a better way. if (ComponentMod.__next_app__) { // @ts-ignore - globalThis.__next_require__ = ComponentMod.__next_app__.require + globalThis.__next_require__ = (...args: any[]) => { + if (clientComponentLoadStart === 0) { + clientComponentLoadStart = Date.now() + } + + const startTime = Date.now() + try { + clientComponentLoadCount += 1 + return ComponentMod.__next_app__.require(...args) + } finally { + clientComponentLoadTimes += Date.now() - startTime + } + } // @ts-ignore - globalThis.__next_chunk_load__ = ComponentMod.__next_app__.loadChunk + globalThis.__next_chunk_load__ = (...args: any[]) => { + const startTime = Date.now() + try { + clientComponentLoadCount += 1 + return ComponentMod.__next_app__.loadChunk(...args) + } finally { + clientComponentLoadTimes += Date.now() - startTime + } + } } + req.on('end', () => { + const type = NextNodeServerSpan.clientComponentLoading + const startTime = clientComponentLoadStart + const endTime = clientComponentLoadStart + clientComponentLoadTimes + getTracer() + .startSpan(type, { + startTime, + attributes: { + 'next.clientComponentLoadCount': clientComponentLoadCount, + }, + }) + .end(endTime) + + if ( + typeof performance !== 'undefined' && + process.env.NEXT_OTEL_PERFORMANCE_PREFIX + ) { + const { timeOrigin } = performance + performance.measure( + `${process.env.NEXT_OTEL_PERFORMANCE_PREFIX}:next-${( + type.split('.').pop() || '' + ).replace(/[A-Z]/g, (match: string) => '-' + match.toLowerCase())}`, + { + start: startTime - timeOrigin, + end: endTime - timeOrigin, + } + ) + } + }) const metadata: AppPageRenderResultMetadata = {} diff --git a/packages/next/src/server/lib/trace/constants.ts b/packages/next/src/server/lib/trace/constants.ts index 93450fcf067c0..b452194adade4 100644 --- a/packages/next/src/server/lib/trace/constants.ts +++ b/packages/next/src/server/lib/trace/constants.ts @@ -39,6 +39,7 @@ enum NextNodeServerSpan { compression = 'NextNodeServer.compression', getBuildId = 'NextNodeServer.getBuildId', createComponentTree = 'NextNodeServer.createComponentTree', + clientComponentLoading = 'NextNodeServer.clientComponentLoading', getLayoutOrPageModule = 'NextNodeServer.getLayoutOrPageModule', generateStaticRoutes = 'NextNodeServer.generateStaticRoutes', generateFsStaticRoutes = 'NextNodeServer.generateFsStaticRoutes', @@ -134,6 +135,7 @@ export const NextVanillaSpanAllowlist = [ NextNodeServerSpan.findPageComponents, NextNodeServerSpan.getLayoutOrPageModule, NextNodeServerSpan.startResponse, + NextNodeServerSpan.clientComponentLoading, ] // These Spans are allowed to be always logged @@ -141,6 +143,7 @@ export const NextVanillaSpanAllowlist = [ export const LogSpanAllowList = [ NextNodeServerSpan.findPageComponents, NextNodeServerSpan.createComponentTree, + NextNodeServerSpan.clientComponentLoading, ] export { diff --git a/packages/next/src/server/lib/trace/tracer.ts b/packages/next/src/server/lib/trace/tracer.ts index f4003106d1e1c..3d35bb76b57c3 100644 --- a/packages/next/src/server/lib/trace/tracer.ts +++ b/packages/next/src/server/lib/trace/tracer.ts @@ -144,6 +144,7 @@ type NextAttributeNames = | 'next.segment' | 'next.span_name' | 'next.span_type' + | 'next.clientComponentLoadCount' type OTELAttributeNames = `http.${string}` | `net.${string}` type AttributeNames = NextAttributeNames | OTELAttributeNames From 4aec8092ffabbcdab0eb101507593e5834597860 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 20 Feb 2024 10:45:30 -0800 Subject: [PATCH 2/3] fix edge runtime case --- .../next/src/server/app-render/app-render.tsx | 61 ++++++++++--------- 1 file changed, 32 insertions(+), 29 deletions(-) diff --git a/packages/next/src/server/app-render/app-render.tsx b/packages/next/src/server/app-render/app-render.tsx index be64e2166c7d8..69c07ce8192ef 100644 --- a/packages/next/src/server/app-render/app-render.tsx +++ b/packages/next/src/server/app-render/app-render.tsx @@ -649,35 +649,38 @@ async function renderToHTMLOrFlightImpl( } } } - req.on('end', () => { - const type = NextNodeServerSpan.clientComponentLoading - const startTime = clientComponentLoadStart - const endTime = clientComponentLoadStart + clientComponentLoadTimes - getTracer() - .startSpan(type, { - startTime, - attributes: { - 'next.clientComponentLoadCount': clientComponentLoadCount, - }, - }) - .end(endTime) - - if ( - typeof performance !== 'undefined' && - process.env.NEXT_OTEL_PERFORMANCE_PREFIX - ) { - const { timeOrigin } = performance - performance.measure( - `${process.env.NEXT_OTEL_PERFORMANCE_PREFIX}:next-${( - type.split('.').pop() || '' - ).replace(/[A-Z]/g, (match: string) => '-' + match.toLowerCase())}`, - { - start: startTime - timeOrigin, - end: endTime - timeOrigin, - } - ) - } - }) + + if (typeof req.on === 'function') { + req.on('end', () => { + const type = NextNodeServerSpan.clientComponentLoading + const startTime = clientComponentLoadStart + const endTime = clientComponentLoadStart + clientComponentLoadTimes + getTracer() + .startSpan(type, { + startTime, + attributes: { + 'next.clientComponentLoadCount': clientComponentLoadCount, + }, + }) + .end(endTime) + + if ( + typeof performance !== 'undefined' && + process.env.NEXT_OTEL_PERFORMANCE_PREFIX + ) { + const { timeOrigin } = performance + performance.measure( + `${process.env.NEXT_OTEL_PERFORMANCE_PREFIX}:next-${( + type.split('.').pop() || '' + ).replace(/[A-Z]/g, (match: string) => '-' + match.toLowerCase())}`, + { + start: startTime - timeOrigin, + end: endTime - timeOrigin, + } + ) + } + }) + } const metadata: AppPageRenderResultMetadata = {} From f492487240a711f00bc175050049ca36ba838808 Mon Sep 17 00:00:00 2001 From: JJ Kasper Date: Tue, 20 Feb 2024 11:24:41 -0800 Subject: [PATCH 3/3] update tests --- test/e2e/opentelemetry/opentelemetry.test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/e2e/opentelemetry/opentelemetry.test.ts b/test/e2e/opentelemetry/opentelemetry.test.ts index 941ede9ce0526..e1e204bf31df1 100644 --- a/test/e2e/opentelemetry/opentelemetry.test.ts +++ b/test/e2e/opentelemetry/opentelemetry.test.ts @@ -162,6 +162,16 @@ createNextDescribe( kind: 0, status: { code: 0 }, }, + { + attributes: { + 'next.clientComponentLoadCount': 4, + }, + kind: 0, + name: 'NextNodeServer.clientComponentLoading', + status: { + code: 0, + }, + }, { name: 'start response', attributes: { @@ -771,6 +781,9 @@ createNextDescribe( { name: 'generateMetadata /app/[param]/rsc-fetch/page', }, + { + name: 'NextNodeServer.clientComponentLoading', + }, { name: 'start response', },