tracy(1)

Tracy is a frame profiler, supporting manual code instrumentation and providing a sampling profiler.

One can either record and visualize the profiling data live using tracy-profiler or record the profiling data to a file using tracy-capture.

tracy-profiler [file] [-p port]

tracy-capture -o file [-f] [-p port]
    -f   overwrite <file> if it exists

Example

The example showcases different cases:

  1. Use tracy from a single binary. In that case the TracyClient.cpp can be directly linked / included in the instrumented binary.
  2. Use tracy from different binaries (eg main executable + shared library). In this case the TracyClient.cpp should be compiled into its own shared library, such that there is a single tracy client.
  3. Use tracy from different binaries on windows. In this case the TracyClient.cpp must be compiled again into a separate shared library, while defining TRACY_EXPORTS. The code being instrumented must be compiled with TRACY_IMPORTS defined.

An instrumented c++ example:

#include <chrono>
#include <thread>

#include <tracy/Tracy.hpp>

#ifdef USE_FOO
extern "C" void foo_comp_hook(int64_t);
#endif

void init() {
  // Create a named zone (active for the current scope).
  // Name will be used when rendering the zone in the thread timeline.
  ZoneScopedN("init()");
  // Set explicit color for the rendered zone.
  ZoneColor(0xff0000);

  std::this_thread::sleep_for(std::chrono::seconds(1));
}

void comp(const char* name) {
  // Track call count.
  static int64_t ccnt = 0;
  ccnt += 1;

  // Create an unnamed zone for the current scope.
  ZoneScoped;
  // Name the zone by formatting the name dynamically.
  // This name is shown for the zone in the thread timeline, however
  // in the zone statistics they are all accounted under one common
  // zone "comp".
  ZoneNameF("comp(%s)", name);
  // Additional text to attach to the zone.
  ZoneTextF("text(%s)", name);
  // Additional value to attach to the zone measurement.
  ZoneValue(ccnt);

  // Statistics for dynamic names, text and values can be looked at in the zone
  // statistics.There measurements can be grouped by different categories.

  // Add a simple plot.
  TracyPlot("comp-plot", ccnt % 4);

  std::this_thread::sleep_for(std::chrono::milliseconds(100));

#ifdef USE_FOO
  foo_comp_hook(ccnt);
#endif
}

void post_comp() {
  // Create an unnamed zone for the current scope and capture callstack (max
  // depth 10). Capturing callstack requires platform with TRACY_HAS_CALLSTACK
  // support.
  ZoneScopedS(10);
  // Name the zone, w/o formatting.
  const char name[] = "post_comp()";
  ZoneName(name, sizeof(name));

  // Add trace messages to the timeline.
  TracyMessageL("start sleep in post_comp()");
  std::this_thread::sleep_for(std::chrono::milliseconds(50));
  TracyMessageL("end sleep in post_comp()");
}

void fini() {
  // Create a named zone with an explicit color.
  ZoneScopedNC("fini()", 0x00ff00);
  std::this_thread::sleep_for(std::chrono::seconds(1));
}

int main() {
  // Create a named zone.
  ZoneScopedN("main()");

  init();

  int step = 0;
  while (step++ < 10) {
    // Create a frame message, this start a new frame with the name
    // "step" and end the previous frame with the name "step".
    FrameMarkNamed("step");
    // Create a named scope.
    ZoneScopedN("step()");
    comp("a");
    comp("b");
    comp("c");
    post_comp();
  }

  fini();
}

An instrumented c example:

#include <stdint.h>
#include <inttypes.h>
#include <stdio.h>

#include <tracy/TracyC.h>

static void comp_helper(int64_t i) {
  char buf[64];
  int cnt = snprintf(buf, sizeof(buf), "helper(%" PRId64 ")", i);

  // Create an active unnamed zone.
  TracyCZone(ctx, 1);

  // Name the zone.
  TracyCZoneName(ctx, buf, cnt);
  // Add custom text to the zone measurement.
  TracyCZoneText(ctx, buf, cnt);
  // Add custom value to the zone measurement.
  TracyCZoneValue(ctx, i);

  for (int ii = 0; ii < i * 100000; ++ii) {
    /* fake work */
  }

  // End the zone measurement.
  TracyCZoneEnd(ctx);
}

void foo_comp_hook(int64_t cnt) {
  // Create an active named zone.
  TracyCZoneN(ctx, "foo", 1);

  for (int i = 0; i < cnt; ++i) {
    // Plot value.
    TracyCPlot("foo_comp_hook", cnt + i);

    comp_helper(i);
  }

  // Configure plot "foo", probably best done once during initialization..
  TracyCPlotConfig("foo", TracyPlotFormatNumber, 1 /* step */, 1 /* fill */,
                   0xff0000);
  // Plot value.
  TracyCPlot("foo", cnt);

  // End the zone measurement.
  TracyCZoneEnd(ctx);
}

Raw build commands to demonstrate compiling tracy w/o cmake, in case we need to integrate it into a different build system.

B := BUILD

main: $(B)/main-static $(B)/main-dynamic $(B)/main-dynamic-win
tracy: $(B)/tracy
.PHONY: main tracy

# -- TRACY STATIC ---------------------------------------------------------------

$(B)/main-static: main.cpp | $(B)
	clang++ -DTRACY_ENABLE -I$(B)/tracy/public -o $@ $^ $(B)/tracy/public/TracyClient.cpp

# -- TRACY DYNAMIC --------------------------------------------------------------

$(B)/main-dynamic: main.cpp $(B)/foo.so $(B)/TracyClient.so | $(B)
	clang++ -DTRACY_ENABLE -I$(B)/tracy/public -DUSE_FOO -o $@ $^

$(B)/foo.so: foo.c $(B)/TracyClient.so
	clang -DTRACY_ENABLE -I$(B)/tracy/public -fPIC -shared -o $@ $^

$(B)/TracyClient.so: $(B)/tracy/public/TracyClient.cpp
	clang++ -DTRACY_ENABLE -I$(B)/tracy/public -fPIC -shared -o $@ $^

# -- TRACY DYNAMIC WINDOWS ------------------------------------------------------

$(B)/main-dynamic-win: main.cpp $(B)/foo.dll $(B)/TracyClient.dll
	@# eg run with wine
	zig c++ -target x86_64-windows -DTRACY_ENABLE -DTRACY_IMPORTS -DUSE_FOO -o $@ $^ -I $(B)/tracy/public

$(B)/foo.dll: foo.c $(B)/TracyClient.dll
	zig c++ -target x86_64-windows -DTRACY_ENABLE -DTRACY_IMPORTS -fPIC -shared -o $@ $^ -I $(B)/tracy/public

$(B)/TracyClient.dll: $(B)/tracy/public/TracyClient.cpp
	@# win libs from 'pragma comment(lib, ..)'
	zig c++ -target x86_64-windows -DTRACY_ENABLE -DTRACY_EXPORTS -fPIC -shared -o $@ $^ -lws2_32 -ldbghelp -ladvapi32 -luser32

# -- TRACY ----------------------------------------------------------------------

# Get latest tracy and build profiler.
$(B)/tracy: $(B)
	cd $(B); bash $(CURDIR)/get-tracy.sh
.PHONY: $(B)/tracy

$B:
	mkdir -p $(B)
.PHONY: $(B)

# -- CLEAN ----------------------------------------------------------------------

clean:
	$(RM) $(B)/*.so $(B)/*.dll $(B)/*.pdb $(B)/*.lib $(B)/main*

distclean:
	rm -rf $(B)

Find get-tracy.sh here.