| name | dftracer-annotate-c |
| description | Complete C annotation rules for dftracer — START/END placement, comp types, linker setup, coverage verification, and the full checklist |
C Annotation Rules (dftracer)
Before reading the detail rules below, check the dftracer-cheatsheet skill for
the five critical rules (C1–C5), seven corner cases (CC1–CC7), and the top-10
known mistakes (M1–M10). The rules below provide the full explanation and examples.
C Rule 1 — Include and one START per function, at the very top
Add the include after all existing #include lines in the .c file (never in a header):
#include <dftracer/dftracer.h>
Then for each selected function:
return_type function_name(params) {
DFTRACER_C_FUNCTION_START();
...
}
- One and only one START per function body.
- START goes after the opening
{, never before it.
- It must be the first executable statement — no variable declarations, no assignments before it.
- Never place START inside a control-flow block (
if, else, for, while,
switch, do). Control-flow blocks are NOT functions.
- Never annotate: struct/union/enum definitions, typedefs, macro bodies,
#ifdef guard blocks, forward declarations, or function pointer declarations.
C Rule 2 — DFTRACER_C_FUNCTION_END before every exit point
return_type function_name(params) {
DFTRACER_C_FUNCTION_START();
...
if (error) {
DFTRACER_C_FUNCTION_END();
return -1;
}
...
DFTRACER_C_FUNCTION_END();
return 0;
}
C Rule 3 — DFTRACER_C_FINI in main before every exit; order around MPI
Without MPI — INIT goes right after the opening { of main:
int main(int argc, char **argv) {
DFTRACER_C_INIT(NULL, NULL, NULL);
DFTRACER_C_FUNCTION_START();
...
DFTRACER_C_FUNCTION_END();
DFTRACER_C_FINI();
return 0;
}
With MPI — INIT must come AFTER MPI_Init/MPI_Init_thread, and FINI must
come BEFORE MPI_Finalize. dftracer uses MPI internals; initialising before MPI
is ready causes crashes, and finalising after MPI shuts down loses trace data:
int main(int argc, char **argv) {
MPI_Init(&argc, &argv);
DFTRACER_C_INIT(NULL, NULL, NULL);
DFTRACER_C_FUNCTION_START();
...
if (bad) {
DFTRACER_C_FUNCTION_END();
DFTRACER_C_FINI();
MPI_Finalize();
return 1;
}
...
DFTRACER_C_FUNCTION_END();
DFTRACER_C_FINI();
MPI_Finalize();
return 0;
}
- Scan main for
MPI_Init or MPI_Init_thread before placing DFTRACER_C_INIT.
If found, place INIT on the line immediately after the MPI_Init(...) call.
- Scan every
return path and every MPI_Finalize() call in main. Place
DFTRACER_C_FUNCTION_END() then DFTRACER_C_FINI() immediately before
each MPI_Finalize().
- The full order at every exit from an MPI main:
FUNCTION_END → DFTRACER_C_FINI → MPI_Finalize → return
- Also place FINI before any process-exit call (
exit(), _exit(), abort(),
quick_exit()) that bypasses MPI_Finalize.
DFTRACER_C_FINI() must appear **in the same function as DFTRACER_C_INIT() (i.e., main).
- INIT/FINI summary:
MPI_Init → DFTRACER_C_INIT → ... → DFTRACER_C_FINI → MPI_Finalize
C Rule 4 — Classify every annotated function with comp=TYPE
Every annotated function MUST include a comp classification UPDATE immediately
after DFTRACER_C_FUNCTION_START(). This tags functions in the trace so analysis
tools can group and filter by operation type.
The four types:
| Type | When to use | Typical functions |
|---|
"io" | File system I/O: open, read, write, close, fsync, stat, delete, rename, getfilesize, mknod | POSIX_Create, POSIX_Xfer, POSIX_Fsync, POSIX_Close, POSIX_Delete, HDF5_Xfer, MMAP_Open |
"mem" | Memory operations: large memcpy, mmap region setup, buffer alloc/free, data copy between memory regions | MMAP_Xfer (memcpy path), any malloc/free of large buffers |
"cpu" | Compute: checksums, compression, encryption, hash, data encoding, format conversion | Any function that transforms data without doing file I/O |
"comm" | Communication: MPI sends/recvs, network I/O, REST API calls, distributed FS ops | MPIIO_Xfer, NCMPI_Xfer, HDFS_Xfer, S3_Xfer, RADOS ops, DFS ops, any MPI_File_* wrapper |
Usage — always the first UPDATE after START:
aiori_fd_t *POSIX_Create(char *testFileName, int flags, aiori_mod_opt_t *param)
{
DFTRACER_C_FUNCTION_START();
DFTRACER_C_FUNCTION_UPDATE_STR("comp", "io");
DFTRACER_C_FUNCTION_UPDATE_STR("filename", testFileName);
DFTRACER_C_FUNCTION_UPDATE_INT("flags", flags);
...
}
Classification guide for common patterns:
Create, Open, Close, Fsync, Sync, Delete, Rename, GetFileSize, Mknod → "io"
Xfer in POSIX, MMAP, HDF5, NCMPI where data goes to/from a file → "io"
Xfer in MPIIO, S3, HDFS, RADOS, DFS, CEPHFS where a network/RPC call is made → "comm"
Xfer in MMAP where the transfer is a memcpy into the mmap region → "mem"
init, final, initialize, finalize for any backend → "io" (I/O stack lifecycle)
- Functions doing checksums, compression, encryption, hashing →
"cpu"
C Rule 5 — Track important I/O metadata with FUNCTION_UPDATE (after comp=TYPE)
ssize_t my_read(const char *filename, void *buf, size_t count, off_t offset) {
DFTRACER_C_FUNCTION_START();
DFTRACER_C_FUNCTION_UPDATE_STR("filename", filename);
DFTRACER_C_FUNCTION_UPDATE_INT("count", (int)count);
DFTRACER_C_FUNCTION_UPDATE_INT("offset", (int)offset);
...
DFTRACER_C_FUNCTION_END();
return result;
}
- String params (
const char *): use DFTRACER_C_FUNCTION_UPDATE_STR("name", ptr)
- Numeric params (
size_t, off_t, int, long): use DFTRACER_C_FUNCTION_UPDATE_INT("name", (int)val)
- The variable name in the UPDATE call must exactly match the parameter name in
the function definition — otherwise you get
undeclared identifier compile errors.
- Opaque handle typedefs (
MPI_File, hid_t, ncid, hsize_t) use UPDATE_INT with (int) cast.
C Rule 5 — Error-checking macros that embed early exits
ssize_t my_write(...) {
DFTRACER_C_FUNCTION_START();
...
DFTRACER_C_FUNCTION_END();
NCMPI_CHECK(ncmpi_put_vara(...), "write failed");
...
DFTRACER_C_FUNCTION_END();
return 0;
err:
DFTRACER_C_FUNCTION_END();
return -1;
}
For goto-based error handling (HDF5):
ssize_t hdf5_write(...) {
DFTRACER_C_FUNCTION_START();
...
if ((fid = H5Fcreate(...)) < 0) goto done;
...
done:
DFTRACER_C_FUNCTION_END();
return ret;
}
C Rule 7 — Forward declarations vs definitions: only annotate definitions
static IOR_offset_t POSIX_Xfer(int, aiori_fd_t *, IOR_size_t *,
IOR_offset_t, IOR_offset_t, aiori_mod_opt_t *);
static IOR_offset_t POSIX_Xfer(int access, aiori_fd_t *file, IOR_size_t *buffer,
IOR_offset_t length, IOR_offset_t offset,
aiori_mod_opt_t *param)
{
DFTRACER_C_FUNCTION_START();
...
}
Quick filter to find only definitions: grep -n "FUNCTION_NAME" file.c | grep -v ";$"
C Rule 8 — Vendor-specific filesystem functions are I/O functions
| Prefix | Filesystem | Examples |
|---|
gpfs_* | IBM GPFS/Spectrum Scale | gpfs_free_all_locks, gpfs_access_start |
beegfs_* | BeeGFS | beegfs_getStriping, beegfs_createFilePath |
lustre_* | Lustre | lustre_disable_file_locks |
hdfs_* | Hadoop HDFS | Any HDFS JNI wrapper |
ceph_* | Ceph | Any libcephfs wrapper |
daos_* | DAOS | Any DAOS API wrapper |
C Rule 9 — Coverage verification: scan all definitions after each file
awk '/^[a-zA-Z].*\(/ {func=$0} /DFTRACER_C_FUNCTION_START/ {print NR": "func}' \
annotated/src/foo.c
grep -c "DFTRACER_C_FUNCTION_START" annotated/src/foo.c
grep -c 'DFTRACER_C_FUNCTION_UPDATE_STR.*comp' annotated/src/foo.c
C Rule 10 — Header include and linker setup
10a — Header placement
Add #include <dftracer/dftracer.h> as the last #include in each .c/.cpp file.
Never add it to a .h header file.
10b — Linker flags
Makefile/autotools:
CFLAGS += -I$(DFTRACER_PREFIX)/include
LDFLAGS += -L$(DFTRACER_PREFIX)/lib -Wl,-rpath,$(DFTRACER_PREFIX)/lib
LIBS += -ldftracer_core
CMake:
target_link_libraries(${MY_TARGET} PRIVATE dftracer::dftracer_core)
target_include_directories(${MY_TARGET} PRIVATE ${DFTRACER_PREFIX}/include)
pip/venv install — library lives inside the Python package tree:
DFTRACER_SITE=$(python3 -c \
"import importlib.util, pathlib; \
p=importlib.util.find_spec('dftracer'); \
print(pathlib.Path(p.origin).parent)")
DFTRACER_INC="${DFTRACER_SITE}/include"
DFTRACER_LIB="${DFTRACER_SITE}/lib"
10c — Transitive deps (if linker reports undefined symbols)
LIBS += -ldftracer_core -lcpp-logger -lbrahma
10d — Verify build setup before annotating any source
echo '#include <dftracer/dftracer.h>' >> annotated/src/one_file.c
make -C annotated/src 2>&1 | grep -i "error\|cannot find"