ANDRVV/zprof
🧮 Cross-allocator profiler for Zig
Zprof is a cross-allocator wrapper for profiling memory data.
Developed for use in Debug or official modes, it guarantees nearly the same performance as the wrapped allocator. Zprof's development is based on a primary priority: ease of use, improved efficiency, readability, clean, minimal, and well-documented code.
Add Zprof
to your project's build.zig.zon
:
.{
.name = "my-project",
.version = "1.3.0",
.dependencies = .{
.zprof = .{
.url = "https://github.com/ANDRVV/zprof/archive/v1.3.0.zip",
.hash = "...",
},
},
}
Then in your build.zig
, add:
// Add Zprof as a dependency
const zprof_dep = b.dependency("zprof", .{
.target = target,
.optimize = optimize,
});
// Add the module to your executable
exe.root_module.addImport("zprof", zprof_dep.module("zprof"));
Else you can put zprof.zig
in your project's path and import it.
Zig version 0.15.1 or newer is required to compile Zprof
Here's how to use Zprof
in three easy steps:
const std = @import("std");
const Zprof = @import("zprof.zig").Zprof;
pub fn main() !void {
var stdout_buf: [4096]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&stdout_buf);
const stdout = &stdout_writer.interface;
defer stdout.flush() catch {};
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var gpa_allocator = gpa.allocator();
// 1. Create a profiler by wrapping your allocator
var zprof = try Zprof(false).init(&gpa_allocator, stdout);
// false disable thread-safe mode and passing in a writer enables logging
defer zprof.deinit(); // deallocates Zprof instance
// 2. Use the profiler's allocator instead of your original one
const allocator = zprof.allocator;
// 3. Use the allocator as normal
const data = try allocator.alloc(u8, 1024);
defer allocator.free(data);
// Check for leaks
stdout.print("Has leaks: {}\n", .{zprof.profiler.hasLeaks()}) catch {};
}
To start profiling memory usage, simply wrap your allocator with Zprof
:
var zprof = try Zprof(false).init(&allocator, null); // on init, null disables automatic logging
const tracked_allocator = zprof.allocator;
To use Zprof
with mutex, you must enable thread-safe mode:
var zprof = try Zprof(true).init(&allocator, null); // true enables thread-safe mode
const tracked_allocator = zprof.allocator;
If logging is enabled, logs allocated/deallocated bytes when allocator allocates or deallocates.
var zprof = try Zprof(false).init(&allocator, arraylist_writer); // Passing in a writer enables automatic logging
const tracked_allocator = zprof.allocator;
const data = try allocator.alloc(u8, 1024); // prints: Zprof::ALLOC allocated=1024
allocator.free(data); // prints: Zprof::FREE deallocated=1024
Zprof
makes it easy to detect memory leaks in your application:
// At the end of your program or test
const has_leaks = zprof.profiler.hasLeaks();
if (has_leaks) {
// Handle leaks (e.g., report, abort in tests)
std.debug.print("Memory leak detected!\n", .{});
return error.MemoryLeak;
}
The Profiler
struct contains several fields and methods:
Field | Type | Description |
---|---|---|
allocated |
u64 |
Total bytes allocated since initialization |
alloc_count |
u64 |
Number of allocation operations |
free_count |
u64 |
Number of deallocation operations |
live_peak |
u64 |
Maximum memory usage at any point |
live_bytes |
u64 |
Current memory usage |
Method | Description |
---|---|
hasLeaks() |
Returns true if there are memory leaks |
reset() |
Resets all profiling statistics |
test "no memory leaks" {
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var arena_allocator = arena.allocator();
var zprof = try Zprof(false).init(&arena_allocator, null);
defer zprof.deinit();
const allocator = zprof.allocator;
// Perform allocations
const data = try allocator.alloc(u8, 1024);
defer allocator.free(data);
// Verify no leaks
try std.testing.expect(!zprof.profiler.hasLeaks());
}
Made with ❤️ for the Zig community
Copyright (c) 2025 Andrea Vaccaro