Skip to content

Commit 3cd2054

Browse files
jasnellMylesBorins
authored andcommitted
http2: use aliased buffer for perf stats, add stats
Add an aliased buffer for session and stream statistics, add a few more metrics Backport-PR-URL: #20456 PR-URL: #18020 Reviewed-By: Anna Henningsen <anna@addaleax.net> Reviewed-By: Tiancheng "Timothy" Gu <timothygu99@gmail.com>
1 parent 9404573 commit 3cd2054

File tree

6 files changed

+240
-66
lines changed

6 files changed

+240
-66
lines changed

doc/api/http2.md

+16-4
Original file line numberDiff line numberDiff line change
@@ -3022,23 +3022,35 @@ The `name` property of the `PerformanceEntry` will be equal to either
30223022
If `name` is equal to `Http2Stream`, the `PerformanceEntry` will contain the
30233023
following additional properties:
30243024

3025+
* `bytesRead` {number} The number of DATA frame bytes received for this
3026+
`Http2Stream`.
3027+
* `bytesWritten` {number} The number of DATA frame bytes sent for this
3028+
`Http2Stream`.
3029+
* `id` {number} The identifier of the associated `Http2Stream`
30253030
* `timeToFirstByte` {number} The number of milliseconds elapsed between the
30263031
`PerformanceEntry` `startTime` and the reception of the first `DATA` frame.
3032+
* `timeToFirstByteSent` {number} The number of milliseconds elapsed between
3033+
the `PerformanceEntry` `startTime` and sending of the first `DATA` frame.
30273034
* `timeToFirstHeader` {number} The number of milliseconds elapsed between the
30283035
`PerformanceEntry` `startTime` and the reception of the first header.
30293036

30303037
If `name` is equal to `Http2Session`, the `PerformanceEntry` will contain the
30313038
following additional properties:
30323039

3040+
* `bytesRead` {number} The number of bytes received for this `Http2Session`.
3041+
* `bytesWritten` {number} The number of bytes sent for this `Http2Session`.
3042+
* `framesReceived` {number} The number of HTTP/2 frames received by the
3043+
`Http2Session`.
3044+
* `framesSent` {number} The number of HTTP/2 frames sent by the `Http2Session`.
3045+
* `maxConcurrentStreams` {number} The maximum number of streams concurrently
3046+
open during the lifetime of the `Http2Session`.
30333047
* `pingRTT` {number} The number of milliseconds elapsed since the transmission
30343048
of a `PING` frame and the reception of its acknowledgment. Only present if
30353049
a `PING` frame has been sent on the `Http2Session`.
3036-
* `streamCount` {number} The number of `Http2Stream` instances processed by
3037-
the `Http2Session`.
30383050
* `streamAverageDuration` {number} The average duration (in milliseconds) for
30393051
all `Http2Stream` instances.
3040-
* `framesReceived` {number} The number of HTTP/2 frames received by the
3041-
`Http2Session`.
3052+
* `streamCount` {number} The number of `Http2Stream` instances processed by
3053+
the `Http2Session`.
30423054
* `type` {string} Either `'server'` or `'client'` to identify the type of
30433055
`Http2Session`.
30443056

lib/perf_hooks.js

+68
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,70 @@ const observerableTypes = [
6666
'http2'
6767
];
6868

