> ## Documentation Index
> Fetch the complete documentation index at: https://wireblast.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Finding your max rate

> Ramp until something gives, then work out whether it was the box, the NIC or the wire.

"How fast can this go" is really three questions: how fast can the sender build packets, how fast can the NIC put them on the wire, and how fast can the far end take them. Wireblast can tell you which one you hit.

## Ramp interactively

Start somewhere comfortable and use the hotkeys.

```bash theme={null}
sudo wireblast -i eno2 --dst-ip 192.0.2.10 --packet-size 64 --pps 1M -d 0
```

`-d 0` runs until you stop it. Then:

* `+` raises the rate 10%, `-` lowers it
* `g` switches the graph between packets/sec and bits/sec
* `r` zeroes the visible counters after each change, so you're reading the new steady state rather than an average that includes the old one

Watch two things: whether **actual** keeps up with **set** in the header, and whether `Errors` starts moving.

```text theme={null}
  rate       set 1Mpps  actual 999.4 kpps
```

When actual stops tracking set, you've found the limit.

## Or jump straight to unlimited

```bash theme={null}
sudo wireblast -i eno2 --dst-ip 192.0.2.10 --packet-size 64 --pps unlimited -d 30s
```

<Warning>
  Unlimited on an interface carrying your default route will starve the host's own traffic, and Wireblast will make you confirm it. Do this on a test NIC.
</Warning>

Whatever comes back is the box's ceiling for that frame size.

## Frame size changes everything

Small frames are a packet-rate problem; large frames are a bit-rate problem. They fail differently, so test both:

```bash theme={null}
--packet-size 64      # maximum packets/sec: 14.88 Mpps is 10G line rate
--packet-size 512     # a realistic middle
--packet-size 1518    # maximum bits/sec: easy on packet rate
--mode imix           # a mix closer to real traffic
```

If 1518-byte frames hit line rate but 64-byte frames don't, you're CPU-bound rather than link-bound. That's the most common result on a first attempt, and it's usually fixable.

## Reading the failure

<AccordionGroup>
  <Accordion title="Actual rate below set, TX errors zero">
    The sender can't build packets fast enough. Try more queues:

    ```bash theme={null}
    --queues 0    # every queue on the interface (the default)
    ```

    Rates are aggregate, so more queues shouldn't change the target, only whether the box can reach it. If `--queues 1` and `--queues 12` give the same number, adding cores isn't your problem.

    Also check you got `native XDP` and `zero-copy` in the header. Generic mode or copy mode will cap you well below the hardware.
  </Accordion>

  <Accordion title="TX errors climbing">
    The kernel is rejecting descriptors. Usually the packet is bigger than the interface MTU, or bigger than a UMEM frame. Wireblast checks both before starting, so mid-run errors are worth investigating rather than ignoring.
  </Accordion>

  <Accordion title="Sender is fine, receiver drops">
    You've found the *receiver's* limit, not the sender's. Per-queue detail appears under the dashboard:

    ```text theme={null}
    ! queue 3: rx ring full (1.2 k)
    ```

    "Ring full" means packets arrived faster than they were collected. More queues on the receiver is the first thing to try.
  </Accordion>

  <Accordion title="Both ends fine, but packets go missing">
    The path is dropping them: a policer, a shaper, a congested uplink, or a link that isn't the speed you assumed. Neither end will show errors, because neither end is at fault. That's the signature.
  </Accordion>
</AccordionGroup>

## Isolating variables

The useful property is that a run is deterministic. The same flags produce the same packets in the same order, so you can change one thing at a time and trust the comparison.

```bash theme={null}
# Does queue count matter?
--queues 1 ... then --queues 4 ... then --queues 12

# Does flow count matter? (it shouldn't, for the sender)
--flows 1 ... then --flows 10000

# Does frame size matter, at a fixed packet rate?
--pps 1M --packet-size 64 ... then --pps 1M --packet-size 1518
```

Changing `--pps`, `--duration`, `--flows` or ports doesn't require reattaching XDP, so in the TUI you can press `e`, change one number, and be running again in seconds. Changing `--queues` does force a reattach and another link bounce.

[Example 019](https://github.com/atoonk/wireblast/tree/main/examples/019-queue-scaling) runs the queue sweep for you.

## A reference point

Aggregate rate holds steady regardless of queue count. Measured on a 10G ixgbe NIC at `--pps 1M`:

| Queues | Achieved    |
| ------ | ----------- |
| 1      | 999.34 kpps |
| 4      | 999.35 kpps |
| 12     | 999.44 kpps |

That's what a working rate limiter looks like. If your numbers drift with queue count, something else is going on.

## Line rate, for reference

10G, for the sizes above:

| Frame        | L1 bytes | Packets/sec at 10G |
| ------------ | -------- | ------------------ |
| 64 B         | 84       | 14.88 M            |
| 512 B        | 532      | 2.35 M             |
| 1518 B       | 1538     | 812 k              |
| IMIX (362 B) | 382      | 3.27 M             |

L1 includes the preamble, start-frame delimiter and interframe gap, which is why 64-byte frames cap at 14.88 Mpps rather than 19.5. See [reading the numbers](/concepts/numbers).
