Repository navigation
Feature request: per-peer piece availability in PeerStats #643
Description
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_messagereceivesBitfieldandHave,
which would be ideal, butmod peer_connectionis private so the trait is
not reachable from a dependent crate.StorageFactory/TorrentStorageare public and injectable through
SessionOptions.default_storage_factory, but they see disk I/O rather than
peer messages.BitV/BitVFactoryare 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
feat(stats): optionally expose each peer's bitfield
feat(stats): re-export the peer stats types
adamgreenwell commentedon Aug 28, 2026
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 ararest-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_bitfieldvalidates the bytelength 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
BFby up to 7 — reachable from the wire, since the spec saysthose 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 piecesthat do not exist, though I appreciate that changes existing behaviour.
PeerStatsFilterandPeerStatsare 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 afield 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.
ikatson commentedon Aug 28, 2026
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