nghttp: Update resource timing terminology according to Resource Timing TR

This commit is contained in:
Tatsuhiro Tsujikawa 2015-02-18 00:38:20 +09:00
parent dbfc02cba0
commit 9845729709
2 changed files with 152 additions and 103 deletions

View File

@ -293,19 +293,19 @@ Headers::value_type *Request::get_req_header(int16_t token) {
return &req_nva[idx]; return &req_nva[idx];
} }
void Request::record_request_time() { void Request::record_request_start_time() {
timing.state = RequestState::ON_REQUEST; timing.state = RequestState::ON_REQUEST;
timing.on_request_time = get_time(); timing.request_start_time = get_time();
} }
void Request::record_response_time() { void Request::record_response_start_time() {
timing.state = RequestState::ON_RESPONSE; timing.state = RequestState::ON_RESPONSE;
timing.on_response_time = get_time(); timing.response_start_time = get_time();
} }
void Request::record_complete_time() { void Request::record_response_end_time() {
timing.state = RequestState::ON_COMPLETE; timing.state = RequestState::ON_COMPLETE;
timing.on_complete_time = get_time(); timing.response_end_time = get_time();
} }
namespace { namespace {
@ -708,7 +708,6 @@ int HttpClient::connected() {
std::cout << " Connected" << std::endl; std::cout << " Connected" << std::endl;
} }
record_connect_time();
state = ClientState::CONNECTED; state = ClientState::CONNECTED;
ev_io_start(loop, &rev); ev_io_start(loop, &rev);
@ -767,7 +766,7 @@ size_t populate_settings(nghttp2_settings_entry *iv) {
int HttpClient::on_upgrade_connect() { int HttpClient::on_upgrade_connect() {
ssize_t rv; ssize_t rv;
record_handshake_time(); record_connect_end_time();
assert(!reqvec.empty()); assert(!reqvec.empty());
std::array<nghttp2_settings_entry, 32> iv; std::array<nghttp2_settings_entry, 32> iv;
size_t niv = populate_settings(iv.data()); size_t niv = populate_settings(iv.data());
@ -812,7 +811,7 @@ int HttpClient::on_upgrade_connect() {
// record request time if this is GET request // record request time if this is GET request
if (!reqvec[0]->data_prd) { if (!reqvec[0]->data_prd) {
reqvec[0]->record_request_time(); reqvec[0]->record_request_start_time();
} }
on_writefn = &HttpClient::noop; on_writefn = &HttpClient::noop;
@ -884,6 +883,10 @@ int HttpClient::do_write() { return writefn(*this); }
int HttpClient::on_connect() { int HttpClient::on_connect() {
int rv; int rv;
if (!need_upgrade()) {
record_connect_end_time();
}
if (ssl) { if (ssl) {
// Check NPN or ALPN result // Check NPN or ALPN result
const unsigned char *next_proto = nullptr; const unsigned char *next_proto = nullptr;
@ -914,10 +917,6 @@ int HttpClient::on_connect() {
} }
} }
if (!need_upgrade()) {
record_handshake_time();
}
rv = nghttp2_session_client_new2(&session, callbacks, this, rv = nghttp2_session_client_new2(&session, callbacks, this,
config.http2_option); config.http2_option);
@ -1234,21 +1233,19 @@ bool HttpClient::add_request(const std::string &uri,
return true; return true;
} }
void HttpClient::record_handshake_time() { void HttpClient::record_start_time() {
timing.on_handshake_time = get_time(); timing.system_start_time = std::chrono::system_clock::now();
timing.start_time = get_time();
} }
void HttpClient::record_started_time() { void HttpClient::record_domain_lookup_end_time() {
timing.started_system_time = std::chrono::system_clock::now(); timing.domain_lookup_end_time = get_time();
timing.on_started_time = get_time();
} }
void HttpClient::record_dns_complete_time() { void HttpClient::record_connect_end_time() {
timing.on_dns_complete_time = get_time(); timing.connect_end_time = get_time();
} }
void HttpClient::record_connect_time() { timing.on_connect_time = get_time(); }
void HttpClient::on_request(Request *req) { void HttpClient::on_request(Request *req) {
if (req->pri == 0 && req->dep) { if (req->pri == 0 && req->dep) {
assert(req->dep->deps.empty()); assert(req->dep->deps.empty());
@ -1306,7 +1303,7 @@ void HttpClient::output_har(FILE *outfile) {
json_object_set_new( json_object_set_new(
page, "startedDateTime", page, "startedDateTime",
json_string(util::format_iso8601(timing.started_system_time).c_str())); json_string(util::format_iso8601(timing.system_start_time).c_str()));
json_object_set_new(page, "id", json_string(PAGE_ID)); json_object_set_new(page, "id", json_string(PAGE_ID));
json_object_set_new(page, "title", json_string("")); json_object_set_new(page, "title", json_string(""));
@ -1317,11 +1314,11 @@ void HttpClient::output_har(FILE *outfile) {
auto dns_delta = auto dns_delta =
std::chrono::duration_cast<std::chrono::microseconds>( std::chrono::duration_cast<std::chrono::microseconds>(
timing.on_dns_complete_time - timing.on_started_time).count() / timing.domain_lookup_end_time - timing.start_time).count() /
1000.0; 1000.0;
auto connect_delta = auto connect_delta =
std::chrono::duration_cast<std::chrono::microseconds>( std::chrono::duration_cast<std::chrono::microseconds>(
timing.on_connect_time - timing.on_dns_complete_time).count() / timing.connect_end_time - timing.domain_lookup_end_time).count() /
1000.0; 1000.0;
for (size_t i = 0; i < reqvec.size(); ++i) { for (size_t i = 0; i < reqvec.size(); ++i) {
@ -1336,26 +1333,26 @@ void HttpClient::output_har(FILE *outfile) {
auto &req_timing = req->timing; auto &req_timing = req->timing;
auto request_time = auto request_time =
(i == 0) ? timing.started_system_time (i == 0) ? timing.system_start_time
: timing.started_system_time + : timing.system_start_time +
std::chrono::duration_cast< std::chrono::duration_cast<
std::chrono::system_clock::duration>( std::chrono::system_clock::duration>(
req_timing.on_request_time - timing.on_started_time); req_timing.request_start_time - timing.start_time);
auto wait_delta = auto wait_delta = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::duration_cast<std::chrono::microseconds>( req_timing.response_start_time -
req_timing.on_response_time - req_timing.on_request_time).count() / req_timing.request_start_time).count() /
1000.0; 1000.0;
auto receive_delta = auto receive_delta = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::duration_cast<std::chrono::microseconds>( req_timing.response_end_time -
req_timing.on_complete_time - req_timing.on_response_time).count() / req_timing.response_start_time).count() /
1000.0; 1000.0;
auto time_sum = auto time_sum =
std::chrono::duration_cast<std::chrono::microseconds>( std::chrono::duration_cast<std::chrono::microseconds>(
(i == 0) ? (req_timing.on_complete_time - timing.on_started_time) (i == 0) ? (req_timing.response_end_time - timing.start_time)
: (req_timing.on_complete_time - : (req_timing.response_end_time -
req_timing.on_request_time)).count() / req_timing.request_start_time)).count() /
1000.0; 1000.0;
json_object_set_new( json_object_set_new(
@ -1508,7 +1505,7 @@ int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags,
user_data); user_data);
} }
if (req->status == 0) { if (req->expect_final_response) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id, nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id,
NGHTTP2_PROTOCOL_ERROR); NGHTTP2_PROTOCOL_ERROR);
return 0; return 0;
@ -1617,6 +1614,24 @@ int on_begin_headers_callback(nghttp2_session *session,
const nghttp2_frame *frame, void *user_data) { const nghttp2_frame *frame, void *user_data) {
auto client = get_client(user_data); auto client = get_client(user_data);
switch (frame->hd.type) { switch (frame->hd.type) {
case NGHTTP2_HEADERS: {
auto req = static_cast<Request *>(
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
if (!req) {
break;
}
switch (frame->headers.cat) {
case NGHTTP2_HCAT_RESPONSE:
case NGHTTP2_HCAT_PUSH_RESPONSE:
req->record_response_start_time();
break;
default:
break;
}
break;
}
case NGHTTP2_PUSH_PROMISE: { case NGHTTP2_PUSH_PROMISE: {
auto stream_id = frame->push_promise.promised_stream_id; auto stream_id = frame->push_promise.promised_stream_id;
http_parser_url u; http_parser_url u;
@ -1632,7 +1647,7 @@ int on_begin_headers_callback(nghttp2_session *session,
nghttp2_session_set_stream_user_data(session, stream_id, req.get()); nghttp2_session_set_stream_user_data(session, stream_id, req.get());
client->on_request(req.get()); client->on_request(req.get());
req->record_request_time(); req->record_request_start_time();
client->reqvec.push_back(std::move(req)); client->reqvec.push_back(std::move(req));
break; break;
@ -1729,37 +1744,63 @@ int on_frame_recv_callback2(nghttp2_session *session,
auto client = get_client(user_data); auto client = get_client(user_data);
switch (frame->hd.type) { switch (frame->hd.type) {
case NGHTTP2_DATA: {
auto req = static_cast<Request *>(
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
if (!req) {
return 0;
;
}
if (req->expect_final_response) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id,
NGHTTP2_PROTOCOL_ERROR);
return 0;
}
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
req->record_response_end_time();
}
break;
}
case NGHTTP2_HEADERS: { case NGHTTP2_HEADERS: {
auto req = static_cast<Request *>( auto req = static_cast<Request *>(
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
// If this is the HTTP Upgrade with OPTIONS method to avoid POST, // If this is the HTTP Upgrade with OPTIONS method to avoid POST,
// req is nullptr. // req is nullptr.
if (!req) { if (!req) {
break; return 0;
;
} }
if (frame->headers.cat == NGHTTP2_HCAT_RESPONSE || switch (frame->headers.cat) {
frame->headers.cat == NGHTTP2_HCAT_PUSH_RESPONSE) { case NGHTTP2_HCAT_RESPONSE:
req->record_response_time(); case NGHTTP2_HCAT_PUSH_RESPONSE:
check_response_header(session, req); check_response_header(session, req);
break; break;
} case NGHTTP2_HCAT_HEADERS:
if (frame->headers.cat == NGHTTP2_HCAT_HEADERS) {
if (req->expect_final_response) { if (req->expect_final_response) {
check_response_header(session, req); check_response_header(session, req);
} else {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
break; break;
} }
if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
return 0;
}
break;
default:
assert(0);
} }
if (req->status == 0 && (frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) { if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) {
nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, if (req->expect_final_response) {
NGHTTP2_PROTOCOL_ERROR); nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE,
break; frame->hd.stream_id, NGHTTP2_PROTOCOL_ERROR);
return 0;
}
req->record_response_end_time();
} }
break; break;
@ -1836,7 +1877,7 @@ int before_frame_send_callback(nghttp2_session *session,
auto req = static_cast<Request *>( auto req = static_cast<Request *>(
nghttp2_session_get_stream_user_data(session, frame->hd.stream_id)); nghttp2_session_get_stream_user_data(session, frame->hd.stream_id));
assert(req); assert(req);
req->record_request_time(); req->record_request_start_time();
return 0; return 0;
} }
@ -1854,7 +1895,6 @@ int on_stream_close_callback(nghttp2_session *session, int32_t stream_id,
} }
update_html_parser(client, req, nullptr, 0, 1); update_html_parser(client, req, nullptr, 0, 1);
req->record_complete_time();
++client->complete; ++client->complete;
if (client->all_requests_processed()) { if (client->all_requests_processed()) {
@ -1885,40 +1925,43 @@ void print_stats(const HttpClient &client) {
[](const Request *lhs, const Request *rhs) { [](const Request *lhs, const Request *rhs) {
const auto &ltiming = lhs->timing; const auto &ltiming = lhs->timing;
const auto &rtiming = rhs->timing; const auto &rtiming = rhs->timing;
return ltiming.on_complete_time < rtiming.on_complete_time || return ltiming.response_end_time < rtiming.response_end_time ||
(ltiming.on_complete_time == rtiming.on_complete_time && (ltiming.response_end_time == rtiming.response_end_time &&
ltiming.on_request_time < rtiming.on_request_time); ltiming.request_start_time < rtiming.request_start_time);
}); });
std::cout << R"( std::cout << R"(
Request timing: Request timing:
complete: relative time from protocol handshake to stream close responseEnd: the time when last byte of response was received
request: relative time from protocol handshake to request relative to connectEnd
transmission. If '*' is shown, this was pushed by server. requestStart: the time just before first byte of request was sent
process: time for request and response relative to connectEnd. If '*' is shown, this was
code: HTTP status code pushed by server.
size: number of bytes received as response body without process: responseEnd - requestStart
inflation. code: HTTP status code
URI: request URI size: number of bytes received as response body without
inflation.
URI: request URI
see http://www.w3.org/TR/resource-timing/#processing-model
sorted by 'complete' sorted by 'complete'
complete request process code size request path)" << std::endl; responseEnd requestStart process code size request path)" << std::endl;
const auto &base = client.timing.on_handshake_time; const auto &base = client.timing.connect_end_time;
for (const auto &req : reqs) { for (const auto &req : reqs) {
auto completed_delta = auto response_end = std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::duration_cast<std::chrono::microseconds>( req->timing.response_end_time - base);
req->timing.on_complete_time - base); auto request_start = std::chrono::duration_cast<std::chrono::microseconds>(
auto request_delta = std::chrono::duration_cast<std::chrono::microseconds>( req->timing.request_start_time - base);
req->timing.on_request_time - base);
auto total = std::chrono::duration_cast<std::chrono::microseconds>( auto total = std::chrono::duration_cast<std::chrono::microseconds>(
req->timing.on_complete_time - req->timing.on_request_time); req->timing.response_end_time - req->timing.request_start_time);
auto pushed = req->stream_id % 2 == 0; auto pushed = req->stream_id % 2 == 0;
std::cout << std::setw(9) << ("+" + util::format_duration(completed_delta)) std::cout << std::setw(11) << ("+" + util::format_duration(response_end))
<< " " << (pushed ? "*" : " ") << std::setw(9) << " " << (pushed ? "*" : " ") << std::setw(11)
<< ("+" + util::format_duration(request_delta)) << " " << ("+" + util::format_duration(request_start)) << " "
<< std::setw(8) << util::format_duration(total) << " " << std::setw(8) << util::format_duration(total) << " "
<< std::setw(4) << req->status << " " << std::setw(4) << std::setw(4) << req->status << " " << std::setw(4)
<< util::utos_with_unit(req->response_len) << " " << util::utos_with_unit(req->response_len) << " "
@ -2045,13 +2088,13 @@ int communicate(
} }
client.update_hostport(); client.update_hostport();
client.record_started_time(); client.record_start_time();
if (client.resolve_host(host, port) != 0) { if (client.resolve_host(host, port) != 0) {
goto fin; goto fin;
} }
client.record_dns_complete_time(); client.record_domain_lookup_end_time();
if (client.initiate_connection() != 0) { if (client.initiate_connection() != 0) {
goto fin; goto fin;

View File

@ -88,9 +88,15 @@ struct Config {
enum class RequestState { INITIAL, ON_REQUEST, ON_RESPONSE, ON_COMPLETE }; enum class RequestState { INITIAL, ON_REQUEST, ON_RESPONSE, ON_COMPLETE };
struct RequestTiming { struct RequestTiming {
std::chrono::steady_clock::time_point on_request_time; // The point in time when request is started to be sent.
std::chrono::steady_clock::time_point on_response_time; // Corresponds to requestStart in Resource Timing TR.
std::chrono::steady_clock::time_point on_complete_time; std::chrono::steady_clock::time_point request_start_time;
// The point in time when first byte of response is received.
// Corresponds to responseStart in Resource Timing TR.
std::chrono::steady_clock::time_point response_start_time;
// The point in time when last byte of response is received.
// Corresponds to responseEnd in Resource Timing TR.
std::chrono::steady_clock::time_point response_end_time;
RequestState state; RequestState state;
RequestTiming() : state(RequestState::INITIAL) {} RequestTiming() : state(RequestState::INITIAL) {}
}; };
@ -128,9 +134,9 @@ struct Request {
Headers::value_type *get_res_header(int16_t token); Headers::value_type *get_res_header(int16_t token);
Headers::value_type *get_req_header(int16_t token); Headers::value_type *get_req_header(int16_t token);
void record_request_time(); void record_request_start_time();
void record_response_time(); void record_response_start_time();
void record_complete_time(); void record_response_end_time();
Headers res_nva; Headers res_nva;
Headers req_nva; Headers req_nva;
@ -160,17 +166,18 @@ struct Request {
}; };
struct SessionTiming { struct SessionTiming {
// The point in time when download was started. // The point in time when operation was started. Corresponds to
std::chrono::system_clock::time_point started_system_time; // startTime in Resource Timing TR, but recorded in system clock time.
// The point of time when download was started. std::chrono::system_clock::time_point system_start_time;
std::chrono::steady_clock::time_point on_started_time; // Same as above, but recorded in steady clock time.
// The point of time when DNS resolution was completed. std::chrono::steady_clock::time_point start_time;
std::chrono::steady_clock::time_point on_dns_complete_time; // The point in time when DNS resolution was completed. Corresponds
// The point of time when connection was established or SSL/TLS // to domainLookupEnd in Resource Timing TR.
// handshake was completed. std::chrono::steady_clock::time_point domain_lookup_end_time;
std::chrono::steady_clock::time_point on_connect_time; // The point in time when connection was established or SSL/TLS
// The point of time when HTTP/2 commnucation was started. // handshake was completed. Corresponds to connectEnd in Resource
std::chrono::steady_clock::time_point on_handshake_time; // Timing TR.
std::chrono::steady_clock::time_point connect_end_time;
}; };
enum class ClientState { IDLE, CONNECTED }; enum class ClientState { IDLE, CONNECTED };
@ -215,10 +222,9 @@ struct HttpClient {
const nghttp2_priority_spec &pri_spec, const nghttp2_priority_spec &pri_spec,
std::shared_ptr<Dependency> dep, int pri = 0, int level = 0); std::shared_ptr<Dependency> dep, int pri = 0, int level = 0);
void record_handshake_time(); void record_start_time();
void record_started_time(); void record_domain_lookup_end_time();
void record_dns_complete_time(); void record_connect_end_time();
void record_connect_time();
#ifdef HAVE_JANSSON #ifdef HAVE_JANSSON
void output_har(FILE *outfile); void output_har(FILE *outfile);