callsight — compile-time function tracing for C/C++
Docs: https://harshithsunku.github.io/callsight/ · Status & roadmap
Getting started · Configuration · Analysis · Web UI · Streaming · Architecture · Reference
Add entry/exit timing hooks to every function in a C/C++ project at compile time, with zero edits to its sources, control exactly which files, folders, or functions get hooks from one config file, and turn the resulting traces into per-function hotspot reports and flame graphs. C and C++, GNU Make and CMake, on Linux.
Compiler: selective instrumentation needs GCC — the exclude flags it
builds on are GCC-only (LLVM issue
#15627). Clang can
instrument everything (a config with no include/exclude directives);
callsight detects the toolchain and tells you up front rather than letting
the build fail one file at a time.
Install
uv tool install callsight # from PyPI; puts `callsight` on your PATH
uv tool install 'callsight[ui]' # same, plus the optional web UI
uv tool install 'callsight[stream]' # same, plus the streaming server
uv tool install . # or from a source checkout
(Requires uv. The tool itself is Python stdlib-only; the runtime it injects is dependency-free C.)
Web UI
callsight ui # serves http://127.0.0.1:8321
A local web app that walks the whole workflow against any project on the
machine: browse for the project folder, edit its trace.config, preview
the instrumentation selection, build the instrumented profile (Make or
CMake — CMake is fetched ephemerally via uvx if not installed), run the
binary with tracing enabled, and view the sortable hotspot report. A
second tab, the config builder, scans the project folder into
searchable checkbox panes of files and functions (enumerated with ctags
— auto-downloaded on first scan when the system has none — with a regex
fallback) and generates the trace.config from your picks. No root
needed; bind address defaults to localhost.
Adopt it in your project (2 steps)
cd /path/to/your/project
callsight init . # copies runtime + build wiring, writes trace.config
Then follow the printed wiring snippet for your build system:
- Make:
include callsight/Makefile.callsight, add aninstrumenttarget linking$(TRACE_OBJ)with-no-pie(snippet printed byinit). - CMake:
include(CallSight)+callsight_instrument(<target>), then configure with-DCALLSIGHT_INSTRUMENT=ON.
Collect and analyze
make instrument # or: cmake --build build-instr
TRACE_ENABLE=1 TRACE_MAX=1000000 ./yourapp # collect (inert without TRACE_ENABLE=1)
callsight analyze traces/ --exe ./yourapp --top 20
The analyzer reports calls / inclusive / self / max time per function,
resolving symbols — including static functions — with addr2line.
unmatched_exits=0 means a clean trace. Trace files are streamed, so a
multi-million-event run costs a few MB of analyzer memory, not gigabytes.
Flame graphs and machine-readable output
callsight analyze traces/ --exe ./yourapp --format folded > out.folded
flamegraph.pl out.folded > out.svg # or drag out.folded into speedscope.app
callsight analyze traces/ --exe ./yourapp --format json --top 0 | jq '.rows[0]'
--format folded prints one collapsed stack per call path
(main;handle_request;parse <self_ns>), the input format understood by
flamegraph.pl and
speedscope. --format json emits the whole report
— summary counters, per-function rows, per-thread timing — for your own
tooling (--top 0 keeps every row).
How it works
callsight flagsturnstrace.config+ your source list into-finstrument-functionsplus compile-time exclude lists (-finstrument-functions-exclude-file-list/-exclude-function-list).- The compiler emits calls to
__cyg_profile_func_enter/exitat the entry/exit of every selected function. Excluded code emits no hook at all — compile-time exclusion is free at runtime. - The hook runtime (
trace.c, itself compiled without the flag) appends 32-byte events to a per-thread buffer — no locks, no malloc, no I/O in the hot path — flushingtrace.<pid>.<tid>.binwhen full or at exit. callsight analyzematches enter/exit events per thread and prints hotspot tables.
Selection strategy (important at scale)
Event volume is the main cost — a call-heavy program easily generates millions of events per second. Levers, cheapest first:
- Compile-time excludes in
trace.config: run wide once, sort analyzer output bycalls, exclude the chatty leaf helpers, rebuild. Typically cuts volume 10–100×. includedirectives: instrument only the subsystem under investigation.include-func(function/task level): name one entry function and callsight instruments exactly its call subtree, resolved statically from the sources —include-func workload_sorttracesworkload_sortand everything it calls, nothing else. Explore first withcallsight select src/ --function workload_sort.- Runtime gating:
TRACE_ENABLE/TRACE_MAXcontrol when and how much you pay;TRACE_THREADS="sort-*"traces only matching threads. - Source opt-out (optional):
__attribute__((no_instrument_function)).
callsight scan <dir> --config trace.config previews what a config selects.
Runtime knobs: TRACE_ENABLE (default off), TRACE_DIR (default
./traces), TRACE_MAX (global event cap — always set one for long runs),
TRACE_THREADS (thread-name glob filter), TRACE_SHM / TRACE_SHM_SIZE
(streaming mode, see below).
Remote streaming (devices, embedded)
On a constrained device you can't accumulate trace files — a busy program generates millions of events per second. Streaming mode keeps nothing on the device: the runtime flushes into a shared-memory ring, and a tiny on-device client forwards events ZSTD-compressed over raw TCP.
# analysis host (powerful machine):
callsight serve --port 9001 --out traces/ # needs callsight[stream]
# adopt with streaming support:
callsight init --stream /path/to/project # adds trace_stream.c + zstd.c
# on the device:
cc -O2 -o callsight/trace_stream callsight/trace_stream.c callsight/zstd.c
./callsight/trace_stream /callsight0 <server-ip> 9001 &
TRACE_ENABLE=1 TRACE_SHM=/callsight0 ./yourapp.instr
- The traced process does no disk or network I/O — only a shared-memory memcpy per buffered batch.
- If the ring fills faster than the client drains (network slow, ring too
small), events are dropped and counted, never blocking the workload;
the server reports the drop count. Size the ring with
TRACE_SHM_SIZE. - The server writes standard
trace.stream.*.binfiles —callsight analyzeand the web UI consume them unchanged. - The client is self-contained C built against the vendored single-file
zstd v1.5.7 (
src/callsight/stream/zstd.c, generated from the official repo'sbuild/single_file_libs; BSD license inzstd.LICENSE).
How it compares
| tool | granularity | selection | needs |
|---|---|---|---|
| callsight | every function entry/exit, exact timing | compile time, from one config file — excluded code emits no hook at all | rebuild with GCC |
| uftrace | same mechanism, richer live TUI/replay | mostly runtime filters (-F/-N), so filtered functions still pay the hook |
rebuild (-pg/-finstrument-functions) |
perf record |
sampled, statistical | none needed | no rebuild; often root/perf_event_paranoid |
gprof (-pg) |
sampled + call counts | none | rebuild; single-threaded accounting |
| Clang XRay | entry/exit with runtime patching | per-function attributes / lists | rebuild with Clang only |
Reach for perf first when you want a cheap statistical profile of a whole
system. Reach for callsight when you need exact per-call timing for a
chosen subsystem — every call counted, nothing sampled — and you want the
cost of the functions you did not choose to be exactly zero, because they
were never given a hook. The selection lives in trace.config next to the
code, and the same config drives the traced device and the analysis host.
CLI reference
| command | purpose |
|---|---|
callsight init <dir> [--build make|cmake] |
adopt into a project |
callsight scan <dir> [--config c] |
preview instrumentation selection |
callsight select <dir> --function F [--depth N] |
show a function's call subtree; emit config lines |
callsight flags --config c -- srcs... |
print compiler flags (build integrations use this) |
callsight analyze [traces/] [--exe bin] [--top N] [--format text|json|folded] |
hotspot report, JSON, or collapsed stacks |
callsight ui [--host H] [--port P] |
web UI (needs callsight[ui]) |
callsight provision [--force] |
download the bundled static ctags used by the UI config builder |
callsight serve [--host H] [--port P] [--out dir] |
TCP server for remote streams (needs callsight[stream]) |
Repo layout
src/callsight/— the tool:cli.py,flags.py(config → compiler flags),analyze.py(offline analyzer),callgraph.py(static call graph behindinclude-func),symbols.py(function enumeration for the config builder),provision.py(bundled ctags download); the core is stdlib-only,serve.py(streaming TCP server) needs thestreamextra.src/callsight/ui/is the optional web UI (FastAPI, only imported bycallsight ui).src/callsight/runtime/—trace.c/trace.h/trace_shm.h, the hook runtime copied into adopted projects. Self-contained C, no deps beyond pthreads.src/callsight/stream/—trace_stream.con-device streaming client + vendored single-file zstd v1.5.7 (zstd.c, BSD — seezstd.LICENSE).src/callsight/share/Makefile.callsight,src/callsight/cmake/CallSight.cmake— build-system integrations.tests/matrixlab/— multi-threaded C11 demo workload; doubles as the end-to-end smoke test (make instrument→ run →callsight analyze).tests/cmake_demo/— tiny CMake fixture for the CMake integration.docs/— the documentation site (hand-built static HTML, published to GitHub Pages).docs/architecture.htmlcarries the survey of GCC/Clang instrumentation mechanisms and how they map to callsight's roadmap.
Known limitations
- Selective instrumentation is GCC-only; Clang has
-finstrument-functionsbut not the exclude lists (LLVM #15627). Under Clang, only an unfiltered "instrument everything" config builds. - Linux only: the runtime uses
SYS_gettid,pthread_getname_npand POSIX shared memory. - Inlined functions emit no hooks (there is no call boundary).
- Overhead ~30–60 ns per event; a profiling build, not a production one.
- A crashed/killed process loses each thread's buffered tail; clean exits flush everything.
exclude-file-listmatching by the compiler is substring-based; unusually overlapping directory names can over-match.- Analysis needs the recorded addresses to match link addresses, so link with
-no-pie(both build integrations do).analyzewarns when most addresses fail to resolve, which is what a PIE binary looks like.
Roadmap
- Phase 2 — web UI: done (
callsight ui, optionalcallsight[ui]extra). Flame graphs: done viaanalyze --format folded(flamegraph.pl / speedscope). Next: richer in-UI report views, live build-log streaming. - Phase 3 — remote streaming: done (
TRACE_SHMring →trace_streamclient → ZSTD/TCP →callsight serve). Next: runtime on/off via-fpatchable-function-entry(see the compiler-mechanism survey), live stream view in the web UI.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file callsight-0.3.0.tar.gz.
File metadata
- Download URL: callsight-0.3.0.tar.gz
- Upload date:
- Size: 6.4 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6dbf2dd759995f39e15218cd4b3966101d28aab73a0d879fca69e76c6f5c651f
|
|
| MD5 |
96e2241fccc1c20cbea12fe3741962a9
|
|
| BLAKE2b-256 |
08bd146729f71ee1f13b37ec89534a5b515fc42f30bf964f438e911f9aeaaaba
|
Provenance
The following attestation bundles were made for callsight-0.3.0.tar.gz:
Publisher:
release.yml on harshithsunku/callsight
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
callsight-0.3.0.tar.gz -
Subject digest:
6dbf2dd759995f39e15218cd4b3966101d28aab73a0d879fca69e76c6f5c651f - Sigstore transparency entry: 2489891131
- Sigstore integration time:
-
Permalink:
harshithsunku/callsight@71d3c6e084a7f7e0e8cf2c485d5301479ef89500 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/harshithsunku
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@71d3c6e084a7f7e0e8cf2c485d5301479ef89500 -
Trigger Event:
push
-
Statement type:
File details
Details for the file callsight-0.3.0-py3-none-any.whl.
File metadata
- Download URL: callsight-0.3.0-py3-none-any.whl
- Upload date:
- Size: 585.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0e2eaf27f02dc00a8160268699494286e67129022d366d18dc4e8ae4a77466f4
|
|
| MD5 |
f799656037395c62bc3d8b2e2ed865e0
|
|
| BLAKE2b-256 |
2784a2ac45cf457c8d92f0770664fb5219774951e64d0f8004c573bd86e077c8
|
Provenance
The following attestation bundles were made for callsight-0.3.0-py3-none-any.whl:
Publisher:
release.yml on harshithsunku/callsight
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
callsight-0.3.0-py3-none-any.whl -
Subject digest:
0e2eaf27f02dc00a8160268699494286e67129022d366d18dc4e8ae4a77466f4 - Sigstore transparency entry: 2489891310
- Sigstore integration time:
-
Permalink:
harshithsunku/callsight@71d3c6e084a7f7e0e8cf2c485d5301479ef89500 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/harshithsunku
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@71d3c6e084a7f7e0e8cf2c485d5301479ef89500 -
Trigger Event:
push
-
Statement type: