kanejaku.org

Tracy Profiler ZoneScoped in Rust

11 Oct 2020

Tracy is a feature rich, frame-oriented profiler which is very useful for developing games and emulators. I’ve been using the profiler for my Famicom/NES emulator written in Rust.

Tracy provides C++ and C APIs so it’s fairly easy to create Rust bindings for most of them. An exception is the ZoneScoped macro which automatically records function name and source location. It’s not trivial to create an equivalent in Rust because it relies on both RAII and C/C++ predefined macros such as __LINE__. I’ve been trying to figure out ways to create an equivalent in Rust because I think the macro is the most useful API of Tracy.

My current approach is to use procedural macros combined with a struct that implements Drop. The struct ties up ___tracy_emit_zone_begin() in the constructor and ___tracy_emit_zone_end() in the destructor. A procedural macro injects code that creates the struct at the beginning of the annotated function. In this approach, the following code:

#[rustracy::zone_scoped]
fn execute_frame(engine: &mut Engine) {
  // …
}

will produce something like the below:

Note that zone name and location are recorded automatically.

A major problem of the approach is that it can’t automatically generate struct/trait names. The following code:

impl Engine {
  #[rustracy::zone_scoped]
  fn execute_frame(&mut self) {
    // …
  }
}

doesn’t generate zone name Engine::execute_frame() but generates execute_frame(). I added another producedral macro that takes a “prefix” to work around the problem, but I don’t like the workaround.

You can find my current approach at GitHub.