Skip to content

Commit 1ad7df6

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 PR-URL: #18020 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Tiancheng "Timothy" Gu <[email protected]>
1 parent ef31701 commit 1ad7df6

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
@@ -3003,23 +3003,35 @@ The `name` property of the `PerformanceEntry` will be equal to either
30033003
If `name` is equal to `Http2Stream`, the `PerformanceEntry` will contain the
30043004
following additional properties:
30053005

3006+
* `bytesRead` {number} The number of DATA frame bytes received for this
3007+
`Http2Stream`.
3008+
* `bytesWritten` {number} The number of DATA frame bytes sent for this
3009+
`Http2Stream`.
3010+
* `id` {number} The identifier of the associated `Http2Stream`
30063011
* `timeToFirstByte` {number} The number of milliseconds elapsed between the
30073012
`PerformanceEntry` `startTime` and the reception of the first `DATA` frame.
3013+
* `timeToFirstByteSent` {number} The number of milliseconds elapsed between
3014+
the `PerformanceEntry` `startTime` and sending of the first `DATA` frame.
30083015
* `timeToFirstHeader` {number} The number of milliseconds elapsed between the
30093016
`PerformanceEntry` `startTime` and the reception of the first header.
30103017

30113018
If `name` is equal to `Http2Session`, the `PerformanceEntry` will contain the
30123019
following additional properties:
30133020

3021+
* `bytesRead` {number} The number of bytes received for this `Http2Session`.
3022+
* `bytesWritten` {number} The number of bytes sent for this `Http2Session`.
3023+
* `framesReceived` {number} The number of HTTP/2 frames received by the
3024+
`Http2Session`.
3025+
* `framesSent` {number} The number of HTTP/2 frames sent by the `Http2Session`.
3026+
* `maxConcurrentStreams` {number} The maximum number of streams concurrently
3027+
open during the lifetime of the `Http2Session`.
30143028
* `pingRTT` {number} The number of milliseconds elapsed since the transmission
30153029
of a `PING` frame and the reception of its acknowledgment. Only present if
30163030
a `PING` frame has been sent on the `Http2Session`.
3017-
* `streamCount` {number} The number of `Http2Stream` instances processed by
3018-
the `Http2Session`.
30193031
* `streamAverageDuration` {number} The average duration (in milliseconds) for
30203032
all `Http2Stream` instances.
3021-
* `framesReceived` {number} The number of HTTP/2 frames received by the
3022-
`Http2Session`.
3033+
* `streamCount` {number} The number of `Http2Stream` instances processed by
3034+
the `Http2Session`.
30233035
* `type` {string} Either `'server'` or `'client'` to identify the type of
30243036
`Http2Session`.
30253037

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

@@ -962,6 +954,14 @@ inline int Http2Session::OnFrameNotSent(nghttp2_session* handle,
962954
return 0;
963955
}
964956

957+
inline int Http2Session::OnFrameSent(nghttp2_session* handle,
958+
const nghttp2_frame* frame,
959+
void* user_data) {
960+
Http2Session* session = static_cast<Http2Session*>(user_data);
961+
session->statistics_.frame_sent += 1;
962+
return 0;
963+
}
964+
965965
// Called by nghttp2 when a stream closes.
966966
inline int Http2Session::OnStreamClose(nghttp2_session* handle,
967967
int32_t id,
@@ -1039,6 +1039,7 @@ inline int Http2Session::OnDataChunkReceived(nghttp2_session* handle,
10391039
// If the stream has been destroyed, ignore this chunk
10401040
if (stream->IsDestroyed())
10411041
return 0;
1042+
stream->statistics_.received_bytes += len;
10421043
stream->AddChunk(data, len);
10431044
}
10441045
return 0;
@@ -1493,6 +1494,7 @@ void Http2Session::SendPendingData() {
14931494
size_t offset = 0;
14941495
size_t i = 0;
14951496
for (const nghttp2_stream_write& write : outgoing_buffers_) {
1497+
statistics_.data_sent += write.buf.len;
14961498
if (write.buf.base == nullptr) {
14971499
bufs[i++] = uv_buf_init(
14981500
reinterpret_cast<char*>(outgoing_storage_.data() + offset),
@@ -1642,6 +1644,7 @@ void Http2Session::OnStreamReadImpl(ssize_t nread,
16421644
if (bufs->len > 0) {
16431645
// Only pass data on if nread > 0
16441646
uv_buf_t buf[] { uv_buf_init((*bufs).base, nread) };
1647+
session->statistics_.data_received += nread;
16451648
ssize_t ret = session->Write(buf, 1);
16461649

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

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

2199+
stream->statistics_.sent_bytes += numchars;
21942200
return numchars;
21952201
}
21962202

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

22212229
size_t amount = 0; // amount of data being sent in this data frame.
@@ -2249,6 +2257,8 @@ ssize_t Http2Stream::Provider::Stream::OnRead(nghttp2_session* handle,
22492257
if (session->IsDestroyed())
22502258
return NGHTTP2_ERR_CALLBACK_FAILURE;
22512259
}
2260+
2261+
stream->statistics_.sent_bytes += amount;
22522262
return amount;
22532263
}
22542264

@@ -2862,6 +2872,10 @@ void Initialize(Local<Object> target,
28622872
"settingsBuffer", state->settings_buffer.GetJSArray());
28632873
SET_STATE_TYPEDARRAY(
28642874
"optionsBuffer", state->options_buffer.GetJSArray());
2875+
SET_STATE_TYPEDARRAY(
2876+
"streamStats", state->stream_stats_buffer.GetJSArray());
2877+
SET_STATE_TYPEDARRAY(
2878+
"sessionStats", state->session_stats_buffer.GetJSArray());
28652879
#undef SET_STATE_TYPEDARRAY
28662880

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

0 commit comments

Comments
 (0)