Architecture
June 12, 2026 · View on GitHub
Developer-focused reference for the LoliProfiler codebase. Covers data flow, components, patterns, and pitfalls for contributors.
Core Data Flow
GUI Mode:
- Android agent hooks malloc/free and sends
RawStackInforecords via TCP MainWindowreceives records in real-time, buildsStackTraceModel- User interacts with visualizations (timeline, tree map, call tree, fragmentation)
- Data can be saved to
.lolifiles for later analysis
CLI Mode:
- Android agent hooks malloc/free and sends
RawStackInforecords via TCP CliProfilerreceives records and caches to disk (cache/directory)- After capture completes (duration timeout or Ctrl+C), processes cached records
- Saves complete
.lolifile for GUI analysis
Key Components
Entry Points:
src/main.cpp— GUI mode entry point (Qt Widgets application)src/main_cli.cpp— CLI mode entry point (QCoreApplication, no GUI)
Controllers:
MainWindow(src/mainwindow.cpp) — GUI controller, manages all UI and profiling operationsCliProfiler(src/cliprofiler.cpp) — CLI controller, manages headless profiling workflowProfileComparator(src/profilecomparator.cpp) — Comparison engine for detecting memory regressions between two.lolifiles
Process Management (Base: AdbProcess):
StartAppProcess— Launches target application via ADBStackTraceProcess— TCP socket connection to Android profiling agent, receives allocation/deallocation recordsMemInfoProcess— Captures system memory info (/proc/meminfo) via ADBScreenshotProcess— Takes device screenshots for correlationAddressProcess— Resolves memory addresses to function symbols
Data Models:
StackTraceModel— Table model containingStackRecordentries (UUID, time, size, address, library)StackTraceProxyModel— Filtering/sorting proxy for stack tracesRawStackInfo— Raw allocation/deallocation record format from Android agentSMapsSection— Memory mapping information from/proc/pid/smaps
Android Native Libraries (plugins/Android/):
- Built with NDK for multiple architectures (armeabi, armeabi-v7a, arm64-v8a)
- Hooks malloc/calloc/realloc/free using custom interception techniques
- Communicates with desktop client via TCP socket on port 44515
- Sends stack traces with allocation/deallocation metadata
Key Data Structures
StackRecord:
struct StackRecord {
QUuid uuid_; // Call stack UUID
quint32 seq_; // Sequence number
qint32 time_; // Timestamp
qint32 size_; // Allocation size
quint64 addr_; // Memory address
quint64 funcAddr_; // Function address
HashString library_; // Library name (optimized string storage)
};
RawStackInfo (from Android agent):
- Contains allocation/deallocation flag, timestamp, size, address, call stack frames
Call Stack Maps:
callStackMap_— Maps UUIDs to call stack sequences (library + function address pairs)symbloMap_— Address-to-symbol resolution cachefreeAddrMap_— Tracks deallocated addresses to filter out freed memory
Conditional Compilation
The codebase uses the NO_GUI_MODE preprocessor flag to enable CLI-only builds:
- When defined, excludes Qt Widgets/Charts/OpenGL dependencies
- Allows shared components like
ConfigDialogto function as data containers without UI - Process classes (
StartAppProcess,ScreenshotProcess) work in both modes - Build system automatically defines this flag when compiling LoliProfilerCLI
When adding features to shared components:
- Guard GUI-specific code with
#ifndef NO_GUI_MODE - Ensure core functionality works without Qt Widgets dependencies
- Test both executables after changes
- Update
CMakeLists.txtif adding new files
Configuration Sharing
Both GUI and CLI modes share the same configuration file:
- Windows:
%LOCALAPPDATA%\MoreFun\LoliProfiler\loli3.conf - macOS/Linux:
~/.local/share/MoreFun/LoliProfiler/loli3.conf
Both executables set organization name ("MoreFun") and application name ("LoliProfiler") to ensure config compatibility. This allows CLI to use settings configured in GUI (compiler type, architecture, whitelist, blacklist).
Android Agent Protocol
The Android agent communicates via TCP socket with binary protocol:
- Port: 44515 (forwarded via ADB)
- Commands:
START_CAPTURE,STOP_CAPTURE,SMAPS_DUMP - Data format: LZ4-compressed
RawStackInforecords
Multi-threading
- GUI operations run on main Qt thread
- ADB processes run asynchronously (
QProcess) - TCP socket communication is event-driven
- Data processing may use
Qt::Concurrentfor heavy operations - CLI mode uses
QCoreApplication(no GUI event loop)
Signal Handling (CLI Mode)
CLI implements graceful shutdown via SIGINT/SIGTERM handlers:
- Signal handler is async-signal-safe (uses
write()instead of stdio) - Invokes
CliProfiler::RequestStop()viaQt::QueuedConnection(thread-safe) - Ensures proper SMAPS dump and file save before exit
- Allows profiling across app restarts (doesn't auto-exit when app exits)
Comparison Algorithm
ProfileComparator compares two .loli files in five steps:
- Load both profiles and build allocation maps
- Group allocations by
(library_name, function_address)key - Calculate statistics (new, removed, changed allocations)
- Build call tree with size/count deltas
- Export as text or
.loliformat
File Format
.loli files use a binary format:
- Magic number:
0xA4B3C2D1 - Version:
106 - Contains: stack records, call stacks, symbols, memory info series, screenshots, SMAPS sections
Common Pitfalls
- Path spaces on Windows — Always quote paths with spaces when passing to ADB commands
- JDWP injection — Requires debuggable apps or rooted devices
- Symbol file structure — Must match Android library directory layout
- Memory optimization — Use streaming mode for large datasets (CLI enables by default)
- Comparison version mismatch — Both
.lolifiles must have the same version/magic number