69+
const IDX_STREAM_STATS_ID = 0;
70+
const IDX_STREAM_STATS_TIMETOFIRSTBYTE = 1;
71+
const IDX_STREAM_STATS_TIMETOFIRSTHEADER = 2;
72+
const IDX_STREAM_STATS_TIMETOFIRSTBYTESENT = 3;
73+
const IDX_STREAM_STATS_SENTBYTES = 4;
74+
const IDX_STREAM_STATS_RECEIVEDBYTES = 5;
75+
76+
const IDX_SESSION_STATS_TYPE = 0;
77+
const IDX_SESSION_STATS_PINGRTT = 1;
78+
const IDX_SESSION_STATS_FRAMESRECEIVED = 2;
79+
const IDX_SESSION_STATS_FRAMESSENT = 3;
80+
const IDX_SESSION_STATS_STREAMCOUNT = 4;
81+
const IDX_SESSION_STATS_STREAMAVERAGEDURATION = 5;
82+
const IDX_SESSION_STATS_DATA_SENT = 6;
83+
const IDX_SESSION_STATS_DATA_RECEIVED = 7;
84+
const IDX_SESSION_STATS_MAX_CONCURRENT_STREAMS = 8;
85+
86+
let sessionStats;
87+
let streamStats;
88+
89+
function collectHttp2Stats(entry) {
90+
switch (entry.name) {
91+
case 'Http2Stream':
92+
if (streamStats === undefined)
93+
streamStats = process.binding('http2').streamStats;
94+
entry.id =
95+
streamStats[IDX_STREAM_STATS_ID] >>> 0;
96+
entry.timeToFirstByte =
97+
streamStats[IDX_STREAM_STATS_TIMETOFIRSTBYTE];
98+
entry.timeToFirstHeader =
99+
streamStats[IDX_STREAM_STATS_TIMETOFIRSTHEADER];
100+
entry.timeToFirstByteSent =
101+
streamStats[IDX_STREAM_STATS_TIMETOFIRSTBYTESENT];
102+
entry.bytesWritten =
103+
streamStats[IDX_STREAM_STATS_SENTBYTES];
104+
entry.bytesRead =
105+
streamStats[IDX_STREAM_STATS_RECEIVEDBYTES];
106+
break;
107+
case 'Http2Session':
108+
if (sessionStats === undefined)
109+
sessionStats = process.binding('http2').sessionStats;
110+
entry.type =
111+
sessionStats[IDX_SESSION_STATS_TYPE] >>> 0 === 0 ? 'server' : 'client';
112+
entry.pingRTT =
113+
sessionStats[IDX_SESSION_STATS_PINGRTT];
114+
entry.framesReceived =
115+
sessionStats[IDX_SESSION_STATS_FRAMESRECEIVED];
116+
entry.framesSent =
117+
sessionStats[IDX_SESSION_STATS_FRAMESSENT];
118+
entry.streamCount =
119+
sessionStats[IDX_SESSION_STATS_STREAMCOUNT];
120+
entry.streamAverageDuration =
121+
sessionStats[IDX_SESSION_STATS_STREAMAVERAGEDURATION];
122+
entry.bytesWritten =
123+
sessionStats[IDX_SESSION_STATS_DATA_SENT];
124+
entry.bytesRead =
125+
sessionStats[IDX_SESSION_STATS_DATA_RECEIVED];
126+
entry.maxConcurrentStreams =
127+
sessionStats[IDX_SESSION_STATS_MAX_CONCURRENT_STREAMS];
128+
break;
129+
}
130+
}
131+
132+
69133
let errors;
70134
function lazyErrors() {
71135
if (errors === undefined)
@@ -467,6 +531,10 @@ function doNotify() {
467531
// Set up the callback used to receive PerformanceObserver notifications
468532
function observersCallback(entry) {
469533
const type = mapTypes(entry.entryType);
534+
535+
if (type === NODE_PERFORMANCE_ENTRY_TYPE_HTTP2)
536+
collectHttp2Stats(entry);
537+
470538
performance[kInsertEntry](entry);
471539
const list = getObserversList(type);
472540

src/node_http2.cc

+67-53
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,8 @@ Http2Session::Callbacks::Callbacks(bool kHasGetPaddingCallback) {
471471
callbacks, OnSendData);
472472
nghttp2_session_callbacks_set_on_invalid_frame_recv_callback(
473473
callbacks, OnInvalidFrame);
474+
nghttp2_session_callbacks_set_on_frame_send_callback(
475+
callbacks, OnFrameSent);
474476

475477
if (kHasGetPaddingCallback) {
476478
nghttp2_session_callbacks_set_select_padding_callback(
@@ -560,28 +562,35 @@ inline void Http2Stream::EmitStatistics() {
560562
if (!HasHttp2Observer(env()))
561563
return;
562564
Http2StreamPerformanceEntry* entry =
563-
new Http2StreamPerformanceEntry(env(), statistics_);
565+
new Http2StreamPerformanceEntry(env(), id_, statistics_);
564566
env()->SetImmediate([](Environment* env, void* data) {
565-
Local<Context> context = env->context();
566567
Http2StreamPerformanceEntry* entry =
567568
static_cast<Http2StreamPerformanceEntry*>(data);
568569
if (HasHttp2Observer(env)) {
569-
Local<Object> obj = entry->ToObject();
570-
v8::PropertyAttribute attr =
571-
static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete);
572-
obj->DefineOwnProperty(
573-
context,
574-
FIXED_ONE_BYTE_STRING(env->isolate(), "timeToFirstByte"),
575-
Number::New(env->isolate(),
576-
(entry->first_byte() - entry->startTimeNano()) / 1e6),
577-
attr).FromJust();
578-
obj->DefineOwnProperty(
579-
context,
580-
FIXED_ONE_BYTE_STRING(env->isolate(), "timeToFirstHeader"),
581-
Number::New(env->isolate(),
582-
(entry->first_header() - entry->startTimeNano()) / 1e6),
583-
attr).FromJust();
584-
entry->Notify(obj);
570+
AliasedBuffer<double, v8::Float64Array>& buffer =
571+
env->http2_state()->stream_stats_buffer;
572+
buffer[IDX_STREAM_STATS_ID] = entry->id();
573+
if (entry->first_byte() != 0) {
574+
buffer[IDX_STREAM_STATS_TIMETOFIRSTBYTE] =
575+
(entry->first_byte() - entry->startTimeNano()) / 1e6;
576+
} else {
577+
buffer[IDX_STREAM_STATS_TIMETOFIRSTBYTE] = 0;
578+
}
579+
if (entry->first_header() != 0) {
580+
buffer[IDX_STREAM_STATS_TIMETOFIRSTHEADER] =
581+
(entry->first_header() - entry->startTimeNano()) / 1e6;
582+
} else {
583+
buffer[IDX_STREAM_STATS_TIMETOFIRSTHEADER] = 0;
584+
}
585+
if (entry->first_byte_sent() != 0) {
586+
buffer[IDX_STREAM_STATS_TIMETOFIRSTBYTESENT] =
587+
(entry->first_byte_sent() - entry->startTimeNano()) / 1e6;
588+
} else {
589+
buffer[IDX_STREAM_STATS_TIMETOFIRSTBYTESENT] = 0;
590+
}
591+
buffer[IDX_STREAM_STATS_SENTBYTES] = entry->sent_bytes();
592+
buffer[IDX_STREAM_STATS_RECEIVEDBYTES] = entry->received_bytes();
593+
entry->Notify(entry->ToObject());
585594
}
586595
delete entry;
587596
}, static_cast<void*>(entry));
@@ -591,45 +600,25 @@ inline void Http2Session::EmitStatistics() {
591600
if (!HasHttp2Observer(env()))
592601
return;
593602
Http2SessionPerformanceEntry* entry =
594-
new Http2SessionPerformanceEntry(env(), statistics_, TypeName());
603+
new Http2SessionPerformanceEntry(env(), statistics_, session_type_);
595604
env()->SetImmediate([](Environment* env, void* data) {
596-
Local<Context> context = env->context();
597605
Http2SessionPerformanceEntry* entry =
598606
static_cast<Http2SessionPerformanceEntry*>(data);
599607
if (HasHttp2Observer(env)) {
600-
Local<Object> obj = entry->ToObject();
601-
v8::PropertyAttribute attr =
602-
static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete);
603-
obj->DefineOwnProperty(
604-
context,
605-
FIXED_ONE_BYTE_STRING(env->isolate(), "type"),
606-
String::NewFromUtf8(env->isolate(),
607-
entry->typeName(),
608-
v8::NewStringType::kInternalized)
609-
.ToLocalChecked(), attr).FromJust();
610-
if (entry->ping_rtt() != 0) {
611-
obj->DefineOwnProperty(
612-
context,
613-
FIXED_ONE_BYTE_STRING(env->isolate(), "pingRTT"),
614-
Number::New(env->isolate(), entry->ping_rtt() / 1e6),
615-
attr).FromJust();
616-
}
617-
obj->DefineOwnProperty(
618-
context,
619-
FIXED_ONE_BYTE_STRING(env->isolate(), "framesReceived"),
620-
Integer::NewFromUnsigned(env->isolate(), entry->frame_count()),
621-
attr).FromJust();
622-
obj->DefineOwnProperty(
623-
context,
624-
FIXED_ONE_BYTE_STRING(env->isolate(), "streamCount"),
625-
Integer::New(env->isolate(), entry->stream_count()),
626-
attr).FromJust();
627-
obj->DefineOwnProperty(
628-
context,
629-
FIXED_ONE_BYTE_STRING(env->isolate(), "streamAverageDuration"),
630-
Number::New(env->isolate(), entry->stream_average_duration()),
631-
attr).FromJust();
632-
entry->Notify(obj);
608+
AliasedBuffer<double, v8::Float64Array>& buffer =
609+
env->http2_state()->session_stats_buffer;
610+
buffer[IDX_SESSION_STATS_TYPE] = entry->type();
611+
buffer[IDX_SESSION_STATS_PINGRTT] = entry->ping_rtt() / 1e6;
612+
buffer[IDX_SESSION_STATS_FRAMESRECEIVED] = entry->frame_count();
613+
buffer[IDX_SESSION_STATS_FRAMESSENT] = entry->frame_sent();
614+
buffer[IDX_SESSION_STATS_STREAMCOUNT] = entry->stream_count();
615+
buffer[IDX_SESSION_STATS_STREAMAVERAGEDURATION] =
616+
entry->stream_average_duration();
617+
buffer[IDX_SESSION_STATS_DATA_SENT] = entry->data_sent();
618+
buffer[IDX_SESSION_STATS_DATA_RECEIVED] = entry->data_received();
619+
buffer[IDX_SESSION_STATS_MAX_CONCURRENT_STREAMS] =
620+
entry->max_concurrent_streams();
621+
entry->Notify(entry->ToObject());
633622
}
634623
delete entry;
635624
}, static_cast<void*>(entry));
@@ -695,6 +684,9 @@ inline bool Http2Session::CanAddStream() {
695684
inline void Http2Session::AddStream(Http2Stream* stream) {
696685
CHECK_GE(++statistics_.stream_count, 0);
697686
streams_[stream->id()] = stream;
687+
size_t size = streams_.size();
688+
if (size > statistics_.max_concurrent_streams)
689+
statistics_.max_concurrent_streams = size;
698690
IncrementCurrentSessionMemory(stream->self_size());
699691
}
700692

@@ -963,6 +955,14 @@ inline int Http2Session::OnFrameNotSent(nghttp2_session* handle,
963955
return 0;
964956
}
965957

958+
inline int Http2Session::OnFrameSent(nghttp2_session* handle,
959+
const nghttp2_frame* frame,
960+
void* user_data) {
961+
Http2Session* session = static_cast<Http2Session*>(user_data);
962+
session->statistics_.frame_sent += 1;
963+
return 0;
964+
}
965+
966966
// Called by nghttp2 when a stream closes.
967967
inline int Http2Session::OnStreamClose(nghttp2_session* handle,
968968
int32_t id,
@@ -1040,6 +1040,7 @@ inline int Http2Session::OnDataChunkReceived(nghttp2_session* handle,
10401040
// If the stream has been destroyed, ignore this chunk
10411041
if (stream->IsDestroyed())
10421042
return 0;
1043+
stream->statistics_.received_bytes += len;
10431044
stream->AddChunk(data, len);
10441045
}
10451046
return 0;
@@ -1494,6 +1495,7 @@ void Http2Session::SendPendingData() {
14941495
size_t offset = 0;
14951496
size_t i = 0;
14961497
for (const nghttp2_stream_write& write : outgoing_buffers_) {
1498+
statistics_.data_sent += write.buf.len;
14971499
if (write.buf.base == nullptr) {
14981500
bufs[i++] = uv_buf_init(
14991501
reinterpret_cast<char*>(outgoing_storage_.data() + offset),
@@ -1643,6 +1645,7 @@ void Http2Session::OnStreamReadImpl(ssize_t nread,
16431645
if (bufs->len > 0) {
16441646
// Only pass data on if nread > 0
16451647
uv_buf_t buf[] { uv_buf_init((*bufs).base, nread) };
1648+
session->statistics_.data_received += nread;
16461649
ssize_t ret = session->Write(buf, 1);
16471650

16481651
// Note: if ssize_t is not defined (e.g. on Win32), nghttp2 will typedef
@@ -2142,6 +2145,8 @@ ssize_t Http2Stream::Provider::FD::OnRead(nghttp2_session* handle,
21422145
void* user_data) {
21432146
Http2Session* session = static_cast<Http2Session*>(user_data);
21442147
Http2Stream* stream = session->FindStream(id);
2148+
if (stream->statistics_.first_byte_sent == 0)
2149+
stream->statistics_.first_byte_sent = uv_hrtime();
21452150

21462151
DEBUG_HTTP2SESSION2(session, "reading outbound file data for stream %d", id);
21472152
CHECK_EQ(id, stream->id());
@@ -2192,6 +2197,7 @@ ssize_t Http2Stream::Provider::FD::OnRead(nghttp2_session* handle,
21922197
return NGHTTP2_ERR_CALLBACK_FAILURE;
21932198
}
21942199

2200+
stream->statistics_.sent_bytes += numchars;
21952201
return numchars;
21962202
}
21972203

@@ -2217,6 +2223,8 @@ ssize_t Http2Stream::Provider::Stream::OnRead(nghttp2_session* handle,
22172223
Http2Session* session = static_cast<Http2Session*>(user_data);
22182224
DEBUG_HTTP2SESSION2(session, "reading outbound data for stream %d", id);
22192225
Http2Stream* stream = GetStream(session, id, source);
2226+
if (stream->statistics_.first_byte_sent == 0)
2227+
stream->statistics_.first_byte_sent = uv_hrtime();
22202228
CHECK_EQ(id, stream->id());
22212229

22222230
size_t amount = 0; // amount of data being sent in this data frame.
@@ -2250,6 +2258,8 @@ ssize_t Http2Stream::Provider::Stream::OnRead(nghttp2_session* handle,
22502258
if (session->IsDestroyed())
22512259
return NGHTTP2_ERR_CALLBACK_FAILURE;
22522260
}
2261+
2262+
stream->statistics_.sent_bytes += amount;
22532263
return amount;
22542264
}
22552265

@@ -2863,6 +2873,10 @@ void Initialize(Local<Object> target,
28632873
"settingsBuffer", state->settings_buffer.GetJSArray());
28642874
SET_STATE_TYPEDARRAY(
28652875
"optionsBuffer", state->options_buffer.GetJSArray());
2876+
SET_STATE_TYPEDARRAY(
2877+
"streamStats", state->stream_stats_buffer.GetJSArray());
2878+
SET_STATE_TYPEDARRAY(
2879+
"sessionStats", state->session_stats_buffer.GetJSArray());
28662880
#undef SET_STATE_TYPEDARRAY
28672881

28682882
env->set_http2_state(std::move(state));

0 commit comments

Comments
 (0)