From c7ecc395248bf44a548fa7845b21a947e6ad7af3 Mon Sep 17 00:00:00 2001 From: Marten Richter Date: Sat, 16 Sep 2023 12:14:32 +0000 Subject: [PATCH] Add basic getStats support from upstream --- CMakeLists.txt | 2 + lib/dom.ts | 1 + lib/session.js | 112 ++++++++++++++++++++++++++++++------ lib/types.ts | 27 ++++++++- src/http3eventloop.cc | 101 ++++++++++++++++++++++++++++++++ src/http3eventloop.h | 11 ++++ src/http3wtsessionvisitor.h | 36 ++++++++++++ test/test.js | 2 + 8 files changed, 272 insertions(+), 20 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 924fcd31..43ba12b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -758,6 +758,8 @@ third_party/quiche/quiche/quic/core/uber_quic_stream_id_manager.cc third_party/quiche/quiche/quic/core/uber_quic_stream_id_manager.h third_party/quiche/quiche/quic/core/uber_received_packet_manager.cc third_party/quiche/quiche/quic/core/uber_received_packet_manager.h +third_party/quiche/quiche/quic/core/web_transport_stats.h +third_party/quiche/quiche/quic/core/web_transport_stats.cc third_party/quiche/quiche/quic/platform/api/quic_bug_tracker.h third_party/quiche/quiche/quic/platform/api/quic_client_stats.h third_party/quiche/quiche/quic/platform/api/quic_export.h diff --git a/lib/dom.ts b/lib/dom.ts index be52348a..bf1532cf 100644 --- a/lib/dom.ts +++ b/lib/dom.ts @@ -20,6 +20,7 @@ export interface WebTransportStats { smoothedRtt: number rttVariation: number minRtt: number + estimatedSendRate: bigint datagrams: WebTransportDatagramStats } diff --git a/lib/session.js b/lib/session.js index 0dbd3b4c..a047e49b 100644 --- a/lib/session.js +++ b/lib/session.js @@ -9,6 +9,8 @@ import { Http3WTStream } from './stream.js' * @typedef {import('./types').DatagramReceivedEvent} DatagramReceivedEvent * @typedef {import('./types').DatagramSendEvent} DatagramSendEvent * @typedef {import('./types').GoawayReceivedEvent} GoawayReceivedEvent + * @typedef {import('./types').DatagramStatsEvent} DatagramStatsEvent + * @typedef {import('./types').SessionStatsEvent} SessionStatsEvent * @typedef {import('./types').NewStreamEvent} NewStreamEvent * * @typedef {import('./dom').WebTransportCloseInfo} WebTransportCloseInfo @@ -19,6 +21,7 @@ import { Http3WTStream } from './stream.js' * @typedef {import('./dom').WebTransportReliabilityMode} WebTransportReliabilityMode * @typedef {import('./dom').WebTransportCongestionControl} WebTransportCongestionControl * @typedef {import('./dom').WebTransportStats} WebTransportStats + * @typedef {import('./dom').WebTransportDatagramStats} WebTransportDatagramStats * * @typedef {import('./types').NativeHttp3WTSession} NativeHttp3WTSession * @@ -148,6 +151,16 @@ export class Http3WTSession { /** @type {Array<(err?: Error) => void>} */ this.rejectUniDi = [] + /** @type {Array<(stats: WebTransportStats) => void>} */ + this.resolveSessionStats = [] + /** @type {Array<(err?: Error) => void>} */ + this.rejectSessionStats = [] + + /** @type {Array<(stats: WebTransportDatagramStats) => void>} */ + this.resolveDatagramStats = [] + /** @type {Array<(err?: Error) => void>} */ + this.rejectDatagramStats = [] + /** @type {Set} */ this.sendStreams = new Set() /** @type {Set} */ @@ -172,25 +185,72 @@ export class Http3WTSession { } getStats() { - return Promise.resolve({ - timestamp: Date.now(), - bytesSent: BigInt(0), - packetsSent: BigInt(0), - packetsLost: BigInt(0), - numOutgoingStreamsCreated: 0, - numIncomingStreamsCreated: 0, - bytesReceived: BigInt(0), - packetsReceived: BigInt(0), - smoothedRtt: 0, - rttVariation: 0, - minRtt: 0, - datagrams: { - timestamp: Date.now(), - expiredOutgoing: BigInt(0), - droppedIncoming: BigInt(0), - lostOutgoing: BigInt(0) - } + if (this.objint == null) { + throw new Error('this.objint not set') + } + const prom = new Promise((resolve, reject) => { + this.resolveSessionStats.push(resolve) + this.rejectSessionStats.push(reject) }) + this.objint.orderSessionStats() + return prom + } + + /** + * @param {SessionStatsEvent} evt + */ + onSessionStats({ + timestamp, + expiredOutgoing = BigInt(0), + lostOutgoing = BigInt(0), + // non Datagram + minRtt = 0, + smoothedRtt = 0, + rttVariation = 0, + estimatedSendRateBps + }) { + const res = this.resolveSessionStats.pop() + this.rejectSessionStats.pop() + if (res) + res({ + timestamp, + bytesSent: BigInt(0), + packetsSent: BigInt(0), + packetsLost: BigInt(0), + numOutgoingStreamsCreated: 0, + numIncomingStreamsCreated: 0, + bytesReceived: BigInt(0), + packetsReceived: BigInt(0), + smoothedRtt, + rttVariation, + minRtt, + estimatedSendRate: estimatedSendRateBps, + datagrams: { + timestamp, + expiredOutgoing, + droppedIncoming: BigInt(0), + lostOutgoing + } + }) + } + + /** + * @param {DatagramStatsEvent} evt + */ + onDatagramStats({ + timestamp, + expiredOutgoing = BigInt(0), + lostOutgoing = BigInt(0) + }) { + const res = this.resolveDatagramStats.pop() + this.rejectDatagramStats.pop() + if (res) + res({ + timestamp, + expiredOutgoing, + droppedIncoming: BigInt(0), + lostOutgoing + }) } async waitForDatagramsSend() { @@ -324,6 +384,9 @@ export class Http3WTSession { for (const rej of this.rejectBiDi) rej() for (const rej of this.rejectUniDi) rej() for (const rej of this.writeDatagramRej) rej() + for (const rej of this.rejectSessionStats) rej() + for (const rej of this.rejectDatagramStats) rej() + this.writeDatagramRej = [] this.writeDatagramRes = [] this.writeDatagramProm = [] @@ -332,6 +395,11 @@ export class Http3WTSession { this.rejectBiDi = [] this.rejectUniDi = [] + this.resolveSessionStats = [] + this.rejectSessionStats = [] + this.resolveDatagramStats = [] + this.rejectDatagramStats = [] + this.incomBiDiController.close() this.incomUniDiController.close() this.incomDatagramController.close() @@ -458,7 +526,7 @@ export class Http3WTSession { } /** - * @param {SessionReadyEvent | SessionCloseEvent | DatagramReceivedEvent | DatagramSendEvent | GoawayReceivedEvent | NewStreamEvent} args + * @param {SessionReadyEvent | SessionCloseEvent | DatagramReceivedEvent | DatagramSendEvent | GoawayReceivedEvent | SessionStatsEvent | DatagramStatsEvent | NewStreamEvent} args */ static callback(args) { // console.log('Session callback called', args) @@ -483,6 +551,12 @@ export class Http3WTSession { case 'GoawayReceived': visitor.onGoAwayReceived(args) break + case 'SessionStats': + visitor.onSessionStats(args) + break + case 'DatagramStats': + visitor.onDatagramStats(args) + break case 'Http3WTStreamVisitor': if ( visitor && diff --git a/lib/types.ts b/lib/types.ts index 9ee381b0..b9016381 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -8,7 +8,8 @@ import type { WebTransport } from './dom' writeDatagram: (chunk: Uint8Array) => void orderUnidiStream: () => void orderBidiStream: () => void - // orderStats: () => void + orderSessionStats: () => void + orderDatagramStats: () => void notifySessionDraining(): () => void close: (arg: { code: number, reason: string }) => void } @@ -82,6 +83,28 @@ export interface SessionCloseEvent { error: string } +export interface SessionStatsEvent { + object: NativeHttp3WTSession + purpose: 'SessionStats' + timestamp: number + expiredOutgoing: bigint + lostOutgoing: bigint + + // non Datagram + minRtt: number + smoothedRtt: number + rttVariation: number + estimatedSendRateBps: bigint +} + +export interface DatagramStatsEvent { + object: NativeHttp3WTSession + purpose: 'DatagramStats' + timestamp: number + expiredOutgoing: bigint + lostOutgoing: bigint +} + export interface DatagramReceivedEvent { object: NativeHttp3WTSession purpose: 'DatagramReceived' @@ -113,6 +136,8 @@ export interface WebTransportSessionEventHandler { onDatagramReceived: (evt: DatagramReceivedEvent) => void onDatagramSend: (evt: DatagramSendEvent) => void onGoAwayReceived: (evt: GoawayReceivedEvent) => void + onSessionStats: (evt: SessionStatsEvent) => void + onDatagramStats: (evt: DatagramStatsEvent) => void onStream: (evt: NewStreamEvent) => void closeHook?: (() => void) | null } diff --git a/src/http3eventloop.cc b/src/http3eventloop.cc index 3d06b5b8..6df55105 100644 --- a/src/http3eventloop.cc +++ b/src/http3eventloop.cc @@ -220,6 +220,31 @@ namespace quic progress_->Send(&report, 1); } + void Http3EventLoop::informSessionStats(Http3WTSession *sessionobj, webtransport::SessionStats * sessstats) + { + Http3ProgressReport report; + report.type = Http3ProgressReport::SessionStats; + report.sessionobj = sessionobj; + report.sessionStats = sessstats; + + report.timestamp = new absl::Duration(); + report.timestamp[0] = absl::Now() - absl::UnixEpoch(); + if (progress_) + progress_->Send(&report, 1); + } + + void Http3EventLoop::informDatagramStats(Http3WTSession *sessionobj, webtransport::DatagramStats * datastats) + { + Http3ProgressReport report; + report.type = Http3ProgressReport::DatagramStats; + report.sessionobj = sessionobj; + report.datagramStats = datastats; + report.timestamp = new absl::Duration(); + report.timestamp[0] = absl::Now() - absl::UnixEpoch(); + if (progress_) + progress_->Send(&report, 1); + } + void Http3EventLoop::informUnref(LifetimeHelper *obj) { Http3ProgressReport report; @@ -591,6 +616,70 @@ namespace quic cbsession_.Call({retObj}); } + void Http3EventLoop::processSessionStats(Http3WTSession *sessionobj, absl::Duration* timestamp, webtransport::SessionStats * sessstats) + { + if (!checkQw()) + return; + HandleScope scope(qw_->Env()); + + + auto session = sessionobj->getJS(); + if (!session) + return; + Napi::Object objVal = session->Value(); + + Napi::Object retObj = Napi::Object::New(qw_->Env()); + retObj.Set("purpose", "SessionStats"); + retObj.Set("object", objVal); + // expiredOutgoing: bigint + // lostOutgoing: bigint + + // non Datagram + // minRtt: number + // smoothedRtt: number + // rttVariation: number + // estimatedSendRateBps: bigint + retObj.Set("timestamp", absl::ToDoubleMilliseconds(*timestamp)); // absl::Duration + // datagram + retObj.Set("expiredOutgoing", Napi::BigInt::New(qw_->Env(), sessstats->datagram_stats.expired_outgoing)); //uint64_t + retObj.Set("lostOutgoing", Napi::BigInt::New(qw_->Env(), sessstats->datagram_stats.lost_outgoing)); //uint64_t + + // non Datagram + retObj.Set("minRtt", absl::ToDoubleMilliseconds(sessstats->min_rtt)); // absl::Duration + retObj.Set("smoothedRtt", absl::ToDoubleMilliseconds(sessstats->smoothed_rtt)); // absl::Duration + retObj.Set("rttVariation", absl::ToDoubleMilliseconds(sessstats->rtt_variation)); // absl::Duration + retObj.Set("estimatedSendRateBps", sessstats->estimated_send_rate_bps); // absl::Duration + + cbsession_.Call({retObj}); + delete sessstats; + delete timestamp; + } + + void Http3EventLoop::processDatagramStats(Http3WTSession *sessionobj, absl::Duration* timestamp, webtransport::DatagramStats * datastats) + { + if (!checkQw()) + return; + HandleScope scope(qw_->Env()); + + + auto session = sessionobj->getJS(); + if (!session) + return; + Napi::Object objVal = session->Value(); + + Napi::Object retObj = Napi::Object::New(qw_->Env()); + retObj.Set("purpose", "DatagramStats"); + retObj.Set("object", objVal); + retObj.Set("timestamp", absl::ToDoubleMilliseconds(*timestamp)); // absl::Duration + // datagram + retObj.Set("expiredOutgoing", Napi::BigInt::New(qw_->Env(), datastats->expired_outgoing)); //uint64_t + retObj.Set("lostOutgoing", Napi::BigInt::New(qw_->Env(), datastats->lost_outgoing)); //uint64_t + + cbsession_.Call({retObj}); + delete datastats; + delete timestamp; + } + void Http3EventLoop::processNewSessionRequest(Http3Server *serverobj, WebTransportSession *session, spdy::Http2HeaderBlock *reqheadcopy, WebTransportRespPromisePtr *promise) { if (!checkQw()) @@ -883,6 +972,18 @@ namespace quic processGoawayReceived(cur.sessionobj); } break; + case Http3ProgressReport::SessionStats: + { + processSessionStats(cur.sessionobj, cur.timestamp, cur.sessionStats); + cur.para = nullptr; // take ownership of the data + } + break; + case Http3ProgressReport::DatagramStats: + { + processDatagramStats(cur.sessionobj, cur.timestamp, cur.datagramStats); + cur.para = nullptr; // take ownership of the data + } + break; case Http3ProgressReport::Unref: { cur.obj->doUnref(); diff --git a/src/http3eventloop.h b/src/http3eventloop.h index fd04f250..966f74e0 100644 --- a/src/http3eventloop.h +++ b/src/http3eventloop.h @@ -94,6 +94,8 @@ namespace quic DatagramReceived, DatagramSend, GoawayReceived, + SessionStats, + DatagramStats, Unref } type; union @@ -125,12 +127,15 @@ namespace quic { std::string *para = nullptr; // for session and others, we own it, and must delete it ServerStatusDetails *details; + webtransport::SessionStats * sessionStats; // we own it and must delete it after return from js + webtransport::DatagramStats * datagramStats; // we own it and must delete it after return from js }; union { bool success; WebTransportRespPromisePtr *promise; Napi::Reference *header; + absl::Duration *timestamp; // for session and others, we own it, and must delete it }; }; @@ -219,6 +224,9 @@ namespace quic void informDatagramReceived(Http3WTSession *sessionobj, absl::string_view datagram); void informDatagramSend(Http3WTSession *sessionobj, Napi::ObjectReference *bufferhandle); + void informSessionStats(Http3WTSession *sessionobj, webtransport::SessionStats * sessstats); + void informDatagramStats(Http3WTSession *sessionobj, webtransport::DatagramStats * datastats); + void informGoawayReceived(Http3WTSession *sessionobj); void informUnref(LifetimeHelper *obj); @@ -286,6 +294,9 @@ namespace quic void processDatagramReceived(Http3WTSession *sessionobj, std::string *datagram); void processDatagramSend(Http3WTSession *sessionobj, Napi::ObjectReference *bufferhandle); + void processSessionStats(Http3WTSession *sessionobj, absl::Duration* timestamp, webtransport::SessionStats * sessstats); + void processDatagramStats(Http3WTSession *sessionobj, absl::Duration* timestamp, webtransport::DatagramStats * datastats); + void processGoawayReceived(Http3WTSession *sessionobj); bool shutDownEventLoopInt(); diff --git a/src/http3wtsessionvisitor.h b/src/http3wtsessionvisitor.h index cb9c6f04..45aedb3d 100644 --- a/src/http3wtsessionvisitor.h +++ b/src/http3wtsessionvisitor.h @@ -230,6 +230,28 @@ namespace quic eventloop_->Schedule(task); } + void orderSessionStatsInt() + { + std::function task = [this](){ + if (session_) { + webtransport::SessionStats * stats = new webtransport::SessionStats(); + *stats = session_->GetSessionStats(); + eventloop_->informSessionStats(this, stats); + }}; + eventloop_->Schedule(task); + } + + void orderDatagramStatsInt() + { + std::function task = [this]() + { if (session_) { + webtransport::DatagramStats * stats = new webtransport::DatagramStats(); + *stats = session_->GetDatagramStats(); + eventloop_->informDatagramStats(this, stats); + } }; + eventloop_->Schedule(task); + } + void closeInt(int code, std::string &reason) { std::function task = [this, code, reason]() @@ -300,6 +322,16 @@ namespace quic wtsession_->notifySessionDrainingInt(); } + void orderSessionStats(const Napi::CallbackInfo &info) + { + wtsession_->orderSessionStatsInt(); + } + + void orderDatagramStats(const Napi::CallbackInfo &info) + { + wtsession_->orderDatagramStatsInt(); + } + void close(const Napi::CallbackInfo &info) { int code = 0; @@ -338,6 +370,10 @@ namespace quic static_cast(napi_writable | napi_configurable)), InstanceMethod<&Http3WTSessionJS::notifySessionDraining>("notifySessionDraining", static_cast(napi_writable | napi_configurable)), + InstanceMethod<&Http3WTSessionJS::orderSessionStats>("orderSessionStats", + static_cast(napi_writable | napi_configurable)), + InstanceMethod<&Http3WTSessionJS::orderDatagramStats>("orderDatagramStats", + static_cast(napi_writable | napi_configurable)), InstanceMethod<&Http3WTSessionJS::close>("close", static_cast(napi_writable | napi_configurable))}); constr->session = Napi::Persistent(tplwt); diff --git a/test/test.js b/test/test.js index f8dc9859..23ec05de 100644 --- a/test/test.js +++ b/test/test.js @@ -98,6 +98,8 @@ async function run() { await client.ready console.log('client is ready') await echoTestsConnection(client) + console.log('Test if getStats works') + console.log('getStats returned', await client.getStats()) console.log('client test finished, now close the client but wait 2 seconds') await new Promise((resolve) => setTimeout(resolve, 2000))