Valgrind leak checking¶
Developer tooling for the native gather extension — not part of the installed
package; it lives under scripts/ and is run from a source checkout.
A Memcheck harness exercises every gather path under Valgrind, then judges the result on leaks attributable to colstore’s own code — so the benign interpreter/dependency teardown that any Python process produces does not drown out a real leak.
Three pieces plus a suppression file:
File |
Role |
|---|---|
|
Driver. Sets the environment, runs Memcheck, calls the analyzer, manages the log. |
|
The deterministic workload it runs — writes synthetic stores and touches every gather route, looped so per-call leaks accumulate. |
|
Parses the (large) log, categorises leak records by origin, writes a summary, extracts colstore-attributable records, sets the exit code. |
|
Suppressions for known-benign OpenMP/NumPy/loader and CPython import noise. Loaded automatically. |
Requirements¶
Valgrind on
PATH(module load valgrindon an HPC cluster with environment modules, orconda install -c conda-forge valgrind).The native extension built with debug info, so leak stacks resolve to source lines instead of
???. The--buildflag below does this (RelWithDebInfo); otherwise build it yourself first.
Quick start¶
From the repository root:
# build with debug info, then leak-check
scripts/run_valgrind.sh --build
# or, if the extension is already installed with debug info
scripts/run_valgrind.sh
Memcheck runs ~20–50× slower than native, so the defaults are deliberately
small (100k rows × 4 cols × 8 records, 3 iterations). The run ends with a
categorised verdict; it exits 1 if — and only if — a leak is attributable to
colstore.
What it writes¶
For a log valgrind-colstore-<timestamp>.log, the run produces:
…-<timestamp>.summary.txt— the full readable breakdown (totals, records by origin, top stacks, verdict).…-<timestamp>.colstore-leaks.txt— the full text of every colstore-attributable leak record. This is the file to open if the verdict is FAIL. It says(no leak records attributable to colstore)on a clean run.…-<timestamp>.log.gz— the raw Valgrind log, gzip-compressed (kept only as evidence; you should rarely need it).
Options¶
--build build the package (RelWithDebInfo) before running
--python PATH interpreter to use (default: python3)
--rows N rows per synthetic store (default 100000)
--cols N columns per store (default 4)
--records N records per store (default 8)
--iterations N repeat the workload N times (default 3)
--threads N gather thread cap; >1 also runs OpenMP under Memcheck
(default 1 — serial kernels give the cleanest report)
--track-origins add --track-origins=yes (origins of uninitialised values)
--gen-suppressions emit ready-to-vet suppression stanzas (off by default;
it greatly enlarges the log)
--top N show the N most frequent leaking stacks (default 10)
--keep-log leave the raw log uncompressed
--delete-log delete the raw log after analysis (keep the summary)
--supp PATH extra suppression file (repeatable)
--out PATH log file path
-h, --help full help
A heavier pass with threaded kernels:
scripts/run_valgrind.sh --iterations 5 --threads 4 --track-origins
Analysing an existing log¶
The analyzer runs standalone on any saved log, plain or .gz:
scripts/analyze_valgrind.py valgrind-colstore-20260613-195901.log
scripts/analyze_valgrind.py some-run.log.gz --top 20
It writes the same *.summary.txt / *.colstore-leaks.txt files and exits 1
iff colstore is implicated.
Why a clean run still reports thousands of “leaks”¶
On a Python not built --with-valgrind (conda, most system Pythons), the
interpreter deserialises .pyc code objects and interns strings at import and
startup that are never freed before exit. The OS reclaims them, so they are not
real leaks, but Memcheck reports them as “definitely lost” in their thousands.
colstore.supp quiets the bulk; the analyzer buckets the rest as
cpython-import / numpy / openmp / … and the verdict ignores them because
none pass through colstore code. A clean run therefore looks like:
PASS: 0 leak records attributable to colstore.
5368 definite/indirect record(s) are interpreter/dependency teardown …
Use in CI¶
The non-zero exit on a colstore-attributable leak makes the driver usable as a gate:
scripts/run_valgrind.sh --build --delete-log