Skip to content

feat(heapwatch): add crate scaffolding and initial design - #613

Draft
martintmk wants to merge 6 commits into
mainfrom
user/martintmk/20260728-add-heapwatch-crate
Draft

feat(heapwatch): add crate scaffolding and initial design#613
martintmk wants to merge 6 commits into
mainfrom
user/martintmk/20260728-add-heapwatch-crate

Conversation

@martintmk

@martintmk martintmk commented Jul 28, 2026

Copy link
Copy Markdown
Member

Introduces heapwatch, a #[global_allocator] wrapper that continuously accounts heap usage -- live bytes, cumulative bytes allocated and freed, and allocation / free / reallocation counts.

This PR lands the design ahead of the code, so the architecture can be reviewed on its own terms rather than through an implementation diff. The crate compiles and is registered in the workspace, but exposes no public API yet. The implementation follows in a separate change.

What is heapwatch

It answers how much, never who. No backtraces, no per-allocation metadata, no pointer-to-size shadow map -- which is what keeps the per-allocation cost to a few non-atomic adds, cheap enough to leave enabled in production.

Each thread accumulates its own totals without synchronization and publishes them in batches into atomic totals owned by the HeapWatch instance, once churn since its last publication crosses a compile-time threshold. That removes the atomic read-modify-write per allocation that an exact accounting wrapper pays, in exchange for a bounded, stated inaccuracy of at most threshold x live threads -- a bound that does not grow with uptime. Reading goes through a Watch handle obtained from the instance, and is O(1) in the thread count.

Contents

File What it is
crates/heapwatch/docs/DESIGN.md The shipped architecture: measurement boundary, the per-thread / batched-commit mechanism and rejected alternatives, ordering and signedness, the instance-owned public surface with registration and reading examples, the accuracy contract, re-entrancy, operation counting, edge cases, invariants, and the verification strategy.
crates/heapwatch/docs/TODO.md Ideas deliberately excluded from the design (peak tracking, reported error bar, telemetry integration, usable-size accounting, scoped accounting, sampled attribution, size histogram), each with its reasoning.
crates/heapwatch/src/lib.rs Crate-level rustdoc summarising the above; no items.
crates/heapwatch/Cargo.toml Package metadata at 0.1.0, no dependencies.
crates/heapwatch/README.md Generated by cargo doc2readme against crates/README.j2.
crates/heapwatch/CHANGELOG.md Seeded, as for other brand-new crates.
Cargo.toml / Cargo.lock Registered in [workspace.dependencies].
.spelling Adds heapwatch / Heapwatch.

Notes for reviewers

  • No tests. There is no behavior yet; the compile and lint gates are the verification. Coverage is unaffected -- a crate with no executable lines contributes nothing to the 100% project target.
  • No logo.png / favicon.ico. Each crate has bespoke artwork, so rather than copy another crate's, the html_logo_url / html_favicon_url doc attributes are omitted for now (matching data_privacy_core and multitude_macros).
  • The design notes that heapwatch requires std -- a deliberate exception to the workspace no_std preference, since the mechanism needs a thread-local with a thread-exit destructor.
  • Peak is deferred, not dropped forever. It was cut from the design in review: a consumer that samples live bytes can keep its own maximum, and folding it in-crate drags along a reset operation and a subtler contract, since a peak candidate mixes a local maximum and a total read at different instants and so acquires a persistent bias where the other counters carry only a transient one. The rationale is recorded as item 1 in TODO.md.

Validation

Run locally against the workspace:

  • cargo check --workspace --all-features --all-targets --locked
  • cargo clippy -p heapwatch --all-features --all-targets --locked
  • cargo doc -p heapwatch --no-deps --all-features with RUSTDOCFLAGS=-D warnings
  • cargo fmt -p heapwatch -- --config-path ./unstable-rustfmt.toml --check
  • cargo sort --check --grouped --workspace
  • cargo heather (license headers)
  • cargo doc2readme --check --lib --template ../README.j2

cargo spellcheck could not run locally (its libclang build dependency is unavailable on this machine); CI covers it.

Introduce `heapwatch`, a global allocator wrapper that continuously
accounts heap usage: live bytes, peak, cumulative bytes allocated and
freed, and allocation / free / reallocation counts.

This change lands the design ahead of the code so the architecture can be
reviewed on its own terms. The crate compiles and is registered in the
workspace, but exposes no API yet:

- `crates/heapwatch/docs/DESIGN.md` -- the shipped architecture: the
  measurement boundary, per-thread accumulation with batched publication
  into process-wide atomics, the accuracy contract and its bounds, peak
  tracking, re-entrancy, edge cases, and the verification strategy.
- `crates/heapwatch/docs/TODO.md` -- ideas deliberately left out of the
  design, with the reasoning for each.
- `crates/heapwatch/src/lib.rs` -- crate-level rustdoc summarising the
  above and pointing at both documents.

Registered in `[workspace.dependencies]` so dependents can pick it up
once the implementation lands.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: def144ff-f8ea-4dc1-822f-f58665301c4d
Copilot AI review requested due to automatic review settings July 28, 2026 09:55
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown

✅ Version increments look sufficient

cargo semver-checks compared the 1 crate(s) this PR publishes against their previous version-bump commit in git history. Every version increment is sufficient for the detected API changes.

Crate Baseline Baseline commit This PR Minimum required Status
heapwatch new crate 0.1.0 0.1.0 ✅ ok

This check is informational and does not block the merge.

View the check run

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds the new heapwatch crate to the workspace as scaffolding and lands its initial, implementation-agnostic design documentation so the architecture can be reviewed before API/behavior is introduced.

Changes:

  • Introduces the crates/heapwatch crate with package metadata and crate-level documentation.
  • Adds detailed design/architecture and “future work” docs for the planned allocator-wrapper mechanism.
  • Registers heapwatch in workspace dependencies and updates lockfile/spellcheck allowlist.

Reviewed changes

Copilot reviewed 8 out of 9 changed files in this pull request and generated no comments.

Show a summary per file
File Description
crates/heapwatch/src/lib.rs Crate-level rustdoc describing purpose, status, mechanism, and measurement boundary (no API yet).
crates/heapwatch/README.md Generated README mirroring the crate-level docs with project badges and links to design docs.
crates/heapwatch/docs/TODO.md Captures intentionally out-of-scope features/ideas and rationale.
crates/heapwatch/docs/DESIGN.md Full architecture and accuracy/semantics contract for the upcoming implementation.
crates/heapwatch/CHANGELOG.md Seeds the changelog file for the new crate.
crates/heapwatch/Cargo.toml Adds initial crate metadata (0.1.0) and inherits workspace settings/lints/include allowlist.
Cargo.toml Registers heapwatch under [workspace.dependencies] (path + version).
Cargo.lock Adds the new path package entry.
.spelling Allows “heapwatch” and “Heapwatch” for spellcheck.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.0%. Comparing base (78aa447) to head (228dfbb).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #613   +/-   ##
=======================================
  Coverage   100.0%   100.0%           
=======================================
  Files         443      443           
  Lines       42764    42764           
=======================================
  Hits        42764    42764           
Flag Coverage Δ
linux ?
linux-arm ?
scheduled ?
windows ?

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The impact-scoped coverage run (`anvil-llvm-cov`) invokes
`cargo llvm-cov nextest --package heapwatch@0.1.0`, and nextest exits 4
on "no tests to run". Because heapwatch is scaffolding -- design
documents and crate-level rustdoc, no code and no tests -- it was the
sole affected package, so `pr-test` failed on all four platforms.

Declare `[package.metadata.coverage-gate] min-lines-percent = 0`, the
opt-out the recipe already understands for packages with nothing to
instrument, so the affected set resolves to empty and the run skips.
The comment marks the section for removal once the implementation lands
and the workspace-default 100% gate should apply again.

Also from code review:

- Add `[package.metadata.docs.rs]`. `lib.rs` declares
  `#![cfg_attr(docsrs, feature(doc_cfg))]`, but without this section
  docs.rs never sets the cfg, so feature-gated items would be silently
  omitted once features exist.
- Qualify the peak invariant in `DESIGN.md`. Publishing a commit is two
  atomics (`CURRENT.fetch_add` then `PEAK.fetch_max`), so a reader that
  lands between them observes a live total the peak has not caught up
  with. The invariant holds for completed commits; `peak >= current` is
  not assertable across a reading.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: def144ff-f8ea-4dc1-822f-f58665301c4d
