xfr 0.7.1

Modern network bandwidth testing with TUI
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# Scripting & CI/CD Guide

xfr is designed for automation with non-interactive modes, structured output, and CI-friendly features.

## Non-Interactive Modes

For scripts and CI pipelines, use these flags:

```bash
xfr <host> --no-tui         # Disable TUI, plain text output
xfr <host> --json           # JSON summary at end
xfr <host> --json-stream    # JSON per interval (for real-time parsing)
xfr <host> --csv            # CSV output
xfr <host> -q               # Quiet mode (summary only)
```

### One-Off Server Mode

For scripted or CI tests, use `--one-off` so the server exits after a single test
completes. This works with both TCP and QUIC clients:

```bash
# Server: start in one-off mode (exits after one test)
xfr serve --one-off &
SERVER_PID=$!

# Client: run the test
xfr localhost --json --no-tui -t 10s > result.json

# Server will exit automatically after the test completes
wait $SERVER_PID
```

### Exit Codes

| Code | Meaning |
|------|---------|
| 0 | Test completed successfully |
| 1 | Connection or protocol error |
| 2 | Invalid arguments |

## Firewall / Port Requirements

**TCP** uses single-port mode: both the control channel and all data streams
run over port 5201 (or whichever port you configure with `-p`). No ephemeral
data ports are opened, so only one port needs to be allowed through the
firewall.

**QUIC** also uses a single port (5201/UDP by default). The server listens on
the same port number for both TCP and QUIC simultaneously.

**UDP** mode allocates separate ephemeral data ports on the server for each
stream. The control channel still uses TCP port 5201, but data transfer happens
on dynamically assigned UDP ports. If you are behind a restrictive firewall,
you may need to allow a range of high ports or use TCP/QUIC instead.

## JSON Output

### Summary JSON (`--json`)

```bash
xfr <host> --json --no-tui > result.json
```

The output matches the `TestResult` structure (protocol version 1.1):

```json
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "bytes_total": 1234567890,
  "duration_ms": 10000,
  "throughput_mbps": 987.65,
  "streams": [
    {
      "id": 0,
      "bytes": 1234567890,
      "throughput_mbps": 987.65,
      "retransmits": 42
    }
  ],
  "tcp_info": {
    "retransmits": 42,
    "rtt_us": 1200,
    "rtt_var_us": 100,
    "cwnd": 10
  }
}
```

Notes:
- `tcp_info` is present only for TCP tests.
- `udp_stats` (with fields `packets_sent`, `packets_received`, `lost`,
  `lost_percent`, `jitter_ms`, `out_of_order`) is present only for UDP tests.
- Per-stream `retransmits` appears for TCP, `jitter_ms` and `lost` for UDP.
  Fields that do not apply are omitted (not set to zero).

### Streaming JSON (`--json-stream`)

One JSON object per line, per interval:

```bash
xfr <host> --json-stream --no-tui
```

```
{"timestamp":"1.000","elapsed_secs":1.0,"bytes":118775000,"throughput_mbps":950.2,"retransmits":0,"rtt_us":1200,"cwnd":65535}
{"timestamp":"2.000","elapsed_secs":2.0,"bytes":122562500,"throughput_mbps":980.5,"retransmits":2,"rtt_us":1150,"cwnd":65535}
...
```

Each line may also include optional fields: `retransmits`, `rtt_us`, `cwnd`
(TCP), `jitter_ms` and `lost` (UDP). Fields that do not apply are omitted.

### Parsing with jq

```bash
# Get final throughput
xfr <host> --json --no-tui | jq '.throughput_mbps'

# Get average from streaming output
xfr <host> --json-stream --no-tui | jq -s 'map(.throughput_mbps) | add / length'

# Check if throughput meets threshold
xfr <host> --json --no-tui | jq -e '.throughput_mbps > 900' || echo "FAIL"

# Get TCP retransmits from summary
xfr <host> --json --no-tui | jq '.tcp_info.retransmits // 0'
```

## CSV Output

```bash
xfr <host> --csv --no-tui > results.csv
```

Interval output format (one line per reporting interval):
```
timestamp,elapsed_secs,bytes,throughput_mbps,retransmits,jitter_ms,lost,rtt_us,cwnd
1.000,1.00,118775000,950.20,0,0,0,1200,65535
2.000,2.00,122562500,980.50,2,0,0,1150,65535
```

After intervals, a summary line is printed:
```
test_id,duration_secs,transfer_bytes,throughput_mbps,retransmits,jitter_ms,lost,lost_percent
550e8400-...,10.00,1234567890,987.65,42,0.00,0,0.00
```

## CI/CD Examples

### GitHub Actions

```yaml
name: Network Performance

on:
  schedule:
    - cron: '0 */4 * * *'  # Every 4 hours

jobs:
  bandwidth-test:
    runs-on: ubuntu-latest
    steps:
      - name: Install xfr
        run: cargo install xfr

      - name: Run bandwidth test
        run: |
          xfr ${{ secrets.TEST_SERVER }} \
            --json --no-tui \
            -t 30s -P 4 \
            > result.json

      - name: Check threshold
        run: |
          THROUGHPUT=$(jq '.throughput_mbps' result.json)
          if (( $(echo "$THROUGHPUT < 100" | bc -l) )); then
            echo "::error::Throughput $THROUGHPUT Mbps below 100 Mbps threshold"
            exit 1
          fi

      - name: Upload results
        uses: actions/upload-artifact@v4
        with:
          name: bandwidth-result
          path: result.json
```

### GitHub Actions (Self-Contained with One-Off Server)

For integration tests that do not depend on an external server:

```yaml
jobs:
  bandwidth-test:
    runs-on: ubuntu-latest
    steps:
      - name: Install xfr
        run: cargo install xfr

      - name: Run self-contained test
        run: |
          xfr serve --one-off &
          sleep 1
          xfr localhost --json --no-tui -t 5s > result.json
          wait
          jq '.throughput_mbps' result.json
```

### GitLab CI

```yaml
network-test:
  image: rust:latest
  script:
    - cargo install xfr
    - xfr $TEST_SERVER --json --no-tui -t 30s > result.json
    - |
      THROUGHPUT=$(jq '.throughput_mbps' result.json)
      if [ $(echo "$THROUGHPUT < 100" | bc) -eq 1 ]; then
        echo "Throughput too low: $THROUGHPUT Mbps"
        exit 1
      fi
  artifacts:
    paths:
      - result.json
```

## Docker Usage

xfr doesn't require special capabilities (unlike tools needing raw sockets).

For TCP and QUIC, only port 5201 needs to be exposed. UDP tests require
additional ephemeral ports for data transfer.

### Run as Server

```bash
# TCP + QUIC (single port)
docker run --rm -p 5201:5201 -p 5201:5201/udp rust:slim sh -c \
  'cargo install xfr && xfr serve'
```

### Run as Client

```bash
docker run --rm rust:slim sh -c \
  'cargo install xfr && xfr host.docker.internal --json --no-tui'
```

### Docker Compose

```yaml
version: '3.8'
services:
  xfr-server:
    image: rust:slim
    command: sh -c 'cargo install xfr && xfr serve'
    ports:
      - "5201:5201"       # TCP control + data
      - "5201:5201/udp"   # QUIC
```

## Shell Script Examples

### Baseline Comparison

```bash
#!/bin/bash
# Compare current performance against baseline

BASELINE="baseline.json"
THRESHOLD=5  # Percent regression allowed

# Run test
xfr $SERVER --json --no-tui -t 30s > current.json

# Compare
if [ -f "$BASELINE" ]; then
    xfr diff "$BASELINE" current.json --threshold ${THRESHOLD}
    if [ $? -ne 0 ]; then
        echo "Performance regression detected!"
        exit 1
    fi
fi

# Update baseline on success
cp current.json "$BASELINE"
```

### Multi-Protocol Test Suite

```bash
#!/bin/bash
# Test all protocols and aggregate results

SERVER=$1
RESULTS_DIR="results/$(date +%Y%m%d_%H%M%S)"
mkdir -p "$RESULTS_DIR"

echo "Testing TCP..."
xfr $SERVER --json --no-tui -t 10s > "$RESULTS_DIR/tcp.json"

echo "Testing UDP at 1G..."
xfr $SERVER -u -b 1G --json --no-tui -t 10s > "$RESULTS_DIR/udp.json"

echo "Testing QUIC..."
xfr $SERVER --quic --json --no-tui -t 10s > "$RESULTS_DIR/quic.json"

echo "Testing BBR congestion control..."
xfr $SERVER --congestion bbr --json --no-tui -t 10s > "$RESULTS_DIR/tcp-bbr.json"

echo "Testing multi-stream..."
xfr $SERVER -P 4 --json --no-tui -t 10s > "$RESULTS_DIR/multi.json"

# Summary
echo ""
echo "Results:"
for f in "$RESULTS_DIR"/*.json; do
    NAME=$(basename "$f" .json)
    MBPS=$(jq '.throughput_mbps' "$f")
    printf "%-10s %8.2f Mbps\n" "$NAME" "$MBPS"
done
```

### Regression Detection

```bash
#!/bin/bash
# Detect performance regressions in CI

set -e

SERVER=$1
MIN_THROUGHPUT=${2:-100}  # Minimum acceptable Mbps

# Run test
RESULT=$(xfr $SERVER --json --no-tui -t 30s)
THROUGHPUT=$(echo "$RESULT" | jq '.throughput_mbps')
RETRANSMITS=$(echo "$RESULT" | jq '.tcp_info.retransmits // 0')

echo "Throughput: $THROUGHPUT Mbps"
echo "Retransmits: $RETRANSMITS"

# Check throughput
if (( $(echo "$THROUGHPUT < $MIN_THROUGHPUT" | bc -l) )); then
    echo "FAIL: Throughput below ${MIN_THROUGHPUT} Mbps"
    exit 1
fi

# Check retransmit rate (>1% is concerning)
BYTES=$(echo "$RESULT" | jq '.bytes_total')
RETRANSMIT_RATE=$(echo "scale=4; $RETRANSMITS * 1500 * 100 / $BYTES" | bc)
if (( $(echo "$RETRANSMIT_RATE > 1" | bc -l) )); then
    echo "WARN: High retransmit rate: ${RETRANSMIT_RATE}%"
fi

echo "PASS"
```

## Prometheus Integration

### Push Gateway

Push results after each test (useful for CI):

```bash
# Server configured to push on test completion
xfr serve --push-gateway http://pushgateway:9091

# Or push from client-side script
xfr <host> --json --no-tui | jq -r '
  "xfr_throughput_mbps \(.throughput_mbps)\n" +
  "xfr_bytes_total \(.bytes_total)\n" +
  "xfr_tcp_retransmits_total \(.tcp_info.retransmits // 0)"
' | curl --data-binary @- http://pushgateway:9091/metrics/job/xfr/instance/$(hostname)
```

### Metrics Endpoint

For continuous monitoring (requires `--features prometheus` at build time):

```bash
xfr serve --prometheus 9090
```

Metrics available at `http://localhost:9090/metrics`:

```
# HELP xfr_bytes_total Total bytes transferred
# TYPE xfr_bytes_total counter
xfr_bytes_total 1234567890

# HELP xfr_throughput_mbps Current throughput
# TYPE xfr_throughput_mbps gauge
xfr_throughput_mbps 987.65

# HELP xfr_duration_seconds Test duration
# TYPE xfr_duration_seconds gauge
xfr_duration_seconds 10.0
```

### Grafana Dashboard

See `examples/grafana-dashboard.json` for a pre-built dashboard with:

- Throughput over time graph
- Active tests gauge
- Retransmit rate panel
- Per-client breakdown

## Environment Variables

Configure xfr via environment for containerized deployments:

```bash
export XFR_PORT=9000           # Server/client port (-p)
export XFR_DURATION=30s        # Test duration (-t)
export XFR_LOG_LEVEL=debug     # Log level (--log-level)
export XFR_LOG_FILE=/var/log/xfr.log  # Log file (--log-file)
export XFR_PSK=my-secret-key   # Pre-shared key (--psk)
export XFR_PUSH_GATEWAY=http://pushgateway:9091  # Push gateway URL (serve only)
export XFR_TIMESTAMP_FORMAT=iso8601  # Timestamp format (--timestamp-format)
```

## Tips for Automation

1. **Always use `--no-tui`** in scripts -- prevents terminal escape codes in output
2. **Use `--json-stream`** for real-time monitoring -- parse each line as it arrives
3. **Set explicit durations** with `-t` -- don't rely on defaults
4. **Use `xfr diff`** for regression detection -- handles threshold comparison
5. **Use `--one-off`** for CI servers -- server exits after one test completes
6. **Check exit codes** -- non-zero means failure
7. **Log to file** with `--log-file` for debugging -- keeps stdout clean for JSON
8. **TCP needs only port 5201** -- single-port mode means no ephemeral data ports
9. **UDP needs extra ports** -- data ports are dynamically allocated on the server