# Max out pagination

This document describes the best ways to calculate the size both in terms of
bytes and handles of elements as they are added to a vector. This should be
done in order to maximize the number of elements that can be batched at once
while satisfying the kernel caps on channel writes.

> [!NOTE]
> **Note:** Use the [measure-tape](https://cs.opensource.google/fuchsia/fuchsia/+/main:/tools/fidl/measure-tape/) tool to implement the techniques described below. In the Fuchsia Source Tree, this tool has direct build integration.

## Summary

To maximize throughput through a channel, it's common to batch large responses
as multiple vectors of things, for instance by [using a pagination
API](https://fuchsia.dev/docs/development/api/fidl#pagination). Since channels are capped at [64K bytes and 64
handles](https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/zircon/system/public/zircon/types.h#296), comes the question of how many elements
can be batched in the vector to max out the capacity (and yet, be just under the
byte size and handle count thresholds).

The key reference document for the following is the [FIDL wire
format](https://fuchsia.dev/docs/reference/fidl/language/wire-format) specification.

There are various examples that explain the best ways to max out pagination:

- [Bluetooth `WatchPeers` method](https://fuchsia.dev/fuchsia-src/development/languages/fidl/guides/max-out-pagination#bluetooth-watchpeers-method)
- [Scenic `Enqueue` method](https://fuchsia.dev/fuchsia-src/development/languages/fidl/guides/max-out-pagination#scenic-enqueue-method)

## Bluetooth `WatchPeers` method

Consider the [WatchPeers](https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/sdk/fidl/fuchsia.bluetooth.sys/access.fidl#100) method of the
`fuchsia.bluetooth.sys.Access` protocol, defined as:

    WatchPeers() -> (vector<Peer>:MAX updated, vector<bt.PeerId>:MAX removed);

First, a request or response is preceded by a header, i.e. a fixed 16 bytes or
`sizeof(fidl_message_header_t)` as [defined here](https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/zircon/system/public/zircon/fidl.h#358).

Each vector has a 16 bytes header `sizeof(fidl_vector_t)`, followed by the
content.

Since `bt.PeerId` is a `struct{uint64}` ([defined here](https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/sdk/fidl/fuchsia.bluetooth/id.fidl#13)) it is a
fixed 8 bytes, and therefore the `removed` vector's content is the number of
elements \* 8 bytes.

Next, we need to estimate the size of `Peer`, which is [defined as a
table](https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/sdk/fidl/fuchsia.bluetooth.sys/peer.fidl#16). Tables are essentially a [vector of envelopes](https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/zircon/system/public/zircon/fidl.h#328),
where each envelope then points to the field content. Estimating the size must
be done in two steps:

1. Determine the largest field ordinal used (a.k.a. `max_set_ordinal`)
2. Determine the size of each present field

The size of `Peer` is then the table header -- i.e. `sizeof(fidl_table_t)`, 16
bytes -- plus the largest set ordinal \* envelope header (16 bytes) -- i.e.
`max_set_ordinal * sizeof(fidl_envelope_t)` -- plus the total size of the
content, that is, each present field's content added.

Fields are relatively easy to size, many are primitives or wrappers thereof,
hence result in 8 bytes (due to padding). The `bt.Address` [field](https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/sdk/fidl/fuchsia.bluetooth/address.fidl#16)
is also 8 bytes since it's definition reduces to `struct{uint8;
array<uint8>:6}`. The `string` field is a vector of bytes, i.e.
`sizeof(fidl_vector_t) + len(name)`, and padded to the nearest 8 bytes boundary.

## Scenic `Enqueue` method

Consider the [Enqueue](https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/sdk/fidl/fuchsia.ui.scenic/session.fidl#54) method of the
`fuchsia.scenic.Session` protocol, defined as:

    Enqueue(vector<Command>:MAX cmds);

A request or response is preceded by a header, i.e. a fixed 16 bytes or
`sizeof(fidl_message_header_t)` from [zircon/fidl.h](https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/zircon/system/public/zircon/fidl.h#358).
Then, the vector has a 16 bytes header `sizeof(fidl_vector_t)`, followed by the
content of the vector, which are the actual commands. As a result, before you
account for the size of each individual command, there is a fixed size of 32
bytes.

A [command](https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/sdk/fidl/fuchsia.ui.scenic/commands.fidl#12) is a [union](https://fuchsia.dev/docs/reference/fidl/language/wire-format#unions) that has a 24
bytes header (i.e. `sizeof(fidl_xunion_t)`) followed by the content, which is 8
bytes aligned.

The size of a `Command` union content depends on the variant selected. This
example uses the `input` variant of type
[`fuchsia.ui.input.Command`](https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/sdk/fidl/fuchsia.ui.input/commands.fidl#7).

The `input` variant (of the scenic command) is itself a union, which adds
another 24 bytes header, followed by the content of that union, such as a
`send_pointer_input` of type
[`SendPointerInputCmd`](https://fuchsia.googlesource.com/fuchsia/+/b7840e772fccb93be4fff73a9cb83f978095eac2/sdk/fidl/fuchsia.ui.input/commands.fidl#25).

The simplified definition of `SendPointerInputCmd` and all transitively
reachable types through this struct is provided below:

    type SendPointerInputCmd = struct {
        compositor_id uint32;
        pointer_event PointerEvent;
    };

    type PointerEvent = struct {
        event_time uint64;
        device_id uint32;
        pointer_id uint32;
        type PointerEventType;
        phase PointerEventPhase;
        x float32;
        y float32;
        radius_major float32;
        radius_minor float32;
        buttons uint32;
    };

    type PointerEventType = flexible enum {
        // members elided
    };

    type PointerEventPhase = flexible enum {
        // members elided
    };

Both enums `PointerEventType` and `PointerEventPhase`
[default](https://fuchsia.dev/docs/reference/fidl/language/language#enums) to an underlying representation of `uint32`. You
can reduce the sizing of `SendPointerInputCmd` to the struct:

    struct {
        uint32;   // 4 bytes, total 4
                  // 4 bytes (padding due to increase in alignment), total 8
        uint64;   // 8 bytes, total 16
        uint32;   // 4 bytes, total 20
        uint32;   // 4 bytes, total 24
        uint32;   // 4 bytes, total 28
        uint32;   // 4 bytes, total 32
        float32;  // 4 bytes, total 36
        float32;  // 4 bytes, total 40
        float32;  // 4 bytes, total 44
        float32;  // 4 bytes, total 48
        uint32;   // 4 bytes, total 52
    };

Therefore, the size of the `SendPointerInputCmd` struct is 52 bytes. For more
information on struct sizing calculation, see [The Lost Art of Structure
Packing](http://www.catb.org/esr/structure-packing/).

Now that you have sized all the pieces of a command, you add the total size:

- Header of `fuchsia.ui.scenic.Command`: 24 bytes, i.e. `sizeof(fidl_xunion_t)`
- Content with variant `input`:
  - Header of `fuchsia.ui.input.Command`: 24 bytes, i.e. `sizeof(fidl_xunion_t)`
  - Content with variant `set_hard_keyboard_delivery`:
  - Struct `SendPointerInputCmd`: 52 bytes
  - Padding to align to 8 bytes: 4 bytes

This results in a total size of 104 bytes.
[](https://issues.fuchsia.dev/issues/new?component=1439230&template=1881640&description=Issue+on:+https://fuchsia.dev/docs/development/languages/fidl/guides/max-out-pagination.md "Report a bug or suggest improvements on this page") [](https://cs.opensource.google/fuchsia/fuchsia/+/main:docs/development/languages/fidl/guides/max-out-pagination.md "View the source code of this page") [](https://ci.android.com/edit?repo=fuchsia/fuchsia/main&file=docs/development/languages/fidl/guides/max-out-pagination.md&cloneRepo=fuchsia/fuchsia:refs/heads/main&fullPath=fuchsia/docs/development/languages/fidl/guides/max-out-pagination.md "Edit the source code of this page")