From 455f96554a4366fc174007e249418758d3458314 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Tue, 1 Oct 2019 20:17:36 +0200 Subject: [PATCH] quic: support memory tracking Fixes: https://github.com/nodejs/quic/issues/59 PR-URL: https://github.com/nodejs/quic/pull/145 Reviewed-By: James M Snell --- src/node_quic_session.cc | 28 ++++++ src/node_quic_session.h | 8 +- src/node_quic_socket.cc | 8 +- src/node_quic_stream.cc | 8 ++ src/node_quic_stream.h | 4 +- src/node_quic_util.h | 8 +- test/pummel/test-heapdump-quic.js | 144 ++++++++++++++++++++++++++++++ 7 files changed, 200 insertions(+), 8 deletions(-) create mode 100644 test/pummel/test-heapdump-quic.js diff --git a/src/node_quic_session.cc b/src/node_quic_session.cc index cb23f47d88..41472d974f 100644 --- a/src/node_quic_session.cc +++ b/src/node_quic_session.cc @@ -1883,6 +1883,24 @@ bool QuicSession::UpdateKey() { &crypto_ctx_); } +void QuicSession::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackField("alpn", alpn_); + tracker->TrackField("idle", idle_); + tracker->TrackField("retransmit", retransmit_); + tracker->TrackField("rx_secret", rx_secret_); + tracker->TrackField("tx_secret", tx_secret_); + tracker->TrackField("sendbuf", sendbuf_); + tracker->TrackField("handshake", handshake_); + tracker->TrackField("txbuf", txbuf_); + tracker->TrackField("peer_handshake", peer_handshake_); + tracker->TrackField("streams", streams_); + tracker->TrackField("state", state_); + tracker->TrackField("crypto_rx_ack", crypto_rx_ack_); + tracker->TrackField("crypto_handshake_rate", crypto_handshake_rate_); + tracker->TrackField("stats_buffer", stats_buffer_); + tracker->TrackField("recovery_stats_buffer", recovery_stats_buffer_); + tracker->TrackFieldWithSize("current_ngtcp2_memory", current_ngtcp2_memory_); +} // QuicServerSession QuicServerSession::InitialPacketResult QuicServerSession::Accept( @@ -2376,6 +2394,11 @@ int QuicServerSession::VerifyPeerIdentity(const char* hostname) { return VerifyPeerCertificate(ssl()); } +void QuicServerSession::MemoryInfo(MemoryTracker* tracker) const { + QuicSession::MemoryInfo(tracker); + tracker->TrackField("conn_closebuf", conn_closebuf_); + tracker->TrackField("ocsp_response", ocsp_response_); +} // QuicClientSession @@ -2888,6 +2911,11 @@ int QuicClientSession::VerifyPeerIdentity(const char* hostname) { return 0; } +void QuicClientSession::MemoryInfo(MemoryTracker* tracker) const { + QuicSession::MemoryInfo(tracker); + tracker->TrackField("hostname", hostname_); +} + // Static ngtcp2 callbacks are registered when ngtcp2 when a new ngtcp2_conn is // created. These are static functions that, for the most part, simply defer to // a QuicSession instance that is passed through as user_data. diff --git a/src/node_quic_session.h b/src/node_quic_session.h index ec317bf5e0..b79cfce0c0 100644 --- a/src/node_quic_session.h +++ b/src/node_quic_session.h @@ -364,6 +364,8 @@ class QuicSession : public AsyncWrap, QuicSession* session_; }; + void MemoryInfo(MemoryTracker* tracker) const override; + private: // Returns true if the QuicSession has entered the // closing period following a call to ImmediateClose. @@ -1063,7 +1065,8 @@ class QuicServerSession : public QuicSession { const ngtcp2_cid* rcid() const { return &rcid_; } ngtcp2_cid* pscid() { return &pscid_; } - void MemoryInfo(MemoryTracker* tracker) const override {} + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(QuicServerSession) SET_SELF_SIZE(QuicServerSession) @@ -1168,7 +1171,8 @@ class QuicClientSession : public QuicSession { bool SendConnectionClose() override; - void MemoryInfo(MemoryTracker* tracker) const override {} + void MemoryInfo(MemoryTracker* tracker) const override; + SET_MEMORY_INFO_NAME(QuicClientSession) SET_SELF_SIZE(QuicClientSession) diff --git a/src/node_quic_socket.cc b/src/node_quic_socket.cc index f7d808df26..b6e7af6904 100644 --- a/src/node_quic_socket.cc +++ b/src/node_quic_socket.cc @@ -120,7 +120,13 @@ QuicSocket::~QuicSocket() { } void QuicSocket::MemoryInfo(MemoryTracker* tracker) const { - // TODO(@jasnell): Implement memory tracking information + tracker->TrackField("sessions", sessions_); + tracker->TrackField("dcid_to_scid", dcid_to_scid_); + tracker->TrackFieldWithSize("addr_counts", + addr_counts_.size() * (sizeof(sockaddr*) + sizeof(size_t))); + tracker->TrackField("validated_addrs", validated_addrs_); + tracker->TrackField("stats_buffer", stats_buffer_); + tracker->TrackFieldWithSize("current_ngtcp2_memory", current_ngtcp2_memory_); } void QuicSocket::AddSession( diff --git a/src/node_quic_stream.cc b/src/node_quic_stream.cc index dd6c2574a4..fa6df71669 100644 --- a/src/node_quic_stream.cc +++ b/src/node_quic_stream.cc @@ -379,6 +379,14 @@ BaseObjectPtr QuicStream::New( return stream; } +void QuicStream::MemoryInfo(MemoryTracker* tracker) const { + tracker->TrackField("buffer", &streambuf_); + tracker->TrackField("data_rx_rate", data_rx_rate_); + tracker->TrackField("data_rx_size", data_rx_size_); + tracker->TrackField("data_rx_ack", data_rx_ack_); + tracker->TrackField("stats_buffer", stats_buffer_); +} + // JavaScript API namespace { void QuicStreamGetID(const FunctionCallbackInfo& args) { diff --git a/src/node_quic_stream.h b/src/node_quic_stream.h index 3fbe524638..c67cee3890 100644 --- a/src/node_quic_stream.h +++ b/src/node_quic_stream.h @@ -296,9 +296,7 @@ class QuicStream : public AsyncWrap, public StreamBase { AsyncWrap* GetAsyncWrap() override { return this; } - void MemoryInfo(MemoryTracker* tracker) const override { - tracker->TrackField("buffer", &streambuf_); - } + void MemoryInfo(MemoryTracker* tracker) const override; SET_MEMORY_INFO_NAME(QuicStream) SET_SELF_SIZE(QuicStream) diff --git a/src/node_quic_util.h b/src/node_quic_util.h index cb3ae0866e..b8acc86bc2 100644 --- a/src/node_quic_util.h +++ b/src/node_quic_util.h @@ -384,7 +384,7 @@ void IncrementStat( // Simple timer wrapper that is used to implement the internals // for idle and retransmission timeouts. Call Update to start or // reset the timer; Stop to halt the timer. -class Timer { +class Timer final : public MemoryRetainer { public: explicit Timer(Environment* env, std::function fn) : stopped_(false), @@ -395,7 +395,7 @@ class Timer { env->AddCleanupHook(CleanupHook, this); } - ~Timer() { + ~Timer() override { env_->RemoveCleanupHook(CleanupHook, this); } @@ -423,6 +423,10 @@ class Timer { static void Free(Timer* timer); + SET_NO_MEMORY_INFO() + SET_MEMORY_INFO_NAME(Timer) + SET_SELF_SIZE(Timer) + private: static void OnTimeout(uv_timer_t* timer); static void CleanupHook(void* data); diff --git a/test/pummel/test-heapdump-quic.js b/test/pummel/test-heapdump-quic.js new file mode 100644 index 0000000000..a3305091d7 --- /dev/null +++ b/test/pummel/test-heapdump-quic.js @@ -0,0 +1,144 @@ +// Flags: --expose-internals +'use strict'; +const common = require('../common'); +if (!common.hasQuic) + common.skip('missing quic'); + +const quic = require('quic'); + +const { recordState } = require('../common/heap'); +const fixtures = require('../common/fixtures'); +const key = fixtures.readKey('agent1-key.pem', 'binary'); +const cert = fixtures.readKey('agent1-cert.pem', 'binary'); +const ca = fixtures.readKey('ca1-cert.pem', 'binary'); + +{ + const state = recordState(); + state.validateSnapshotNodes('Node / QuicStream', []); + state.validateSnapshotNodes('Node / QuicSession', []); + state.validateSnapshotNodes('Node / QuicSocket', []); +} + +const server = quic.createSocket({ port: 0, validateAddress: true }); + +server.listen({ + key, + cert, + ca, + rejectUnauthorized: false, + maxCryptoBuffer: 4096, + alpn: 'meow' +}); + +server.on('session', common.mustCall((session) => { + session.on('secure', common.mustCall((servername, alpn, cipher) => { + // eslint-disable-next-line no-unused-vars + const stream = session.openStream({ halfOpen: false }); + + const state = recordState(); + + state.validateSnapshotNodes('Node / QuicSocket', [ + { + children: [ + { node_name: 'QuicSocket', edge_name: 'wrapped' }, + { node_name: 'BigUint64Array', edge_name: 'stats_buffer' }, + { node_name: 'Node / sessions', edge_name: 'sessions' }, + { node_name: 'Node / dcid_to_scid', edge_name: 'dcid_to_scid' }, + ] + } + ], { loose: true }); + + state.validateSnapshotNodes('Node / QuicStream', [ + { + children: [ + { node_name: 'QuicStream', edge_name: 'wrapped' }, + { node_name: 'BigUint64Array', edge_name: 'stats_buffer' }, + { node_name: 'Node / QuicBuffer', edge_name: 'buffer' }, + { node_name: 'Node / HistogramBase', edge_name: 'data_rx_rate' }, + { node_name: 'Node / HistogramBase', edge_name: 'data_rx_size' }, + { node_name: 'Node / HistogramBase', edge_name: 'data_rx_ack' } + ] + } + ], { loose: true }); + + state.validateSnapshotNodes('Node / QuicBuffer', [ + { + children: [ + { node_name: 'Node / length', edge_name: 'length' } + ] + } + ], { loose: true }); + + state.validateSnapshotNodes('Node / QuicServerSession', [ + { + children: [ + { node_name: 'QuicServerSession', edge_name: 'wrapped' }, + { node_name: 'Node / rx_secret', edge_name: 'rx_secret' }, + { node_name: 'Node / tx_secret', edge_name: 'tx_secret' }, + { node_name: 'Node / HistogramBase', edge_name: 'crypto_rx_ack' }, + { node_name: 'Node / HistogramBase', + edge_name: 'crypto_handshake_rate' }, + { node_name: 'Node / Timer', edge_name: 'retransmit' }, + { node_name: 'Node / Timer', edge_name: 'idle' }, + { node_name: 'Node / QuicBuffer', edge_name: 'sendbuf' }, + { node_name: 'Node / QuicBuffer', edge_name: 'txbuf' }, + { node_name: 'Node / peer_handshake', edge_name: 'peer_handshake' }, + { node_name: 'Float64Array', edge_name: 'recovery_stats_buffer' }, + { node_name: 'BigUint64Array', edge_name: 'stats_buffer' }, + { node_name: 'Node / current_ngtcp2_memory', + edge_name: 'current_ngtcp2_memory' }, + { node_name: 'Node / streams', edge_name: 'streams' }, + { node_name: 'Node / QuicBuffer', edge_name: 'handshake' }, + { node_name: 'Node / std::basic_string', edge_name: 'alpn' }, + { node_name: 'Float64Array', edge_name: 'state' }, + ] + } + ], { loose: true }); + + state.validateSnapshotNodes('Node / QuicClientSession', [ + { + children: [ + { node_name: 'QuicClientSession', edge_name: 'wrapped' }, + { node_name: 'Node / rx_secret', edge_name: 'rx_secret' }, + { node_name: 'Node / tx_secret', edge_name: 'tx_secret' }, + { node_name: 'Node / HistogramBase', edge_name: 'crypto_rx_ack' }, + { node_name: 'Node / HistogramBase', + edge_name: 'crypto_handshake_rate' }, + { node_name: 'Node / Timer', edge_name: 'retransmit' }, + { node_name: 'Node / Timer', edge_name: 'idle' }, + { node_name: 'Node / QuicBuffer', edge_name: 'sendbuf' }, + { node_name: 'Node / QuicBuffer', edge_name: 'txbuf' }, + { node_name: 'Node / peer_handshake', edge_name: 'peer_handshake' }, + { node_name: 'Float64Array', edge_name: 'recovery_stats_buffer' }, + { node_name: 'BigUint64Array', edge_name: 'stats_buffer' }, + { node_name: 'Node / current_ngtcp2_memory', + edge_name: 'current_ngtcp2_memory' }, + { node_name: 'Node / QuicBuffer', edge_name: 'handshake' }, + { node_name: 'Node / std::basic_string', edge_name: 'alpn' }, + { node_name: 'Node / std::basic_string', edge_name: 'hostname' }, + { node_name: 'Float64Array', edge_name: 'state' }, + ] + } + ], { loose: true }); + + session.destroy(); + server.close(); + })); +})); + +server.on('ready', common.mustCall(() => { + const client = quic.createSocket({ + port: 0, + client: { + key, + cert, + ca, + alpn: 'meow' + } + }); + + client.connect({ + address: 'localhost', + port: server.address.port + }).on('close', common.mustCall(() => client.close())); +}));