A Julia interface for the Tracy instrumentation-based profiler, designed for multithreaded simulations.
ZoneProfilers provides utilities to mark up code for logging and timing.
ZoneProfilerTracy provides a wrapper of the Tracy C library v0.9.1 for profiling marked-up code.
ZoneProfilers uses an explicit profiler parameter to be passed through your call chain:
# ZoneProfilers approach - explicit profiler parameter
using ZoneProfilers
function simulate_physics(; profiler=NullProfiler())
@zone profiler sleep(0.01)
end
function run_simulation(; profiler=NullProfiler())
@zone profiler name="simulation" begin
for step in 1:10
simulate_physics(; profiler) # Pass profiler down
end
end
end
# Production: zero runtime overhead due to specialization
run_simulation() # Uses NullProfiler(), compiled away
# Development: full profiling
# Use TracyProfiler_jll to run the tracy GUI
import TracyProfiler_jll
using ZoneProfilerTracy: TracyProfiler
profiler = TracyProfiler(TracyProfiler_jll)
run_simulation(;profiler) # Full instrumentationwait_for_connection(profiler)- Wait for the profiler recorder to be connectedis_connected(profiler)- Check if the recorder is connectedapp_info!(profiler, text)- Write the trace descriptionnew_stack(profiler, name) -> new_profiler- Create a new zone stack
message!(profiler, text; [color])- Log standalone message
@zone profiler [name=, color=, active=] expression- Profile an expression@zone_begin profiler [name=, color=, active=]- Start a profiling zonezone_end!(profiler)- End current zonezone_active(profiler)- Check if current zone is active
zone_color!(profiler, color)- Set zone color at runtimezone_text!(profiler, text)- Add a line of text to the current zone@zone_show profiler vars...- Convenience macro to display variable names and values as zone text@zone_repr profiler vars...- Convenience macro to display repr of variables as zone text
frame_mark!(profiler, [name])- Mark frame boundaryframe_mark_begin!(profiler, name)- Start named frameframe_mark_end!(profiler, name)- End named frame
plot!(profiler, name, value)- Add data point to plot
In addition, ZoneProfilerTracy is not compatible with julia built with the WITH_TRACY=1 make option.
Every instance of string or symbol data passed to the profiler can't be longer than 64 KB.
Symbols passed to the profiler are required to have stable pointers for the rest of the lifetime of the process. Currently, in Julia, Symbols are not garbage collected, but this might change in a future version of the language. To be future-proof, you must ensure there is a global reference to a symbol before passing it to a profiler.
The name passed to the @zone macros is limited to 511 bytes.
Source file path lengths are limited to 2047 bytes.
Due to the mismatch between how multithreading works in Julia vs C++, currently, the GUI display of context switches is not accurate. The "Draw context switches" option should be disabled in the Tracy GUI.
Currently, there is no support for callstack logging. Help in supporting this would be greatly appreciated.
A connected tracy server can read arbitrary memory in the profiled process, so only use ZoneProfilerTracy in trusted environments.
Tracy is not supported on 32-bit systems.
message!(profiler, "hello world")
message!(profiler, Symbol("this string is interned"))
message!(profiler, "warning"; color=:yellow)Zones represent a block of code you want to profile:
@zone profiler name="physics_update" color=:blue begin
# physics_update
sleep(0.1)
endZones can be nested to form a stack:
@zone profiler name="physics_update" color=:blue begin
# physics_update
sleep(0.1)
@zone profiler name="part one" begin
sleep(0.1)
end
@zone profiler name="part two" begin
sleep(0.1)
end
endWhen using the @zone macro, name and color must be literals.
Use zone_color! to set the color of a zone at runtime.
Use zone_text! to append a line of text to the zone annotation.
For convenience, use @zone_show to display variable names and values, or @zone_repr to display just the repr output.
The @zone_show and @zone_repr macros are particularly useful because expressions are only evaluated when the zone is active,
avoiding performance overhead and side effects when profiling is disabled.
@zone profiler name="physics_update" begin
# physics_update
sleep(0.1)
@zone profiler name="part one" color=0x0000FF begin
u = rand()
if u < 0.5
# make the zone red and note the value of `u`
zone_color!(profiler, 0xFF0000)
# Guard with `zone_active(profiler) &&` to avoid constructing the string when not profiling.
zone_active(profiler) && zone_text!(profiler, "Rare event, u = $(u)")
# Or use @zone_show as a convenience - expression is only evaluated when zone is active
@zone_show profiler u
# Or use @zone_repr to show just the repr without the variable name
@zone_repr profiler u
end
sleep(0.1)
end
@zone profiler name="part two" begin
sleep(0.1)
end
endZoneProfilers supports task-local zone stacks.
A profiler with a new zone stack can be made using new_stack.
Asynchronous tasks that create or modify zones or send messages should do so to a local zone stack. Zone stack names can be reused if the previous zone stack with that name is empty.
Multiple tasks should not concurrently share a zone stack.
# Each task gets its own profiling context
Threads.@threads for i in 1:10
local task_profiler = new_stack(profiler, ()->Symbol("worker_$i"))
@zone task_profiler name="parallel_work" begin
if zone_active(task_profiler)
message!(task_profiler, "Hello from worker $(i)")
end
# task-specific work
sleep(rand()*0.1)
# task local information
zone_text!(task_profiler, "Chunk")
zone_value!(task_profiler, UInt64(i))
end
endTrack key simulation metrics over time:
# Monitor performance metrics
particles = Float64[]
for step in 1:1000
@zone profiler name="simulation_step" begin
# Your simulation code
# advance_simulation!(system, dt)
push!(particles, rand())
# Track metrics over time
plot!(profiler, :particle_count, Float64(length(particles)))
end
frame_mark!(profiler) # mark the end of a step and the beginning of the next step.
endFrame marking and plotting are safe to do concurrently with a shared profiler.
Enable profiling only when needed to minimize overhead:
const PROFILING_ENABLED = false
# Profile only when explicitly enabled
@zone profiler active=PROFILING_ENABLED name="expensive_computation" begin
# expensive_computation()
end
# Or use dynamic conditions, for example, only profile on Monday
using Dates
should_profile() = Dates.dayofweek(Dates.now()) == Dates.Monday
@zone profiler active=should_profile name="expensive_computation" begin
# expensive_computation()
endThe expression in the zone is run if the profiling is active or not.
The function passed to active will not be evaluated if the profiler is a NullProfiler, for even less overhead.
- Tracy user manual - The underlying profiling engine and GUI.
- Tracy.jl - An alternative interface for tracy.
- TimerOutputs.jl - Simple timing of sections of code.
- TrackingTimers.jl - Timing of sections of code in the presence of parallelism.
- Extrae.jl - Julia bindings to BSC's extrae HPC profiler.
- IntelITT.jl - Instrumentation for the proprietary Intel VTune profiler.
- NVTX.jl - Instrumentation for the proprietary Nsight systems profiler.
- Statistical profiling in Julia
- Profiling the Julia runtime with Tracy
- Viewing Tracy files in your browser




