Guide

Getting started

Install, adopt a project, collect a trace, read the report — without editing a single line of the project's source.

Requirements

Install

$ uv tool install callsight             # core CLI
$ uv tool install 'callsight[ui]'       # + the web UI
$ uv tool install 'callsight[stream]'   # + the remote streaming server

Both extras can be combined: 'callsight[ui,stream]'. From a source checkout, uv tool install . does the same.

Adopt a project

callsight init copies the hook runtime and the build wiring into a callsight/ directory inside your project, writes a starter trace.config (an existing one is never overwritten), and prints the exact snippet to paste into your build file.

$ cd /path/to/your/project
$ callsight init .
wrote /path/to/your/project/trace.config
copied runtime + make integration into /path/to/your/project/callsight/ (35 sources found)

The build system is auto-detected — CMakeLists.txt means CMake, otherwise Make. Force it with --build make or --build cmake, and add --stream to also copy the on-device streaming client.

Wire it into your build

After your SRCS/OBJS/CFLAGS_SYMBOLS are defined:

CALLSIGHT_DIR ?= callsight
include $(CALLSIGHT_DIR)/Makefile.callsight

instrument: CFLAGS = $(CFLAGS_INSTRUMENT)
instrument: $(BINDIR)/$(TARGET).instr
$(BINDIR)/$(TARGET).instr: $(OBJS) $(TRACE_OBJ) | $(BINDIR)
	$(CC) $(CFLAGS_INSTRUMENT) -no-pie -o $@ $(OBJS) $(TRACE_OBJ) $(LDFLAGS)

The fragment expects SRCS, BUILDDIR, BINDIR, TARGET, CC, CFLAGS_SYMBOLS and LDFLAGS, and defines CFLAGS_INSTRUMENT (your flags + hooks + exclude lists) and TRACE_OBJ (the runtime object, compiled without instrumentation).

After the target is defined:

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/callsight")
include(CallSight)
callsight_instrument(<your-target>)

Normal builds are completely untouched — instrumentation is opt-in per configure:

$ cmake -DCALLSIGHT_INSTRUMENT=ON -B build-instr
$ cmake --build build-instr

Full details for both, including every overridable variable, are on the reference page.

Why -no-pie? It keeps runtime addresses equal to link addresses, so the analyzer can hand them straight to addr2line. Both integrations add it for you. If you link the instrumented binary yourself and skip it, analyze will warn that nothing resolved.

Build the instrumented profile

$ make clean && make instrument       # clean when switching profiles

The instrumented binary lives beside your normal one (bin/yourapp.instr by convention with the Make integration), so a normal make still produces an uninstrumented build with zero hooks in it.

Collect a trace

The hooks are compiled in but inert until you ask for a trace, so an instrumented binary is safe to run normally.

$ TRACE_ENABLE=1 TRACE_MAX=1000000 ./bin/yourapp.instr

Each thread writes traces/trace.<pid>.<tid>.bin. A clean exit flushes everything; a killed process loses each thread's buffered tail.

Analyze

$ callsight analyze traces/ --exe ./bin/yourapp.instr --top 20
events=1000000 threads=26 functions=139 span=48.7ms unmatched_exits=0 unclosed_enters=136

== TOP BY SELF TIME ==
     calls      incl_ms      self_ms       max_ms  function (first location)
       272      529.382      529.382        9.983  timer_sleep_us (src/utils/timer.c:38)
    127048       52.470       52.470        6.643  qs_swap (src/sort/quicksort.c:5)
     57284      366.418       35.370        5.738  fft_recursive (src/signal/fft.c:63)

unmatched_exits=0 means every exit was matched to an enter — a clean trace. Read the columns, export a flame graph, and interpret the summary line on the analysis page.

Narrow the selection

Your first wide run is a survey. Sort it by calls, find the chatty leaf helpers, and exclude them — they usually account for most of the volume and none of the insight.

# trace.config
exclude src/utils/rng.c        # RNG helpers: pure noise
exclude-func crc32_update     # one hot function, by name

Then rebuild. Or go the other way and name a single entry point — callsight resolves its whole call subtree from the sources and instruments exactly that:

$ callsight select src/ --function handle_request
$ callsight scan . --config trace.config     # preview without building

The whole selection model is on the configuration page.

Try it without a project

The repository ships tests/matrixlab, a multi-threaded C11 workload used as the end-to-end fixture — the traces in these docs come from it.

$ git clone https://github.com/harshithsunku/callsight && cd callsight/tests/matrixlab
$ make clean && make instrument
$ TRACE_ENABLE=1 TRACE_MAX=1000000 timeout 5 ./bin/matrixlab.instr
$ uv run callsight analyze traces/ --exe bin/matrixlab.instr --top 20