LoliProfiler: native allocation capture for Android games with a treemap and a call tree
Memory instrumentation tool with CI and AI support for android app&game developers.
At a glance
- What is it?
- LoliProfiler is a Tencent open source memory instrumentation tool that captures native allocations inside an Android application and shows them as call stacks, a treemap, a memory timeline, screenshots and smaps data, with a desktop GUI and a headless CLI sharing one core. It suits a game or app developer chasing a native memory growth problem, and it needs a debuggable build to attach to.
- Who is it for?
- LoliProfiler fits an Android developer or game studio that has found native memory growth and needs the allocation call tree rather than a Java heap dump, and it does not fit someone looking for a general purpose profiler or for a release build they cannot modify.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Yes. The repository last received commits 23 days ago.
- What is it written in?
- Mainly C++, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Capturing native allocations, not the Java heap
LoliProfiler captures native allocations in Android applications and displays call stacks, a treemap, a memory timeline, screenshots, and smaps data. That sentence is the whole scope of the tool, and every word in it narrows it.
Native allocations means the C and C++ side of the process, which is where a Unity or Unreal game spends most of its memory and where the Java heap dump does not reach. The treemap is the visual model for that: a rectangle per allocation site, sized by the bytes it accounts for, which makes a leak or a runaway buffer visible at a glance in a way a flat list of the top ten offenders does not.
The smaps data and the screenshots are there to give the memory numbers context. smaps is the kernel's own view of a process's mappings, so it lets you check the profiler's accounting against the operating system's. The screenshots let you correlate a memory event with what was on screen, which is how you find the moment the frame rate dropped.
The project description calls it a memory instrumentation tool with CI and AI support for Android app and game developers, and the repository topics name the two engines it is used with, unity3d and unreal-engine-4, alongside android, cpp, memory, profiler and imgui. So the audience is a game or application developer on Android who has a native memory problem, and the primary language is C++.
The last push was on 2026-09-30, the same day as the v1.4.0 release, with v1.3.1 from 2026-06-10 and v1.3.0 from 2026-04-29 before it.
A GUI and a CLI that share one core
The README says the desktop GUI and the headless capture CLI use the same Qt-free C++ core, and that single sentence explains the shape of the repository and the reason to care about it.
The tools table gives the four entry points. LoliProfilerImGui launches or attaches, inspects live and saved captures, and saves .loli files. LoliProfilerCLI captures headlessly, exports .txt or a SQLite .db, compares captures, and symbolizes offline. LoliProfilerCompare compares two saved captures in independent, dockable Base, Comparer and Diff panels. The agentcli Python package queries the SQLite call tree from scripts or agents, and the loli command remains available.
The Qt-free point is the one that matters most in practice, and it is not a detail. Dear ImGui renders its own widgets and does not own a widget toolkit, so there is no Qt runtime to install on the developer machine and no Qt to ship in the container that builds it. The build Dockerfile is a plain ubuntu:20.04 image with the X11 and GTK development packages and no Qt among them, which is the practical confirmation.
The split between the GUI and the CLI is what makes the tool usable in CI, and the README documents the shared logging API that makes the two behave the same way. The GUI's Console tab shows capture and load timings, and the same diagnostics can be written to a file by adding --log-file profiler.log to save timestamped diagnostics and --log-level debug for more detail. So the same binary produces the same logs whether a human is watching or a pipeline is not.
The agentcli package is the third leg. Its pyproject has no dependencies at all, with a comment that tree_model uses only stdlib plus re, and it registers two console scripts, loli and agentcli, pointing at the same entry point. A zero-dependency query tool over a .db file is exactly the shape that an automated analysis step or an agent can call.
The build asks before it downloads an SDK
Building from the repository root is one command.
python scripts/build.pyThe interactive menu builds the GUI, CLI, Android hooks and release zip, and the README is explicit about one behaviour: SDK and NDK downloads require your confirmation. That is a deliberate design choice for a toolchain that fetches an NDK, and it means a build in an automated environment has to be told to proceed, which the build guide covers with its non-interactive commands.
The documentation set is broader than the README, and it is split by audience. There is a quick start with a Chinese version alongside it, macOS build notes and Linux build notes, a capture, dump and compare CLI document, the agentcli README for indexed .db heap exploration, game engine integration with a Chinese version, and troubleshooting plus architecture documents. The Chinese translations are a pattern worth noting, because a tool used by game studios in China tends to carry them, and they are not an afterthought here since the quick start and the engine integration guide both have one.
The Dockerfile shows what the build environment actually needs, and it is a smaller list than you might expect. On ubuntu:20.04 with DEBIAN_FRONTEND set to noninteractive, it installs build-essential, wget, unzip, git, python3 with pip, pkg-config, the GTK 3 development package, the X11 development packages including xrandr, xcursor and xi, libudev, the Mesa GL development package, and freetype. It then installs cmake through pip with a version constraint of at least 3.24 and below 4, downloads the Android NDK r20 for Linux x86_64 from Google's repository, points ANDROID_NDK_HOME and Ndk_R20_CMD at it, and adds it to the PATH. The working directory is /workspace/loli_profiler and the default command is a shell.
That NDK version is pinned to r20, which is an old release, and pinning it is what makes the Android hooks build reproducibly rather than tracking the newest NDK. The cost is that the hooks are compiled against a toolchain from a specific era, and the make install path for the agent side is not the same as the native side.
Hooks, injection, and what the tool depends on upstream
The Android side of LoliProfiler is where the interesting engineering sits, and the README is unusually clear about where the ideas come from.
The allocation hooks come from xHook, an Android native allocation hooking library, and the symbol lookup helper is Nougat_dlfunctions. The README states that the Android hook also draws on upstream stack-trace and sampling techniques documented in Chromium and Perfetto, and that the JDWP injection approach is described in a linked article on library injection for debuggable Android apps.
The JDWP reference is the operational constraint to hold in mind. Injection through the Java Debug Wire Protocol works on debuggable applications, which means the app you are profiling has to be a debuggable build. For a game studio shipping on the Play Store, that means a build signed with a debug key or with the debuggable flag set, and a workflow that can produce one.
The comparison to a third-party memory analysis tool is at the component level rather than the product level, and it is worth reading as a list of what you would otherwise have to write. Dear ImGui provides the dockable desktop interface. ImGui-SFML handles ImGui rendering and event integration. SFML provides the window, the graphics, and the device socket transport. nativefiledialog-extended gives native file and folder dialogs. tiny-process-library runs the ADB, Python and symbol-tool subprocesses. RapidJSON holds the application settings. SQLite provides the indexed .db snapshot export and the agent queries. LZ4 compresses the capture packets. Nougat_dlfunctions handles the Android symbol lookup.
The one credit that is unusual is the comparer: the README says it builds on leoin2012's fork of loli_profiler, whose Loli Compare module credits shuchangliu. The project also credits Smashicons and Freepik for the legacy toolbar assets. A tool that names its own lineage this precisely is easier to trust about the parts it did not write.
Every allocation, or only the ones still alive at stop
One configuration decision during capture determines what you can learn afterwards, and it is worth understanding before you start rather than after.
During capture, you choose whether to keep every allocation record or retain only allocations still live at stop. Those are two quite different datasets. The first is a complete log of every allocation and free over the capture window, which is what you want when you are chasing a leak that has already been freed and reused, or when you need the full history of a churn problem. The second is a snapshot of what is still resident when the capture stops, which is smaller, faster to store, and answers the question of what is holding memory right now.
The second mode is the natural default for most investigations and the first is the one you reach for when the simple version is not enough. The storage difference is substantial on a game that allocates and frees continuously, because the first mode is dominated by short-lived objects that never mattered.
The same distinction reappears in the saved records, where the Stacktrace panel has an All Allocations / Persistent selector that changes the inspection view for saved records. So the choice is not only made at capture time; it is also recorded in the file and can be reinterpreted later. The .loli file that LoliProfilerImGui saves carries enough information to switch views, and the CLI can export the same data as .txt or as a SQLite .db.
The one hard limit is stated plainly: symbol names require a matching application library with suitable debug information. Without matching debug symbols for the libraries in the captured stacks, the profiler can show you the addresses and the allocation sizes but not the function names, which removes most of the value of a call tree. That requirement is also why the project depends on a symbol tool subprocess, and why the issue reporting checklist asks for the AKS-style identifiers: without the right binaries the result is a wall of addresses.
The Compare window, and the case for looking at two captures side by side
The Compare feature is what separates a memory profiler from a memory dump viewer, and the README treats it as a headline capability.
Compare opens dockable Base, Comparer and Diff views for two captures, and there is a separate LoliProfilerCompare tool for comparing two saved captures in independent, dockable Base, Comparer and Diff panels. The three panels are a sensible decomposition. Base is one capture, Comparer puts two side by side for direct comparison, and Diff is the change between them, which is the view that answers the question a developer actually has, which is what grew between yesterday and today.
The workflow that makes this useful is the one the CLI supports: capture headlessly, export to .db, and then query the SQLite call tree from a script or an agent. The agentcli package exists to make that last step scriptable, and since it has no dependencies beyond the standard library, it can run anywhere a Python interpreter does, including in a pipeline that has no build step.
There is a second, cheaper way to read a capture, which is the treemap. It answers a different question, the one about distribution rather than change: a rectangle per allocation site sized by bytes makes the largest consumers obvious, and in a game a single oversized allocation, such as a texture buffer or an audio ring, is usually visible as one dominating block that a call tree buries.
The project also ships a top level analyze_heap.py and a markdown_to_html.py, and an openspec directory and a plugins directory, which suggests the analysis is meant to be extended rather than taken as fixed. The agentcli README is the entry point for that.
The docs list a CLI compare mode guide of its own, docs/CLI_COMPARE_MODE.md, which is where the details of the compare workflow live, and the quick start document is where the capture flow is described. The README is deliberately a summary, and a reader who wants to use the tool rather than understand its architecture should start with those two.
Where the licence is not what you expect
The licence entry is the part of this repository that needs checking before use, and the README is brief about it.
The licence is recorded as a custom one that GitHub cannot classify, and the README says to see the LICENSE file and the licenses in each vendored component. That second half matters as much as the first. This project vendors a stack of third-party libraries, each with its own terms, and Dear ImGui, SFML, SQLite, LZ4, RapidJSON, xHook and the rest are each licensed differently. Anyone redistributing a build of LoliProfiler, whether as a binary to a team or as a tool inside a larger pipeline, is distributing those components too, and each one's terms travel with it.
The vendored set is listed in the README's table of open-source components, and the top level carries a .gitmodules file, which is how those components are brought in. The rest of the top level is a CMakeLists.txt, a Dockerfile, a pyproject.toml for the agentcli package, a loliprofiler.pro project file, an include directory for the public headers, and directories for src, thirdparty, plugins, scripts, tests, docs, python and res.
The authoring setup is also worth a note, because it explains some of the odd files. The repository contains a CLAUDE.md, and the top level includes an openspec directory, which together suggest the project is being maintained with the help of an AI coding assistant as part of its workflow. That is a change in how a project is run rather than a change in what it does, but for anyone evaluating a dependency it is a reasonable thing to know, and it explains the presence of an agentcli package whose stated purpose is to let scripts and agents query the capture data.
Editorial conclusion
LoliProfiler fits an Android developer or game studio that has found native memory growth and needs the allocation call tree rather than a Java heap dump, and it does not fit someone looking for a general purpose profiler or for a release build they cannot modify. Before adopting it, confirm the application under test is debuggable, because the injection approach the project documents works on debuggable apps, and budget for the build step, since the SDK and NDK downloads require your confirmation and the build guide has separate notes for Windows, macOS and Linux.
Frequently asked questions
What does LoliProfiler capture?
It captures native allocations in Android applications and displays call stacks, a treemap, a memory timeline, screenshots and smaps data. During capture you choose whether to keep every allocation record or only allocations still live when the capture stops.
How do I build LoliProfiler?
From the repository root, run python scripts/build.py, which opens an interactive menu that builds the GUI, the CLI, the Android hooks and the release zip. SDK and NDK downloads require your confirmation, and the build guide covers prerequisites, non-interactive commands and Windows, macOS and Linux specifics.
What is the agentcli Python package for?
It lets you query the SQLite call tree of a .db export from scripts or agents, and it has no dependencies beyond the standard library. It installs two console scripts, loli and agentcli, that both point at the same entry point.
Why do captured stacks show addresses instead of function names?
Symbol names require a matching application library with suitable debug information. Without matching debug symbols for the libraries in the captured stacks, the profiler can show allocation addresses and sizes but not the function names behind them.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/tencent-loli-profiler)Was this useful?