Skip to content

Feature request: per-peer piece availability in PeerStats #643

Open

Description

@adamgreenwell

What I am trying to do

I am embedding librqbit in a desktop application and would like to answer,
for a running torrent: will this finish?

That turns out to be a different question from "is it downloading". A torrent
connected to forty peers who all stopped at 6% will not finish; one connected
to six peers who between them hold every piece will. Distinguishing those needs
piece availability — the union of the connected peers' bitfields, or at minimum
a rarest-piece copy count.

What I found

PeerStats (torrent_state/live/peer/stats/snapshot.rs) currently carries:

pub struct PeerStats {
    pub counters: PeerCounters,
    pub state: &'static str,
    pub conn_kind: Option<ConnectionKind>,
    pub client_name: Option<String>,
}

PeerCounters::downloaded_and_checked_pieces is how many pieces a peer has
sent me, rather than how many that peer holds. api_dump_haves returns my own
bitfield. So I do not think either gets me there, though I would be glad to be
wrong about that.

I looked at main as well as 9.0.0 and PeerStats reads the same on both.

The underlying data seems to be present

LivePeerState (torrent_state/live/peer/mod.rs) has, per peer:

// This is used to track the pieces the peer has.
pub bitfield: BF,

which is maintained for piece picking. So the tracking already happens; what I
am missing is a way to read it from outside.

Things I tried first

I looked for a way to do this without changing the crate:

  • PeerConnectionHandler::on_received_message receives Bitfield and Have,
    which would be ideal, but mod peer_connection is private so the trait is
    not reachable from a dependent crate.
  • StorageFactory / TorrentStorage are public and injectable through
    SessionOptions.default_storage_factory, but they see disk I/O rather than
    peer messages.
  • BitV / BitVFactory are in private modules and relate to persisting my own
    bitfield.
  • I could not find an observer or callback field on SessionOptions.

If there is an approach I have overlooked, I would much rather use it than ask
for an API change.

Possible shapes

A count on PeerStats:

pub struct PeerStats {
    // ...
    /// How many pieces this peer has, from its bitfield.
    pub have_pieces: u32,
}

populated in impl From<&Peer> for PeerStats. This would be enough for a
rarest-piece copy count and a swarm-health verdict, which is all I need.

The bitfield itself, e.g. as Option<Vec<u8>> or behind an accessor. A
larger payload, but it would also allow a per-region availability histogram
rather than a single number. I mention it only because it is the same data; the
count alone would cover my use case.

A third option would be making PeerConnectionHandler reachable so embedders
can observe protocol messages directly, though that is a much larger surface to
commit to.

Offer

If one of these looks reasonable, I am happy to put together a PR — or to adapt
to a different shape if you have one in mind. I did not want to send an
unsolicited patch that changes a public type without checking what you would
prefer first.

Either way, no urgency from my side.


Checked against 9.0.0 and main. Thanks for the library; the v9 reorganisation
made embedding it noticeably cleaner.

Activity

  1. adamgreenwell commented on Aug 28, 2026

    @adamgreenwell
    Author

    Correcting myself on the main point above, before anyone spends time on it.

    I wrote that a per-peer count (have_pieces) "would be enough for a
    rarest-piece copy count". That is wrong. A count gives the mean copies per
    piece; the verdict needs the minimum, and the minimum is not recoverable from
    counts. Two peers holding 500 pieces each may overlap completely or not at all
    — identical counts, identical averages, and only one of those torrents can
    finish.

    So the useful ask is the second shape I listed, the bitfield itself, not the
    first. Sorry for pointing at the cheaper one first.

    Two smaller things I ran into while checking this, both possibly worth their
    own fix regardless of what happens with the above:

    Bitfield padding is not masked on ingest. on_bitfield validates the byte
    length and then stores the peer's bytes verbatim. Since a bitfield is
    byte-padded, a peer that sets the spare trailing bits inflates any count taken
    over the whole BF by up to 7 — reachable from the wire, since the spec says
    those bits are zero but nothing enforces it. The code immediately below is
    already careful about this, slicing ..total_pieces() before .all() for the
    "peer has full torrent" check. Masking once at ingest would make
    count_ones() correct everywhere and would also stop a peer claiming pieces
    that do not exist, though I appreciate that changes existing behaviour.

    PeerStatsFilter and PeerStats are not nameable by a dependent crate.
    They are a parameter and the return type of the public
    per_peer_stats_snapshot, but live in a private module and are not re-exported,
    so a caller can only obtain a filter via Default::default() and cannot set a
    field on it.

    I have the three changes — count, opt-in bitfield behind a filter flag, and the
    re-exports — sitting on a fork with tests, since I needed them to keep going.
    Happy to open a PR with any subset, in whatever shape you prefer, or to leave it
    entirely if you would rather solve it differently. Still no urgency.

  2. ikatson commented on Aug 28, 2026

    @ikatson
    Owner

    If you got it all working already anyway, just send the PR and I'll take a look.

    Indeed it's obvious that just a number won't cut it.

    My main concerns are: making the API more complex, the code more complex, and reduced performance for the cases where it's not needed. If it looks reasonable and simple, and doesn't degrade perf for the baseline case it's very likely I'll merge it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions