Skip to content

Custom C Collectors

The native_c monitor is a compiled binary that reads /proc directly. Its metric backends are C translation units — not Python classes — and are registered at compile time, not via collectors.yaml.

Note

For the Python monitor backends (thread, subprocess_python) see Custom Python Collectors.


Source layout

native_c/
├── monitor.c / monitor.h   # main loop, shared types, registry
├── Makefile
└── metrics/
    ├── cpu/    cpu.c / cpu.h
    ├── memory/ memory.c / memory.h
    ├── io/     io.c / io.h
    └── gpu/    gpu.c / gpu.h

Each backend exposes a single const CCollector <name>_collector symbol and includes monitor.h (available via the -I$(DIR) compile flag).


CCollector interface

typedef struct {
    const char *name;
    void (*setup)(void);
    void (*snapshot)(TickContext *ctx);
    void (*emit_columns)(FILE *fp, int level_idx, int n_cpus, int n_gpus);
    void (*emit_sample)(FILE *fp, int level_idx, TickContext *ctx, double dt);
    void (*post_tick)(void);
    void (*teardown)(void);
} CCollector;
Callback When called Set to NULL if
setup once, before main loop no one-time init needed
snapshot every tick, before any emit no per-tick /proc reads needed
emit_columns once per level at start (ready handshake) backend adds no columns
emit_sample every tick, per level backend adds no columns
post_tick after all levels emitted no post-emit cache work needed
teardown on clean shutdown no resources to release

TickContext carries the PID sets for the current tick (proc, user, slurm, all). Use ctx->all_pids / ctx->n_all to iterate over all tracked processes.


Adding a new backend

1. Create metrics/<name>/<name>.h:

#ifndef JUMPER_METRICS_<NAME>_H
#define JUMPER_METRICS_<NAME>_H
#include "monitor.h"
extern const CCollector <name>_collector;
#endif

2. Implement metrics/<name>/<name>.c — define all needed callbacks and expose:

const CCollector <name>_collector = {
    .name          = "<name>",
    .setup         = <name>_setup,
    .snapshot      = <name>_snapshot,
    .emit_columns  = <name>_emit_columns,
    .emit_sample   = <name>_emit_sample,
    .post_tick     = NULL,
    .teardown      = NULL,
};

3. Add to Makefile:

SRCS := \
    ...
    metrics/<name>/<name>.c

4. Register in monitor.c:

#include "metrics/<name>/<name>.h"

static const CCollector * const g_registry[] = {
    &cpu_collector, &memory_collector, &io_collector, &gpu_collector,
    &<name>_collector,
};

5. Add to config/collectors/c/collectors.yaml:

collectors:
  - cpu
  - memory
  - io
  - gpu
  - <name>

After these changes, rebuild with make (or trigger ensure_native_c(force_build=True) from Python) — the new collector will be active on the next monitor start.