node/lib/internal/quic/stats.js
James M Snell b4d30e7a78
quic: add stream idle timeout
Signed-off-by: James M Snell <jasnell@gmail.com>
Assisted-by: Opencode:Opus 4.6
PR-URL: https://github.com/nodejs/node/pull/63483
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
2026-05-27 11:35:48 +02:00

1116 lines
31 KiB
JavaScript

'use strict';
// TODO(@jasnell) Temporarily ignoring c8 covrerage for this file while tests
// are still being developed.
/* c8 ignore start */
const {
BigUint64Array,
JSONStringify,
Symbol,
TypedArrayPrototypeSubarray,
} = primordials;
const {
getOptionValue,
} = require('internal/options');
if (!process.features.quic || !getOptionValue('--experimental-quic')) {
return;
}
const {
isArrayBuffer,
} = require('util/types');
const {
codes: {
ERR_ILLEGAL_CONSTRUCTOR,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_THIS,
},
} = require('internal/errors');
const { inspect } = require('internal/util/inspect');
const assert = require('internal/assert');
const {
kFinishClose,
kInspect,
kPrivateConstructor,
} = require('internal/quic/symbols');
// This file defines the helper objects for accessing statistics collected
// by various QUIC objects. Each of these wraps a BigUint64Array. Every
// stats object is read-only via the API, and the underlying buffer is
// only updated by the QUIC internals. When the stats object is no longer
// needed, it is closed to prevent further updates to the buffer.
const {
// All of the IDX_STATS_* constants are the index positions of the stats
// fields in the relevant BigUint64Array's that underlie the *Stats objects.
// These are not exposed to end users.
IDX_STATS_ENDPOINT_CREATED_AT,
IDX_STATS_ENDPOINT_DESTROYED_AT,
IDX_STATS_ENDPOINT_BYTES_RECEIVED,
IDX_STATS_ENDPOINT_BYTES_SENT,
IDX_STATS_ENDPOINT_PACKETS_RECEIVED,
IDX_STATS_ENDPOINT_PACKETS_SENT,
IDX_STATS_ENDPOINT_SERVER_SESSIONS,
IDX_STATS_ENDPOINT_CLIENT_SESSIONS,
IDX_STATS_ENDPOINT_SERVER_BUSY_COUNT,
IDX_STATS_ENDPOINT_RETRY_COUNT,
IDX_STATS_ENDPOINT_RETRY_RATE_LIMITED,
IDX_STATS_ENDPOINT_VERSION_NEGOTIATION_COUNT,
IDX_STATS_ENDPOINT_VERSION_NEGOTIATION_RATE_LIMITED,
IDX_STATS_ENDPOINT_STATELESS_RESET_COUNT,
IDX_STATS_ENDPOINT_STATELESS_RESET_RATE_LIMITED,
IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT,
IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_RATE_LIMITED,
IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED,
IDX_STATS_ENDPOINT_PACKETS_BLOCKED,
IDX_STATS_SESSION_CREATED_AT,
IDX_STATS_SESSION_DESTROYED_AT,
IDX_STATS_SESSION_CLOSING_AT,
IDX_STATS_SESSION_HANDSHAKE_COMPLETED_AT,
IDX_STATS_SESSION_HANDSHAKE_CONFIRMED_AT,
IDX_STATS_SESSION_BYTES_RECEIVED,
IDX_STATS_SESSION_BIDI_IN_STREAM_COUNT,
IDX_STATS_SESSION_BIDI_OUT_STREAM_COUNT,
IDX_STATS_SESSION_UNI_IN_STREAM_COUNT,
IDX_STATS_SESSION_UNI_OUT_STREAM_COUNT,
IDX_STATS_SESSION_MAX_BYTES_IN_FLIGHT,
IDX_STATS_SESSION_BYTES_IN_FLIGHT,
IDX_STATS_SESSION_BLOCK_COUNT,
IDX_STATS_SESSION_CWND,
IDX_STATS_SESSION_LATEST_RTT,
IDX_STATS_SESSION_MIN_RTT,
IDX_STATS_SESSION_RTTVAR,
IDX_STATS_SESSION_SMOOTHED_RTT,
IDX_STATS_SESSION_SSTHRESH,
IDX_STATS_SESSION_PKT_SENT,
IDX_STATS_SESSION_BYTES_SENT,
IDX_STATS_SESSION_PKT_RECV,
IDX_STATS_SESSION_BYTES_RECV,
IDX_STATS_SESSION_PKT_LOST,
IDX_STATS_SESSION_BYTES_LOST,
IDX_STATS_SESSION_PING_RECV,
IDX_STATS_SESSION_PKT_DISCARDED,
IDX_STATS_SESSION_DATAGRAMS_RECEIVED,
IDX_STATS_SESSION_DATAGRAMS_SENT,
IDX_STATS_SESSION_DATAGRAMS_ACKNOWLEDGED,
IDX_STATS_SESSION_DATAGRAMS_LOST,
IDX_STATS_SESSION_STREAMS_IDLE_TIMED_OUT,
IDX_STATS_SESSION_COUNT,
IDX_STATS_STREAM_CREATED_AT,
IDX_STATS_STREAM_OPENED_AT,
IDX_STATS_STREAM_RECEIVED_AT,
IDX_STATS_STREAM_ACKED_AT,
IDX_STATS_STREAM_DESTROYED_AT,
IDX_STATS_STREAM_BYTES_RECEIVED,
IDX_STATS_STREAM_BYTES_SENT,
IDX_STATS_STREAM_MAX_OFFSET,
IDX_STATS_STREAM_MAX_OFFSET_ACK,
IDX_STATS_STREAM_MAX_OFFSET_RECV,
IDX_STATS_STREAM_FINAL_SIZE,
IDX_STATS_STREAM_BYTES_ACCUMULATED,
IDX_STATS_STREAM_MAX_BYTES_ACCUMULATED,
IDX_STATS_STREAM_COUNT,
} = internalBinding('quic');
assert(IDX_STATS_ENDPOINT_CREATED_AT !== undefined);
assert(IDX_STATS_ENDPOINT_DESTROYED_AT !== undefined);
assert(IDX_STATS_ENDPOINT_BYTES_RECEIVED !== undefined);
assert(IDX_STATS_ENDPOINT_BYTES_SENT !== undefined);
assert(IDX_STATS_ENDPOINT_PACKETS_RECEIVED !== undefined);
assert(IDX_STATS_ENDPOINT_PACKETS_SENT !== undefined);
assert(IDX_STATS_ENDPOINT_SERVER_SESSIONS !== undefined);
assert(IDX_STATS_ENDPOINT_CLIENT_SESSIONS !== undefined);
assert(IDX_STATS_ENDPOINT_SERVER_BUSY_COUNT !== undefined);
assert(IDX_STATS_ENDPOINT_RETRY_COUNT !== undefined);
assert(IDX_STATS_ENDPOINT_RETRY_RATE_LIMITED !== undefined);
assert(IDX_STATS_ENDPOINT_VERSION_NEGOTIATION_COUNT !== undefined);
assert(IDX_STATS_ENDPOINT_VERSION_NEGOTIATION_RATE_LIMITED !== undefined);
assert(IDX_STATS_ENDPOINT_STATELESS_RESET_COUNT !== undefined);
assert(IDX_STATS_ENDPOINT_STATELESS_RESET_RATE_LIMITED !== undefined);
assert(IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT !== undefined);
assert(IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_RATE_LIMITED !== undefined);
assert(IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED !== undefined);
assert(IDX_STATS_ENDPOINT_PACKETS_BLOCKED !== undefined);
assert(IDX_STATS_SESSION_CREATED_AT !== undefined);
assert(IDX_STATS_SESSION_DESTROYED_AT !== undefined);
assert(IDX_STATS_SESSION_CLOSING_AT !== undefined);
assert(IDX_STATS_SESSION_HANDSHAKE_COMPLETED_AT !== undefined);
assert(IDX_STATS_SESSION_HANDSHAKE_CONFIRMED_AT !== undefined);
assert(IDX_STATS_SESSION_BYTES_RECEIVED !== undefined);
assert(IDX_STATS_SESSION_BIDI_IN_STREAM_COUNT !== undefined);
assert(IDX_STATS_SESSION_BIDI_OUT_STREAM_COUNT !== undefined);
assert(IDX_STATS_SESSION_UNI_IN_STREAM_COUNT !== undefined);
assert(IDX_STATS_SESSION_UNI_OUT_STREAM_COUNT !== undefined);
assert(IDX_STATS_SESSION_MAX_BYTES_IN_FLIGHT !== undefined);
assert(IDX_STATS_SESSION_BYTES_IN_FLIGHT !== undefined);
assert(IDX_STATS_SESSION_BLOCK_COUNT !== undefined);
assert(IDX_STATS_SESSION_CWND !== undefined);
assert(IDX_STATS_SESSION_LATEST_RTT !== undefined);
assert(IDX_STATS_SESSION_MIN_RTT !== undefined);
assert(IDX_STATS_SESSION_RTTVAR !== undefined);
assert(IDX_STATS_SESSION_SMOOTHED_RTT !== undefined);
assert(IDX_STATS_SESSION_SSTHRESH !== undefined);
assert(IDX_STATS_SESSION_PKT_SENT !== undefined);
assert(IDX_STATS_SESSION_BYTES_SENT !== undefined);
assert(IDX_STATS_SESSION_PKT_RECV !== undefined);
assert(IDX_STATS_SESSION_BYTES_RECV !== undefined);
assert(IDX_STATS_SESSION_PKT_LOST !== undefined);
assert(IDX_STATS_SESSION_BYTES_LOST !== undefined);
assert(IDX_STATS_SESSION_PING_RECV !== undefined);
assert(IDX_STATS_SESSION_PKT_DISCARDED !== undefined);
assert(IDX_STATS_SESSION_DATAGRAMS_RECEIVED !== undefined);
assert(IDX_STATS_SESSION_DATAGRAMS_SENT !== undefined);
assert(IDX_STATS_SESSION_DATAGRAMS_ACKNOWLEDGED !== undefined);
assert(IDX_STATS_SESSION_DATAGRAMS_LOST !== undefined);
assert(IDX_STATS_SESSION_STREAMS_IDLE_TIMED_OUT !== undefined);
assert(IDX_STATS_STREAM_CREATED_AT !== undefined);
assert(IDX_STATS_STREAM_OPENED_AT !== undefined);
assert(IDX_STATS_STREAM_RECEIVED_AT !== undefined);
assert(IDX_STATS_STREAM_ACKED_AT !== undefined);
assert(IDX_STATS_STREAM_DESTROYED_AT !== undefined);
assert(IDX_STATS_STREAM_BYTES_RECEIVED !== undefined);
assert(IDX_STATS_STREAM_BYTES_SENT !== undefined);
assert(IDX_STATS_STREAM_MAX_OFFSET !== undefined);
assert(IDX_STATS_STREAM_MAX_OFFSET_ACK !== undefined);
assert(IDX_STATS_STREAM_MAX_OFFSET_RECV !== undefined);
assert(IDX_STATS_STREAM_FINAL_SIZE !== undefined);
assert(IDX_STATS_STREAM_BYTES_ACCUMULATED !== undefined);
assert(IDX_STATS_STREAM_MAX_BYTES_ACCUMULATED !== undefined);
assert(IDX_STATS_STREAM_COUNT !== undefined);
assert(IDX_STATS_SESSION_COUNT !== undefined);
const kCreateDisconnected = Symbol('kCreateDisconnected');
let assertIsQuicEndpointStats;
let assertIsQuicSessionStats;
let assertIsQuicStreamStats;
let isQuicEndpointStats;
let isQuicSessionStats;
let isQuicStreamStats;
function assertIsPrivateConstructor(privateSymbol) {
if (privateSymbol !== kPrivateConstructor) {
throw new ERR_ILLEGAL_CONSTRUCTOR();
}
}
class QuicEndpointStats {
/** @type {BigUint64Array} */
#handle;
/** @type {boolean} */
#disconnected = false;
static {
isQuicEndpointStats = function(val) {
return val != null && typeof val === 'object' && #handle in val;
};
assertIsQuicEndpointStats = function(val) {
if (!isQuicEndpointStats(val)) {
throw new ERR_INVALID_THIS('QuicEndpointStats');
}
};
}
/**
* @param {symbol} privateSymbol
* @param {ArrayBuffer} buffer
*/
constructor(privateSymbol, buffer) {
// We use the kPrivateConstructor symbol to restrict the ability to
// create new instances of QuicEndpointStats to internal code.
assertIsPrivateConstructor(privateSymbol);
if (!isArrayBuffer(buffer)) {
throw new ERR_INVALID_ARG_TYPE('buffer', ['ArrayBuffer'], buffer);
}
this.#handle = new BigUint64Array(buffer);
}
/** @type {bigint} */
get createdAt() {
assertIsQuicEndpointStats(this);
return this.#handle[IDX_STATS_ENDPOINT_CREATED_AT];
}
/** @type {bigint} */
get destroyedAt() {
assertIsQuicEndpointStats(this);
return this.#handle[IDX_STATS_ENDPOINT_DESTROYED_AT];
}
/** @type {bigint} */
get bytesReceived() {
assertIsQuicEndpointStats(this);
return this.#handle[IDX_STATS_ENDPOINT_BYTES_RECEIVED];
}
/** @type {bigint} */
get bytesSent() {
assertIsQuicEndpointStats(this);
return this.#handle[IDX_STATS_ENDPOINT_BYTES_SENT];
}
/** @type {bigint} */
get packetsReceived() {
assertIsQuicEndpointStats(this);
return this.#handle[IDX_STATS_ENDPOINT_PACKETS_RECEIVED];
}
/** @type {bigint} */
get packetsSent() {
assertIsQuicEndpointStats(this);
return this.#handle[IDX_STATS_ENDPOINT_PACKETS_SENT];
}
/** @type {bigint} */
get serverSessions() {
assertIsQuicEndpointStats(this);
return this.#handle[IDX_STATS_ENDPOINT_SERVER_SESSIONS];
}
/** @type {bigint} */
get clientSessions() {
assertIsQuicEndpointStats(this);
return this.#handle[IDX_STATS_ENDPOINT_CLIENT_SESSIONS];
}
/** @type {bigint} */
get serverBusyCount() {
assertIsQuicEndpointStats(this);
return this.#handle[IDX_STATS_ENDPOINT_SERVER_BUSY_COUNT];
}
/** @type {bigint} */
get retryCount() {
assertIsQuicEndpointStats(this);
return this.#handle[IDX_STATS_ENDPOINT_RETRY_COUNT];
}
/** @type {bigint} */
get retryRateLimited() {
assertIsQuicEndpointStats(this);
return this.#handle[IDX_STATS_ENDPOINT_RETRY_RATE_LIMITED];
}
/** @type {bigint} */
get versionNegotiationCount() {
assertIsQuicEndpointStats(this);
return this.#handle[IDX_STATS_ENDPOINT_VERSION_NEGOTIATION_COUNT];
}
/** @type {bigint} */
get versionNegotiationRateLimited() {
assertIsQuicEndpointStats(this);
return this.#handle[IDX_STATS_ENDPOINT_VERSION_NEGOTIATION_RATE_LIMITED];
}
/** @type {bigint} */
get statelessResetCount() {
assertIsQuicEndpointStats(this);
return this.#handle[IDX_STATS_ENDPOINT_STATELESS_RESET_COUNT];
}
/** @type {bigint} */
get statelessResetRateLimited() {
assertIsQuicEndpointStats(this);
return this.#handle[IDX_STATS_ENDPOINT_STATELESS_RESET_RATE_LIMITED];
}
/** @type {bigint} */
get immediateCloseCount() {
assertIsQuicEndpointStats(this);
return this.#handle[IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_COUNT];
}
/** @type {bigint} */
get immediateCloseRateLimited() {
assertIsQuicEndpointStats(this);
return this.#handle[IDX_STATS_ENDPOINT_IMMEDIATE_CLOSE_RATE_LIMITED];
}
/** @type {bigint} */
get sessionCreationRateLimited() {
assertIsQuicEndpointStats(this);
return this.#handle[IDX_STATS_ENDPOINT_SESSION_CREATION_RATE_LIMITED];
}
/** @type {bigint} */
get packetsBlocked() {
assertIsQuicEndpointStats(this);
return this.#handle[IDX_STATS_ENDPOINT_PACKETS_BLOCKED];
}
toString() {
return JSONStringify(this.toJSON());
}
toJSON() {
assertIsQuicEndpointStats(this);
const {
createdAt,
destroyedAt,
bytesReceived,
bytesSent,
packetsReceived,
packetsSent,
serverSessions,
clientSessions,
serverBusyCount,
retryCount,
retryRateLimited,
versionNegotiationCount,
versionNegotiationRateLimited,
statelessResetCount,
statelessResetRateLimited,
immediateCloseCount,
immediateCloseRateLimited,
sessionCreationRateLimited,
packetsBlocked,
} = this;
return {
__proto__: null,
connected: this.isConnected,
// We need to convert the values to strings because JSON does not
// support BigInts.
createdAt: `${createdAt}`,
destroyedAt: `${destroyedAt}`,
bytesReceived: `${bytesReceived}`,
bytesSent: `${bytesSent}`,
packetsReceived: `${packetsReceived}`,
packetsSent: `${packetsSent}`,
serverSessions: `${serverSessions}`,
clientSessions: `${clientSessions}`,
serverBusyCount: `${serverBusyCount}`,
retryCount: `${retryCount}`,
retryRateLimited: `${retryRateLimited}`,
versionNegotiationCount: `${versionNegotiationCount}`,
versionNegotiationRateLimited: `${versionNegotiationRateLimited}`,
statelessResetCount: `${statelessResetCount}`,
statelessResetRateLimited: `${statelessResetRateLimited}`,
immediateCloseCount: `${immediateCloseCount}`,
immediateCloseRateLimited: `${immediateCloseRateLimited}`,
sessionCreationRateLimited: `${sessionCreationRateLimited}`,
packetsBlocked: `${packetsBlocked}`,
};
}
[kInspect](depth, options) {
assertIsQuicEndpointStats(this);
if (depth < 0) {
return 'QuicEndpointStats { }';
}
const opts = {
__proto__: null,
...options,
depth: options.depth == null ? null : options.depth - 1,
};
const {
createdAt,
destroyedAt,
bytesReceived,
bytesSent,
packetsReceived,
packetsSent,
serverSessions,
clientSessions,
serverBusyCount,
retryCount,
retryRateLimited,
versionNegotiationCount,
versionNegotiationRateLimited,
statelessResetCount,
statelessResetRateLimited,
immediateCloseCount,
immediateCloseRateLimited,
sessionCreationRateLimited,
packetsBlocked,
} = this;
return `QuicEndpointStats ${inspect({
connected: this.isConnected,
createdAt,
destroyedAt,
bytesReceived,
bytesSent,
packetsReceived,
packetsSent,
serverSessions,
clientSessions,
serverBusyCount,
retryCount,
retryRateLimited,
versionNegotiationCount,
versionNegotiationRateLimited,
statelessResetCount,
statelessResetRateLimited,
immediateCloseCount,
immediateCloseRateLimited,
sessionCreationRateLimited,
packetsBlocked,
}, opts)}`;
}
/**
* True if this QuicEndpointStats object is still connected to the underlying
* Endpoint stats source. If this returns false, then the stats object is
* no longer being updated and should be considered stale.
* @type {boolean}
*/
get isConnected() {
assertIsQuicEndpointStats(this);
return !this.#disconnected;
}
[kFinishClose]() {
this.#handle = new BigUint64Array(this.#handle);
this.#disconnected = true;
}
}
class QuicSessionStats {
/** @type {BigUint64Array} */
#handle;
#disconnected = false;
#offset = 0;
static {
isQuicSessionStats = function(val) {
return val != null && typeof val === 'object' && #handle in val;
};
assertIsQuicSessionStats = function(val) {
if (!isQuicSessionStats(val)) {
throw new ERR_INVALID_THIS('QuicSessionStats');
}
};
}
/**
* @param {symbol} privateSymbol
* @param {ArrayBuffer} view
* @param {number} [byteOffset]
*/
constructor(privateSymbol, view, byteOffset = 0) {
// We use the kPrivateConstructor symbol to restrict the ability to
// create new instances of QuicSessionStats to internal code.
assertIsPrivateConstructor(privateSymbol);
if (isArrayBuffer(view)) {
this.#handle = new BigUint64Array(view);
} else {
this.#handle = view;
}
this.#offset = byteOffset / 8;
}
/** @type {bigint} */
get createdAt() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_CREATED_AT];
}
/** @type {bigint} */
get destroyedAt() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_DESTROYED_AT];
}
/** @type {bigint} */
get closingAt() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_CLOSING_AT];
}
/** @type {bigint} */
get handshakeCompletedAt() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_HANDSHAKE_COMPLETED_AT];
}
/** @type {bigint} */
get handshakeConfirmedAt() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_HANDSHAKE_CONFIRMED_AT];
}
/** @type {bigint} */
get bytesReceived() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_BYTES_RECEIVED];
}
/** @type {bigint} */
get bidiInStreamCount() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_BIDI_IN_STREAM_COUNT];
}
/** @type {bigint} */
get bidiOutStreamCount() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_BIDI_OUT_STREAM_COUNT];
}
/** @type {bigint} */
get uniInStreamCount() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_UNI_IN_STREAM_COUNT];
}
/** @type {bigint} */
get uniOutStreamCount() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_UNI_OUT_STREAM_COUNT];
}
/** @type {bigint} */
get maxBytesInFlight() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_MAX_BYTES_IN_FLIGHT];
}
/** @type {bigint} */
get bytesInFlight() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_BYTES_IN_FLIGHT];
}
/** @type {bigint} */
get blockCount() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_BLOCK_COUNT];
}
/** @type {bigint} */
get cwnd() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_CWND];
}
/** @type {bigint} */
get latestRtt() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_LATEST_RTT];
}
/** @type {bigint} */
get minRtt() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_MIN_RTT];
}
/** @type {bigint} */
get rttVar() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_RTTVAR];
}
/** @type {bigint} */
get smoothedRtt() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_SMOOTHED_RTT];
}
/** @type {bigint} */
get ssthresh() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_SSTHRESH];
}
get pktSent() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_PKT_SENT];
}
get bytesSent() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_BYTES_SENT];
}
get pktRecv() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_PKT_RECV];
}
get bytesRecv() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_BYTES_RECV];
}
get pktLost() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_PKT_LOST];
}
get bytesLost() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_BYTES_LOST];
}
get pingRecv() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_PING_RECV];
}
get pktDiscarded() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_PKT_DISCARDED];
}
/** @type {bigint} */
get datagramsReceived() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_DATAGRAMS_RECEIVED];
}
/** @type {bigint} */
get datagramsSent() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_DATAGRAMS_SENT];
}
/** @type {bigint} */
get datagramsAcknowledged() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_DATAGRAMS_ACKNOWLEDGED];
}
/** @type {bigint} */
get datagramsLost() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset + IDX_STATS_SESSION_DATAGRAMS_LOST];
}
/** @type {bigint} */
get streamsIdleTimedOut() {
assertIsQuicSessionStats(this);
return this.#handle[this.#offset +
IDX_STATS_SESSION_STREAMS_IDLE_TIMED_OUT];
}
toString() {
return JSONStringify(this.toJSON());
}
toJSON() {
assertIsQuicSessionStats(this);
const {
createdAt,
closingAt,
handshakeCompletedAt,
handshakeConfirmedAt,
bytesReceived,
bidiInStreamCount,
bidiOutStreamCount,
uniInStreamCount,
uniOutStreamCount,
maxBytesInFlight,
bytesInFlight,
blockCount,
cwnd,
latestRtt,
minRtt,
rttVar,
smoothedRtt,
ssthresh,
pktSent,
bytesSent,
pktRecv,
bytesRecv,
pktLost,
bytesLost,
pingRecv,
pktDiscarded,
datagramsReceived,
datagramsSent,
datagramsAcknowledged,
datagramsLost,
streamsIdleTimedOut,
} = this;
return {
__proto__: null,
connected: this.isConnected,
// We need to convert the values to strings because JSON does not
// support BigInts.
createdAt: `${createdAt}`,
closingAt: `${closingAt}`,
handshakeCompletedAt: `${handshakeCompletedAt}`,
handshakeConfirmedAt: `${handshakeConfirmedAt}`,
bytesReceived: `${bytesReceived}`,
bidiInStreamCount: `${bidiInStreamCount}`,
bidiOutStreamCount: `${bidiOutStreamCount}`,
uniInStreamCount: `${uniInStreamCount}`,
uniOutStreamCount: `${uniOutStreamCount}`,
maxBytesInFlight: `${maxBytesInFlight}`,
bytesInFlight: `${bytesInFlight}`,
blockCount: `${blockCount}`,
cwnd: `${cwnd}`,
latestRtt: `${latestRtt}`,
minRtt: `${minRtt}`,
rttVar: `${rttVar}`,
smoothedRtt: `${smoothedRtt}`,
ssthresh: `${ssthresh}`,
pktSent: `${pktSent}`,
bytesSent: `${bytesSent}`,
pktRecv: `${pktRecv}`,
bytesRecv: `${bytesRecv}`,
pktLost: `${pktLost}`,
bytesLost: `${bytesLost}`,
pingRecv: `${pingRecv}`,
pktDiscarded: `${pktDiscarded}`,
datagramsReceived: `${datagramsReceived}`,
datagramsSent: `${datagramsSent}`,
datagramsAcknowledged: `${datagramsAcknowledged}`,
datagramsLost: `${datagramsLost}`,
streamsIdleTimedOut: `${streamsIdleTimedOut}`,
};
}
[kInspect](depth, options) {
if (depth < 0) {
return 'QuicSessionStats { }';
}
const opts = {
__proto__: null,
...options,
depth: options.depth == null ? null : options.depth - 1,
};
const {
createdAt,
closingAt,
handshakeCompletedAt,
handshakeConfirmedAt,
bytesReceived,
bidiInStreamCount,
bidiOutStreamCount,
uniInStreamCount,
uniOutStreamCount,
maxBytesInFlight,
bytesInFlight,
blockCount,
cwnd,
latestRtt,
minRtt,
rttVar,
smoothedRtt,
ssthresh,
pktSent,
bytesSent,
pktRecv,
bytesRecv,
pktLost,
bytesLost,
pingRecv,
pktDiscarded,
datagramsReceived,
datagramsSent,
datagramsAcknowledged,
datagramsLost,
streamsIdleTimedOut,
} = this;
return `QuicSessionStats ${inspect({
connected: this.isConnected,
createdAt,
closingAt,
handshakeCompletedAt,
handshakeConfirmedAt,
bytesReceived,
bidiInStreamCount,
bidiOutStreamCount,
uniInStreamCount,
uniOutStreamCount,
maxBytesInFlight,
bytesInFlight,
blockCount,
cwnd,
latestRtt,
minRtt,
rttVar,
smoothedRtt,
ssthresh,
pktSent,
bytesSent,
pktRecv,
bytesRecv,
pktLost,
bytesLost,
pingRecv,
pktDiscarded,
datagramsReceived,
datagramsSent,
datagramsAcknowledged,
datagramsLost,
streamsIdleTimedOut,
}, opts)}`;
}
/**
* True if this QuicSessionStats object is still connected to the underlying
* Session stats source. If this returns false, then the stats object is
* no longer being updated and should be considered stale.
* @type {boolean}
*/
get isConnected() {
return !this.#disconnected;
}
[kFinishClose]() {
const view = TypedArrayPrototypeSubarray(this.#handle,
this.#offset,
this.#offset + IDX_STATS_STREAM_COUNT);
this.#handle = new BigUint64Array(view);
this.#offset = 0;
this.#disconnected = true;
}
}
class QuicStreamStats {
/** @type {BigUint64Array} */
#handle;
#offset = 0;
#disconnected = false;
static {
isQuicStreamStats = function(val) {
return val != null && typeof val === 'object' && #handle in val;
};
assertIsQuicStreamStats = function(val) {
if (!isQuicStreamStats(val)) {
throw new ERR_INVALID_THIS('QuicStreamStats');
}
};
}
/**
* @param {symbol} privateSymbol
* @param {BigUint64Array|ArrayBuffer} view
* @param {number} [byteOffset] - byte offset into the shared page view
*/
constructor(privateSymbol, view, byteOffset = 0) {
// We use the kPrivateConstructor symbol to restrict the ability to
// create new instances of QuicStreamStats to internal code.
assertIsPrivateConstructor(privateSymbol);
if (isArrayBuffer(view)) {
this.#handle = new BigUint64Array(view);
} else {
this.#handle = view;
}
this.#offset = byteOffset / 8;
}
/** @type {bigint} */
get createdAt() {
assertIsQuicStreamStats(this);
return this.#handle[this.#offset + IDX_STATS_STREAM_CREATED_AT];
}
/** @type {bigint} */
get openedAt() {
assertIsQuicStreamStats(this);
return this.#handle[this.#offset + IDX_STATS_STREAM_OPENED_AT];
}
/** @type {bigint} */
get receivedAt() {
assertIsQuicStreamStats(this);
return this.#handle[this.#offset + IDX_STATS_STREAM_RECEIVED_AT];
}
/** @type {bigint} */
get ackedAt() {
assertIsQuicStreamStats(this);
return this.#handle[this.#offset + IDX_STATS_STREAM_ACKED_AT];
}
/** @type {bigint} */
get destroyedAt() {
assertIsQuicStreamStats(this);
return this.#handle[this.#offset + IDX_STATS_STREAM_DESTROYED_AT];
}
/** @type {bigint} */
get bytesReceived() {
assertIsQuicStreamStats(this);
return this.#handle[this.#offset + IDX_STATS_STREAM_BYTES_RECEIVED];
}
/** @type {bigint} */
get bytesSent() {
assertIsQuicStreamStats(this);
return this.#handle[this.#offset + IDX_STATS_STREAM_BYTES_SENT];
}
/** @type {bigint} */
get maxOffset() {
assertIsQuicStreamStats(this);
return this.#handle[this.#offset + IDX_STATS_STREAM_MAX_OFFSET];
}
/** @type {bigint} */
get maxOffsetAcknowledged() {
assertIsQuicStreamStats(this);
return this.#handle[this.#offset + IDX_STATS_STREAM_MAX_OFFSET_ACK];
}
/** @type {bigint} */
get maxOffsetReceived() {
assertIsQuicStreamStats(this);
return this.#handle[this.#offset + IDX_STATS_STREAM_MAX_OFFSET_RECV];
}
/** @type {bigint} */
get finalSize() {
assertIsQuicStreamStats(this);
return this.#handle[this.#offset + IDX_STATS_STREAM_FINAL_SIZE];
}
/** @type {bigint} Current bytes in the receive accumulation buffer. */
get bytesAccumulated() {
assertIsQuicStreamStats(this);
return this.#handle[this.#offset + IDX_STATS_STREAM_BYTES_ACCUMULATED];
}
/** @type {bigint} Peak bytes accumulated over the stream's lifetime. */
get maxBytesAccumulated() {
assertIsQuicStreamStats(this);
return this.#handle[this.#offset + IDX_STATS_STREAM_MAX_BYTES_ACCUMULATED];
}
toString() {
return JSONStringify(this.toJSON());
}
toJSON() {
assertIsQuicStreamStats(this);
const {
createdAt,
openedAt,
receivedAt,
ackedAt,
destroyedAt,
bytesReceived,
bytesSent,
maxOffset,
maxOffsetAcknowledged,
maxOffsetReceived,
finalSize,
bytesAccumulated,
maxBytesAccumulated,
} = this;
return {
__proto__: null,
connected: this.isConnected,
// We need to convert the values to strings because JSON does not
// support BigInts.
createdAt: `${createdAt}`,
openedAt: `${openedAt}`,
receivedAt: `${receivedAt}`,
ackedAt: `${ackedAt}`,
destroyedAt: `${destroyedAt}`,
bytesReceived: `${bytesReceived}`,
bytesSent: `${bytesSent}`,
maxOffset: `${maxOffset}`,
maxOffsetAcknowledged: `${maxOffsetAcknowledged}`,
maxOffsetReceived: `${maxOffsetReceived}`,
finalSize: `${finalSize}`,
bytesAccumulated: `${bytesAccumulated}`,
maxBytesAccumulated: `${maxBytesAccumulated}`,
};
}
[kInspect](depth, options) {
if (depth < 0) {
return 'QuicStreamStats { }';
}
const opts = {
__proto__: null,
...options,
depth: options.depth == null ? null : options.depth - 1,
};
const {
createdAt,
openedAt,
receivedAt,
ackedAt,
destroyedAt,
bytesReceived,
bytesSent,
maxOffset,
maxOffsetAcknowledged,
maxOffsetReceived,
finalSize,
bytesAccumulated,
maxBytesAccumulated,
} = this;
return `QuicStreamStats ${inspect({
connected: this.isConnected,
createdAt,
openedAt,
receivedAt,
ackedAt,
destroyedAt,
bytesReceived,
bytesSent,
maxOffset,
maxOffsetAcknowledged,
maxOffsetReceived,
finalSize,
bytesAccumulated,
maxBytesAccumulated,
}, opts)}`;
}
/**
* True if this QuicStreamStats object is still connected to the underlying
* Stream stats source. If this returns false, then the stats object is
* no longer being updated and should be considered stale.
* @type {boolean}
*/
get isConnected() {
return !this.#disconnected;
}
[kFinishClose]() {
const view = TypedArrayPrototypeSubarray(this.#handle,
this.#offset,
this.#offset + IDX_STATS_STREAM_COUNT);
this.#handle = new BigUint64Array(view);
this.#offset = 0;
this.#disconnected = true;
}
// Creates an immediately disconnected QuicStreamStats object. Used when
// lazily creating stats for a stream that has already been destroyed.
static [kCreateDisconnected]() {
const count = IDX_STATS_STREAM_COUNT;
const stats = new QuicStreamStats(kPrivateConstructor, new BigUint64Array(count), 0);
stats.#disconnected = true;
return stats;
}
}
module.exports = {
QuicEndpointStats,
QuicSessionStats,
QuicStreamStats,
kCreateDisconnected,
};
/* c8 ignore stop */