@@ -51,6 +51,9 @@ import {
5151 op_http2_callbacks ,
5252} from "ext:core/ops" ;
5353import { enqueueNodePerformanceEntry } from "node:perf_hooks" ;
54+ const { performance : webPerformance } = core . loadExtScript (
55+ "ext:deno_web/15_performance.js" ,
56+ ) ;
5457import net from "node:net" ;
5558import assert from "node:assert" ;
5659import http from "node:http" ;
@@ -259,6 +262,89 @@ function getURLOrigin(urlStr) {
259262 }
260263}
261264
265+ function perfNow ( ) {
266+ return webPerformance . now ( ) ;
267+ }
268+
269+ function emitSessionPerfEntry ( session ) {
270+ if ( session [ kPerfEmitted ] ) return ;
271+ session [ kPerfEmitted ] = true ;
272+ const stats = session [ kPerfStats ] ;
273+ if ( ! stats ) return ;
274+
275+ const startTime = stats . startTime ;
276+ const duration = perfNow ( ) - startTime ;
277+ const handle = session [ kHandle ] ;
278+ const framesReceived = handle && typeof handle . framesReceived === "function"
279+ ? handle . framesReceived ( )
280+ : 0 ;
281+ const framesSent = handle && typeof handle . framesSent === "function"
282+ ? handle . framesSent ( )
283+ : 0 ;
284+ const streamCount = stats . streamCount ;
285+ const streamAverageDuration = streamCount > 0
286+ ? stats . streamTotalDuration / streamCount
287+ : 0 ;
288+ const type = session [ kType ] === NGHTTP2_SESSION_SERVER ? "server" : "client" ;
289+ const detail = {
290+ bytesRead : stats . bytesRead ,
291+ bytesWritten : stats . bytesWritten ,
292+ framesReceived,
293+ framesSent,
294+ maxConcurrentStreams : stats . maxConcurrentStreams ,
295+ pingRTT : stats . pingRTT ,
296+ streamAverageDuration,
297+ streamCount,
298+ type,
299+ } ;
300+
301+ enqueueNodePerformanceEntry ( {
302+ name : "Http2Session" ,
303+ entryType : "http2" ,
304+ startTime,
305+ duration,
306+ detail,
307+ } ) ;
308+ }
309+
310+ function emitStreamPerfEntry ( stream ) {
311+ if ( stream [ kPerfEmitted ] ) return ;
312+ stream [ kPerfEmitted ] = true ;
313+ const stats = stream [ kPerfStats ] ;
314+ if ( ! stats ) return ;
315+
316+ const startTime = stats . startTime ;
317+ const duration = perfNow ( ) - startTime ;
318+ const detail = {
319+ bytesRead : stats . bytesRead ,
320+ bytesWritten : stats . bytesWritten ,
321+ timeToFirstByte : stats . firstByte > 0 ? stats . firstByte - startTime : 0 ,
322+ timeToFirstByteSent : stats . firstByteSent > 0
323+ ? stats . firstByteSent - startTime
324+ : 0 ,
325+ timeToFirstHeader : stats . firstHeader > 0
326+ ? stats . firstHeader - startTime
327+ : 0 ,
328+ } ;
329+
330+ enqueueNodePerformanceEntry ( {
331+ name : "Http2Stream" ,
332+ entryType : "http2" ,
333+ startTime,
334+ duration,
335+ detail,
336+ } ) ;
337+
338+ // Roll the stream's lifetime into the parent session's averageDuration.
339+ const session = stream [ kSession ] ;
340+ if ( session ) {
341+ const sstats = session [ kPerfStats ] ;
342+ if ( sstats ) {
343+ sstats . streamTotalDuration += duration ;
344+ }
345+ }
346+ }
347+
262348// Schedule a deferred sendPending() call on the session's native handle.
263349// This is deferred via queueMicrotask to avoid re-entrancy: nghttp2's
264350// send_pending_data can invoke callbacks that call back into JS ops.
@@ -399,6 +485,13 @@ const kType = Symbol("type");
399485const kWriteGeneric = Symbol ( "write-generic" ) ;
400486const kSessions = Symbol ( "sessions" ) ;
401487
488+ // Symbols for tracking perf_hooks `http2` performance entry stats. The
489+ // `pingRTT`, `streamCount`, etc. fields surfaced via `entry.detail` are
490+ // populated by Http2Session/Http2Stream methods when those events occur,
491+ // then read back when the entry is enqueued at session/stream destroy.
492+ const kPerfStats = Symbol ( "perf-stats" ) ;
493+ const kPerfEmitted = Symbol ( "perf-emitted" ) ;
494+
402495const kMaxOutstandingSettings = Symbol ( "maxOutstandingSettings" ) ;
403496const kMaxOutstandingPings = Symbol ( "maxOutstandingPings" ) ;
404497
@@ -1748,6 +1841,16 @@ class Http2Stream extends Duplex {
17481841 this [ kRequest ] = null ;
17491842 this [ kProxySocket ] = null ;
17501843
1844+ this [ kPerfEmitted ] = false ;
1845+ this [ kPerfStats ] = {
1846+ startTime : perfNow ( ) ,
1847+ firstByte : 0 ,
1848+ firstByteSent : 0 ,
1849+ firstHeader : 0 ,
1850+ bytesRead : 0 ,
1851+ bytesWritten : 0 ,
1852+ } ;
1853+
17511854 this . on ( "pause" , streamOnPause ) ;
17521855
17531856 this . on ( "newListener" , streamListenerAdded ) ;
@@ -1774,6 +1877,11 @@ class Http2Stream extends Duplex {
17741877 session [ kState ] . pendingStreams . delete ( this ) ;
17751878 session [ kState ] . streams . set ( id , this ) ;
17761879
1880+ const sstats = session [ kPerfStats ] ;
1881+ if ( sstats ) {
1882+ sstats . streamCount ++ ;
1883+ }
1884+
17771885 this [ kID ] = id ;
17781886 //this[async_id_symbol] = handle.getAsyncId();
17791887 handle [ kOwner ] = this ;
@@ -2315,6 +2423,8 @@ class Http2Stream extends Duplex {
23152423 err = new ERR_HTTP2_STREAM_ERROR ( nameForErrorCode [ code ] || code ) ;
23162424 }
23172425
2426+ emitStreamPerfEntry ( this ) ;
2427+
23182428 this [ kSession ] = undefined ;
23192429 this [ kHandle ] = undefined ;
23202430
@@ -3424,25 +3534,7 @@ function cleanupSession(session) {
34243534function finishSessionClose ( session , error ) {
34253535 debugSessionObj ( session , "finishSessionClose" ) ;
34263536
3427- // Emit a `Http2Session` PerformanceObserver entry with frame statistics
3428- // from the native session before cleanupSession() drops the handle. Mirrors
3429- // Node's `Http2Session::Close` reporting via PerformanceEntry. We surface
3430- // only the fields the perf-hooks docs document and the in-tree tests
3431- // observe.
3432- const perfHandle = session [ kHandle ] ;
3433- if ( perfHandle && typeof perfHandle . framesReceived === "function" ) {
3434- const framesReceived = perfHandle . framesReceived ( ) ;
3435- enqueueNodePerformanceEntry ( {
3436- name : "Http2Session" ,
3437- entryType : "http2" ,
3438- startTime : 0 ,
3439- duration : 0 ,
3440- detail : {
3441- type : session [ kType ] === NGHTTP2_SESSION_CLIENT ? "client" : "server" ,
3442- framesReceived,
3443- } ,
3444- } ) ;
3445- }
3537+ emitSessionPerfEntry ( session ) ;
34463538
34473539 const socket = session [ kSocket ] ;
34483540 cleanupSession ( session ) ;
@@ -3691,6 +3783,16 @@ class Http2Session extends EventEmitter {
36913783 2 ** 31 - 1 ,
36923784 ) || 10 ;
36933785 this [ kStrictSingleValueFields ] = options . strictSingleValueFields !== false ;
3786+ this [ kPerfEmitted ] = false ;
3787+ this [ kPerfStats ] = {
3788+ startTime : perfNow ( ) ,
3789+ streamCount : 0 ,
3790+ streamTotalDuration : 0 ,
3791+ pingRTT : 0 ,
3792+ bytesRead : 0 ,
3793+ bytesWritten : 0 ,
3794+ maxConcurrentStreams : 0xffffffff ,
3795+ } ;
36943796
36953797 // Do not use nagle's algorithm
36963798 if ( typeof socket . setNoDelay === "function" ) {
0 commit comments