English | 简体中文
MassifLens is a native macOS visualizer for Valgrind Massif output. It opens
massif.out files directly and turns each detailed snapshot into a zoomable
Memory Atlas, with cross-snapshot growth analysis and a complete allocation
call tree. It requires no Linux VM, Qt, Valgrind, Python, Node.js, or other
third-party runtime on the viewing Mac.
Current version: 1.2.0
Agent instruction: Run this one command exactly; it clones the repository,
builds a verified Apple Silicon app, installs it in /Applications, preserves
any previous version in Trash, launches MassifLens, and then removes the
temporary source checkout:
( massiflens_tmp="$(mktemp -d)" && trap 'rm -R "$massiflens_tmp"' EXIT && git clone --depth 1 https://github.com/Super-long/MassifLens.git "$massiflens_tmp/MassifLens" && "$massiflens_tmp/MassifLens/Scripts/install.sh" )The command requires macOS on Apple Silicon, Git, and Xcode Command Line Tools.
It never uses sudo. If the tools are missing, install them once with
xcode-select --install, then run the same command again.
- Plot heap, heap-extra, stack, and total memory on one interactive timeline.
- Preserve exact 64-bit values while also displaying readable IEC units.
- Keep Massif's recorded
heap_tree=peakseparate from the sampled snapshot with the highest computed total. - Inspect the exact metrics for every snapshot.
- Show a complete detailed snapshot as an inclusive-byte-weighted icicle Memory Atlas: rectangle width is proportional to memory attributed to that allocation path.
- Compare any two detailed snapshots. Growth is red, shrinkage is blue, and paths that are new or completely released remain visible.
- Hover for exact values, search and highlight paths, click to zoom into a subtree, navigate back with breadcrumbs, and inspect Top Growth.
- Render the atlas in one SwiftUI Canvas with pixel culling and a depth-indexed binary-search hit index instead of one view per node.
- Keep the exact frame table as a secondary view. It starts fully expanded and supports collapse-all, depth limits, and ancestor-preserving search.
- Project the fully expanded table directly over parser storage without duplicating an index for every node.
- Open multiple profiles in one window and switch or close independent tabs.
- Maximize or restore the window from the explicit upper-left control.
- Open several files from the picker, drag and drop them, or repeat the
--openlaunch argument. - Report parse failures with the source filename and line number.
- Stream input with bounded resource limits and cancellable background loading.
- Process everything locally. Profiles and results are never uploaded.
- macOS 14 Sonoma or later.
- Apple Silicon Mac: M1, M2, M3, M4, or later.
- Xcode 16.4 or a compatible Swift 6 toolchain.
- Command Line Tools installed with
xcode-select --install.
The project uses Apple frameworks and Swift Package Manager only. It has no third-party package dependencies.
After building the repository, the application is available at:
dist/MassifLens.app
Double-click it or drag it into /Applications. Profiles can then be opened by:
- Clicking Open and selecting one or more
massif.outfiles. - Dragging one or more files onto the application window.
- Starting the executable with a profile path:
dist/MassifLens.app/Contents/MacOS/MassifLens --open /path/to/massif.outTo open several profiles:
dist/MassifLens.app/Contents/MacOS/MassifLens \
--open /path/to/first.massif \
--open /path/to/second.massifLocal builds use an ad-hoc signature. If Finder blocks the first launch, right-click the application and choose Open. Use the Developer ID workflow below before distributing the application to other users.
For profiles in macOS-protected locations such as Desktop, Documents, or
Downloads—especially quarantined files downloaded by a browser or messaging
application—prefer the in-app Open command or drag and drop. This lets
macOS grant access to the selected file explicitly. --open is intended for
paths that are already readable by the application.
Run from the repository root:
Scripts/build-app.sh
open dist/MassifLens.appThe build script:
- Compiles the Swift package in release mode.
- Assembles a standard macOS
.appbundle. - Installs the icon and
Info.plist. - Applies an ad-hoc signature.
- Creates
dist/MassifLens-1.2.0-arm64.zipfrom the version inInfo.plist.
To install an existing checkout and launch the app:
Scripts/install.shTo build only the Swift product:
swift build -c releaseRun all unit, integration, and SwiftUI rendering tests:
swift testRun real-profile regression tests and the coverage gate:
MASSIF_SAMPLE_DIR=/path/to/samples swift test --enable-code-coverage
MASSIF_SAMPLE_DIR=/path/to/samples Scripts/check-coverage.shVerify the application structure, deployment target, architecture, icon, and signature:
Scripts/build-app.sh
Scripts/verify-app.shRun a full launch smoke test with a real profile:
Scripts/smoke-app.sh dist/MassifLens.app /path/to/massif.outWithout MASSIF_SAMPLE_DIR, only tests that depend on external samples are
skipped. All repository-contained tests still run. See
the verification record and
acceptance journeys for the current release evidence.
The repository includes a separate sandboxed Mac App Store packaging flow, privacy manifest, localized store copy, privacy policy, and static readiness gate. Run the credential-free checks with:
Scripts/test-app-store-readiness.shCreating the signed .pkg requires an Apple Developer Program membership,
Mac App Distribution and Mac Installer Distribution identities, and a matching
provisioning profile. Follow the complete
Mac App Store release runbook. The app's public
privacy policy states that profiles are processed locally and no
data is collected.
Internet distribution requires an Apple Developer ID Application certificate
and notarization credentials. Store credentials in Keychain with
notarytool store-credentials, then run:
export DEVELOPER_ID_APPLICATION="Developer ID Application: Example (TEAMID)"
export NOTARY_KEYCHAIN_PROFILE="massiflens-notary"
Scripts/sign-and-notarize.shThe script enables the hardened runtime, submits the application, staples the ticket, verifies Gatekeeper, and rebuilds the ZIP. Certificates and passwords are never written into the repository.
MassifLens/
├── Package.swift Swift Package definition
├── Sources/
│ ├── MassifModel/ 64-bit domain model and allocation trees
│ ├── MassifParser/ Streaming parser and parse errors
│ ├── MassifPresentation/ Layout, formatting, search, and selection
│ ├── MassifApp/ SwiftUI views and application state
│ └── MassifLens/ macOS application entry point
├── Tests/ Unit, integration, and rendering tests
├── Packaging/ Info.plist, icon, and bundle resources
├── AppStore/ Localized product-page metadata
├── Scripts/ Build, coverage, verification, and release tools
├── Docs/ Acceptance and verification records
├── AGENTS.md Agent-facing maintenance guide
├── PRIVACY.md Public privacy policy
├── CHANGELOG.md Release history
└── LICENSE MIT license
MassifLens executable
│
▼
MassifApp
│
├──────────────┐
▼ ▼
MassifPresentation MassifParser
│ │
└──────┬───────┘
▼
MassifModel
MassifModel is independent of the parser and UI. MassifParser has no
third-party dependencies. The app reads data through public model and
presentation types, so parsing remains independently testable and new export
formats or frontends can be added without coupling them to SwiftUI.
Only peak and detailed snapshots contain allocation trees. When one is
selected, the atlas shows the complete tree: vertical position is call depth,
and horizontal width is inclusive bytes. Clicking a rectangle remaps that
subtree to the full viewport instead of expanding it one row at a time. Hover
details show exact snapshot, baseline, and delta values. Sub-pixel nodes remain
part of parent totals and layout geometry but do not waste drawing work.
In Δ Compare, another detailed snapshot becomes the baseline. Trees are aligned using their complete call paths and same-name sibling occurrence. Layout width uses the larger value from either side, so newly added and fully released branches do not disappear. Red means growth, blue means shrinkage, and gray means unchanged; signed values ensure color is not the only signal.
Massif does not contain object references, GC roots, or object lifetimes. MassifLens therefore does not present call edges as object relationships and does not claim to provide a dominator tree or retained size. The Memory Atlas answers: “Which allocation call paths are responsible for memory still present in this snapshot?”
The parser stores allocation nodes contiguously in depth-first order. In the
fully expanded frame view, the presentation layer exposes this storage as a
random-access collection and lets macOS Table instantiate only visible rows.
It does not build a second array of every node index or maintain a set entry for
every expanded node. Collapsing, depth limiting, or searching creates only the
compact visible index that is needed. A reverse pass includes ancestors of
search matches in O(n) time.
The Memory Atlas scans the current and baseline trees once, aligns them with a stable dual-64-bit path digest, and produces normalized 0...1 geometry. One SwiftUI Canvas draws the result and skips rectangles smaller than a pixel. Hover hit testing buckets nodes by depth and binary-searches horizontal ranges. The tree layout is rebuilt only when the snapshot, baseline, or zoom focus changes—not during ordinary mouse movement.
The parser supports standard Valgrind Massif text output, including:
- Header fields such as
desc,cmd, andtime_unit. snapshot,time,mem_heap_B,mem_heap_extra_B, andmem_stacks_B.heap_tree=empty,heap_tree=peak, andheap_tree=detailed.n0roots,n1regular nodes, andn2aggregate nodes.- Labels containing spaces, templates, and C++ symbols.
Default safety limits prevent malformed or hostile input from exhausting the machine:
- Maximum file size: 128 MiB.
- Maximum line length: 1 MiB.
- Maximum snapshots: 1,000,000.
- Maximum nodes in an allocation tree: 5,000,000.
- Maximum tree depth: 4,096.
Invalid structure, numeric overflow, child-count mismatches, or exceeded limits produce an explicit error instead of a partial or misleading visualization.
Massif's heap_tree=peak is the heap peak recorded by Valgrind. It does not
necessarily equal the sampled point with the largest
heap + heap extra + stacks total. MassifLens preserves both meanings:
- Recorded Peak: the snapshot explicitly marked
heap_tree=peak. - Highest Total: the snapshot with the largest computed sampled total.
Only heap_tree=peak and heap_tree=detailed snapshots contain tree data. An
empty snapshot still has totals and timing information, but no allocation
path distribution to display.
An empty snapshot contains totals only and cannot support a truthful path
diff. MassifLens selects the nearest earlier snapshot with a tree, and the
baseline menu lists only snapshots that contain allocation data.
Real profiles may contain command lines, paths, function symbols, or business
information. Tests access them read-only through MASSIF_SAMPLE_DIR; they are
never copied into source control.
swift package cleanBoth .build/ and dist/ are reproducible and ignored by Git.
The repository uses main as its primary branch. Source, tests, documentation,
and packaging scripts are versioned; caches, derived files, and distribution
artifacts are ignored. A typical change is:
git switch -c feature/my-change
swift test
git add Sources Tests README.md
git commit -m "feat: describe the change"Run at least swift test before committing. Changes that affect packaging or
distribution should also run Scripts/build-app.sh and
Scripts/verify-app.sh.
MassifLens is available under the MIT License. See the changelog for release history.