ipyrf 2.0.0__tar.gz → 2.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. {ipyrf-2.0.0/src/ipyrf.egg-info → ipyrf-2.1.0}/PKG-INFO +131 -21
  2. {ipyrf-2.0.0 → ipyrf-2.1.0}/README.rst +127 -20
  3. {ipyrf-2.0.0 → ipyrf-2.1.0}/pyproject.toml +6 -2
  4. {ipyrf-2.0.0 → ipyrf-2.1.0}/src/ipyrf/cli.py +169 -8
  5. ipyrf-2.1.0/src/ipyrf/clock_offset.py +317 -0
  6. {ipyrf-2.0.0 → ipyrf-2.1.0}/src/ipyrf/controllers.py +2 -10
  7. {ipyrf-2.0.0 → ipyrf-2.1.0}/src/ipyrf/interactive.py +2 -10
  8. {ipyrf-2.0.0 → ipyrf-2.1.0}/src/ipyrf/logger.py +3 -3
  9. {ipyrf-2.0.0 → ipyrf-2.1.0}/src/ipyrf/pacer.py +6 -3
  10. ipyrf-2.1.0/src/ipyrf/recorder.py +190 -0
  11. ipyrf-2.1.0/src/ipyrf/report/__init__.py +15 -0
  12. ipyrf-2.1.0/src/ipyrf/report/__main__.py +4 -0
  13. ipyrf-2.1.0/src/ipyrf/report/analysis.py +579 -0
  14. ipyrf-2.1.0/src/ipyrf/report/cli.py +143 -0
  15. ipyrf-2.1.0/src/ipyrf/report/data.py +209 -0
  16. ipyrf-2.1.0/src/ipyrf/report/export.py +41 -0
  17. ipyrf-2.1.0/src/ipyrf/report/html.py +777 -0
  18. ipyrf-2.1.0/src/ipyrf/report/loader.py +215 -0
  19. ipyrf-2.1.0/src/ipyrf/report/pipeline.py +47 -0
  20. ipyrf-2.1.0/src/ipyrf/report/plots.py +593 -0
  21. {ipyrf-2.0.0 → ipyrf-2.1.0}/src/ipyrf/tcp.py +104 -43
  22. {ipyrf-2.0.0 → ipyrf-2.1.0}/src/ipyrf/test.py +20 -4
  23. {ipyrf-2.0.0 → ipyrf-2.1.0}/src/ipyrf/udp.py +42 -11
  24. {ipyrf-2.0.0 → ipyrf-2.1.0}/src/ipyrf/utils.py +35 -11
  25. {ipyrf-2.0.0 → ipyrf-2.1.0/src/ipyrf.egg-info}/PKG-INFO +131 -21
  26. {ipyrf-2.0.0 → ipyrf-2.1.0}/src/ipyrf.egg-info/SOURCES.txt +17 -2
  27. {ipyrf-2.0.0 → ipyrf-2.1.0}/src/ipyrf.egg-info/entry_points.txt +1 -0
  28. ipyrf-2.1.0/src/ipyrf.egg-info/requires.txt +8 -0
  29. ipyrf-2.1.0/test/test_clock_offset.py +290 -0
  30. {ipyrf-2.0.0 → ipyrf-2.1.0}/test/test_ipyrf.py +45 -15
  31. ipyrf-2.1.0/test/test_pacer.py +22 -0
  32. ipyrf-2.1.0/test/test_recorder.py +338 -0
  33. ipyrf-2.1.0/test/test_report.py +414 -0
  34. ipyrf-2.1.0/test/test_utils.py +25 -0
  35. ipyrf-2.0.0/src/ipyrf/packet_recorder.py +0 -115
  36. ipyrf-2.0.0/src/ipyrf.egg-info/requires.txt +0 -4
  37. ipyrf-2.0.0/test/test_packet_recorder.py +0 -262
  38. {ipyrf-2.0.0 → ipyrf-2.1.0}/LICENSE +0 -0
  39. {ipyrf-2.0.0 → ipyrf-2.1.0}/setup.cfg +0 -0
  40. {ipyrf-2.0.0 → ipyrf-2.1.0}/src/ipyrf/__init__.py +0 -0
  41. {ipyrf-2.0.0 → ipyrf-2.1.0}/src/ipyrf/__main__.py +0 -0
  42. {ipyrf-2.0.0 → ipyrf-2.1.0}/src/ipyrf/handshake.py +0 -0
  43. {ipyrf-2.0.0 → ipyrf-2.1.0}/src/ipyrf/latency.py +0 -0
  44. {ipyrf-2.0.0 → ipyrf-2.1.0}/src/ipyrf/traffic_pattern.py +0 -0
  45. {ipyrf-2.0.0 → ipyrf-2.1.0}/src/ipyrf/video_trace.py +0 -0
  46. {ipyrf-2.0.0 → ipyrf-2.1.0}/src/ipyrf.egg-info/dependency_links.txt +0 -0
  47. {ipyrf-2.0.0 → ipyrf-2.1.0}/src/ipyrf.egg-info/top_level.txt +0 -0
  48. {ipyrf-2.0.0 → ipyrf-2.1.0}/test/test_bind_to_device.py +0 -0
  49. {ipyrf-2.0.0 → ipyrf-2.1.0}/test/test_controllers.py +0 -0
  50. {ipyrf-2.0.0 → ipyrf-2.1.0}/test/test_handshake.py +0 -0
  51. {ipyrf-2.0.0 → ipyrf-2.1.0}/test/test_tcp_framing.py +0 -0
  52. {ipyrf-2.0.0 → ipyrf-2.1.0}/test/test_traffic_pattern.py +0 -0
  53. {ipyrf-2.0.0 → ipyrf-2.1.0}/test/test_video_trace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ipyrf
