Skip to content

Repository files navigation

MassifLens

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

One-command install for agents

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.

Features

  • 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=peak separate 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 --open launch 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.

Requirements

Prebuilt application

  • macOS 14 Sonoma or later.
  • Apple Silicon Mac: M1, M2, M3, M4, or later.

Building from source

  • 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.

Quick start

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:

  1. Clicking Open and selecting one or more massif.out files.
  2. Dragging one or more files onto the application window.
  3. Starting the executable with a profile path:
dist/MassifLens.app/Contents/MacOS/MassifLens --open /path/to/massif.out

To open several profiles:

dist/MassifLens.app/Contents/MacOS/MassifLens \
  --open /path/to/first.massif \
  --open /path/to/second.massif

Local 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.

Build from source

Run from the repository root:

Scripts/build-app.sh
open dist/MassifLens.app

The build script:

  1. Compiles the Swift package in release mode.
  2. Assembles a standard macOS .app bundle.
  3. Installs the icon and Info.plist.
  4. Applies an ad-hoc signature.
  5. Creates dist/MassifLens-1.2.0-arm64.zip from the version in Info.plist.

To install an existing checkout and launch the app:

Scripts/install.sh

To build only the Swift product:

swift build -c release

Tests and quality gates

Run all unit, integration, and SwiftUI rendering tests:

swift test

Run 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.sh

Verify the application structure, deployment target, architecture, icon, and signature:

Scripts/build-app.sh
Scripts/verify-app.sh

Run a full launch smoke test with a real profile:

Scripts/smoke-app.sh dist/MassifLens.app /path/to/massif.out

Without 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.

Mac App Store

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.sh

Creating 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.

Developer ID signing and notarization

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.sh

The 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.

Repository layout

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

Module dependencies

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.

Memory Atlas semantics

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?”

Efficient expansion and rendering

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.

Supported Massif data

The parser supports standard Valgrind Massif text output, including:

  • Header fields such as desc, cmd, and time_unit.
  • snapshot, time, mem_heap_B, mem_heap_extra_B, and mem_stacks_B.
  • heap_tree=empty, heap_tree=peak, and heap_tree=detailed.
  • n0 roots, n1 regular nodes, and n2 aggregate 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.

Peak semantics

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.

FAQ

Why is there no allocation tree?

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.

Why does Compare skip the immediately previous snapshot?

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.

Why are the real external samples not committed?

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.

How do I remove build products?

swift package clean

Both .build/ and dist/ are reproducible and ignored by Git.

Git workflow

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.

License

MassifLens is available under the MIT License. See the changelog for release history.

About

Native macOS visualizer for Valgrind Massif memory profiles

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages