From 6eb2c2461ff7ea1697404935caf9331b58c269ac Mon Sep 17 00:00:00 2001 From: Dima Voytenko Date: Mon, 29 Jan 2024 09:36:12 -0800 Subject: [PATCH] Telemetry: add time-to-first-byte signal (#61238) --- .../06-optimizing/10-open-telemetry.mdx | 6 +++ .../next/src/server/lib/trace/constants.ts | 2 + packages/next/src/server/pipe-readable.ts | 9 ++++ test/e2e/opentelemetry/opentelemetry.test.ts | 51 ++++++++++++++++--- 4 files changed, 62 insertions(+), 6 deletions(-) diff --git a/docs/02-app/01-building-your-application/06-optimizing/10-open-telemetry.mdx b/docs/02-app/01-building-your-application/06-optimizing/10-open-telemetry.mdx index 8bc9ec6032bc7..5a9d0b2181c50 100644 --- a/docs/02-app/01-building-your-application/06-optimizing/10-open-telemetry.mdx +++ b/docs/02-app/01-building-your-application/06-optimizing/10-open-telemetry.mdx @@ -357,3 +357,9 @@ Attributes: - `next.span_name` - `next.span_type` - `next.segment` + +### `start response` + +- `next.span_type`: `NextNodeServer.startResponse`. + +This zero-length span represents the time when the first byte has been sent in the response. diff --git a/packages/next/src/server/lib/trace/constants.ts b/packages/next/src/server/lib/trace/constants.ts index b014ab11b8e9d..3a07c0ca9a53a 100644 --- a/packages/next/src/server/lib/trace/constants.ts +++ b/packages/next/src/server/lib/trace/constants.ts @@ -60,6 +60,7 @@ enum NextNodeServerSpan { renderError = 'NextNodeServer.renderError', renderErrorToHTML = 'NextNodeServer.renderErrorToHTML', render404 = 'NextNodeServer.render404', + startResponse = 'NextNodeServer.startResponse', // nested inner span, does not require parent scope name route = 'route', @@ -132,6 +133,7 @@ export const NextVanillaSpanAllowlist = [ NextNodeServerSpan.createComponentTree, NextNodeServerSpan.findPageComponents, NextNodeServerSpan.getLayoutOrPageModule, + NextNodeServerSpan.startResponse, ] export { diff --git a/packages/next/src/server/pipe-readable.ts b/packages/next/src/server/pipe-readable.ts index 89fe9f8c5afee..aedbcaef7c393 100644 --- a/packages/next/src/server/pipe-readable.ts +++ b/packages/next/src/server/pipe-readable.ts @@ -5,6 +5,8 @@ import { createAbortController, } from './web/spec-extension/adapters/next-request' import { DetachedPromise } from '../lib/detached-promise' +import { getTracer } from './lib/trace/tracer' +import { NextNodeServerSpan } from './lib/trace/constants' export function isAbortError(e: any): e is Error & { name: 'AbortError' } { return e?.name === 'AbortError' || e?.name === ResponseAbortedName @@ -47,6 +49,13 @@ function createWriterFromResponse( if (!started) { started = true res.flushHeaders() + getTracer().trace( + NextNodeServerSpan.startResponse, + { + spanName: 'start response', + }, + () => undefined + ) } try { diff --git a/test/e2e/opentelemetry/opentelemetry.test.ts b/test/e2e/opentelemetry/opentelemetry.test.ts index e19c2b8cbc41c..302f718b47125 100644 --- a/test/e2e/opentelemetry/opentelemetry.test.ts +++ b/test/e2e/opentelemetry/opentelemetry.test.ts @@ -114,8 +114,8 @@ createNextDescribe( const numberOfRootTraces = env.span.rootParentId === undefined ? 1 : 0 const traces = await getSanitizedTraces(numberOfRootTraces) - if (traces.length < 9) { - return `not enough traces, expected 9, but got ${traces.length}` + if (traces.length < 10) { + return `not enough traces, expected 10, but got ${traces.length}` } expect(traces).toMatchInlineSnapshot(` [ @@ -226,6 +226,19 @@ createNextDescribe( }, "traceId": "${env.span.traceId}", }, + { + "attributes": { + "next.span_name": "start response", + "next.span_type": "NextNodeServer.startResponse", + }, + "kind": 0, + "name": "start response", + "parentId": "[parent-id]", + "status": { + "code": 0, + }, + "traceId": "${env.span.traceId}", + }, { "attributes": { "next.page": "/app/[param]/layout", @@ -267,8 +280,8 @@ createNextDescribe( const numberOfRootTraces = env.span.rootParentId === undefined ? 1 : 0 const traces = await getSanitizedTraces(numberOfRootTraces) - if (traces.length < 3) { - return `not enough traces, expected 3, but got ${traces.length}` + if (traces.length < 4) { + return `not enough traces, expected 4, but got ${traces.length}` } expect(traces).toMatchInlineSnapshot(` [ @@ -322,6 +335,19 @@ createNextDescribe( }, "traceId": "${env.span.traceId}", }, + { + "attributes": { + "next.span_name": "start response", + "next.span_type": "NextNodeServer.startResponse", + }, + "kind": 0, + "name": "start response", + "parentId": "[parent-id]", + "status": { + "code": 0, + }, + "traceId": "${env.span.traceId}", + }, ] `) return 'success' @@ -638,8 +664,8 @@ createNextDescribe( await check(async () => { const traces = await getSanitizedTraces(1) - if (traces.length < 5) { - return `not enough traces, expected 5, but got ${traces.length}` + if (traces.length < 6) { + return `not enough traces, expected 6, but got ${traces.length}` } expect(traces).toMatchInlineSnapshot(` [ @@ -730,6 +756,19 @@ createNextDescribe( }, "traceId": "[trace-id]", }, + { + "attributes": { + "next.span_name": "start response", + "next.span_type": "NextNodeServer.startResponse", + }, + "kind": 0, + "name": "start response", + "parentId": "[parent-id]", + "status": { + "code": 0, + }, + "traceId": "[trace-id]", + }, { "attributes": { "next.page": "/app/[param]/layout",