Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/rbitcoin-net/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,10 @@ sha3 = "0.12"
# POLLRDHUP: peer FIN with unread bytes still in the receive buffer.
libc = "0.2"

[target.'cfg(target_os = "macos")'.dependencies]
# proc_pid_rusage: resident size for `ibd: sizes` / `tip: perf`; Darwin
# has no /proc.
libc = "0.2"

[lints]
workspace = true
4 changes: 2 additions & 2 deletions crates/rbitcoin-net/src/ibd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ mod state;
mod status;

pub use dial::connect_timeout_for;
pub use perf_log::{format_tip_perf_sizes, read_proc_rss, ProcRss, TipPerfSizes};
pub use perf_log::{format_tip_perf_sizes, read_platform_rss, ProcessRss, TipPerfSizes};

use archive::{rehydrate_block_queue_into_confirm, rehydrate_class_a_into_body_queue};
use assign_plan::want_headers_beyond_soft_cap;
Expand Down Expand Up @@ -829,7 +829,7 @@ pub async fn ibd_cancellable(
conf_pipe.feed_inflight = feed_inflight;
let work_sizes = st.structure_sizes();
let owned_sizes = hub.query.process_owned_size_snapshot();
let rss = perf_log::read_proc_rss();
let rss = perf_log::read_platform_rss();
let perf = perf_log::sample(
&loop_stats,
st.inflight.len(),
Expand Down
109 changes: 85 additions & 24 deletions crates/rbitcoin-net/src/ibd/perf_log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -666,8 +666,13 @@
/// - `anon_kb` — process-private anonymous (heap, stacks, MAP_ANON)
/// - `file_kb` — file-backed resident (shared libs + **our table mmaps**)
/// - `locked_kb` — `mlock`/`mlockall` only (usually 0 for us)
///
/// Which fields `read_platform_rss` can fill depends on the target: Linux
/// answers all of them, Darwin only `rss_kb`, other targets none. A zero is
/// therefore "not measurable here" as often as it is a real zero — see the
/// `read_platform_rss` arm for the target you are reading.
#[derive(Clone, Copy, Debug, Default)]
pub struct ProcRss {
pub struct ProcessRss {
pub rss_kb: u64,
pub anon_kb: u64,
pub file_kb: u64,
Expand All @@ -676,14 +681,15 @@
pub locked_kb: u64,
}

/// Cheap once-per-tick `/proc` read (not hot path).
/// Cheap once-per-tick resident-size read (not hot path).
///
/// Prefer `/proc/self/status` fields present on modern kernels (`RssAnon` /
/// `RssFile` / `VmRSS`). Fall back to `smaps_rollup` (`Anonymous:`, `Rss:`,
/// `Locked:`) when status split is missing — older rollups do **not** expose
/// Reads `/proc/self/status` for the fields modern kernels expose (`VmRSS`,
/// `VmHWM`, `RssAnon`, `RssFile`), then `smaps_rollup` (`Rss:`, `Anonymous:`,
/// `Locked:`) to fill a missing split — older rollups do **not** expose
/// `RssAnon:` / `RssFile:` (that bug made `ibd: sizes` print `anon=0 file=0`).
pub fn read_proc_rss() -> ProcRss {
let mut out = ProcRss::default();
#[cfg(target_os = "linux")]
pub fn read_platform_rss() -> ProcessRss {
let mut out = ProcessRss::default();
if let Ok(s) = std::fs::read_to_string("/proc/self/status") {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably worth gating these on cfg linux even though they're cheap.

fill_rss_from_status(&mut out, &s);
}
Expand All @@ -693,7 +699,52 @@
out
}

fn fill_rss_from_status(out: &mut ProcRss, s: &str) {
/// Cheap once-per-tick resident-size read (not hot path).
///
/// Darwin has no `/proc`, so this asks `proc_pid_rusage`. That flavor gives
/// `rss` alone: no anon/file resident split and no resident peak, so `anon` /
/// `file` / `hwm` / `locked` stay zero and the `ibd: sizes` `residual≈` heap
/// cross-check (anon minus accounted) reads as `0` here rather than meaning
/// the heap matched. Darwin's only lifetime peak is over `phys_footprint`,
/// which excludes clean file-backed pages and so can read below a mapped-file
/// RSS — a "peak" under the current value is worse than none, so `hwm` is
/// left at zero instead.
#[cfg(target_os = "macos")]
pub fn read_platform_rss() -> ProcessRss {
let mut out = ProcessRss::default();
fill_rss_from_rusage(&mut out);
out

Check warning on line 716 in crates/rbitcoin-net/src/ibd/perf_log.rs

View workflow job for this annotation

GitHub Actions / mutants (1/4)

Missed mutant

replace read_platform_rss -> ProcessRss with Default::default()
}

/// Cheap once-per-tick resident-size read (not hot path).
///
/// Windows has neither `/proc` nor `proc_pid_rusage`, and the `libc` we depend
/// on exposes no process-memory call there, so every field reads zero. Real
/// numbers would need `GetProcessMemoryInfo` (psapi) and a new dependency.
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
pub fn read_platform_rss() -> ProcessRss {
ProcessRss::default()

Check warning on line 726 in crates/rbitcoin-net/src/ibd/perf_log.rs

View workflow job for this annotation

GitHub Actions / mutants (1/4)

Missed mutant

replace read_platform_rss -> ProcessRss with Default::default()
}

#[cfg(target_os = "macos")]
fn fill_rss_from_rusage(out: &mut ProcessRss) {
let mut info: libc::rusage_info_v0 = unsafe { std::mem::zeroed() };
// SAFETY: RUSAGE_INFO_V0 selects the `rusage_info_v0` layout being written
// here; an unsupported flavor returns non-zero without touching `info`.
let rc = unsafe {
libc::proc_pid_rusage(
std::process::id() as libc::c_int,
libc::RUSAGE_INFO_V0,
std::ptr::addr_of_mut!(info).cast(),
)
};
if rc == 0 {

Check warning on line 741 in crates/rbitcoin-net/src/ibd/perf_log.rs

View workflow job for this annotation

GitHub Actions / mutants (2/4)

Missed mutant

replace == with != in fill_rss_from_rusage
out.rss_kb = info.ri_resident_size / 1024;

Check warning on line 742 in crates/rbitcoin-net/src/ibd/perf_log.rs

View workflow job for this annotation

GitHub Actions / mutants (2/4)

Missed mutant

replace / with * in fill_rss_from_rusage

Check warning on line 742 in crates/rbitcoin-net/src/ibd/perf_log.rs

View workflow job for this annotation

GitHub Actions / mutants (2/4)

Missed mutant

replace / with % in fill_rss_from_rusage
}

Check warning on line 743 in crates/rbitcoin-net/src/ibd/perf_log.rs

View workflow job for this annotation

GitHub Actions / mutants (1/4)

Missed mutant

replace fill_rss_from_rusage with ()
}

#[cfg(target_os = "linux")]
fn fill_rss_from_status(out: &mut ProcessRss, s: &str) {
for line in s.lines() {
if let Some(rest) = line.strip_prefix("VmRSS:") {
out.rss_kb = parse_kb_field(rest);
Expand All @@ -712,7 +763,8 @@
}
}

fn fill_rss_from_smaps_rollup(out: &mut ProcRss, s: &str) {
#[cfg(target_os = "linux")]
fn fill_rss_from_smaps_rollup(out: &mut ProcessRss, s: &str) {
for line in s.lines() {
if let Some(rest) = line.strip_prefix("Rss:") {
if out.rss_kb == 0 {
Expand All @@ -739,6 +791,7 @@
}
}

#[cfg(target_os = "linux")]
fn parse_kb_field(rest: &str) -> u64 {
rest.split_whitespace()
.next()
Expand All @@ -753,7 +806,7 @@
/// Occupancy + RSS for the tip-follow 5s DEBUG `tip: perf` line.
#[derive(Clone, Copy, Debug, Default)]
pub struct TipPerfSizes {
pub rss: ProcRss,
pub rss: ProcessRss,
pub cache_bodies: usize,
pub held_bodies: usize,
pub sh_heads: usize,
Expand Down Expand Up @@ -795,7 +848,7 @@
work: WorkStructureSizes,
owned: ProcessOwnedSizes,
conf_pipe: ConfirmPipelineSizes,
rss: ProcRss,
rss: ProcessRss,
stats: &rbitcoin_query::ConfirmStats,
) -> IbdPerfSample {
let (bq_bytes, bq_count, bq_soft_stop) = bq;
Expand Down Expand Up @@ -2352,7 +2405,7 @@
let _ = rbitcoin_log::take_logs();
rbitcoin_log::capture_logs(false);
rbitcoin_log::init(Level::Info);
let rss = read_proc_rss();
let rss = read_platform_rss();
assert!(rss.rss_kb > 0 || cfg!(not(target_os = "linux")));
}

Expand Down Expand Up @@ -2545,9 +2598,10 @@
assert!(!line.contains("residency creates="), "{line}");
}

#[cfg(target_os = "linux")]
#[test]
fn fill_rss_from_status_and_smaps_edges() {
let mut r = ProcRss::default();
let mut r = ProcessRss::default();
fill_rss_from_status(
&mut r,
"Name:\trbitcoin\nVmRSS:\t 1024 kB\nVmHWM:\t 2048 kB\nRssAnon:\t512 kB\nRssFile:\t256 kB\nRssShmem:\t128 kB\n",
Expand All @@ -2557,7 +2611,7 @@
assert_eq!(r.anon_kb, 512);
assert_eq!(r.file_kb, 384);

let mut r = ProcRss::default();
let mut r = ProcessRss::default();
fill_rss_from_smaps_rollup(
&mut r,
"Rss:\t 800 kB\nAnonymous:\t 300 kB\nRssAnon:\t 1 kB\nRssFile:\t 2 kB\nLocked:\t 16 kB\n",
Expand All @@ -2567,21 +2621,28 @@
assert_eq!(r.file_kb, 2);
assert_eq!(r.locked_kb, 16);

let mut r = ProcRss::default();
let mut r = ProcessRss::default();
fill_rss_from_smaps_rollup(&mut r, "Rss:\t 900 kB\nAnonymous:\t 400 kB\n");
assert_eq!(r.rss_kb, 900);
assert_eq!(r.anon_kb, 400);
assert_eq!(r.file_kb, 500);
}

#[cfg(any(target_os = "linux", target_os = "macos"))]
#[test]
fn read_proc_rss_returns_nonzero_on_linux() {
let r = read_proc_rss();
// Agent VM is Linux with /proc; RSS should be readable for this process.
assert!(
r.rss_kb > 0,
"expected VmRSS from /proc/self/status, got {r:?}"
);
fn read_platform_rss_reports_resident_size() {
let r = read_platform_rss();
// Linux reads /proc/self/status; Darwin reads proc_pid_rusage.
// A live test process is resident either way.
assert!(r.rss_kb > 0, "expected a resident size, got {r:?}");
}

#[cfg(target_os = "linux")]
#[test]
fn read_platform_rss_splits_anon_and_file_on_linux() {
let r = read_platform_rss();
// VmHWM is a high-water mark, so it never trails current residency.
assert!(r.hwm_kb >= r.rss_kb, "hwm should not trail rss, got {r:?}");
// Modern kernels expose RssAnon/RssFile on status; at least one side
// of the split should be non-zero for a running process with heap+.text.
assert!(
Expand Down Expand Up @@ -2610,7 +2671,7 @@
let work = WorkStructureSizes::default();
let owned = ProcessOwnedSizes::default();
let conf_pipe = ConfirmPipelineSizes::default();
let rss = read_proc_rss();
let rss = read_platform_rss();
let s = sample(
&loop_stats,
4, // inflight
Expand Down Expand Up @@ -2674,7 +2735,7 @@
#[test]
fn format_tip_perf_sizes_tokens_and_mib() {
let line = super::format_tip_perf_sizes(&super::TipPerfSizes {
rss: super::ProcRss {
rss: super::ProcessRss {
rss_kb: 2 * 1024,
anon_kb: 1024,
file_kb: 512,
Expand Down
4 changes: 2 additions & 2 deletions crates/rbitcoin-net/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ pub use error::NetError;
pub use eviction::{select_inbound_eviction, InboundEvictCandidate};
pub use i2p_sam::I2pSam;
pub use ibd::{
connect_timeout_for, format_tip_perf_sizes, read_proc_rss, rehydrate_block_queue_residue,
IbdConfig, ProcRss, TipPerfSizes, DEFAULT_BLOCKS_IN_TRANSIT_PER_PEER, DEFAULT_IBD_WINDOW,
connect_timeout_for, format_tip_perf_sizes, read_platform_rss, rehydrate_block_queue_residue,
IbdConfig, ProcessRss, TipPerfSizes, DEFAULT_BLOCKS_IN_TRANSIT_PER_PEER, DEFAULT_IBD_WINDOW,
};
pub use most_work::sum_work;
pub use net_permissions::{
Expand Down
4 changes: 2 additions & 2 deletions crates/rbitcoin-node/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use rbitcoin_electrum::{run_electrum, ElectrumConfig, ElectrumHandle, TipNotify}
use rbitcoin_esplora::{run_esplora, BlockTemplateFn, EsploraConfig, EsploraHandle, EsploraListen};
use rbitcoin_log::{debug, enabled, info, warn, Level};
use rbitcoin_net::{
default_port, format_serve_perf, format_tip_perf_sizes, netgroup, read_proc_rss,
default_port, format_serve_perf, format_tip_perf_sizes, netgroup, read_platform_rss,
sample_reset_serve_perf, socks_dns_seed_dests, AddrMan, AsMap, BlockingRegion, ChainHub,
Dialer, IbdConfig, MempoolHub, P2PNode, PeerConnType, TipEvent, TipPerfSizes,
};
Expand Down Expand Up @@ -929,7 +929,7 @@ pub async fn run_p2p(config: NodeConfig) -> Result<(), NodeError> {
let el_avg = el_us.checked_div(el_n).unwrap_or(0);
let serve_s = format_serve_perf(&serve);
let sizes = format_tip_perf_sizes(&TipPerfSizes {
rss: read_proc_rss(),
rss: read_platform_rss(),
cache_bodies: node.hub.cache_body_count(),
held_bodies: node.hub.held_body_count(),
sh_heads: node.query.process_owned_size_snapshot().sh_heads,
Expand Down
Loading