|
|
|
@ -22,6 +22,7 @@ |
|
|
|
|
|
|
|
|
|
#include <grpc/support/log.h> |
|
|
|
|
|
|
|
|
|
#include "src/core/lib/gprpp/sync.h" |
|
|
|
|
#include "src/core/lib/iomgr/port.h" |
|
|
|
|
|
|
|
|
|
#ifdef GRPC_LINUX_ERRQUEUE |
|
|
|
@ -29,72 +30,70 @@ |
|
|
|
|
#include <string.h> |
|
|
|
|
#include <time.h> |
|
|
|
|
|
|
|
|
|
#include "src/core/lib/gprpp/memory.h" |
|
|
|
|
|
|
|
|
|
namespace grpc_core { |
|
|
|
|
namespace { |
|
|
|
|
/** Fills gpr_timespec gts based on values from timespec ts */ |
|
|
|
|
void fill_gpr_from_timestamp(gpr_timespec* gts, const struct timespec* ts) { |
|
|
|
|
// Fills gpr_timespec gts based on values from timespec ts.
|
|
|
|
|
void FillGprFromTimestamp(gpr_timespec* gts, const struct timespec* ts) { |
|
|
|
|
gts->tv_sec = ts->tv_sec; |
|
|
|
|
gts->tv_nsec = static_cast<int32_t>(ts->tv_nsec); |
|
|
|
|
gts->clock_type = GPR_CLOCK_REALTIME; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void default_timestamps_callback(void* /*arg*/, Timestamps* /*ts*/, |
|
|
|
|
grpc_error_handle /*shudown_err*/) { |
|
|
|
|
void DefaultTimestampsCallback(void* /*arg*/, Timestamps* /*ts*/, |
|
|
|
|
absl::Status /*shudown_err*/) { |
|
|
|
|
gpr_log(GPR_DEBUG, "Timestamps callback has not been registered"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** The saved callback function that will be invoked when we get all the
|
|
|
|
|
* timestamps that we are going to get for a TracedBuffer. */ |
|
|
|
|
void (*timestamps_callback)(void*, Timestamps*, |
|
|
|
|
grpc_error_handle shutdown_err) = |
|
|
|
|
default_timestamps_callback; |
|
|
|
|
// The saved callback function that will be invoked when we get all the
|
|
|
|
|
// timestamps that we are going to get for a TracedBuffer.
|
|
|
|
|
void (*g_timestamps_callback)(void*, Timestamps*, |
|
|
|
|
grpc_error_handle shutdown_err) = |
|
|
|
|
DefaultTimestampsCallback; |
|
|
|
|
|
|
|
|
|
/* Used to extract individual opt stats from cmsg, so as to avoid troubles with
|
|
|
|
|
* unaligned reads */ |
|
|
|
|
// Used to extract individual opt stats from cmsg, so as to avoid troubles with
|
|
|
|
|
// unaligned reads.
|
|
|
|
|
template <typename T> |
|
|
|
|
T read_unaligned(const void* ptr) { |
|
|
|
|
T ReadUnaligned(const void* ptr) { |
|
|
|
|
T val; |
|
|
|
|
memcpy(&val, ptr, sizeof(val)); |
|
|
|
|
return val; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Extracts opt stats from the tcp_info struct \a info to \a metrics */ |
|
|
|
|
void extract_opt_stats_from_tcp_info(ConnectionMetrics* metrics, |
|
|
|
|
const tcp_info* info) { |
|
|
|
|
// Extracts opt stats from the tcp_info struct \a info to \a metrics
|
|
|
|
|
void ExtractOptStatsFromTcpInfo(ConnectionMetrics* metrics, |
|
|
|
|
const tcp_info* info) { |
|
|
|
|
if (info == nullptr) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
if (info->length > offsetof(tcp_info, tcpi_sndbuf_limited)) { |
|
|
|
|
metrics->recurring_retrans.emplace(info->tcpi_retransmits); |
|
|
|
|
metrics->is_delivery_rate_app_limited.emplace( |
|
|
|
|
info->tcpi_delivery_rate_app_limited); |
|
|
|
|
metrics->congestion_window.emplace(info->tcpi_snd_cwnd); |
|
|
|
|
metrics->reordering.emplace(info->tcpi_reordering); |
|
|
|
|
metrics->packet_retx.emplace(info->tcpi_total_retrans); |
|
|
|
|
metrics->pacing_rate.emplace(info->tcpi_pacing_rate); |
|
|
|
|
metrics->data_notsent.emplace(info->tcpi_notsent_bytes); |
|
|
|
|
metrics->recurring_retrans = info->tcpi_retransmits; |
|
|
|
|
metrics->is_delivery_rate_app_limited = |
|
|
|
|
info->tcpi_delivery_rate_app_limited; |
|
|
|
|
metrics->congestion_window = info->tcpi_snd_cwnd; |
|
|
|
|
metrics->reordering = info->tcpi_reordering; |
|
|
|
|
metrics->packet_retx = info->tcpi_total_retrans; |
|
|
|
|
metrics->pacing_rate = info->tcpi_pacing_rate; |
|
|
|
|
metrics->data_notsent = info->tcpi_notsent_bytes; |
|
|
|
|
if (info->tcpi_min_rtt != UINT32_MAX) { |
|
|
|
|
metrics->min_rtt.emplace(info->tcpi_min_rtt); |
|
|
|
|
metrics->min_rtt = info->tcpi_min_rtt; |
|
|
|
|
} |
|
|
|
|
metrics->packet_sent.emplace(info->tcpi_data_segs_out); |
|
|
|
|
metrics->delivery_rate.emplace(info->tcpi_delivery_rate); |
|
|
|
|
metrics->busy_usec.emplace(info->tcpi_busy_time); |
|
|
|
|
metrics->rwnd_limited_usec.emplace(info->tcpi_rwnd_limited); |
|
|
|
|
metrics->sndbuf_limited_usec.emplace(info->tcpi_sndbuf_limited); |
|
|
|
|
metrics->packet_sent = info->tcpi_data_segs_out; |
|
|
|
|
metrics->delivery_rate = info->tcpi_delivery_rate; |
|
|
|
|
metrics->busy_usec = info->tcpi_busy_time; |
|
|
|
|
metrics->rwnd_limited_usec = info->tcpi_rwnd_limited; |
|
|
|
|
metrics->sndbuf_limited_usec = info->tcpi_sndbuf_limited; |
|
|
|
|
} |
|
|
|
|
if (info->length > offsetof(tcp_info, tcpi_dsack_dups)) { |
|
|
|
|
metrics->data_sent.emplace(info->tcpi_bytes_sent); |
|
|
|
|
metrics->data_retx.emplace(info->tcpi_bytes_retrans); |
|
|
|
|
metrics->packet_spurious_retx.emplace(info->tcpi_dsack_dups); |
|
|
|
|
metrics->data_sent = info->tcpi_bytes_sent; |
|
|
|
|
metrics->data_retx = info->tcpi_bytes_retrans; |
|
|
|
|
metrics->packet_spurious_retx = info->tcpi_dsack_dups; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** Extracts opt stats from the given control message \a opt_stats to the
|
|
|
|
|
* connection metrics \a metrics */ |
|
|
|
|
void extract_opt_stats_from_cmsg(ConnectionMetrics* metrics, |
|
|
|
|
const cmsghdr* opt_stats) { |
|
|
|
|
// Extracts opt stats from the given control message \a opt_stats to the
|
|
|
|
|
// connection metrics \a metrics.
|
|
|
|
|
void ExtractOptStatsFromCmsg(ConnectionMetrics* metrics, |
|
|
|
|
const cmsghdr* opt_stats) { |
|
|
|
|
if (opt_stats == nullptr) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
@ -108,80 +107,79 @@ void extract_opt_stats_from_cmsg(ConnectionMetrics* metrics, |
|
|
|
|
const void* val = data + offset + NLA_HDRLEN; |
|
|
|
|
switch (attr->nla_type) { |
|
|
|
|
case TCP_NLA_BUSY: { |
|
|
|
|
metrics->busy_usec.emplace(read_unaligned<uint64_t>(val)); |
|
|
|
|
metrics->busy_usec = ReadUnaligned<uint64_t>(val); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case TCP_NLA_RWND_LIMITED: { |
|
|
|
|
metrics->rwnd_limited_usec.emplace(read_unaligned<uint64_t>(val)); |
|
|
|
|
metrics->rwnd_limited_usec = ReadUnaligned<uint64_t>(val); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case TCP_NLA_SNDBUF_LIMITED: { |
|
|
|
|
metrics->sndbuf_limited_usec.emplace(read_unaligned<uint64_t>(val)); |
|
|
|
|
metrics->sndbuf_limited_usec = ReadUnaligned<uint64_t>(val); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case TCP_NLA_PACING_RATE: { |
|
|
|
|
metrics->pacing_rate.emplace(read_unaligned<uint64_t>(val)); |
|
|
|
|
metrics->pacing_rate = ReadUnaligned<uint64_t>(val); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case TCP_NLA_DELIVERY_RATE: { |
|
|
|
|
metrics->delivery_rate.emplace(read_unaligned<uint64_t>(val)); |
|
|
|
|
metrics->delivery_rate = ReadUnaligned<uint64_t>(val); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case TCP_NLA_DELIVERY_RATE_APP_LMT: { |
|
|
|
|
metrics->is_delivery_rate_app_limited.emplace( |
|
|
|
|
read_unaligned<uint8_t>(val)); |
|
|
|
|
metrics->is_delivery_rate_app_limited = ReadUnaligned<uint8_t>(val); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case TCP_NLA_SND_CWND: { |
|
|
|
|
metrics->congestion_window.emplace(read_unaligned<uint32_t>(val)); |
|
|
|
|
metrics->congestion_window = ReadUnaligned<uint32_t>(val); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case TCP_NLA_MIN_RTT: { |
|
|
|
|
metrics->min_rtt.emplace(read_unaligned<uint32_t>(val)); |
|
|
|
|
metrics->min_rtt = ReadUnaligned<uint32_t>(val); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case TCP_NLA_SRTT: { |
|
|
|
|
metrics->srtt.emplace(read_unaligned<uint32_t>(val)); |
|
|
|
|
metrics->srtt = ReadUnaligned<uint32_t>(val); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case TCP_NLA_RECUR_RETRANS: { |
|
|
|
|
metrics->recurring_retrans.emplace(read_unaligned<uint8_t>(val)); |
|
|
|
|
metrics->recurring_retrans = ReadUnaligned<uint8_t>(val); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case TCP_NLA_BYTES_SENT: { |
|
|
|
|
metrics->data_sent.emplace(read_unaligned<uint64_t>(val)); |
|
|
|
|
metrics->data_sent = ReadUnaligned<uint64_t>(val); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case TCP_NLA_DATA_SEGS_OUT: { |
|
|
|
|
metrics->packet_sent.emplace(read_unaligned<uint64_t>(val)); |
|
|
|
|
metrics->packet_sent = ReadUnaligned<uint64_t>(val); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case TCP_NLA_TOTAL_RETRANS: { |
|
|
|
|
metrics->packet_retx.emplace(read_unaligned<uint64_t>(val)); |
|
|
|
|
metrics->packet_retx = ReadUnaligned<uint64_t>(val); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case TCP_NLA_DELIVERED: { |
|
|
|
|
metrics->packet_delivered.emplace(read_unaligned<uint32_t>(val)); |
|
|
|
|
metrics->packet_delivered = ReadUnaligned<uint32_t>(val); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case TCP_NLA_DELIVERED_CE: { |
|
|
|
|
metrics->packet_delivered_ce.emplace(read_unaligned<uint32_t>(val)); |
|
|
|
|
metrics->packet_delivered_ce = ReadUnaligned<uint32_t>(val); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case TCP_NLA_BYTES_RETRANS: { |
|
|
|
|
metrics->data_retx.emplace(read_unaligned<uint64_t>(val)); |
|
|
|
|
metrics->data_retx = ReadUnaligned<uint64_t>(val); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case TCP_NLA_DSACK_DUPS: { |
|
|
|
|
metrics->packet_spurious_retx.emplace(read_unaligned<uint32_t>(val)); |
|
|
|
|
metrics->packet_spurious_retx = ReadUnaligned<uint32_t>(val); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case TCP_NLA_REORDERING: { |
|
|
|
|
metrics->reordering.emplace(read_unaligned<uint32_t>(val)); |
|
|
|
|
metrics->reordering = ReadUnaligned<uint32_t>(val); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
case TCP_NLA_SND_SSTHRESH: { |
|
|
|
|
metrics->snd_ssthresh.emplace(read_unaligned<uint32_t>(val)); |
|
|
|
|
metrics->snd_ssthresh = ReadUnaligned<uint32_t>(val); |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -189,75 +187,68 @@ void extract_opt_stats_from_cmsg(ConnectionMetrics* metrics, |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
int get_socket_tcp_info(tcp_info* info, int fd) { |
|
|
|
|
int GetSocketTcpInfo(struct tcp_info* info, int fd) { |
|
|
|
|
memset(info, 0, sizeof(*info)); |
|
|
|
|
info->length = offsetof(tcp_info, length); |
|
|
|
|
return getsockopt(fd, IPPROTO_TCP, TCP_INFO, info, &(info->length)); |
|
|
|
|
} |
|
|
|
|
} /* namespace */ |
|
|
|
|
|
|
|
|
|
void TracedBuffer::AddNewEntry(TracedBuffer** head, uint32_t seq_no, int fd, |
|
|
|
|
void* arg) { |
|
|
|
|
GPR_DEBUG_ASSERT(head != nullptr); |
|
|
|
|
} // namespace.
|
|
|
|
|
|
|
|
|
|
void TracedBufferList::AddNewEntry(int32_t seq_no, int fd, void* arg) { |
|
|
|
|
TracedBuffer* new_elem = new TracedBuffer(seq_no, arg); |
|
|
|
|
/* Store the current time as the sendmsg time. */ |
|
|
|
|
// Store the current time as the sendmsg time.
|
|
|
|
|
new_elem->ts_.sendmsg_time.time = gpr_now(GPR_CLOCK_REALTIME); |
|
|
|
|
new_elem->ts_.scheduled_time.time = gpr_inf_past(GPR_CLOCK_REALTIME); |
|
|
|
|
new_elem->ts_.sent_time.time = gpr_inf_past(GPR_CLOCK_REALTIME); |
|
|
|
|
new_elem->ts_.acked_time.time = gpr_inf_past(GPR_CLOCK_REALTIME); |
|
|
|
|
|
|
|
|
|
if (get_socket_tcp_info(&new_elem->ts_.info, fd) == 0) { |
|
|
|
|
extract_opt_stats_from_tcp_info(&new_elem->ts_.sendmsg_time.metrics, |
|
|
|
|
&new_elem->ts_.info); |
|
|
|
|
if (GetSocketTcpInfo(&(new_elem->ts_.info), fd) == 0) { |
|
|
|
|
ExtractOptStatsFromTcpInfo(&(new_elem->ts_.sendmsg_time.metrics), |
|
|
|
|
&(new_elem->ts_.info)); |
|
|
|
|
} |
|
|
|
|
if (*head == nullptr) { |
|
|
|
|
*head = new_elem; |
|
|
|
|
return; |
|
|
|
|
MutexLock lock(&mu_); |
|
|
|
|
if (!head_) { |
|
|
|
|
head_ = tail_ = new_elem; |
|
|
|
|
} else { |
|
|
|
|
tail_->next_ = new_elem; |
|
|
|
|
tail_ = new_elem; |
|
|
|
|
} |
|
|
|
|
/* Append at the end. */ |
|
|
|
|
TracedBuffer* ptr = *head; |
|
|
|
|
while (ptr->next_ != nullptr) { |
|
|
|
|
ptr = ptr->next_; |
|
|
|
|
} |
|
|
|
|
ptr->next_ = new_elem; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void TracedBuffer::ProcessTimestamp(TracedBuffer** head, |
|
|
|
|
struct sock_extended_err* serr, |
|
|
|
|
struct cmsghdr* opt_stats, |
|
|
|
|
struct scm_timestamping* tss) { |
|
|
|
|
GPR_DEBUG_ASSERT(head != nullptr); |
|
|
|
|
TracedBuffer* elem = *head; |
|
|
|
|
TracedBuffer* next = nullptr; |
|
|
|
|
void TracedBufferList::ProcessTimestamp(struct sock_extended_err* serr, |
|
|
|
|
struct cmsghdr* opt_stats, |
|
|
|
|
struct scm_timestamping* tss) { |
|
|
|
|
MutexLock lock(&mu_); |
|
|
|
|
TracedBuffer* elem = head_; |
|
|
|
|
while (elem != nullptr) { |
|
|
|
|
/* The byte number refers to the sequence number of the last byte which this
|
|
|
|
|
* timestamp relates to. */ |
|
|
|
|
// The byte number refers to the sequence number of the last byte which this
|
|
|
|
|
// timestamp relates to.
|
|
|
|
|
if (serr->ee_data >= elem->seq_no_) { |
|
|
|
|
switch (serr->ee_info) { |
|
|
|
|
case SCM_TSTAMP_SCHED: |
|
|
|
|
fill_gpr_from_timestamp(&(elem->ts_.scheduled_time.time), |
|
|
|
|
&(tss->ts[0])); |
|
|
|
|
extract_opt_stats_from_cmsg(&(elem->ts_.scheduled_time.metrics), |
|
|
|
|
opt_stats); |
|
|
|
|
FillGprFromTimestamp(&(elem->ts_.scheduled_time.time), &(tss->ts[0])); |
|
|
|
|
ExtractOptStatsFromCmsg(&(elem->ts_.scheduled_time.metrics), |
|
|
|
|
opt_stats); |
|
|
|
|
elem = elem->next_; |
|
|
|
|
break; |
|
|
|
|
case SCM_TSTAMP_SND: |
|
|
|
|
fill_gpr_from_timestamp(&(elem->ts_.sent_time.time), &(tss->ts[0])); |
|
|
|
|
extract_opt_stats_from_cmsg(&(elem->ts_.sent_time.metrics), |
|
|
|
|
opt_stats); |
|
|
|
|
FillGprFromTimestamp(&(elem->ts_.sent_time.time), &(tss->ts[0])); |
|
|
|
|
ExtractOptStatsFromCmsg(&(elem->ts_.sent_time.metrics), opt_stats); |
|
|
|
|
elem = elem->next_; |
|
|
|
|
break; |
|
|
|
|
case SCM_TSTAMP_ACK: |
|
|
|
|
fill_gpr_from_timestamp(&(elem->ts_.acked_time.time), &(tss->ts[0])); |
|
|
|
|
extract_opt_stats_from_cmsg(&(elem->ts_.acked_time.metrics), |
|
|
|
|
opt_stats); |
|
|
|
|
/* Got all timestamps. Do the callback and free this TracedBuffer.
|
|
|
|
|
* The thing below can be passed by value if we don't want the |
|
|
|
|
* restriction on the lifetime. */ |
|
|
|
|
timestamps_callback(elem->arg_, &(elem->ts_), absl::OkStatus()); |
|
|
|
|
next = elem->next_; |
|
|
|
|
delete static_cast<TracedBuffer*>(elem); |
|
|
|
|
*head = elem = next; |
|
|
|
|
FillGprFromTimestamp(&(elem->ts_.acked_time.time), &(tss->ts[0])); |
|
|
|
|
ExtractOptStatsFromCmsg(&(elem->ts_.acked_time.metrics), opt_stats); |
|
|
|
|
// Got all timestamps. Do the callback and free this TracedBuffer. The
|
|
|
|
|
// thing below can be passed by value if we don't want the restriction
|
|
|
|
|
// on the lifetime.
|
|
|
|
|
g_timestamps_callback(elem->arg_, &(elem->ts_), absl::OkStatus()); |
|
|
|
|
// Safe to update head_ to elem->next_ because the list is ordered by
|
|
|
|
|
// seq_no. Thus if elem is to be deleted, it has to be the first
|
|
|
|
|
// element in the list.
|
|
|
|
|
head_ = elem->next_; |
|
|
|
|
delete elem; |
|
|
|
|
elem = head_; |
|
|
|
|
break; |
|
|
|
|
default: |
|
|
|
|
abort(); |
|
|
|
@ -266,27 +257,26 @@ void TracedBuffer::ProcessTimestamp(TracedBuffer** head, |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
tail_ = !head_ ? head_ : tail_; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void TracedBuffer::Shutdown(TracedBuffer** head, void* remaining, |
|
|
|
|
grpc_error_handle shutdown_err) { |
|
|
|
|
GPR_DEBUG_ASSERT(head != nullptr); |
|
|
|
|
TracedBuffer* elem = *head; |
|
|
|
|
while (elem != nullptr) { |
|
|
|
|
timestamps_callback(elem->arg_, &(elem->ts_), shutdown_err); |
|
|
|
|
auto* next = elem->next_; |
|
|
|
|
void TracedBufferList::Shutdown(void* remaining, absl::Status shutdown_err) { |
|
|
|
|
MutexLock lock(&mu_); |
|
|
|
|
while (head_) { |
|
|
|
|
TracedBuffer* elem = head_; |
|
|
|
|
g_timestamps_callback(elem->arg_, &(elem->ts_), shutdown_err); |
|
|
|
|
head_ = head_->next_; |
|
|
|
|
delete elem; |
|
|
|
|
elem = next; |
|
|
|
|
} |
|
|
|
|
*head = nullptr; |
|
|
|
|
if (remaining != nullptr) { |
|
|
|
|
timestamps_callback(remaining, nullptr, shutdown_err); |
|
|
|
|
g_timestamps_callback(remaining, nullptr, shutdown_err); |
|
|
|
|
} |
|
|
|
|
tail_ = head_; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void grpc_tcp_set_write_timestamps_callback( |
|
|
|
|
void (*fn)(void*, Timestamps*, grpc_error_handle error)) { |
|
|
|
|
timestamps_callback = fn; |
|
|
|
|
g_timestamps_callback = fn; |
|
|
|
|
} |
|
|
|
|
} /* namespace grpc_core */ |
|
|
|
|
|
|
|
|
@ -301,6 +291,6 @@ void grpc_tcp_set_write_timestamps_callback( |
|
|
|
|
(void)fn; |
|
|
|
|
gpr_log(GPR_DEBUG, "Timestamps callback is not enabled for this platform"); |
|
|
|
|
} |
|
|
|
|
} /* namespace grpc_core */ |
|
|
|
|
} // namespace grpc_core
|
|
|
|
|
|
|
|
|
|
#endif /* GRPC_LINUX_ERRQUEUE */ |
|
|
|
|