Skip to content

Commit bc94fb4

Browse files
999Mariusclaude
andcommitted
utils: iio_stream_test: add configurable pipeline depth and latency stats
Adds -n/--num-blocks to control RX pipeline depth and -q/--quiet to suppress progress output for timing-sensitive runs. Also reports per-block min/max/mean/p50/p95/p99 latency, and fixes a segfault in the RX-only and RX/TX cleanup paths caused by treating an error pointer as a valid buffer pointer. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 925c0c1 commit bc94fb4

1 file changed

Lines changed: 124 additions & 17 deletions

File tree

utils/iio_stream_test.c

Lines changed: 124 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@
4343
#define BUFFER_SIZE_128 128 /* 128 complex samples */
4444
#define BUFFER_SIZE_256 256 /* 256 complex samples */
4545
#define BYTES_PER_COMPLEX_SAMPLE 4 /* I and Q are int16_t each */
46+
#define DEFAULT_PIPELINE_DEPTH 4 /* Number of in-flight blocks in the RX stream */
47+
#define MAX_LATENCY_SAMPLES (16 * 1024 * 1024) /* Safety cap, ~128 MiB of uint64_t */
4648

4749
/* Test modes */
4850
enum test_mode {
@@ -51,24 +53,35 @@ enum test_mode {
5153
MODE_RXTX_128,
5254
};
5355

56+
/* Per-block RX latency statistics, computed from a bounded sample buffer */
57+
struct block_latency_stats {
58+
uint64_t *samples_us; /* Array of per-block latency deltas, in microseconds */
59+
uint64_t count; /* Number of latency samples actually recorded */
60+
uint64_t capacity; /* Allocated capacity of samples_us */
61+
};
62+
5463
static const struct option options[] = {
5564
{ "ip", required_argument, 0, 'i' },
5665
{ "mode", required_argument, 0, 'm' },
5766
{ "samples", required_argument, 0, 's' },
5867
{ "output", required_argument, 0, 'o' },
5968
{ "rate", required_argument, 0, 'r' },
6069
{ "loopback", no_argument, 0, 'l' },
70+
{ "num-blocks", required_argument, 0, 'n' },
71+
{ "quiet", no_argument, 0, 'q' },
6172
{ 0, 0, 0, 0 },
6273
};
6374

6475
static const char *options_descriptions[] = {
65-
"[-i <ip>] [-m <mode>] [-s <samples>] [-o <file>] [-r <rate>] [-l]",
76+
"[-i <ip>] [-m <mode>] [-s <samples>] [-o <file>] [-r <rate>] [-l] [-n <blocks>] [-q]",
6677
"IP address of the remote board (required).",
6778
"Test mode: rx128, rx256, rxtx128 (default: rx128).",
6879
"Number of samples to capture (default: 8M).",
6980
"Output file for captured data (default: capture_<mode>.bin).",
7081
"Sample rate in Hz (default: 8000000).",
7182
"Enable digital loopback mode (default: disabled).",
83+
"RX stream pipeline depth / number of in-flight blocks, RX modes only (default: 4).",
84+
"Suppress progress output (recommended for timing-sensitive runs, e.g. under 'time').",
7285
};
7386

7487
/* Global state */
@@ -212,15 +225,67 @@ static int configure_sample_rate(uint64_t sample_rate)
212225
return 0;
213226
}
214227

