Debug an applet¶
An applet is a shared library the host loads at runtime, so you don't debug it as
its own program — you attach a debugger to the running caliper host. The
dylib carries your own symbols (built Debug, they're right there), so once
attached you get full source-level stepping, breakpoints, and variable
inspection inside on_init / on_frame / on_cleanup.
Attach a debugger to the running host¶
Build both the host and your applet with debug info (the default Debug
configuration), then launch and attach:
LLDB (command line)
cmake --build build --target caliper hello_applet
./build/caliper & # note the PID it prints, or use pgrep
lldb -p "$(pgrep -f 'build/caliper')"
At the (lldb) prompt set a breakpoint by symbol or file:line and continue —
your applet's translation unit is visible even though it lives in a separate
dylib:
(lldb) breakpoint set --name HelloApplet::on_frame
(lldb) breakpoint set --file hello.cpp --line 39
(lldb) continue
CLion
Use Run ▸ Attach to Process…, filter for caliper, and pick the running
host. Set breakpoints in your applet source as usual. Because the applet dylib is
loaded lazily when you open the applet card, a breakpoint in on_frame won't
bind until the applet is opened in the app — CLion resolves it the moment the
dylib is mapped in, which is expected. Breakpoints in on_init bind at open
time; to catch very early setup, break in on_init and step from there.
Because the host loads the dylib after startup, prefer attaching to the running process over launching the host under the debugger — the symbols resolve when the applet is opened, and you avoid stepping through the host's whole boot.
Log through the host, not printf¶
Do not use printf/std::cout for diagnostics. Log through the
caliper.log.v1 service, which the sugar layer exposes on your Host:
bool on_init(caliper::Host& host) override {
host.log_info("hello.on_init"); // caliper.log.v1
// host.log_error("...") for failures
...
}
Host logging is unified: messages go to the same sink as the host's own
diagnostics (the console the host is launched from, and the dev-mode tail), they
carry the applet's identity, and they keep working when your dylib's stdio is not
wired to a terminal — which it often isn't once the app is bundled. The raw
service is caliper.log.v1 (declare it in your manifest's required list, as the
hello applet does); the sugar host.log_info / host.log_error are thin
wrappers over it.
When the loader turns an applet away, the diagnostic is on the applet's card,
not in the console: the card's description gains an [unavailable] line carrying
the contractual refusal reason (see refusals) — a
missing or malformed manifest, an id/version that disagrees with the
descriptor, an unmet required service, or an ABI-epoch mismatch. The same card
surface names the fault for applets that crashed and were quarantined. If an
applet doesn't appear at all, check the two discovery signals side by side in
build/applets/: the dylib and its <stem>.caliper.toml.
Where per-applet data lives¶
The host hands you a writable directory via host.data_dir(), and that is the
only place an applet should read or write files — never build your own paths
or drop dotfiles in $HOME. On the current host this resolves under the app-data
root:
- macOS:
~/Library/Application Support/Caliper/ - Linux:
$XDG_DATA_HOME/Caliper/(or~/.local/share/Caliper/) - Windows:
%APPDATA%/Caliper/
Under loader v2 the host namespaces this directory per applet id, so two applets
never collide; either way, host.data_dir() is the accessor and your code stays
the same. The signal_scope exemplar shows the pattern — it builds
data_dir() + "/settings.txt" and persists a single value across relaunches.
When a stored file looks wrong, inspect it directly on disk under that root (it's plain files, not a database), delete it to reset to defaults, and relaunch.