3
- Version: 2.0.0
3
+ Version: 2.1.0
4
4
  Summary: A tiny iperf3-like tool with JSON output (TCP/UDP)
5
5
  Author: Steinwurf ApS
6
6
  License: MIT License
@@ -40,9 +40,12 @@ Classifier: Topic :: System :: Benchmark
40
40
  Requires-Python: >=3.8
41
41
  Description-Content-Type: text/x-rst
42
42
  License-File: LICENSE
43
+ Requires-Dist: plotly>=5.18
43
44
  Provides-Extra: test
44
45
  Requires-Dist: pytest>=7.0; extra == "test"
45
46
  Requires-Dist: pytest-cov>=4.0; extra == "test"
47
+ Provides-Extra: report-export
48
+ Requires-Dist: kaleido>=0.2; extra == "report-export"
46
49
  Dynamic: license-file
47
50
 
48
51
  ipyrf
@@ -56,9 +59,11 @@ Features
56
59
  - JSON or human-readable output
57
60
  - Optional bandwidth capping (TCP/UDP)
58
61
  - UDP packet loss estimation
59
- - Optional per-datagram UDP packet recording
62
+ - Optional receive-side recording of UDP datagrams and TCP framed records
63
+ - Interactive HTML reports from ``--record`` CSVs (``ipyrf-report``)
60
64
  - Linux TCP congestion control selection (if available)
61
65
  - Client ``--bind-dev`` to transmit via a specific network interface (Linux)
66
+ - Clock-offset measurement to a remote host over SSH (``ipyrf offset``)
62
67
 
63
68
  Installation
64
69
  ------------
@@ -125,7 +130,7 @@ The project includes a comprehensive test suite using pytest:
125
130
  Usage
126
131
  -----
127
132
 
128
- The package installs a console script named ``ipyrf``.
133
+ The package installs console scripts named ``ipyrf`` and ``ipyrf-report``.
129
134
 
130
135
  Quick examples
131
136
  ~~~~~~~~~~~~~~
@@ -135,6 +140,7 @@ TCP server:
135
140
  .. code-block:: bash
136
141
 
137
142
  ipyrf tcp server 0.0.0.0 --port 12345
143
+ ipyrf tcp server 0.0.0.0 --port 12345 --record records.csv
138
144
  ipyrf tcp server 0.0.0.0 --port 12345 --trace-dir /var/traces
139
145
 
140
146
  TCP client:
@@ -154,7 +160,7 @@ UDP server:
154
160
  .. code-block:: bash
155
161
 
156
162
  ipyrf udp server 0.0.0.0 --port 12345
157
- ipyrf udp server 0.0.0.0 --port 12345 --packet-record packets.csv
163
+ ipyrf udp server 0.0.0.0 --port 12345 --record records.csv
158
164
  ipyrf udp server 0.0.0.0 --port 12345 --trace-dir /var/traces
159
165
 
160
166
  UDP client (with bandwidth cap and optional payload size):
@@ -169,6 +175,13 @@ UDP client (with bandwidth cap and optional payload size):
169
175
  ipyrf udp client 127.0.0.1 --port 12345 --bandwidth 50M --time 5 --reverse
170
176
  ipyrf udp client 127.0.0.1 --port 12345 --traffic-pattern trace.json --reverse
171
177
 
178
+ Clock offset (this host vs a remote SSH machine):
179
+
180
+ .. code-block:: bash
181
+
182
+ ipyrf offset user@rasp1
183
+ ipyrf offset user@rasp1 --json
184
+
172
185
  Reverse mode
173
186
  ------------
174
187
 
@@ -320,12 +333,16 @@ Top-level structure:
320
333
 
321
334
  ipyrf [tcp|udp] [server|client] [OPTIONS]
322
335
  ipyrf generate video -o FILE [OPTIONS]
336
+ ipyrf offset HOST [OPTIONS]
337
+ ipyrf-report RECORD [OPTIONS]
323
338
 
324
339
  Common options (both protocols, both roles):
325
340
 
326
341
  - ``--port``: Port (default 5201)
327
342
  - ``--logfile``: Redirect output to a file
328
343
  - ``--json_log``: Emit logs in JSON (newline-delimited)
344
+ - ``--record PATH``: Write received records to a CSV file after the test
345
+ (UDP datagrams or TCP framed records). On a client, requires ``--reverse``
329
346
 
330
347
  TCP-specific options:
331
348
 
@@ -361,7 +378,6 @@ UDP-specific options:
361
378
  is sent; the server must have the same file
362
379
  - ``--loops N``: Replay ``--traffic-pattern`` N times (default 1)
363
380
  - ``-l/--length``: UDP payload length (default 1200)
364
- - ``--packet-record PATH``: UDP server: write received packet traces to a CSV file after the test
365
381
  - ``--inactivity-timeout SECONDS``: UDP server: exit after this many seconds
366
382
  without receiving packets (default 2.0)
367
383
  - ``--trace-dir DIR``: UDP server: directory of traffic-pattern files for
@@ -384,33 +400,127 @@ Generate options:
384
400
  - ``--duration``: Synthetic length in seconds (default 10), or optional
385
401
  truncate when importing from ffprobe
386
402
 
387
- UDP packet recording
388
- --------------------
389
-
390
- Pass ``--packet-record PATH`` to a UDP server to record one trace entry per
391
- received datagram (sequence number, sender timestamp, receiver timestamp,
392
- packet size, and traffic-pattern ``event_id``). Records are packed into
393
- fixed-size in-memory chunks during the test so the receive path stays free of
394
- CSV I/O. Chunks are allocated before the test starts (about 20 s of 1 Gbit/s
395
- traffic by default) so rotation does not stall the receive loop. When the test
396
- ends, the CSV file (columns ``sequence``, ``size_bytes``, ``transmitted_ns``,
397
- ``received_ns``, ``event_id``) is written to ``PATH``. Per-packet latency in
398
- nanoseconds is ``received_ns - transmitted_ns``. ``event_id`` is ``0`` unless
399
- the client used ``--traffic-pattern``.
403
+ Offset options:
404
+
405
+ - ``offset HOST``: Measure clock offset to ``HOST`` over SSH
406
+ - ``--samples N``: Number of round-trip samples (default 30)
407
+ - ``--interval SECONDS``: Delay between samples (default 0.2)
408
+ - ``--json``: Write a JSON summary to stdout
409
+ - ``--python CMD``: Python interpreter on the remote host (default
410
+ ``python3``)
411
+ - ``--ssh-arg ARG``: Extra argument forwarded to ``ssh`` (repeatable)
412
+
413
+ Report options (``ipyrf-report``):
414
+
415
+ - ``RECORD``: Record CSV from ``--record``
416
+ - ``-o/--output FILE``: HTML output path (default: ``RECORD`` with ``.html``)
417
+ - ``--traffic-pattern FILE``: Traffic-pattern JSON used during the test
418
+ - ``--json FILE``: Also write analysis results as JSON
419
+ - ``--png [DIR]`` / ``--svg [DIR]``: Export static plots (requires kaleido)
420
+ - ``--bin-ms MS``: Time-series bin size in milliseconds (default: auto)
421
+ - ``--title TITLE``: Report title
422
+ - ``--clock-offset MS``: Add this many milliseconds to one-way latency
423
+ (value from ``ipyrf offset`` run on the receiving host against the
424
+ sender). The HTML report can also change this after generation
425
+
426
+ Receive recording
427
+ -----------------
428
+
429
+ Pass ``--record PATH`` on the receiving side (a server, or a client with
430
+ ``--reverse``) to write one CSV row per received UDP datagram or complete
431
+ TCP framed record (sequence, sender timestamp, receiver timestamp, size,
432
+ and traffic-pattern ``event_id``). UDP uses the sender sequence number so
433
+ loss and reordering stay visible. TCP assigns sequence in receive order
434
+ (1, 2, 3, …) because the stream has no packet sequence. Records are packed
435
+ into fixed-size in-memory chunks during the test so the receive path stays
436
+ free of CSV I/O. Chunks are allocated before the test starts (about 20 s of
437
+ 1 Gbit/s traffic by default) so rotation does not stall the receive loop.
438
+ When the test ends, the CSV file is written to ``PATH``. It starts with a
439
+ ``# ipyrf-record 1`` magic line and a ``# { ... }`` JSON comment describing
440
+ the run (protocol, sequence kind, addresses, optional traffic pattern),
441
+ then the header row and data (columns ``sequence``, ``size_bytes``,
442
+ ``transmitted_ns``, ``received_ns``, ``event_id``).
443
+ One-way latency in nanoseconds is ``received_ns - transmitted_ns``.
444
+ That difference includes any clock offset between sender and receiver;
445
+ use ``ipyrf offset`` to measure it. ``event_id`` is ``0`` unless the
446
+ sender used ``--traffic-pattern``.
447
+ ``ipyrf-report`` reads that metadata to label the run and to omit TCP
448
+ loss/reorder charts. Pass ``--traffic-pattern`` to override an embedded
449
+ pattern. Spreadsheet tools treat the ``#`` lines as extra rows; skip them
450
+ or use ``ipyrf-report``.
400
451
 
401
452
  Quick local test (two terminals):
402
453
 
403
454
  .. code-block:: bash
404
455
 
405
456
  # Terminal 1 — server with recording
406
- ipyrf udp server 0.0.0.0 --port 12345 --packet-record packets.csv
457
+ ipyrf udp server 0.0.0.0 --port 12345 --record records.csv
458
+ # or: ipyrf tcp server 0.0.0.0 --port 12345 --record records.csv
407
459
 
408
460
  # Terminal 2 — short paced client
409
461
  ipyrf udp client 127.0.0.1 --port 12345 --bandwidth 50M --time 2 -l 1200
462
+ # or: ipyrf tcp client 127.0.0.1 --port 12345 --bandwidth 50M --time 2
410
463
 
411
- When the client finishes, the server exits and ``packets.csv`` contains the
464
+ When the client finishes, the server exits and ``records.csv`` contains the
412
465
  trace. Use ``--enable-latency`` on the client if you also want latency
413
- stats in the server summary.
466
+ stats in the server summary. Turn the CSV into an interactive HTML report
467
+ with ``ipyrf-report`` (see below).
468
+
469
+ Reports (ipyrf-report)
470
+ ----------------------
471
+
472
+ ``ipyrf-report`` reads a ``--record`` CSV (including the JSON metadata
473
+ comment) and writes one self-contained interactive HTML file (Plotly.js is
474
+ embedded, so the file works offline). Pass ``--traffic-pattern`` to override
475
+ a pattern embedded in the record metadata; otherwise the report uses the
476
+ embedded pattern when present. Pass ``--clock-offset MS`` to add that many
477
+ milliseconds to one-way latency (the HTML header also has a clock-offset
478
+ field you can edit later without regenerating).
479
+
480
+ The report is laid out like a performance trace: KPI overview at the top,
481
+ then throughput, latency (p50 / p95 / p99), loss and sequence / reordering
482
+ when the recording is UDP, send vs receive spacing, and latency
483
+ distribution / ECDF.
484
+
485
+ .. code-block:: bash
486
+
487
+ ipyrf-report records.csv
488
+ ipyrf-report records.csv -o report.html --traffic-pattern trace.json
489
+ ipyrf-report records.csv --json report.json --bin-ms 10
490
+ ipyrf-report records.csv --png --svg
491
+ ipyrf-report records.csv --clock-offset 1.23
492
+
493
+ ``-o`` defaults to the record path with a ``.html`` suffix. ``--png`` and
494
+ ``--svg`` write individual plots next to the HTML (or to a directory you
495
+ pass); they require the optional ``kaleido`` extra:
496
+
497
+ .. code-block:: bash
498
+
499
+ python3 -m pip install 'ipyrf[report-export]'
500
+
501
+ Clock offset
502
+ ------------
503
+
504
+ ``ipyrf offset HOST`` measures the wall-clock offset between this machine
505
+ and a remote SSH host. It does not change either clock.
506
+
507
+ This is useful when comparing ``transmitted_ns`` and ``received_ns`` in a
508
+ ``--record`` CSV from a two-host test. One-way latency is
509
+ ``received_ns - transmitted_ns``. If you run ``ipyrf offset`` on the
510
+ receiving host against the sender, pass that value to
511
+ ``ipyrf-report --clock-offset`` (or type it into the HTML report). That
512
+ adds the offset so latency is in the receiver's time base. If this host
513
+ sent and the remote recorded, negate the printed offset first.
514
+
515
+ The remote host needs ``python3`` (or pass ``--python``). SSH uses your
516
+ normal configuration and keys.
517
+
518
+ .. code-block:: bash
519
+
520
+ ipyrf offset user@rasp1
521
+ ipyrf offset user@rasp1 --samples 50 --interval 0.1
522
+ ipyrf offset rasp1 --json
523
+ ipyrf offset rasp1 --ssh-arg=-p --ssh-arg=2222
414
524
 
415
525
  JSON logging
416
526
  ------------
@@ -9,9 +9,11 @@ Features
9
9
  - JSON or human-readable output
10
10
  - Optional bandwidth capping (TCP/UDP)
11
11
  - UDP packet loss estimation
12
- - Optional per-datagram UDP packet recording
12
+ - Optional receive-side recording of UDP datagrams and TCP framed records
13
+ - Interactive HTML reports from ``--record`` CSVs (``ipyrf-report``)
13
14
  - Linux TCP congestion control selection (if available)
14
15
  - Client ``--bind-dev`` to transmit via a specific network interface (Linux)
16
+ - Clock-offset measurement to a remote host over SSH (``ipyrf offset``)
15
17
 
16
18
  Installation
17
19
  ------------
@@ -78,7 +80,7 @@ The project includes a comprehensive test suite using pytest:
78
80
  Usage
79
81
  -----
80
82
 
81
- The package installs a console script named ``ipyrf``.
83
+ The package installs console scripts named ``ipyrf`` and ``ipyrf-report``.
82
84
 
83
85
  Quick examples
84
86
  ~~~~~~~~~~~~~~
@@ -88,6 +90,7 @@ TCP server:
88
90
  .. code-block:: bash
89
91
 
90
92
  ipyrf tcp server 0.0.0.0 --port 12345
93
+ ipyrf tcp server 0.0.0.0 --port 12345 --record records.csv
91
94
  ipyrf tcp server 0.0.0.0 --port 12345 --trace-dir /var/traces
92
95
 
93
96
  TCP client:
@@ -107,7 +110,7 @@ UDP server:
107
110
  .. code-block:: bash
108
111
 
109
112
  ipyrf udp server 0.0.0.0 --port 12345
110
- ipyrf udp server 0.0.0.0 --port 12345 --packet-record packets.csv
113
+ ipyrf udp server 0.0.0.0 --port 12345 --record records.csv
111
114
  ipyrf udp server 0.0.0.0 --port 12345 --trace-dir /var/traces
112
115
 
113
116
  UDP client (with bandwidth cap and optional payload size):
@@ -122,6 +125,13 @@ UDP client (with bandwidth cap and optional payload size):
122
125
  ipyrf udp client 127.0.0.1 --port 12345 --bandwidth 50M --time 5 --reverse
123
126
  ipyrf udp client 127.0.0.1 --port 12345 --traffic-pattern trace.json --reverse
124
127
 
128
+ Clock offset (this host vs a remote SSH machine):
129
+
130
+ .. code-block:: bash
131
+
132
+ ipyrf offset user@rasp1
133
+ ipyrf offset user@rasp1 --json
134
+
125
135
  Reverse mode
126
136
  ------------
127
137
 
@@ -273,12 +283,16 @@ Top-level structure:
273
283
 
274
284
  ipyrf [tcp|udp] [server|client] [OPTIONS]
275
285
  ipyrf generate video -o FILE [OPTIONS]
286
+ ipyrf offset HOST [OPTIONS]
287
+ ipyrf-report RECORD [OPTIONS]
276
288
 
277
289
  Common options (both protocols, both roles):
278
290
 
279
291
  - ``--port``: Port (default 5201)
280
292
  - ``--logfile``: Redirect output to a file
281
293
  - ``--json_log``: Emit logs in JSON (newline-delimited)
294
+ - ``--record PATH``: Write received records to a CSV file after the test
295
+ (UDP datagrams or TCP framed records). On a client, requires ``--reverse``
282
296
 
283
297
  TCP-specific options:
284
298
 
@@ -314,7 +328,6 @@ UDP-specific options:
314
328
  is sent; the server must have the same file
315
329
  - ``--loops N``: Replay ``--traffic-pattern`` N times (default 1)
316
330
  - ``-l/--length``: UDP payload length (default 1200)
317
- - ``--packet-record PATH``: UDP server: write received packet traces to a CSV file after the test
318
331
  - ``--inactivity-timeout SECONDS``: UDP server: exit after this many seconds
319
332
  without receiving packets (default 2.0)
320
333
  - ``--trace-dir DIR``: UDP server: directory of traffic-pattern files for
@@ -337,33 +350,127 @@ Generate options:
337
350
  - ``--duration``: Synthetic length in seconds (default 10), or optional
338
351
  truncate when importing from ffprobe
339
352
 
340
- UDP packet recording
341
- --------------------
342
-
343
- Pass ``--packet-record PATH`` to a UDP server to record one trace entry per
344
- received datagram (sequence number, sender timestamp, receiver timestamp,
345
- packet size, and traffic-pattern ``event_id``). Records are packed into
346
- fixed-size in-memory chunks during the test so the receive path stays free of
347
- CSV I/O. Chunks are allocated before the test starts (about 20 s of 1 Gbit/s
348
- traffic by default) so rotation does not stall the receive loop. When the test
349
- ends, the CSV file (columns ``sequence``, ``size_bytes``, ``transmitted_ns``,
350
- ``received_ns``, ``event_id``) is written to ``PATH``. Per-packet latency in
351
- nanoseconds is ``received_ns - transmitted_ns``. ``event_id`` is ``0`` unless
352
- the client used ``--traffic-pattern``.
353
+ Offset options:
354
+
355
+ - ``offset HOST``: Measure clock offset to ``HOST`` over SSH
356
+ - ``--samples N``: Number of round-trip samples (default 30)
357
+ - ``--interval SECONDS``: Delay between samples (default 0.2)
358
+ - ``--json``: Write a JSON summary to stdout
359
+ - ``--python CMD``: Python interpreter on the remote host (default
360
+ ``python3``)
361
+ - ``--ssh-arg ARG``: Extra argument forwarded to ``ssh`` (repeatable)
362
+
363
+ Report options (``ipyrf-report``):
364
+
365
+ - ``RECORD``: Record CSV from ``--record``
366
+ - ``-o/--output FILE``: HTML output path (default: ``RECORD`` with ``.html``)
367
+ - ``--traffic-pattern FILE``: Traffic-pattern JSON used during the test
368
+ - ``--json FILE``: Also write analysis results as JSON
369
+ - ``--png [DIR]`` / ``--svg [DIR]``: Export static plots (requires kaleido)
370
+ - ``--bin-ms MS``: Time-series bin size in milliseconds (default: auto)
371
+ - ``--title TITLE``: Report title
372
+ - ``--clock-offset MS``: Add this many milliseconds to one-way latency
373
+ (value from ``ipyrf offset`` run on the receiving host against the
374
+ sender). The HTML report can also change this after generation
375
+
376
+ Receive recording
377
+ -----------------
378
+
379
+ Pass ``--record PATH`` on the receiving side (a server, or a client with
380
+ ``--reverse``) to write one CSV row per received UDP datagram or complete
381
+ TCP framed record (sequence, sender timestamp, receiver timestamp, size,
382
+ and traffic-pattern ``event_id``). UDP uses the sender sequence number so
383
+ loss and reordering stay visible. TCP assigns sequence in receive order
384
+ (1, 2, 3, …) because the stream has no packet sequence. Records are packed
385
+ into fixed-size in-memory chunks during the test so the receive path stays
386
+ free of CSV I/O. Chunks are allocated before the test starts (about 20 s of
387
+ 1 Gbit/s traffic by default) so rotation does not stall the receive loop.
388
+ When the test ends, the CSV file is written to ``PATH``. It starts with a
389
+ ``# ipyrf-record 1`` magic line and a ``# { ... }`` JSON comment describing
390
+ the run (protocol, sequence kind, addresses, optional traffic pattern),
391
+ then the header row and data (columns ``sequence``, ``size_bytes``,
392
+ ``transmitted_ns``, ``received_ns``, ``event_id``).
393
+ One-way latency in nanoseconds is ``received_ns - transmitted_ns``.
394
+ That difference includes any clock offset between sender and receiver;
395
+ use ``ipyrf offset`` to measure it. ``event_id`` is ``0`` unless the
396
+ sender used ``--traffic-pattern``.
397
+ ``ipyrf-report`` reads that metadata to label the run and to omit TCP
398
+ loss/reorder charts. Pass ``--traffic-pattern`` to override an embedded
399
+ pattern. Spreadsheet tools treat the ``#`` lines as extra rows; skip them
400
+ or use ``ipyrf-report``.
353
401
 
354
402
  Quick local test (two terminals):
355
403
 
356
404
  .. code-block:: bash
357
405
 
358
406
  # Terminal 1 — server with recording
359
- ipyrf udp server 0.0.0.0 --port 12345 --packet-record packets.csv
407
+ ipyrf udp server 0.0.0.0 --port 12345 --record records.csv
408
+ # or: ipyrf tcp server 0.0.0.0 --port 12345 --record records.csv
360
409
 
361
410
  # Terminal 2 — short paced client
362
411
  ipyrf udp client 127.0.0.1 --port 12345 --bandwidth 50M --time 2 -l 1200
412
+ # or: ipyrf tcp client 127.0.0.1 --port 12345 --bandwidth 50M --time 2
363
413
 
364
- When the client finishes, the server exits and ``packets.csv`` contains the
414
+ When the client finishes, the server exits and ``records.csv`` contains the
365
415
  trace. Use ``--enable-latency`` on the client if you also want latency