Copilot AI review requested due to automatic review settings July 28, 2026 10:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 1 comment.

Comment thread .spelling
The previous commit qualified invariant 5 ("Once a commit has
completed, ...") but left the bolded statement in the Peak section
unqualified, so the two sections made different claims about the same
invariant. Qualify the bolded statement identically, and reword the
following paragraph so it elaborates on the qualification rather than
walking back a stronger claim.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: def144ff-f8ea-4dc1-822f-f58665301c4d
Copilot AI review requested due to automatic review settings July 28, 2026 10:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated no new comments.

Comment thread crates/heapwatch/docs/DESIGN.md
@martintmk martintmk added the agency-rocket Touched by a rocket skill label Jul 28, 2026
Comment thread crates/heapwatch/docs/DESIGN.md
Addresses review feedback on the design:

- Drop peak entirely. A consumer that samples live bytes can maintain
  its own maximum, and every other view of one is better derived
  downstream. Removing it also drops the reset operation and the
  subtler contract a monotone maximum needs, since a candidate
  combines a local peak and a total read at different instants and so
  acquires a persistent bias. Recorded in TODO.md as deferred work.
- Replace the process-wide statics with counters owned by the
  HeapWatch instance, read through a Watch handle. The handle carries
  no type parameters, so a diagnostic endpoint takes it without the
  allocator's generics being threaded through. Adds a section
  explaining which totals a thread's pending block commits to.
- Add public-surface snippets for both registration and reading.
- Tighten the prose throughout: 3872 to 3121 words despite the new
  section and examples.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 26bd2118-4c88-4d5c-bc3e-d12121a95297
Copilot AI review requested due to automatic review settings July 28, 2026 10:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated no new comments.

Review raised the case of swapping the system allocator for mimalloc:
heapwatch's numbers do not move, because it counts what callers
requested and the call pattern is unchanged, yet the memory the
process actually holds does move. That invariance is deliberate --
it is what makes heapwatch the control variable -- but the design
only implied it, in a passing note that allocator-native counters
also report retention and fragmentation.

Say it outright in the measurement boundary, and add a TODO item
sketching the pairing: an optional trait an inner allocator can
implement to report committed bytes on demand, so demand and
footprint can be reported side by side. Notes that an allocator
instance linked into one library also gives the per-component
attribution the OS cannot.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 26bd2118-4c88-4d5c-bc3e-d12121a95297
Copilot AI review requested due to automatic review settings July 28, 2026 11:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated no new comments.

Comment thread crates/heapwatch/docs/DESIGN.md Outdated
Review asked for a surface with one verb. Committing is now entirely
internal -- the churn threshold and thread exit -- so a reader calls
stats() and has nothing to sequence before it.

Removes flush_thread from the public surface, the explicit-flush arrow
from the mechanism diagram, and the flush clause from the concurrency
contract and the rustdoc. States why there is no flush operation:
it would only affect the calling thread, so tightening a process-wide
reading would mean calling it on every thread, which a diagnostic
endpoint cannot do -- to tighten a bound that is already documented
and small. Reworks the idle-pool caveat in the accuracy section, which
previously advised flushing before blocking.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 26bd2118-4c88-4d5c-bc3e-d12121a95297
Copilot AI review requested due to automatic review settings July 28, 2026 11:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated no new comments.

that binary's Rust heap holds and how much has moved through it — continuously,
in production, at a cost low enough to leave on.

It sits among four neighbours:

@sandersaares sandersaares Jul 29, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

How does this compare to alloc_tracker? Are we reinventing some wheels here? It is not clear why we need yet another memory tracker. If there is something novel this design adds, we should make it clear what that novelty is and why it cannot be solved with existing tools. This is the 2nd seeming duplication of an existing OSS crate I see today - our mission is not to reinvent the universe, so I find this general approach of just adding yet another tool to our already large list of crates dubious (but perhaps a clear motivation here will help).

Implementation-agnostic — for the API see the crate-level rustdoc, for
forward-looking ideas see [`TODO.md`](./TODO.md).

## What heapwatch is

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There is a lot of text here about what it is but I do not see text about what the actual use cases or problems to solve are. What is the specific motivation? It is not possible today to judge whether/how the design addresses the problem space.

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

Labels

agency-rocket Touched by a rocket skill

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants