Reference

Reference

Every subcommand, every build-integration variable, every environment variable, and the limitations worth knowing before you rely on a number.

CLI

CommandPurpose
callsight init <dir>Adopt callsight into a project: copy the runtime and build wiring, write a starter config, print the wiring snippet
callsight run -- <cmd>Run a binary with tracing on and report on it, in one step
callsight scan <dir>Preview which sources a config selects, without building
callsight select <dir>Explore a function's call subtree; emit the matching config lines
callsight flagsPrint the compiler flags (what the build integrations call)
callsight analyze [traces/]Hotspot report, JSON, folded stacks, a Perfetto timeline, or hot call sites
callsight diff a.json b.jsonCompare two JSON reports; fail a build on a regression
callsight doctorCheck the toolchain, the config, the trace directory and free space
callsight uiLocal web UI (needs the ui extra)
callsight serveTCP server for remote streams (needs the stream extra)
callsight provisionDownload the bundled static ctags used by the UI config builder
callsight --versionInstalled version

init

callsight init <dir> [--build make|cmake] [--stream]
--buildForce the build system. Default: auto-detect (CMakeLists.txt → cmake, else make)
--streamAlso copy the on-device streaming client (trace_stream.c + vendored zstd)

An existing trace.config is never overwritten.

run

callsight run [options] -- <command> [args...]

Sets the environment, runs the command, then analyzes what it recorded. The four-step loop is identical every time and easy to get subtly wrong — most often by reporting on a traces/ directory that still holds the previous run.

--dirTrace directory. Default traces; files from an earlier run are cleared first
--keepKeep those earlier files instead, and report on both runs together
--timeout NStop the program after N seconds and report on what it recorded
--modeevents (default) or summary
--max-mb, --max-events, --fullCapture limits — see capture limits
--threads, --clockThread filter and timestamp source
--exeBinary to symbolize with. Default: the command itself
--out FILEWrite the report to a file. The traced program shares stdout, so machine-readable output needs somewhere of its own
--top, --format, --addr2line, --subtract-overheadPassed through to analyze
callsight run --timeout 10 --top 20 -- ./bin/app.instr --workload heavy
callsight run --mode summary --out report.json --format json -- ./bin/app.instr

--timeout sends SIGTERM first so a clean exit can flush each thread's buffered tail; only a program that ignores it gets killed.

scan

callsight scan <dir> [--config trace.config]

Prints how many sources would be instrumented versus excluded, lists the excluded ones, and — for an include-func config — the subtree size and any substring-collision warnings.

select

callsight select <dir> --function NAME [--depth N] [--threads GLOB] [--list]
--function, -fSeed function; repeatable
--depth, -dLimit subtree expansion (0 = just the seed, 1 = direct callees). Default: full subtree
--threads, -tAlso print a TRACE_THREADS runtime hint
--list, -lList every function callsight can see, with the files defining it

flags

callsight flags --config CONFIG [--scan DIR] [--format make|raw]
                [--compiler auto|gcc|clang] [--compiler-cmd CC] [--print] -- srcs...
--configConfig path (required)
--scan DIRCollect sources recursively under DIR instead of listing them
--formatmake (default) prints a CFLAGS_INSTRUMENT = … assignment for $(eval $(shell …)); raw prints only the flags
--compilerTarget toolchain. auto (default) detects it by running --compiler-cmd
--compiler-cmdCompiler command used for detection. Default: $CC, else cc
--printHuman-readable selection summary on stderr

A selective config under a detected Clang exits with an explanation rather than emitting GCC-only flags. A failed detection is treated as GCC, so detection can never break a build that would otherwise work.

analyze

callsight analyze [tracedir] [--exe BIN] [--top N]
                  [--format text|json|folded|chrome|callers]
                  [--addr2line CMD] [--subtract-overhead]
tracedirDirectory of trace.*.bin files. Default traces
--exeInstrumented binary for addr2line. Default: the single *.instr under ./bin or .
--topRows per table (default 20). With --format json, 0 means every row
--formattext tables (default), json for tooling, folded collapsed stacks for flamegraph.pl and speedscope, chrome for ui.perfetto.dev, callers for hot call sites
--addr2lineThe addr2line to use. Default $CALLSIGHT_ADDR2LINE, else the host one. A cross-compiled binary needs its own toolchain's copy — host binutils cannot read a foreign ELF
--subtract-overheadDeduct the runtime's own measured per-hook cost from the reported times

Summary traces (TRACE_MODE=summary) are detected automatically and merged. They hold per-function totals rather than call paths, so folded, chrome and callers are refused with an explanation instead of producing an empty result.

diff

callsight diff BASE.json NEW.json [--key self_ms] [--threshold N] [--fail-over PCT]

Compares two --format json reports function by function. Exact call counts make this a real comparison rather than two samples that happened to land differently, so it works as a build gate:

callsight diff base.json new.json --fail-over 10   # exit 1 on a >10% regression
--keyMetric to compare. Default self_ms
--thresholdIgnore changes smaller than this, in --key units
--fail-overExit non-zero if any function regresses by more than this percentage

doctor

callsight doctor [project] [--config trace.config] [--dir traces]

Checks the compiler and whether it is GCC, addr2line, that -finstrument-functions is accepted, that the config selects something, and that the trace directory is writable with room to spare. Exits non-zero if anything is actually broken; observations that are not problems are marked note.

serve · ui · provision

callsight serve [--host 0.0.0.0] [--port 9001] [--out traces]
                [--max-mb 4096] [--seg-mb 256]
callsight ui    [--host 127.0.0.1] [--port 8321]
callsight provision [--force]

serve bounds and rotates its output per connection: a device streaming for an hour should not fill the analysis host either.

provision reports where ctags comes from and installs the bundled static copy into $CALLSIGHT_HOME/bin (default ~/.callsight/bin), verifying its checksum. --force installs it even when a system ctags is present.

Build integrations

Both do the same two things: generate the flags from your config and source list, and compile the runtime without instrumentation so hooks cannot recurse. Neither forces -no-pie any more — link the way you ship.

GNU Make

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

Expects SRCS, BUILDDIR, BINDIR, TARGET, CC, CFLAGS_SYMBOLS and LDFLAGS. Defines:

VariableDefaultMeaning
CALLSIGHT_DIRcallsightWhere trace.c/trace.h live
CALLSIGHT_CONFIGtrace.configSelection config
CALLSIGHTcallsightCommand that prints the flags — point it at python3 …/cli.py to run from a source checkout

When make instrument is requested and no flags could be generated, the build stops with an explanation instead of silently producing an uninstrumented binary. A normal make still succeeds even if callsight isn't installed.

CMake

list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/callsight")
include(CallSight)
callsight_instrument(<target>)
Cache variableDefaultMeaning
CALLSIGHT_INSTRUMENTOFFApply hooks to callsight_instrument() targets
CALLSIGHT_CONFIG<src>/trace.configSelection config
CALLSIGHT_COMMANDcallsightFlag generator; a ;-list works, e.g. -D"CALLSIGHT_COMMAND=python3;/path/cli.py"
CALLSIGHT_NO_PIEOFFLink instrumented targets with -no-pie. Not needed: the trace header carries the load bias
CALLSIGHT_COMMAND is a cache variable — pass it with -D. A plain set() before include(CallSight) gets shadowed by the cache definition under CMP0126.

Environment variables

VariableDefaultMeaning
TRACE_ENABLEoff1 enables collection; hooks are inert otherwise
TRACE_DIR./tracesOutput directory, resolved to an absolute path at startup so a later chdir() cannot scatter segments
TRACE_MODEeventssummary aggregates in-process and writes only totals — constant memory and constant output, whatever the run length
TRACE_MAX_MB512On-disk budget for the process. 0 = unlimited
TRACE_FULLstopAt the budget: stop keeps the start, wrap keeps the end
TRACE_SEG_MB32Segment size, i.e. rotation granularity
TRACE_MIN_FREE_MB64Stop below this much free space. 0 disables the check
TRACE_MAX0 (unlimited)Global event cap, as an upper bound
TRACE_CLOCKautoauto uses the invariant cycle counter where the hardware has one, else CLOCK_MONOTONIC; mono, raw and tsc force it
TRACE_THREADSunset (all)Comma-separated globs matched against thread names
TRACE_SHMunsetStreaming mode: POSIX shm ring name
TRACE_SHM_SIZE16 MiBRing capacity in bytes
CCccRead by callsight flags for compiler detection
CALLSIGHT_ADDR2LINEaddr2lineSymbolizer for analyze; set it to a cross-toolchain copy for foreign binaries
CALLSIGHT_HOME~/.callsightWhere the bundled ctags is installed

File formats

Trace files

trace.<pid>.<tid>.<seq>.bin (file mode) or trace.stream.<id>.<seq>.bin (streamed). An 80-byte version 2 header followed by fixed 32-byte little-endian event records. The architecture page documents the record; a file with a bad magic, unknown version or mismatched event size is skipped with a warning, and a truncated final record is tolerated.

FieldMeaning
magic, version, event_sizeThe first 16 bytes are laid out exactly as version 1, so any reader can identify the file before it knows the rest
header_sizeBytes to the first event. Readers skip to it rather than assuming a size, which is what lets a later version add fields without breaking this one
flagsBit 0: timestamps are raw ticks, not nanoseconds. Bit 1: this capture rotated
load_biasThe PIE relocation offset; analyze subtracts it to get link addresses. Zero for a -no-pie link
tick_hz, t0_ticks, t0_nsClock calibration. A closing anchor written at exit lets the rate be derived across the whole run
hook_nsThe runtime's own measured per-hook cost, for --subtract-overhead
pid, seqOwning process and segment number

Version 1 files still analyze. They carry a bare 16-byte header, nanosecond timestamps, no load bias and no markers.

Summary files

trace.summary.<pid>.<tid>.bin: an MLSUMRY header followed by one 688-byte record per function — address, calls, inclusive and self time, min, max, and a 160-bucket duration histogram (four sub-buckets per octave). Merged across threads by analyze.

Folded stacks

main;handle_request;parse_headers 148230
main;handle_request 92117

One line per distinct call path: semicolon-separated frames, then self time in nanoseconds. Read directly by flamegraph.pl and speedscope.

JSON report

Summary counters (events, threads, functions, span_ms, unmatched_exits, unclosed_enters), a rows array sorted by self time, a per_thread array, plus tool and version. See the analysis page for a full example.

Known limitations