dftracer-annotate-c
Complete C annotation rules for dftracer — START/END placement, comp types, linker setup, coverage verification, and the full checklist
SKILL.md.agents/skills/dftracer-annotate-c/SKILL.md
GitHubC Annotation Rules (dftracer)
Before reading the detail rules below, check the
dftracer-cheatsheetskill 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(); // ← FIRST statement after {, nothing before it
...
}
- 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,
#ifdefguard 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(); // ← before early return
return -1;
}
...
DFTRACER_C_FUNCTION_END(); // ← before normal return
return 0;
}
- Place END immediately before every
returnstatement (on its own line, same indent as return). - For void functions that fall off the end (no explicit
return), place END as the last statement before the closing}:void helper(int x) { DFTRACER_C_FUNCTION_START(); do_work(x); DFTRACER_C_FUNCTION_END(); // ← last statement, void function } - Every function with a START must have a corresponding END at every exit path.
- Never place END after a
return(it would be unreachable). - END indentation must match the surrounding block — not at column 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); // ← first, before anything else
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); // ← MPI first
DFTRACER_C_INIT(NULL, NULL, NULL); // ← dftracer after MPI_Init
DFTRACER_C_FUNCTION_START();
...
if (bad) {
DFTRACER_C_FUNCTION_END(); // ← END first
DFTRACER_C_FINI(); // ← dftracer FINI before MPI_Finalize
MPI_Finalize(); // ← MPI last
return 1;
}
...
DFTRACER_C_FUNCTION_END();
DFTRACER_C_FINI(); // ← dftracer FINI before MPI_Finalize
MPI_Finalize();
return 0;
}
- Scan main for
MPI_InitorMPI_Init_threadbefore placingDFTRACER_C_INIT. If found, place INIT on the line immediately after theMPI_Init(...)call. - Scan every
returnpath and everyMPI_Finalize()call in main. PlaceDFTRACER_C_FUNCTION_END()thenDFTRACER_C_FINI()immediately before eachMPI_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 bypassesMPI_Finalize. DFTRACER_C_FINI()must appear **in the same function asDFTRACER_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:
// File I/O function
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"); // ← type first
DFTRACER_C_FUNCTION_UPDATE_STR("filename", testFileName); // then params
DFTRACER_C_FUNCTION_UPDATE_INT("flags", flags);
...
}
Classification guide for common patterns:
Create,Open,Close,Fsync,Sync,Delete,Rename,GetFileSize,Mknod→"io"Xferin POSIX, MMAP, HDF5, NCMPI where data goes to/from a file →"io"Xferin MPIIO, S3, HDFS, RADOS, DFS, CEPHFS where a network/RPC call is made →"comm"Xferin MMAP where the transfer is amemcpyinto the mmap region →"mem"init,final,initialize,finalizefor 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); // string params
DFTRACER_C_FUNCTION_UPDATE_INT("count", (int)count); // numeric params
DFTRACER_C_FUNCTION_UPDATE_INT("offset", (int)offset);
...
DFTRACER_C_FUNCTION_END();
return result;
}
- String params (
const char *): useDFTRACER_C_FUNCTION_UPDATE_STR("name", ptr) - Numeric params (
size_t,off_t,int,long): useDFTRACER_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 identifiercompile errors. - Opaque handle typedefs (
MPI_File,hid_t,ncid,hsize_t) useUPDATE_INTwith(int)cast.
C Rule 5 — Error-checking macros that embed early exits
ssize_t my_write(...) {
DFTRACER_C_FUNCTION_START();
...
DFTRACER_C_FUNCTION_END(); // ← before macro that may return/goto
NCMPI_CHECK(ncmpi_put_vara(...), "write failed");
...
DFTRACER_C_FUNCTION_END(); // ← before normal return
return 0;
err:
DFTRACER_C_FUNCTION_END(); // ← before goto-target fallthrough
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(); // ← place END at the goto label, not before each goto
return ret;
}
C Rule 7 — Forward declarations vs definitions: only annotate definitions
// ❌ DO NOT annotate — forward declaration (no body, no param names)
static IOR_offset_t POSIX_Xfer(int, aiori_fd_t *, IOR_size_t *,
IOR_offset_t, IOR_offset_t, aiori_mod_opt_t *);
// ✅ Annotate this one — definition with named params and body
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
# Check 1 — list every annotated function
awk '/^[a-zA-Z].*\(/ {func=$0} /DFTRACER_C_FUNCTION_START/ {print NR": "func}' \
annotated/src/foo.c
# Check 2 — verify comp=TYPE present in every annotated function
grep -c "DFTRACER_C_FUNCTION_START" annotated/src/foo.c
grep -c 'DFTRACER_C_FUNCTION_UPDATE_STR.*comp' annotated/src/foo.c
# Both counts must be equal
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"
# → <venv>/lib/python<ver>/site-packages/dftracer/lib/libdftracer_core.so
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"
C Quick checklist
- Build setup verified before first annotation (Rule 10d)
-
#include <dftracer/dftracer.h>added to every annotated.c/.cpp— last include, never in.h - Linker flags:
-ldftracer_core -L<prefix>/lib -Wl,-rpath,<prefix>/lib - ALL non-trivial functions annotated — skip only pure getters/setters/formatters
- START is the first statement after
{(not before{) - START is in the definition body, not a forward declaration (Rule 7)
-
DFTRACER_C_FUNCTION_UPDATE_STR("comp", "<type>")is FIRST UPDATE after every START -
comptype is one of:"io","mem","cpu","comm" - Parameter names in UPDATE match the definition, not the declaration
- END placed before every VISIBLE
return(same indentation level) - Error-checking macros (MPI_CHECK, NCMPI_CHECK): END before them if they wrap a return
- goto-based error handling: single END at the label, not before each goto
- Void functions: END placed as last statement before
} - END indentation is not at column 0
- main: MPI_Init (if present) → DFTRACER_C_INIT → ... → DFTRACER_C_FINI → MPI_Finalize → return
- Vendor-specific functions (gpfs_*, beegfs_*, lustre_*) annotated as comp="io"
- Coverage verified: START count == comp count; no missed functions