Skip to content

Commit 23e22d5

Browse files
committed
Add unit tests for pacing.
1 parent 825d603 commit 23e22d5

File tree

12 files changed

+388
-465
lines changed

12 files changed

+388
-465
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ set(PICOQUIC_TEST_LIBRARY_FILES
158158
picoquictest/multipath_test.c
159159
picoquictest/netperf_test.c
160160
picoquictest/openssl_test.c
161+
picoquictest/pacing_test.c
161162
picoquictest/parseheadertest.c
162163
picoquictest/picoquic_lb_test.c
163164
picoquictest/pn2pn64test.c

UnitTest1/unittest1.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,13 @@ namespace UnitTest1
435435
{
436436
int ret = pacing_test();
437437

438+
Assert::AreEqual(ret, 0);
439+
}
440+
441+
TEST_METHOD(pacing_repeat)
442+
{
443+
int ret = pacing_repeat_test();
444+
438445
Assert::AreEqual(ret, 0);
439446
}
440447

picoquic/pacing.c

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,17 @@
2323
#include <stdlib.h>
2424
#include <string.h>
2525

26+
27+
/* Initialize pacing state to high speed default */
28+
void picoquic_pacing_init(picoquic_pacing_t* pacing, uint64_t current_time)
29+
{
30+
pacing->evaluation_time = current_time;
31+
pacing->bucket_nanosec = 16;
32+
pacing->bucket_max = 16;
33+
pacing->packet_time_nanosec = 1;
34+
pacing->packet_time_microsec = 1;
35+
}
36+
2637
/* Compute nanosec per packet */
2738
static uint64_t picoquic_pacing_time_nanosec(picoquic_pacing_t* pacing, size_t length)
2839
{
@@ -52,6 +63,14 @@ static void picoquic_update_pacing_bucket(picoquic_pacing_t* pacing, uint64_t cu
5263
}
5364
}
5465

66+
/* Check whether pacing authorizes immediate transmission,
67+
* no not send any state
68+
*/
69+
int picoquic_is_pacing_blocked(picoquic_pacing_t* pacing)
70+
{
71+
return (pacing->bucket_nanosec < pacing->packet_time_nanosec);
72+
}
73+
5574
/*
5675
* Check pacing to see whether the next transmission is authorized.
5776
* If if is not, update the next wait time to reflect pacing.
@@ -87,7 +106,9 @@ int picoquic_is_authorized_by_pacing(picoquic_pacing_t * pacing, uint64_t curren
87106
if (next_pacing_time < *next_time) {
88107
pacing->bandwidth_pause = 0;
89108
*next_time = next_pacing_time;
90-
SET_LAST_WAKE(quic, PICOQUIC_SENDER);
109+
if (quic != NULL) {
110+
SET_LAST_WAKE(quic, PICOQUIC_SENDER);
111+
}
91112
}
92113
ret = 0;
93114
}

picoquic/picoquic_internal.h

Lines changed: 8 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -991,24 +991,25 @@ typedef struct st_picoquic_remote_cnxid_stash_t {
991991
* Pacing uses a set of per path variables:
992992
* - rate: bytes per second.
993993
* - evaluation_time: last time the path was evaluated.
994-
* - bucket_nanosec: number of nanoseconds of transmission time that are allowed.
995994
* - bucket_max: maximum value (capacity) of the leaky bucket.
996-
* - packet_time_nanosec: number of nanoseconds required to send a full size packet.
997995
* - packet_time_microsec: max of (packet_time_nano_sec/1024, 1) microsec.
996+
* Internal variables:
997+
* - bucket_nanosec: number of nanoseconds of transmission time that are allowed.
998+
* - packet_time_nanosec: number of nanoseconds required to send a full size packet.
998999
*/
9991000
typedef struct st_picoquic_pacing_t {
10001001
uint64_t rate;
10011002
uint64_t evaluation_time;
1002-
int64_t bucket_nanosec;
10031003
int64_t bucket_max;
1004-
int64_t packet_time_nanosec;
10051004
uint64_t packet_time_microsec;
10061005
uint64_t quantum_max;
10071006
uint64_t rate_max;
10081007
int bandwidth_pause;
1008+
/* High precision variables should only be used inside pacing.c */
1009+
int64_t bucket_nanosec;
1010+
int64_t packet_time_nanosec;
10091011
} picoquic_pacing_t;
10101012

1011-
10121013
/*
10131014
* Per path context.
10141015
* Path contexts are created:
@@ -1177,30 +1178,7 @@ typedef struct st_picoquic_path_t {
11771178
uint64_t last_cwin_blocked_time;
11781179
uint64_t last_time_acked_data_frame_sent;
11791180
void* congestion_alg_state;
1180-
1181-
#if 1
11821181
picoquic_pacing_t pacing;
1183-
#else
1184-
/*
1185-
* Pacing uses a set of per path variables:
1186-
* - pacing_rate: bytes per second.
1187-
* - pacing_evaluation_time: last time the path was evaluated.
1188-
* - pacing_bucket_nanosec: number of nanoseconds of transmission time that are allowed.
1189-
* - pacing_bucket_max: maximum value (capacity) of the leaky bucket.
1190-
* - pacing_packet_time_nanosec: number of nanoseconds required to send a full size packet.
1191-
* - pacing_packet_time_microsec: max of (packet_time_nano_sec/1024, 1) microsec.
1192-
*/
1193-
1194-
uint64_t pacing_rate;
1195-
uint64_t pacing_evaluation_time;
1196-
int64_t pacing_bucket_nanosec;
1197-
int64_t pacing_bucket_max;
1198-
int64_t pacing_packet_time_nanosec;
1199-
uint64_t pacing_packet_time_microsec;
1200-
uint64_t pacing_quantum_max;
1201-
uint64_t pacing_rate_max;
1202-
int pacing_bandwidth_pause;
1203-
#endif
12041182

12051183
/* MTU safety tracking */
12061184
uint64_t nb_mtu_losses;
@@ -1639,6 +1617,8 @@ picoquic_cnx_t* picoquic_cnx_by_icid(picoquic_quic_t* quic, picoquic_connection_
16391617
picoquic_cnx_t* picoquic_cnx_by_secret(picoquic_quic_t* quic, const uint8_t* reset_secret, const struct sockaddr* addr);
16401618

16411619
/* Pacing implementation */
1620+
void picoquic_pacing_init(picoquic_pacing_t* pacing, uint64_t current_time);
1621+
int picoquic_is_pacing_blocked(picoquic_pacing_t* pacing);
16421622
int picoquic_is_authorized_by_pacing(picoquic_pacing_t* pacing, uint64_t current_time, uint64_t* next_time, unsigned int packet_train_mode, picoquic_quic_t * quic);
16431623
void picoquic_update_pacing_parameters(picoquic_pacing_t* pacing, double pacing_rate, uint64_t quantum, size_t send_mtu, uint64_t smoothed_rtt,
16441624
picoquic_path_t* signalled_path);

picoquic/quicctx.c

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,11 +1535,7 @@ int picoquic_create_path(picoquic_cnx_t* cnx, uint64_t start_time, const struct
15351535
path_x->congestion_alg_state = NULL;
15361536

15371537
/* Initialize per path pacing state */
1538-
path_x->pacing.evaluation_time = start_time;
1539-
path_x->pacing.bucket_nanosec = 16;
1540-
path_x->pacing.bucket_max = 16;
1541-
path_x->pacing.packet_time_nanosec = 1;
1542-
path_x->pacing.packet_time_microsec = 1;
1538+
picoquic_pacing_init(&path_x->pacing, start_time);
15431539

15441540
/* Initialize the MTU */
15451541
path_x->send_mtu = (peer_addr == NULL || peer_addr->sa_family == AF_INET) ? PICOQUIC_INITIAL_MTU_IPV4 : PICOQUIC_INITIAL_MTU_IPV6;

picoquic/sender.c

Lines changed: 1 addition & 223 deletions
Original file line numberDiff line numberDiff line change
@@ -913,228 +913,6 @@ static size_t picoquic_protect_packet(picoquic_cnx_t* cnx,
913913
return send_length;
914914
}
915915

916-
#if 0
917-
/* Compute nanosec per packet */
918-
uint64_t picoquic_packet_time_nanosec(picoquic_path_t* path_x, size_t length)
919-
{
920-
const uint64_t nanosec_per_sec = 1000000000ull;
921-
uint64_t packet_time_nanosec = (nanosec_per_sec * length + (path_x->pacing_rate - 1)) / path_x->pacing_rate;
922-
923-
return packet_time_nanosec;
924-
}
925-
926-
/* Update the leaky bucket used for pacing.
927-
*/
928-
static void picoquic_update_pacing_bucket(picoquic_path_t * path_x, uint64_t current_time)
929-
{
930-
if (path_x->pacing.bucket_nanosec < -path_x->pacing_packet_time_nanosec) {
931-
path_x->pacing.bucket_nanosec = -path_x->pacing_packet_time_nanosec;
932-
}
933-
934-
if (current_time > path_x->pacing_evaluation_time) {
935-
path_x->pacing.bucket_nanosec += (current_time - path_x->pacing_evaluation_time) * 1000;
936-
path_x->pacing_evaluation_time = current_time;
937-
if (path_x->pacing.bucket_nanosec > path_x->pacing_bucket_max) {
938-
path_x->pacing.bucket_nanosec = path_x->pacing_bucket_max;
939-
}
940-
}
941-
}
942-
943-
/*
944-
* Check pacing to see whether the next transmission is authorized.
945-
* If if is not, update the next wait time to reflect pacing.
946-
*
947-
* In packet train mode, the wait will last until the bucket is completely full, or
948-
* if at least N packets are received.
949-
*/
950-
int picoquic_is_sending_authorized_by_pacing(picoquic_cnx_t * cnx, picoquic_path_t * path_x, uint64_t current_time, uint64_t * next_time)
951-
{
952-
int ret = 1;
953-
954-
picoquic_update_pacing_bucket(path_x, current_time);
955-
956-
if (path_x->pacing.bucket_nanosec < path_x->pacing_packet_time_nanosec) {
957-
uint64_t next_pacing_time;
958-
int64_t bucket_required;
959-
960-
if (cnx->quic->packet_train_mode || path_x->pacing_bandwidth_pause) {
961-
bucket_required = path_x->pacing_bucket_max;
962-
963-
if (bucket_required > 10 * path_x->pacing_packet_time_nanosec) {
964-
bucket_required = 10 * path_x->pacing_packet_time_nanosec;
965-
}
966-
967-
bucket_required -= path_x->pacing.bucket_nanosec;
968-
}
969-
else {
970-
bucket_required = path_x->pacing_packet_time_nanosec - path_x->pacing_bucket_nanosec;
971-
}
972-
973-
next_pacing_time = current_time + 1 + bucket_required / 1000;
974-
if (next_pacing_time < *next_time) {
975-
path_x->pacing_bandwidth_pause = 0;
976-
*next_time = next_pacing_time;
977-
SET_LAST_WAKE(cnx->quic, PICOQUIC_SENDER);
978-
}
979-
ret = 0;
980-
}
981-
982-
return ret;
983-
}
984-
985-
/* Handle reporting of parameter updates if path is specified.
986-
*/
987-
988-
/* Reset the pacing data after recomputing the pacing rate
989-
*/
990-
void picoquic_update_pacing_rate(picoquic_cnx_t * cnx, picoquic_path_t* path_x, double pacing_rate, uint64_t quantum)
991-
{
992-
#if 0
993-
const uint64_t nanosec_per_sec = 1000000000ull;
994-
995-
path_x->pacing_rate = (uint64_t)pacing_rate;
996-
997-
if (quantum > path_x->pacing_quantum_max) {
998-
path_x->pacing_quantum_max = quantum;
999-
}
1000-
if (path_x->pacing_rate > path_x->pacing_rate_max) {
1001-
path_x->pacing_rate_max = path_x->pacing_rate;
1002-
}
1003-
1004-
path_x->pacing_packet_time_nanosec = picoquic_packet_time_nanosec(path_x, path_x->send_mtu);
1005-
1006-
path_x->pacing_bucket_max = (nanosec_per_sec * quantum) / path_x->pacing_rate;
1007-
if (path_x->pacing_bucket_max <= 0) {
1008-
path_x->pacing_bucket_max = 16 * path_x->pacing_packet_time_nanosec;
1009-
}
1010-
1011-
#else
1012-
double packet_time = (double)path_x->send_mtu / pacing_rate;
1013-
double quantum_time = (double)quantum / pacing_rate;
1014-
uint64_t rtt_nanosec = path_x->smoothed_rtt * 1000;
1015-
1016-
path_x->pacing_rate = (uint64_t)pacing_rate;
1017-
1018-
if (quantum > path_x->pacing_quantum_max) {
1019-
path_x->pacing_quantum_max = quantum;
1020-
}
1021-
if (path_x->pacing_rate > path_x->pacing_rate_max) {
1022-
path_x->pacing_rate_max = path_x->pacing_rate;
1023-
}
1024-
1025-
path_x->pacing_packet_time_nanosec = (uint64_t)(packet_time * 1000000000.0);
1026-
1027-
if (path_x->pacing_packet_time_nanosec <= 0) {
1028-
path_x->pacing_packet_time_nanosec = 1;
1029-
path_x->pacing_packet_time_microsec = 1;
1030-
}
1031-
else {
1032-
if ((uint64_t)path_x->pacing_packet_time_nanosec > rtt_nanosec) {
1033-
path_x->pacing_packet_time_nanosec = rtt_nanosec;
1034-
}
1035-
path_x->pacing_packet_time_microsec = (path_x->pacing_packet_time_nanosec + 999ull) / 1000;
1036-
}
1037-
1038-
path_x->pacing_bucket_max = (uint64_t)(quantum_time * 1000000000.0);
1039-
if (path_x->pacing_bucket_max <= 0) {
1040-
path_x->pacing_bucket_max = 16 * path_x->pacing_packet_time_nanosec;
1041-
}
1042-
#endif
1043-
1044-
if (path_x->pacing_bucket_nanosec > path_x->pacing_bucket_max) {
1045-
path_x->pacing_bucket_nanosec = path_x->pacing_bucket_max;
1046-
}
1047-
1048-
if (cnx->is_pacing_update_requested && path_x == cnx->path[0] &&
1049-
cnx->callback_fn != NULL) {
1050-
if ((path_x->pacing_rate > cnx->pacing_rate_signalled &&
1051-
(path_x->pacing_rate - cnx->pacing_rate_signalled >= cnx->pacing_increase_threshold)) ||
1052-
(path_x->pacing_rate < cnx->pacing_rate_signalled &&
1053-
(cnx->pacing_rate_signalled - path_x->pacing_rate > cnx->pacing_decrease_threshold))){
1054-
(void)cnx->callback_fn(cnx, path_x->pacing_rate, NULL, 0, picoquic_callback_pacing_changed, cnx->callback_ctx, NULL);
1055-
cnx->pacing_rate_signalled = path_x->pacing_rate;
1056-
}
1057-
}
1058-
if (cnx->is_path_quality_update_requested &&
1059-
cnx->callback_fn != NULL) {
1060-
/* TODO: add a function "export path quality" */
1061-
/* TODO: remember previous signalled value for change tests */
1062-
if (path_x->smoothed_rtt < path_x->rtt_threshold_low ||
1063-
path_x->smoothed_rtt > path_x->rtt_threshold_high ||
1064-
path_x->pacing_rate < path_x->pacing_rate_threshold_low ||
1065-
path_x->pacing_rate > path_x->pacing_rate_threshold_high) {
1066-
(void)cnx->callback_fn(cnx, path_x->unique_path_id, NULL, 0, picoquic_callback_path_quality_changed, cnx->callback_ctx, path_x->app_path_ctx);
1067-
picoquic_refresh_path_quality_thresholds(path_x);
1068-
}
1069-
}
1070-
}
1071-
1072-
/*
1073-
* Reset the pacing data after CWIN is updated.
1074-
* The max bucket is set to contain at least 2 packets more than 1/8th of the congestion window.
1075-
*/
1076-
1077-
void picoquic_update_pacing_data(picoquic_cnx_t* cnx, picoquic_path_t * path_x, int slow_start)
1078-
{
1079-
uint64_t rtt_nanosec = path_x->smoothed_rtt * 1000;
1080-
1081-
if ((path_x->cwin < ((uint64_t)path_x->send_mtu) * 8) || rtt_nanosec <= 1000) {
1082-
/* Small windows, should only relie on ACK clocking */
1083-
path_x->pacing_bucket_max = rtt_nanosec;
1084-
path_x->pacing_packet_time_nanosec = 1;
1085-
path_x->pacing_packet_time_microsec = 1;
1086-
1087-
if (path_x->pacing_bucket_nanosec > path_x->pacing_bucket_max) {
1088-
path_x->pacing_bucket_nanosec = path_x->pacing_bucket_max;
1089-
}
1090-
}
1091-
else {
1092-
double pacing_rate = ((double)path_x->cwin / (double)rtt_nanosec) * 1000000000.0;
1093-
uint64_t quantum = path_x->cwin / 4;
1094-
1095-
if (quantum < 2ull * path_x->send_mtu) {
1096-
quantum = 2ull * path_x->send_mtu;
1097-
}
1098-
else {
1099-
if (slow_start && path_x->smoothed_rtt > 4*PICOQUIC_MAX_BANDWIDTH_TIME_INTERVAL_MAX) {
1100-
const uint64_t quantum_min = 0x8000;
1101-
if (quantum < quantum_min){
1102-
quantum = quantum_min;
1103-
}
1104-
else {
1105-
uint64_t quantum2 = (uint64_t)((pacing_rate * PICOQUIC_MAX_BANDWIDTH_TIME_INTERVAL_MAX) / 1000000.0);
1106-
if (quantum2 > quantum) {
1107-
quantum = quantum2;
1108-
}
1109-
}
1110-
}
1111-
else if (quantum > 16ull * path_x->send_mtu) {
1112-
quantum = 16ull * path_x->send_mtu;
1113-
}
1114-
1115-
}
1116-
1117-
if (slow_start) {
1118-
pacing_rate *= 1.25;
1119-
}
1120-
1121-
picoquic_update_pacing_rate(cnx, path_x, pacing_rate, quantum);
1122-
}
1123-
}
1124-
1125-
/*
1126-
* Update the pacing data after sending a packet.
1127-
*/
1128-
void picoquic_update_pacing_after_send(picoquic_path_t * path_x, size_t length, uint64_t current_time)
1129-
{
1130-
uint64_t packet_time_nanosec;
1131-
1132-
picoquic_update_pacing_bucket(path_x, current_time);
1133-
1134-
packet_time_nanosec = ((path_x->pacing_packet_time_nanosec * (uint64_t)length) + (path_x->send_mtu - 1)) / path_x->send_mtu;
1135-
path_x->pacing_bucket_nanosec -= packet_time_nanosec;
1136-
}
1137-
#endif
1138916
/*
1139917
* Final steps in packet transmission: queue for retransmission, etc
1140918
*/
@@ -4678,7 +4456,7 @@ int picoquic_prepare_packet_ex(picoquic_cnx_t* cnx,
46784456
if (cnx->path[path_id]->cwin <= cnx->path[path_id]->bytes_in_transit) {
46794457
cnx->nb_trains_blocked_cwin++;
46804458
}
4681-
else if (cnx->path[path_id]->pacing.bucket_nanosec < cnx->path[path_id]->pacing.packet_time_nanosec){
4459+
else if (picoquic_is_pacing_blocked(&cnx->path[path_id]->pacing)) {
46824460
cnx->nb_trains_blocked_pacing++;
46834461
}
46844462
else {

picoquic_t/picoquic_t.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ static const picoquic_test_def_t test_table[] = {
105105
{ "new_cnxid_stash", cnxid_stash_test },
106106
{ "new_cnxid", new_cnxid_test },
107107
{ "pacing", pacing_test },
108+
{ "pacing_repeat", pacing_repeat_test },
108109
#if 0
109110
/* The TLS API connect test is only useful when debugging issues step by step */
110111
{ "tls_api_connect", tls_api_connect_test },

0 commit comments

Comments
 (0)