diff --git a/src/iperf.h b/src/iperf.h index b7f4575a9..b39a9e771 100644 --- a/src/iperf.h +++ b/src/iperf.h @@ -169,6 +169,8 @@ struct iperf_settings uint64_t fqrate; /* target data rate for FQ pacing*/ int pacing_timer; /* pacing timer in microseconds */ int burst; /* packets per burst */ + uint64_t gap_time; /* (minimum) gap time between packets in miliseconds */ + uint64_t gap_time_max; /* maximum gap time between packets in miliseconds */ int mss; /* for TCP MSS */ int ttl; /* IP TTL option */ int tos; /* type of service bit */ diff --git a/src/iperf_api.c b/src/iperf_api.c index 5b69c0a3f..0735ff669 100644 --- a/src/iperf_api.c +++ b/src/iperf_api.c @@ -444,6 +444,18 @@ iperf_get_mapped_v4(struct iperf_test* ipt) return ipt->mapped_v4; } +uint64_t +iperf_get_test_gap_time(struct iperf_test *ipt) +{ + return ipt->settings->gap_time; +} + +uint64_t +iperf_get_test_gap_time_max(struct iperf_test *ipt) +{ + return ipt->settings->gap_time_max; +} + /************** Setter routines for some fields inside iperf_test *************/ void @@ -876,6 +888,18 @@ iperf_set_test_mss(struct iperf_test *ipt, int mss) ipt->settings->mss = mss; } +void +iperf_set_test_gap_time(struct iperf_test *ipt, uint64_t gap_time) +{ + ipt->settings->gap_time = gap_time; +} + +void +iperf_set_test_gap_time_max(struct iperf_test *ipt, uint64_t gap_time) +{ + ipt->settings->gap_time_max = gap_time; +} + /********************** Get/set test protocol structure ***********************/ struct protocol * @@ -924,15 +948,23 @@ void iperf_on_test_start(struct iperf_test *test) { if (test->json_output) { - cJSON_AddItemToObject(test->json_start, "test_start", iperf_json_printf("protocol: %s num_streams: %d blksize: %d omit: %d duration: %d bytes: %d blocks: %d reverse: %d tos: %d target_bitrate: %d bidir: %d fqrate: %d interval: %f gso: %d gro: %d", test->protocol->name, (int64_t) test->num_streams, (int64_t) test->settings->blksize, (int64_t) test->omit, (int64_t) test->duration, (int64_t) test->settings->bytes, (int64_t) test->settings->blocks, test->reverse?(int64_t)1:(int64_t)0, (int64_t) test->settings->tos, (int64_t) test->settings->rate, (int64_t) test->bidirectional, (uint64_t) test->settings->fqrate, test->stats_interval, (uint64_t) test->settings->gso, (uint64_t) test->settings->gro)); + cJSON_AddItemToObject(test->json_start, "test_start", iperf_json_printf("protocol: %s num_streams: %d blksize: %d omit: %d duration: %d bytes: %d blocks: %d reverse: %d tos: %d target_bitrate: %d bidir: %d fqrate: %d interval: %f gso: %d gro: %d gap_time: %llu gap_time_max: %llu", test->protocol->name, (int64_t) test->num_streams, (int64_t) test->settings->blksize, (int64_t) test->omit, (int64_t) test->duration, (int64_t) test->settings->bytes, (int64_t) test->settings->blocks, test->reverse?(int64_t)1:(int64_t)0, (int64_t) test->settings->tos, (int64_t) test->settings->rate, (int64_t) test->bidirectional, (uint64_t) test->settings->fqrate, test->stats_interval, (uint64_t) test->settings->gso, (uint64_t) test->settings->gro, (int64_t) test->settings->gap_time, (int64_t) test->settings->gap_time_max)); } else { if (test->verbose) { + iperf_printf(test, test_start_begin, test->protocol->name, test->num_streams, test->settings->blksize, test->omit); if (test->settings->bytes) - iperf_printf(test, test_start_bytes, test->protocol->name, test->num_streams, test->settings->blksize, test->omit, test->settings->bytes, test->settings->tos); + iperf_printf(test, test_start_bytes, test->settings->bytes); else if (test->settings->blocks) - iperf_printf(test, test_start_blocks, test->protocol->name, test->num_streams, test->settings->blksize, test->omit, test->settings->blocks, test->settings->tos); + iperf_printf(test, test_start_blocks, test->settings->blocks); else - iperf_printf(test, test_start_time, test->protocol->name, test->num_streams, test->settings->blksize, test->omit, test->duration, test->settings->tos); + iperf_printf(test, test_start_time, test->duration); + + if (test->settings->gap_time_max > 0) + iperf_printf(test, test_start_gap, test->settings->gap_time, test->settings->gap_time_max); + else if (test->settings->rate > 0) + iperf_printf(test, test_start_rate, test->settings->rate); + + iperf_printf(test, test_start_end, test->settings->tos); } } if (test->json_stream) { @@ -1195,6 +1227,10 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) #endif {"gsro", no_argument, NULL, OPT_GSRO}, {"debug", optional_argument, NULL, 'd'}, +#if defined(HAVE_CLOCK_NANOSLEEP) || defined(HAVE_NANOSLEEP) + {"gap", required_argument, NULL, OPT_GAP_TIME}, +#endif /* HAVE_CLOCK_NANOSLEEP || HAVE_NANOSLEEP */ + {"debug", no_argument, NULL, 'd'}, {"help", no_argument, NULL, 'h'}, {NULL, 0, NULL, 0} }; @@ -1214,6 +1250,7 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) struct xbind_entry *xbe; double farg; int rcv_timeout_in = 0; + int pacing_timer_flag = 0; blksize = 0; server_flag = client_flag = rate_flag = duration_flag = rcv_timeout_flag = snd_timeout_flag =0; @@ -1588,6 +1625,32 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) case 'F': test->diskfile_name = optarg; break; +#if defined(HAVE_CLOCK_NANOSLEEP) || defined(HAVE_NANOSLEEP) + case OPT_GAP_TIME: // Gap-time option is in [ms] but saving in [us] + slash = strchr(optarg, '/'); + if (slash) { + *slash = '\0'; + ++slash; + test->settings->gap_time_max = unit_time_atoi(slash); + if (i_errno != 0) { + return -1; + } + } + test->settings->gap_time = unit_time_atoi(optarg); + if (i_errno != 0) { + return -1; + } + if (test->settings->gap_time_max == 0) + test->settings->gap_time_max = test->settings->gap_time * 5; + if (test->settings->gap_time < 0 || test->settings->gap_time_max < 0 || + test->settings->gap_time > test->settings->gap_time_max) + { + i_errno = IEGAP; + return -1; + } + client_flag = 1; + break; +#endif /* HAVE_CLOCK_NANOSLEEP || HAVE_NANOSLEEP */ case OPT_IDLE_TIMEOUT: test->settings->idle_timeout = atoi(optarg); if (test->settings->idle_timeout < 1 || test->settings->idle_timeout > MAX_TIME) { @@ -1966,9 +2029,20 @@ iperf_parse_arguments(struct iperf_test *test, int argc, char **argv) test->settings->blksize = blksize; - if (!rate_flag) + /* Disallow setting gap and either/bot bitrate or pace, as gap practically sets both */ + if (test->settings->gap_time != 0 && (rate_flag || pacing_timer_flag != 0)) { + i_errno = IEGAPCONDITIONS; + return -1; + } + + /* Set UDP default rate if rate was not set */ + if (!rate_flag && test->settings->gap_time == 0) test->settings->rate = test->protocol->id == Pudp ? UDP_RATE : 0; + /* If gap was set and bust was not set - set burst to 1 */ + if (test->settings->burst == 0 && test->settings->gap_time != 0) + test->settings->burst = 1; + /* if no bytes or blocks specified, nor a duration_flag, and we have -F, ** get the file-size as the bytes count to be transferred */ @@ -2081,6 +2155,7 @@ iperf_check_throttle(struct iperf_stream *sp, struct iperf_time *nowP) uint64_t bits_per_second; int64_t missing_rate; uint64_t bits_sent; + uint64_t gap_time = sp->test->settings->gap_time; #if defined(HAVE_CLOCK_NANOSLEEP) || defined(HAVE_NANOSLEEP) struct timespec nanosleep_time; @@ -2091,27 +2166,40 @@ iperf_check_throttle(struct iperf_stream *sp, struct iperf_time *nowP) int64_t ns; #endif /* HAVE_CLOCK_NANOSLEEP */ - if (sp->test->done || sp->test->settings->rate == 0) + if (sp->test->done || (sp->test->settings->rate == 0 && gap_time == 0)) return; - iperf_time_diff(&sp->result->start_time_fixed, nowP, &temp_time); - seconds = iperf_time_in_secs(&temp_time); - bits_sent = sp->result->bytes_sent * 8; - bits_per_second = bits_sent / seconds; - missing_rate = sp->test->settings->rate - bits_per_second; - if (missing_rate > 0) { - sp->green_light = 1; - } else { + // Determine there is already a green light for sending the next message + if (gap_time == 0) { // Check per bit rate + iperf_time_diff(&sp->result->start_time_fixed, nowP, &temp_time); + seconds = iperf_time_in_secs(&temp_time); + bits_sent = sp->result->bytes_sent * 8; + bits_per_second = bits_sent / seconds; + missing_rate = sp->test->settings->rate - bits_per_second; + + if (missing_rate > 0) { + sp->green_light = 1; + } else { + sp->green_light = 0; + } + } else { // Check per gap time - need to wait the gap time sp->green_light = 0; } #if defined(HAVE_CLOCK_NANOSLEEP) || defined(HAVE_NANOSLEEP) // If estimated time to next send is large enough, sleep instead of just CPU looping until green light is set - if (missing_rate < 0) { - delta_bits = bits_sent - (seconds * sp->test->settings->rate); - // Calculate time until next data send is required - time_to_green_light = (SEC_TO_NS * delta_bits / sp->test->settings->rate); - // Whether should wait before next send + if (sp->green_light == 0) { + if (gap_time == 0) { // Wait per bits rate + delta_bits = bits_sent - (seconds * sp->test->settings->rate); + // Calculate time until next data send is required + time_to_green_light = (SEC_TO_NS * delta_bits / sp->test->settings->rate); + // Whether should wait before next send + } else { // Wait per gap time + time_to_green_light = uS_TO_NS * (gap_time + round(((float)rand()/RAND_MAX)*(sp->test->settings->gap_time_max - gap_time))); + if (sp->test->debug_level >= DEBUG_LEVEL_DEBUG) { + printf("Gap time for sending next message is %lu ns\n", time_to_green_light); + } + } if (time_to_green_light >= 0) { #if defined(HAVE_CLOCK_NANOSLEEP) if (clock_gettime(CLOCK_MONOTONIC, &nanosleep_time) == 0) { @@ -2209,7 +2297,7 @@ iperf_send_mt(struct iperf_stream *sp) /* Should bitrate throttle be checked for every send */ #if defined(HAVE_CLOCK_NANOSLEEP) || defined(HAVE_NANOSLEEP) - if (test->settings->rate != 0) { + if (test->settings->rate != 0 || test->settings->gap_time != 0) { throttle_check = 1; if (test->settings->burst == 0) throttle_check_per_message = 1; @@ -2286,6 +2374,8 @@ iperf_init_test(struct iperf_test *test) struct iperf_time now; struct iperf_stream *sp; + srand(time(0)); /* reset random seed using current time for each new test */ + if (test->protocol->init) { if (test->protocol->init(test) < 0) return -1; @@ -2500,6 +2590,10 @@ send_parameters(struct iperf_test *test) cJSON_AddNumberToObject(j, "repeating_payload", test->repeating_payload); if (test->zerocopy) cJSON_AddNumberToObject(j, "zerocopy", test->zerocopy); + if (test->settings->gap_time) + cJSON_AddNumberToObject(j, "gap_time", test->settings->gap_time); + if (test->settings->gap_time_max) + cJSON_AddNumberToObject(j, "gap_time_max", test->settings->gap_time_max); #if defined(HAVE_DONT_FRAGMENT) if (test->settings->dont_fragment) cJSON_AddNumberToObject(j, "dont_fragment", test->settings->dont_fragment); @@ -2782,6 +2876,18 @@ get_parameters(struct iperf_test *test) if ((j_p = iperf_cJSON_GetObjectItemType(j, "zerocopy", cJSON_Number)) != NULL){ test->zerocopy = (j_p->valueint) ? 1: 0; } +#if defined(HAVE_CLOCK_NANOSLEEP) || defined(HAVE_NANOSLEEP) + if ((j_p = iperf_cJSON_GetObjectItemType(j, "gap_time", cJSON_Number)) != NULL) + test->settings->gap_time = j_p->valueint; + if ((j_p = iperf_cJSON_GetObjectItemType(j, "gap_time_max", cJSON_Number)) != NULL) + test->settings->gap_time_max = j_p->valueint; + if (test->settings->gap_time < 0 || test->settings->gap_time_max < 0 || + test->settings->gap_time > test->settings->gap_time_max) + { + i_errno = IEGAP; + r = -1; + } +#endif /* HAVE_CLOCK_NANOSLEEP || HAVE_NANOSLEEP)*/ #if defined(HAVE_DONT_FRAGMENT) if ((j_p = iperf_cJSON_GetObjectItemType(j, "dont_fragment", cJSON_Number)) != NULL){ test->settings->dont_fragment = (j_p->valueint) ? 1: 0; @@ -3780,6 +3886,8 @@ iperf_reset_test(struct iperf_test *test) test->settings->dont_fragment = 0; test->zerocopy = 0; test->settings->skip_rx_copy = 0; + test->settings->gap_time = 0; + test->settings->gap_time_max = 0; #if defined(HAVE_SSL) if (test->settings->authtoken) { diff --git a/src/iperf_api.h b/src/iperf_api.h index 25ac951e0..aeffee13a 100644 --- a/src/iperf_api.h +++ b/src/iperf_api.h @@ -107,6 +107,7 @@ typedef atomic_uint_fast64_t atomic_iperf_size_t; #define OPT_JSON_STREAM_FULL_OUTPUT 33 #define OPT_SERVER_MAX_DURATION 34 #define OPT_GSRO 35 +#define OPT_GAP_TIME 36 /* states */ #define TEST_START 1 @@ -175,6 +176,8 @@ int iperf_get_dont_fragment( struct iperf_test* ipt ); char* iperf_get_test_congestion_control(struct iperf_test* ipt); int iperf_get_test_mss(struct iperf_test* ipt); int iperf_get_mapped_v4(struct iperf_test* ipt); +uint64_t iperf_get_test_gap_time( struct iperf_test* ipt ); +uint64_t iperf_get_test_gap_time_max( struct iperf_test* ipt ); /* Setter routines for some fields inside iperf_test. */ void iperf_set_verbose( struct iperf_test* ipt, int verbose ); @@ -227,6 +230,8 @@ void iperf_set_on_test_start_callback(struct iperf_test* ipt, void (*callback void iperf_set_on_test_connect_callback(struct iperf_test* ipt, void (*callback)(struct iperf_test *)); void iperf_set_on_test_finish_callback(struct iperf_test* ipt, void (*callback)(struct iperf_test *)); +void iperf_set_test_gap_time( struct iperf_test* ipt, uint64_t sleep_timer ); +void iperf_set_test_gap_time_max( struct iperf_test* ipt, uint64_t sleep_timer_max ); #if defined(HAVE_SSL) void iperf_set_test_client_username(struct iperf_test *ipt, const char *client_username); void iperf_set_test_client_password(struct iperf_test *ipt, const char *client_password); @@ -445,6 +450,8 @@ enum { IECNTLKA = 36, // Control connection Keepalive period should be larger than the full retry period (interval * count) IEMAXSERVERTESTDURATIONEXCEEDED = 37, // Client's duration exceeds server's maximum duration IEUNITVAL = 38, // Invalid unit value or suffix + IEGAP = 39, // Illegal gap time value + IEGAPCONDITIONS = 40, // --gap is mutual exclusive with --bitrate and --pacing-timer /* Test errors */ IENEWTEST = 100, // Unable to create a new test (check perror) IEINITTEST = 101, // Test initialization failed (check perror) diff --git a/src/iperf_error.c b/src/iperf_error.c index 40ca492ea..274323214 100644 --- a/src/iperf_error.c +++ b/src/iperf_error.c @@ -563,10 +563,16 @@ iperf_strerror(int int_errno) snprintf(errstr, len, "server test duration expired"); perr = 1; break; - default: - snprintf(errstr, len, "int_errno=%d", int_errno); - perr = 1; - break; + case IEGAP: + snprintf(errstr, len, "illegal gap time value"); + break; + case IEGAPCONDITIONS: + snprintf(errstr, len, "--gap is mutual exclusive with --bitrate and --pacing-timer"); + break; + default: + snprintf(errstr, len, "int_errno=%d", int_errno); + perr = 1; + break; } /* Append the result of strerror() or gai_strerror() if appropriate */ diff --git a/src/iperf_locale.c b/src/iperf_locale.c index eb07c9651..18cad8550 100644 --- a/src/iperf_locale.c +++ b/src/iperf_locale.c @@ -174,6 +174,10 @@ const char usage_longstr[] = "Usage: iperf3 [-s|-c host] [options]\n" " (optional slash and packet count for burst mode)\n" " --pacing-timer #[KMG] set the Server timing for pacing, in microseconds (default %d)\n" " (deprecated - for servers using older versions backward compatibility)\n" +#if defined(HAVE_CLOCK_NANOSLEEP) || defined(HAVE_NANOSLEEP) + " --gap #[SMU][/#[SMU]] range for random time to delay between (burst of) packets - min/max;\n" + " max is optional - default is 5 times the min (units default is M (ms)),\n" +#endif /* HAVE_CLOCK_NANOSLEEP || HAVE_NANOSLEEP */ #if defined(HAVE_SO_MAX_PACING_RATE) " --fq-rate #[KMG] enable fair-queuing based socket pacing in\n" " bits/sec (Linux only)\n" @@ -236,6 +240,7 @@ const char usage_longstr[] = "Usage: iperf3 [-s|-c host] [options]\n" "\n" "[KMG] indicates options that support a K/M/G suffix for kilo-, mega-, or giga-\n" + "[SMU] indicates options that support a S/M/U time suffix for Sec, Mili, or Micro\n" "\n" #ifdef PACKAGE_URL "iperf3 homepage at: " PACKAGE_URL "\n" @@ -304,15 +309,26 @@ const char window_default[] = const char wait_server_threads[] = "Waiting for server threads to complete. Interrupt again to force quit.\n"; +const char test_start_begin[] = +"Starting Test: protocol: %s, %d streams, %d byte blocks, omitting %d seconds"; + const char test_start_time[] = -"Starting Test: protocol: %s, %d streams, %d byte blocks, omitting %d seconds, %d second test, tos %d\n"; +", %d second test"; const char test_start_bytes[] = -"Starting Test: protocol: %s, %d streams, %d byte blocks, omitting %d seconds, %"PRIuFAST64" bytes to send, tos %d\n"; +", %llu bytes to send"; const char test_start_blocks[] = -"Starting Test: protocol: %s, %d streams, %d byte blocks, omitting %d seconds, %"PRIuFAST64" blocks to send, tos %d\n"; +", %d blocks to send"; + +const char test_start_gap[] = +", gap between packets %d to %d us"; + +const char test_start_rate[] = +", %d bps"; +const char test_start_end[] = +", tos %d\n"; /* ------------------------------------------------------------------- * reports diff --git a/src/iperf_locale.h b/src/iperf_locale.h index bc9c96cb4..6319d3c63 100644 --- a/src/iperf_locale.h +++ b/src/iperf_locale.h @@ -45,9 +45,13 @@ extern const char tcp_window_size[] ; extern const char udp_buffer_size[] ; extern const char window_default[] ; extern const char wait_server_threads[] ; +extern const char test_start_begin[]; extern const char test_start_time[]; extern const char test_start_bytes[]; extern const char test_start_blocks[]; +extern const char test_start_gap[]; +extern const char test_start_rate[]; +extern const char test_start_end[]; extern const char report_time[] ; extern const char report_connecting[] ; diff --git a/src/units.c b/src/units.c index 1e8b52e07..b87e616cf 100644 --- a/src/units.c +++ b/src/units.c @@ -221,6 +221,51 @@ extern "C" return (iperf_size_t) n; } /* end unit_atoi */ + +/* ------------------------------------------------------------------- + * unit_time_atoi + * + * Similar to unit_atof, for time, with default of [ms]. + * Return [us]. + * ------------------------------------------------------------------- */ + + uint64_t unit_time_atoi(const char *s) + { + uint64_t n = 0; + char suffix = '\0'; + + assert(s != NULL); + + /* scan the number and any suffices */ + if (sscanf(s, "%lu%c", &n, &suffix) < 1) { + i_errno = IEUNITVAL; + errarg = s; + return 0; + } + + /* convert according to [Tt Gg Mm Kk] */ + switch (suffix) + { + case 's': case 'S': + n *= SEC_TO_US; + break; + case 'm': case 'M': + n *= mS_TO_US; + break; + case 'u': case 'U': + break; + case '\0': + n *= mS_TO_US; + break; + default: + i_errno = IEUNITVAL; + errarg = s; + return 0; + } + return n; + } /* end unit_time_atoi */ + + /* ------------------------------------------------------------------- * constants for byte_printf * ------------------------------------------------------------------- */ diff --git a/src/units.h b/src/units.h index 6ab921613..fdafad440 100644 --- a/src/units.h +++ b/src/units.h @@ -31,4 +31,5 @@ enum { double unit_atof( const char *s ); double unit_atof_rate( const char *s ); iperf_size_t unit_atoi( const char *s ); +uint64_t unit_time_atoi( const char *s ); void unit_snprintf( char *s, int inLen, double inNum, char inFormat );