228+
/* Comparator for qsort() over an array of uint64_t microsecond latencies */
229+
static int compare_u64(const void *a, const void *b)
230+
{
231+
uint64_t va = *(const uint64_t *)a;
232+
uint64_t vb = *(const uint64_t *)b;
233+
234+
if (va < vb)
235+
return -1;
236+
if (va > vb)
237+
return 1;
238+
return 0;
239+
}
240+
241+
/* Compute and print min/max/mean/percentile latency stats from a bounded sample set.
242+
* Sorts the array in place; only ever called once, after capture has completed. */
243+
static void print_latency_stats(struct block_latency_stats *stats)
244+
{
245+
uint64_t i, sum = 0;
246+
uint64_t p50_idx, p95_idx, p99_idx;
247+
248+
if (stats->count == 0) {
249+
printf("Block latency: no samples recorded\n");
250+
return;
251+
}
252+
253+
qsort(stats->samples_us, stats->count, sizeof(*stats->samples_us), compare_u64);
254+
255+
for (i = 0; i < stats->count; i++)
256+
sum += stats->samples_us[i];
257+
258+
/* Nearest-rank percentile method, clamped to the last valid index */
259+
p50_idx = (stats->count * 50) / 100;
260+
p95_idx = (stats->count * 95) / 100;
261+
p99_idx = (stats->count * 99) / 100;
262+
if (p50_idx >= stats->count)
263+
p50_idx = stats->count - 1;
264+
if (p95_idx >= stats->count)
265+
p95_idx = stats->count - 1;
266+
if (p99_idx >= stats->count)
267+
p99_idx = stats->count - 1;
268+
269+
printf("Block latency (get-next-block wait time, %" PRIu64 " samples):\n", stats->count);
270+
printf(" Min: %" PRIu64 " us\n", stats->samples_us[0]);
271+
printf(" Max: %" PRIu64 " us\n", stats->samples_us[stats->count - 1]);
272+
printf(" Mean: %.2f us\n", (double)sum / (double)stats->count);
273+
printf(" p50: %" PRIu64 " us\n", stats->samples_us[p50_idx]);
274+
printf(" p95: %" PRIu64 " us\n", stats->samples_us[p95_idx]);
275+
printf(" p99: %" PRIu64 " us\n", stats->samples_us[p99_idx]);
276+
}
277+
215278
/* RX-only capture using stream API */
216279
static int capture_rx_only(size_t block_size, uint64_t total_samples,
217-
FILE *out_file, uint64_t sample_rate, bool loopback_enabled)
280+
FILE *out_file, uint64_t sample_rate, bool loopback_enabled,
281+
size_t pipeline_depth, bool quiet)
218282
{
219283
struct iio_stream *rx_stream = NULL;
220284
struct iio_buffer *rxbuf = NULL;
221285
struct iio_channels_mask *rxmask = NULL;
222286
struct iio_channel *rx0_i, *rx0_q;
223287
const struct iio_block *rx_block = NULL;
288+
struct block_latency_stats lat_stats = {0};
224289
uint64_t samples_captured = 0;
225290
uint64_t blocks_processed = 0;
226291
uint64_t start_time_us, end_time_us;
@@ -266,8 +331,21 @@ static int capture_rx_only(size_t block_size, uint64_t total_samples,
266331
goto cleanup;
267332
}
268333

334+
/* Allocate per-block latency sample buffer, sized to the expected block count */
335+
lat_stats.capacity = (total_samples + block_size - 1) / block_size;
336+
if (lat_stats.capacity > MAX_LATENCY_SAMPLES)
337+
lat_stats.capacity = MAX_LATENCY_SAMPLES;
338+
339+
lat_stats.samples_us = malloc(lat_stats.capacity * sizeof(*lat_stats.samples_us));
340+
if (!lat_stats.samples_us) {
341+
fprintf(stderr, "Error: Failed to allocate latency sample buffer (%" PRIu64 " entries)\n",
342+
lat_stats.capacity);
343+
ret = -ENOMEM;
344+
goto cleanup;
345+
}
346+
269347
/* Create RX stream */
270-
rx_stream = iio_buffer_create_stream(rxbuf, 4, block_size, rxmask);
348+
rx_stream = iio_buffer_create_stream(rxbuf, pipeline_depth, block_size, rxmask);
271349
ret = iio_err(rx_stream);
272350
if (ret) {
273351
fprintf(stderr, "Error: Failed to create RX stream: %d\n", ret);
@@ -283,15 +361,22 @@ static int capture_rx_only(size_t block_size, uint64_t total_samples,
283361

284362
/* Capture loop */
285363
while (app_running && samples_captured < total_samples) {
364+
uint64_t block_wait_start_us, block_wait_end_us;
365+
286366
/* Get next RX block */
367+
block_wait_start_us = get_time_us();
287368
rx_block = iio_stream_get_next_block(rx_stream);
369+
block_wait_end_us = get_time_us();
288370
ret = iio_err(rx_block);
289371
if (ret) {
290372
if (app_running)
291373
fprintf(stderr, "Error: Failed to get next RX block: %d\n", ret);
292374
break;
293375
}
294376

377+
if (lat_stats.count < lat_stats.capacity)
378+
lat_stats.samples_us[lat_stats.count++] = block_wait_end_us - block_wait_start_us;
379+
295380
const void *start = iio_block_start(rx_block);
296381
const void *end = iio_block_end(rx_block);
297382
ptrdiff_t len = (uintptr_t)end - (uintptr_t)start;
@@ -317,7 +402,7 @@ static int capture_rx_only(size_t block_size, uint64_t total_samples,
317402
blocks_processed++;
318403

319404
/* Progress indicator */
320-
if (blocks_processed % 100 == 0) {
405+
if (!quiet && blocks_processed % 100 == 0) {
321406
double progress = (double)samples_captured / total_samples * 100.0;
322407
printf("\rProgress: %.1f%% (%" PRIu64 " / %" PRIu64 " samples, %"
323408
PRIu64 " blocks)",
@@ -327,14 +412,16 @@ static int capture_rx_only(size_t block_size, uint64_t total_samples,
327412
}
328413

329414
end_time_us = get_time_us();
330-
printf("\n");
415+
if (!quiet)
416+
printf("\n");
331417

332418
/* Print statistics */
333419
printf("\n--- Capture Statistics ---\n");
334420
printf("Samples captured: %" PRIu64 "\n", samples_captured);
335421
printf("Blocks processed: %" PRIu64 "\n", blocks_processed);
336422
printf("Block size: %zu complex samples\n", block_size);
337423
printf("Sample size: %zd bytes\n", sample_size);
424+
printf("Pipeline depth: %zu blocks\n", pipeline_depth);
338425

339426
if (end_time_us > start_time_us) {
340427
uint64_t duration_us = end_time_us - start_time_us;
@@ -349,18 +436,22 @@ static int capture_rx_only(size_t block_size, uint64_t total_samples,
349436
printf("Rate error: %.2f%%\n", rate_error);
350437
}
351438

439+
print_latency_stats(&lat_stats);
440+
352441
cleanup:
353-
if (rx_stream)
442+
if (rx_stream && !iio_err(rx_stream))
354443
iio_stream_destroy(rx_stream);
355444
if (rxmask)
356445
iio_channels_mask_destroy(rxmask);
446+
free(lat_stats.samples_us);
357447

358448
return ret;
359449
}
360450

361451
/* Simultaneous RX/TX capture using block API */
362452
static int capture_rxtx_simultaneous(size_t block_size, uint64_t total_samples,
363-
FILE *rx_file, FILE *tx_file, uint64_t sample_rate)
453+
FILE *rx_file, FILE *tx_file, uint64_t sample_rate,
454+
bool quiet)
364455
{
365456
struct iio_buffer_stream *rx_buf_stream = NULL;
366457
struct iio_buffer_stream *tx_buf_stream = NULL;
@@ -538,7 +629,7 @@ static int capture_rxtx_simultaneous(size_t block_size, uint64_t total_samples,
538629
}
539630

540631
/* Progress indicator */
541-
if (blocks_processed % 100 == 0) {
632+
if (!quiet && blocks_processed % 100 == 0) {
542633
double progress = (double)samples_captured / total_samples * 100.0;
543634
printf("\rProgress: %.1f%% (%" PRIu64 " / %" PRIu64 " samples, %"
544635
PRIu64 " blocks)",
@@ -548,7 +639,8 @@ static int capture_rxtx_simultaneous(size_t block_size, uint64_t total_samples,
548639
}
549640

550641
end_time_us = get_time_us();
551-
printf("\n");
642+
if (!quiet)
643+
printf("\n");
552644

553645
/* Print statistics */
554646
printf("\n--- Capture Statistics ---\n");
@@ -571,13 +663,13 @@ static int capture_rxtx_simultaneous(size_t block_size, uint64_t total_samples,
571663
}
572664

573665
cleanup:
574-
if (rx_block)
666+
if (rx_block && !iio_err(rx_block))
575667
iio_block_destroy(rx_block);
576-
if (tx_block)
668+
if (tx_block && !iio_err(tx_block))
577669
iio_block_destroy(tx_block);
578-
if (rx_buf_stream)
670+
if (rx_buf_stream && !iio_err(rx_buf_stream))
579671
iio_buffer_close(rx_buf_stream);
580-
if (tx_buf_stream)
672+
if (tx_buf_stream && !iio_err(tx_buf_stream))
581673
iio_buffer_close(tx_buf_stream);
582674
if (rxmask)
583675
iio_channels_mask_destroy(rxmask);
@@ -598,11 +690,13 @@ int main(int argc, char **argv)
598690
uint64_t total_samples = DEFAULT_DURATION_SAMPLES;
599691
uint64_t sample_rate = DEFAULT_SAMPLE_RATE_HZ;
600692
bool enable_loopback = false;
693+
bool quiet = false;
601694
struct iio_context_params params = {0};
602695
struct option *opts;
603696
FILE *out_file = NULL;
604697
FILE *tx_ref_file = NULL;
605698
size_t block_size;
699+
size_t pipeline_depth = DEFAULT_PIPELINE_DEPTH;
606700
int c, ret = EXIT_FAILURE;
607701

608702
/* Parse arguments */
@@ -612,7 +706,7 @@ int main(int argc, char **argv)
612706
return EXIT_FAILURE;
613707
}
614708

615-
while ((c = getopt_long(argc, argv, "i:m:s:o:r:l" COMMON_OPTIONS, opts, NULL)) != -1) {
709+
while ((c = getopt_long(argc, argv, "i:m:s:o:r:ln:q" COMMON_OPTIONS, opts, NULL)) != -1) {
616710
switch (c) {
617711
case 'i':
618712
ip_addr = optarg;
@@ -653,6 +747,12 @@ int main(int argc, char **argv)
653747
case 'l':
654748
enable_loopback = true;
655749
break;
750+
case 'n':
751+
pipeline_depth = sanitize_clamp("pipeline depth", optarg, 1, 4096);
752+
break;
753+
case 'q':
754+
quiet = true;
755+
break;
656756
case 'h':
657757
usage(MY_NAME, options, options_descriptions);
658758
free(opts);
@@ -672,7 +772,7 @@ int main(int argc, char **argv)
672772
if (!ip_addr) {
673773
fprintf(stderr, "Error: IP address is required\n");
674774
fprintf(stderr, "Usage: %s -i <ip_address> [-m <mode>] [-s <samples>] "
675-
"[-o <output_file>] [-r <sample_rate>]\n", MY_NAME);
775+
"[-o <output_file>] [-r <sample_rate>] [-n <pipeline_depth>] [-q]\n", MY_NAME);
676776
return EXIT_FAILURE;
677777
}
678778

@@ -768,15 +868,22 @@ int main(int argc, char **argv)
768868
sample_rate, (double)sample_rate / 1000000.0);
769869
printf(" Total samples: %" PRIu64 " (%.3f seconds)\n",
770870
total_samples, (double)total_samples / sample_rate);
871+
if (mode == MODE_RXTX_128) {
872+
if (pipeline_depth != DEFAULT_PIPELINE_DEPTH)
873+
printf(" Note: -n/--num-blocks has no effect in rxtx128 mode "
874+
"(single RX/TX block, no pipelining).\n");
875+
} else {
876+
printf(" RX pipeline depth: %zu blocks\n", pipeline_depth);
877+
}
771878
printf("========================================\n");
772879

773880
/* Run capture */
774881
if (mode == MODE_RXTX_128) {
775882
ret = capture_rxtx_simultaneous(block_size, total_samples,
776-
out_file, tx_ref_file, sample_rate);
883+
out_file, tx_ref_file, sample_rate, quiet);
777884
} else {
778885
ret = capture_rx_only(block_size, total_samples, out_file,
779-
sample_rate, enable_loopback);
886+
sample_rate, enable_loopback, pipeline_depth, quiet);
780887
}
781888

782889
if (ret < 0) {

0 commit comments

Comments
 (0)