{"id":25121568,"url":"https://github.com/backtrace-labs/poireau","last_synced_at":"2026-03-07T14:02:11.624Z","repository":{"id":140430010,"uuid":"240580906","full_name":"backtrace-labs/poireau","owner":"backtrace-labs","description":"Poireau: a sampling allocation debugger","archived":false,"fork":false,"pushed_at":"2022-02-03T15:24:17.000Z","size":81,"stargazers_count":87,"open_issues_count":0,"forks_count":5,"subscribers_count":14,"default_branch":"master","last_synced_at":"2025-04-22T23:33:59.368Z","etag":null,"topics":["allocation-profile","allocator","c","c-plus-plus","debugging-tool","memory-leak","memory-management"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/backtrace-labs.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-02-14T19:11:39.000Z","updated_at":"2025-02-09T15:14:21.000Z","dependencies_parsed_at":null,"dependency_job_id":"fc463a8e-ce39-4fd8-97ae-f0cbc2dd1ef5","html_url":"https://github.com/backtrace-labs/poireau","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/backtrace-labs/poireau","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/backtrace-labs%2Fpoireau","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/backtrace-labs%2Fpoireau/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/backtrace-labs%2Fpoireau/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/backtrace-labs%2Fpoireau/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/backtrace-labs","download_url":"https://codeload.github.com/backtrace-labs/poireau/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/backtrace-labs%2Fpoireau/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30216493,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-07T13:35:13.914Z","status":"ssl_error","status_checked_at":"2026-03-07T13:35:13.569Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["allocation-profile","allocator","c","c-plus-plus","debugging-tool","memory-leak","memory-management"],"created_at":"2025-02-08T06:31:57.865Z","updated_at":"2026-03-07T14:02:11.606Z","avatar_url":"https://github.com/backtrace-labs.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"Poireau: a sampling allocation debugger\n=======================================\n\nThe libpoireau library intercepts a small fraction of calls to\nmalloc/calloc/etc., to generate a statistically representative\noverview of an application's heap footprint.  While the interceptor\ncurrently only tracks long-lived allocations (e.g., leaks), we plan to\nalso implement guard pages, in the spirit of\n[Electric Fence](https://en.wikipedia.org/wiki/Electric_Fence).\n\nThe sampling approach makes it possible to use this library in\nproduction with a minimal impact on performance (see the section on\nPerformance overhead), and without any change to code generation,\nunlike, e.g., [LeakSanitizer](https://clang.llvm.org/docs/LeakSanitizer.html)\nor [Valgrind](https://valgrind.org/).\n\nThe library's implementation strategy, which offloads most of the\ncomplexity to the kernel or an external analysis script, and only\noverrides the system memory allocator (or any other allocator that\nalready overrides the system malloc) for the few sampled allocations,\nmeans the instrumentation is less likely to radically change a\nprogram's behaviour.  Preloading `libpoireau.so` is much less invasive\nthan slotting in, e.g., [tcmalloc](https://github.com/google/tcmalloc)\nonly because one wants to debug allocations.  The code base is also\nmuch smaller, and easier to audit before dropping a new library in\nproduction.\n\nFinally, rather than scanning the heap for references, the\n`poireau.py` analysis script merely reports old allocations.  For\napplication servers, and other workloads that expect to enter a steady\nstate quickly after startup, that is *more useful* than only reporting\nunreachable objects: a slow growth in heap footprint is an issue, even\nif the culprits are reachable, e.g., in a list that isn't getting\ncleared when it should be.\n\nHow to build libpoireau\n-----------------------\n\nlibpoireau currently targets Linux 4.8+ (for statically defined\ntracepoint support) on 64 bit platforms with 4 KB pages.  Execute\n`make.sh` to create `libpoireau.so` in the current directory; the code\nrequires a GCC-compatible C11 implementation.\n\nHow to use libpoireau\n---------------------\n\nAdd `LD_PRELOAD=\"$LD_PRELOAD:$path_to_libpoireau.so\"` to the\nenvironment before executing the program you wish to debug.\n\nBefore using libpoireau, we must register its static probepoints with\nLinux `perf`; this may be done before starting programs with\n`LD_PRELOAD`, or after, it does not matter.\n\n    sudo perf buildid-cache --add ./libpoireau.so\n    sudo perf list | grep poireau  # should show tracepoints\n\nWe can now enable the tracepoints to generate perf events whenever\n`libpoireau` overrides a stdlib call.\n\n    sudo perf probe sdt_libpoireau:*\n\nThat's enough for Linux `perf` to report these events, e.g., in `perf\ntop`.  However, that's a lot of information, not necessarily useful.\n\nExecute `scripts/poireau.sh $PID` to start `perf trace` on that `PID`,\nand feed the output to an allocation tracking script.  Every 10\nminutes, that script will dump a list of currently live old (\u003e 5\nminutes) sampled allocations.  Send `poireau.py` a `HUP` signal to\ninstead get a list of all live sampled allocations.  Old allocations\nwill eventually fill up with known leaks, or startup allocations;\nremove all current old allocations from future reports by sending a\n`USR1` signal to `poireau.py`.\n\nA key advantage of having the analysis out of process is that we can\nstill provide information after a crash.  Send a `USR2` signal to\n`poireau.py` to list some recent calls to `free` or `realloc`, on the\noff chance that it will help debug a use-after-free.\n\nPerf often needs `sudo` access, but it doesn't make sense to run all\nof `poireau.py` as root; `poireau.sh` instead executes only `perf`\nwith sudo.  In order to override the `perf` binary under `sudo`,\nuse ``PERF=`which perf` scripts/poireau.sh ...``.\n\nYou may also enable system-wide tracing by invoking `poireau.sh`\nwithout any argument.  This is mostly useful if only one process at a\ntime will ever `LD_PRELOAD` `libpoireau.so`: the analysis code in\n`poireau.py` does not currently tell processes apart when matching\nallocations and frees (edit the global `COMM` pattern in `poireau.py`\nto only ingest events from programs that match a certain regex).\nSystem-wide tracing makes it easier to track events that happen\nimmediately on program startup.\n\nTL;DR:\n\n1. Register `libpoireau`'s tracepoints with `perf buildid-cache --add`\n   and `perf probe`.\n2. Prepare your program to run with libpoireau.so instrumentation, e.g., with `LD_PRELOAD=/path/to/libpoireau.so`.\n3. Grab libpoireau tracepoint events by doing one of:\n\n   a. Start the instrumented program and run `scripts/poireau.sh $PROGRAM_PID`.\n\n   b. Edit the `COMM` pattern in `scripts/poireau.py` before running `scripts/poireau.sh`, then start the instrumented program.\n\n4. Wait for `poireau.sh` to report stacks for long-lived (\u003e five minutes)\n   sampled allocations, every ten minutes.\n5. Packages for `perf` can be wonky. Try to build from source and point\n   `poireau.sh` to custom executables by setting ``PERF=`which perf` ``\n   before running `poireau.sh`.\n\nInteract with `poireau.py` with signals:\n\n- `SIGHUP`: prints stacks for all live sampled allocations.\n- `SIGUSR1`: prints stacks for all old sampled allocations and stop reporting them in the future.\n- `SIGUSR2`: prints stacks for all recent calls to `free` or `realloc`.\n\nHow to clean up after enabling libpoireau\n-----------------------------------------\n\nDisable the tracepoints with\n\n    sudo perf probe --del sdt_libpoireau:*\n\nand remove libpoireau from `perf`'s cache with\n\n    sudo perf buildid-cache --remove ./libpoireau.so\n\nto erase all traces of libpoireau from the `perf` subsystem.\n\nIf you had to edit an init script to insert the `LD_PRELOAD` variable\nbefore executing a program, it makes sense to undo the edit and\nrestart the instrumented program as soon as possible.\n\nAdvanced usage\n--------------\n\nYou can override the default sample rate (every 32 MB on average) by\nsetting the `POIREAU_SAMPLE_PERIOD_BYTES` to a positive sample rate\nin bytes.\n\nPoireau can also be used to take a snapshot of live sampled\nallocations (and print it) whenever the estimated heap footprint\nreaches a new high water mark.  Simply pass a\n`--track-high-water-mark` argument to `poireau.py`; `poireau.sh`\nconsumes the first argument if any and passes the rest to\n`poireau.py`.  For system-wide tracing, pass `*` as the first argument\nto `poireau.sh`.\n\nThe `poireau.py` analysis script accepts a second positional argument\nafter `--track-high-water-mark`: that's the minimum size (in bytes)\nat which it will report live sampled allocations.\n\nPoireau can also be used with `perf record` for short-lived tasks;\nthat's particularly useful with high water mark heap profiling.  First\nrecord `perf.data` with `perf record -T -e std_libpoireau:*\n--call-graph=dwarf -- ./profilee ...`, then pipe the data to analysis with\n`perf script | ./poireau.py ...`.\n\nHow does it work?\n-----------------\n\nWhen `LD_PRELOAD`ed, libpoireau intercepts every call to\n`malloc`/`calloc`/`realloc`/`free`, and quickly forwards the vast\nmajority of calls to the real implementation that would be used if\nlibpoireau were absent.\n\nOnly those allocations that are marked for sampling are diverted, in\nthe case of `malloc` and `calloc`, and `free` is overridden iff called\non an allocation that was diverted.  Finally, `realloc` is treated as\na pair of `malloc` and `free`, for sampling purposes.\n\nThe sampling logic simulates a process that samples each allocated\nbyte with equal probability.  The (hardcoded) sampling rate aims for\nan average of sampling one allocation every 32 MB; for example, we an\nallocation request for 100 bytes becomes part of the sample with the\nsame probability as if we had flipped 100 times a biased coin that\nlands on \"head\" with probability `1 / (32 * 1024 * 1024)`, and decided\nto make the request part of the sample if any of these coin flip had\nlanded on \"head.\"\n\nThis memory-less sampling strategy makes it possible to derive\nstatistical bounds on the shape of heap allocation calls, even with an\nadversarial workload.  However, a naive implementation is slow.\nRather than flipping biased coins for each allocated byte, we instead\ngenerate the number of consecutive \"tails\" results by generating\nvalues from an Exponential distribution.\n\nWhenever a call to `malloc`, `calloc`, or `realloc` is picked for\nsampling, libpoireau executes code that is instrumented with USDT\n(user statically-defined tracing) probes.  Linux `perf` can annotate\nthat code to generate events (this is a system-wide switch, for every\nprocess that linked the shared library); we use these events to let\nthe kernel capture callstacks for each sampled call.\n\nIn addition, these allocation requests are diverted to an internal\ntracking allocator.  This lets us identify calls to `free` and\n`realloc` on tracked allocations, which is crucial to generate paired\nUSDT events (\"this allocation was freed or reallocated\"); it also\nensures we pass these allocations back to the backup tracking\nallocator, rather than the system malloc.\n\nSome synthetic microbenchmarks\n------------------------------\n\nPerformance sensitive programs tend to avoid dynamic memory allocation\nin hot spots.  That being said, here are a couple microbenchmark to\ntry and upper bound the overhead of `LD_PRELOAD`ing in\n`libpoireau.so`, by repeatedly making pairs of calls to `malloc` and\n`free` (a best case for most memory allocators) in a single thread.\nThe results below were timed on an unloaded AMD EPYC 7601 running\nLinux 5.3.11 and glibc 2.27.\n\nLarge allocations (1 MB), with a sample period of 32 MB (p = 3.2%):\n\n    baseline (glibc malloc): 0.092 us/malloc-free (0.047 user, 0.046 system)\n        preloaded, no probe: 0.153 us/malloc-free (0.058 user, 0.094 system)\n     preloaded, with probes: 0.236 us/malloc-free (0.067 user, 0.169 system)\n    preloaded, with tracing: 0.271 us/malloc-free (0.069 user, 0.203 system)\n\nThis is pretty much our worst case: we expect to trigger allocation\ntracking very frequently, once every 32 allocation, and our tracking\nallocator is slightly more complex than a plain `mmap`/`munmap`\n(something we should still improve).\n\nMid-sized allocations (16 KB), with a sample period of 32 MB (p = 0.049%):\n\n    baseline (glibc malloc): 0.042 us/malloc-free (0.041 user, 0.001 system)\n        preloaded, no probe: 0.044 us/malloc-free (0.043 user, 0.001 system)\n     preloaded, with probes: 0.046 us/malloc-free (0.042 user, 0.004 system)\n    preloaded, with tracing: 0.054 us/malloc-free (0.042 user, 0.012 system)\n\nAt this less unreasonable size, the overhead of diverting sampled\nallocations to a tracking allocator is less that 5%.  We can also\nobserve that, while triggering an interrupt whenever we execute a\ntracepoint isn't free, the time spent servicing the interrupt is\nrelatively small (\u003c 20%) compared to the time it takes to generate a\nbacktrace.  This isn't surprising, since we use the same part of the\nkernel that's exercised when analysing performance issues with `perf`.\n\nSmall-sized allocations (128 B), with a sample period of 32 MB (p = 0.00038%):\n\n    baseline (glibc malloc): 0.017 us/malloc-free (0.017 user, 0.000 system)\n        preloaded, no probe: 0.020 us/malloc-free (0.020 user, 0.000 system)\n     preloaded, with probes: 0.020 us/malloc-free (0.020 user, 0.000 system)\n    preloaded, with tracing: 0.020 us/malloc-free (0.020 user, 0.000 system)\n\nHere, all the slowdown is introduced by trampolining from our\ninterceptor malloc to the base system malloc.\n\nTL;DR: in allocation microbenchmarks, the overhead of libpoireau\ninstrumentation is on the order of 5-20% for small or medium\nallocations, and goes up to ~70% for very large allocations.\n\nEnabling allocation tracing adds another 0-20% for small or medium\nallocations, and ~130% for very large allocations.\n\nThese are worst-case figures, for a program that does *nothing* but\nrepeatedly `malloc` and `free` in a loop.  In practice, a performance\nsensitive program hopefully spends less than 10% of its time in memory\nmanagement (and much less than that in large allocations), which means\nthe total overhead introduced by libpoireau and capturing stack traces\nis probably closer to 1-5%.\n\nVendored dependencies\n---------------------\n\nlibpoireau includes code derived from\n[xoshiro 256+ 1.0](http://prng.di.unimi.it/xoshiro256plus.c),\nwritten in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org)\nand [dedicated to the public domain](http://creativecommons.org/publicdomain/zero/1.0/).\n\nlibpoireau includes Systemtap's `sys/sdt.h`, a file\n[dedicated to the public domain](http://creativecommons.org/publicdomain/zero/1.0/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbacktrace-labs%2Fpoireau","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbacktrace-labs%2Fpoireau","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbacktrace-labs%2Fpoireau/lists"}