366
- stats in the server summary.
416
+ stats in the server summary. Turn the CSV into an interactive HTML report
417
+ with ``ipyrf-report`` (see below).
418
+
419
+ Reports (ipyrf-report)
420
+ ----------------------
421
+
422
+ ``ipyrf-report`` reads a ``--record`` CSV (including the JSON metadata
423
+ comment) and writes one self-contained interactive HTML file (Plotly.js is
424
+ embedded, so the file works offline). Pass ``--traffic-pattern`` to override
425
+ a pattern embedded in the record metadata; otherwise the report uses the
426
+ embedded pattern when present. Pass ``--clock-offset MS`` to add that many
427
+ milliseconds to one-way latency (the HTML header also has a clock-offset
428
+ field you can edit later without regenerating).
429
+
430
+ The report is laid out like a performance trace: KPI overview at the top,
431
+ then throughput, latency (p50 / p95 / p99), loss and sequence / reordering
432
+ when the recording is UDP, send vs receive spacing, and latency
433
+ distribution / ECDF.
434
+
435
+ .. code-block:: bash
436
+
437
+ ipyrf-report records.csv
438
+ ipyrf-report records.csv -o report.html --traffic-pattern trace.json
439
+ ipyrf-report records.csv --json report.json --bin-ms 10
440
+ ipyrf-report records.csv --png --svg
441
+ ipyrf-report records.csv --clock-offset 1.23
442
+
443
+ ``-o`` defaults to the record path with a ``.html`` suffix. ``--png`` and
444
+ ``--svg`` write individual plots next to the HTML (or to a directory you
445
+ pass); they require the optional ``kaleido`` extra:
446
+
447
+ .. code-block:: bash
448
+
449
+ python3 -m pip install 'ipyrf[report-export]'
450
+
451
+ Clock offset
452
+ ------------
453
+
454
+ ``ipyrf offset HOST`` measures the wall-clock offset between this machine
455
+ and a remote SSH host. It does not change either clock.
456
+
457
+ This is useful when comparing ``transmitted_ns`` and ``received_ns`` in a
458
+ ``--record`` CSV from a two-host test. One-way latency is
459
+ ``received_ns - transmitted_ns``. If you run ``ipyrf offset`` on the
460
+ receiving host against the sender, pass that value to
461
+ ``ipyrf-report --clock-offset`` (or type it into the HTML report). That
462
+ adds the offset so latency is in the receiver's time base. If this host
463
+ sent and the remote recorded, negate the printed offset first.
464
+
465
+ The remote host needs ``python3`` (or pass ``--python``). SSH uses your
466
+ normal configuration and keys.
467
+
468
+ .. code-block:: bash
469
+
470
+ ipyrf offset user@rasp1
471
+ ipyrf offset user@rasp1 --samples 50 --interval 0.1
472
+ ipyrf offset rasp1 --json
473
+ ipyrf offset rasp1 --ssh-arg=-p --ssh-arg=2222
367
474
 
368
475
  JSON logging
369
476
  ------------
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ipyrf"
7
- version = "2.0.0"
7
+ version = "2.1.0"
8
8
  description = "A tiny iperf3-like tool with JSON output (TCP/UDP)"
9
9
  readme = "README.rst"
10
10
  requires-python = ">=3.8"
@@ -21,10 +21,13 @@ classifiers = [
21
21
  "Topic :: System :: Networking",
22
22
  "Topic :: System :: Benchmark",
23
23
  ]
24
- dependencies = []
24
+ dependencies = [
25
+ "plotly>=5.18",
26
+ ]
25
27
 
26
28
  [project.optional-dependencies]
27
29
  test = ["pytest>=7.0", "pytest-cov>=4.0"]
30
+ report-export = ["kaleido>=0.2"]
28
31
 
29
32
  [project.urls]
30
33
  Homepage = "https://github.com/steinwurf/ipyrf"
@@ -33,6 +36,7 @@ Issues = "https://github.com/steinwurf/ipyrf/issues"
33
36
 
34
37
  [project.scripts]
35
38
  ipyrf = "ipyrf.__main__:main"
39
+ ipyrf-report = "ipyrf.report.cli:main"
36
40
 
37
41
  [tool.setuptools]
38
42
  package-dir = {"" = "src"}