Skip to content

caliper.feed.v1

Service id caliper.feed.v1 — live telemetry ingestion, the first rung of the physical-twin ladder (feed spec §3). The host owns telemetry providers, buffers their samples in per-channel ring buffers with timestamps + sequence numbers, and vends this tiny pull-based, any-thread, non-blocking read surface. Applets copy samples into their own tensors on their job threads (tensors → pixels stays downstream); embedders reach the same surface through caliper_core_get_service (Compass shows machine telemetry in native panes for free). This page embeds the header verbatim; the docs build fails if the file moves.

#pragma once
/* caliper.feed.v1 — live telemetry ingestion (physical twins, rung one).
 *
 * The HOST owns telemetry providers (v0: the Mac's own sensors), buffers their
 * samples in per-channel ring buffers with timestamps + sequence numbers, and
 * vends this tiny PULL-BASED, ANY-THREAD, NON-BLOCKING read surface. Applets
 * copy samples into their own tensors on their job threads (tensors -> pixels
 * stays downstream); embedders reach the same surface through
 * caliper_core_get_service. Sources are enumerable, reads never block, and data
 * loss is VISIBLE (sequence gaps), never silent.
 *
 * Four entry points, all any-thread and non-blocking:
 *   caps()          — capability bits; bit 0 (CALIPER_FEED_CAP_LIVE) is set iff
 *                     at least one channel is registered (channel_count() > 0),
 *                     i.e. live telemetry is available on THIS host. A host with
 *                     no provider (Windows v0) or no channels reports 0 and every
 *                     read yields nothing — honest degradation, never fake data.
 *   channel_count() — number of registered channels.
 *   channel_info()  — fill a caller-owned CaliperFeedChannelInfo for an index.
 *   read()          — copy new samples for a channel past a caller-held cursor.
 *
 * Rationale for pull + caller cursors: no callbacks across the ABI (no
 * reentrancy/threading contract to defend), no per-subscriber host state (any
 * number of readers, including embedder UI threads, at any cadence). Push can
 * be layered later without breaking this surface (reserved0 holds the slot).
 *
 * Invariants (feed spec): only C types cross the ABI; timestamps are host-clock
 * ns (the same epoch across channels, so series align); loss is visible via seq
 * gaps, never silent; absent capability => inert entry points + honest status.
 *
 * IMMUTABLE once published; additive growth lands as feed.v1_1. */
#include <stdint.h>
#include <stddef.h>

#define CALIPER_FEED_V1 "caliper.feed.v1"

/* caps() bit 0: live telemetry is available (>= 1 channel registered). */
#define CALIPER_FEED_CAP_LIVE (1u << 0)

#ifdef __cplusplus
extern "C" {
#endif

/* One measurement of one channel. seq is per-channel monotonic from 1; t_ns is
 * the host steady clock (mach_absolute-derived ns), same epoch across channels.
 * 24 bytes, 8-aligned, static_asserted. */
typedef struct CaliperFeedSample {
    uint64_t seq;
    int64_t  t_ns;
    float    value;
    float    reserved0;
} CaliperFeedSample;

/* One channel's descriptor. The CALLER sets struct_size = sizeof(this); the host
 * fills the rest. id is a stable string (e.g. "sys.cpu.util"); units is a short
 * label ("%", "degC", "W"); nominal_hz is a rate hint (0 = irregular). */
typedef struct CaliperFeedChannelInfo {
    uint32_t struct_size;   /* caller sets; host fills the rest */
    char     id[64];
    char     name[64];
    char     units[16];
    float    nominal_hz;    /* 0 = irregular */
} CaliperFeedChannelInfo;

typedef struct CaliperFeedV1 {
    uint32_t struct_size;
    uint32_t (*caps)(void);                      /* bit 0: live (see header) */
    uint32_t (*channel_count)(void);
    /* Fill *info for channel `index`. Returns 1 on success, 0 on a bad index
     * (>= channel_count) or a bad size (info->struct_size <
     * sizeof(CaliperFeedChannelInfo)); on 0 the *info is left UNTOUCHED. */
    uint32_t (*channel_info)(uint32_t index, CaliperFeedChannelInfo* info);
    /* Copy up to `max` samples with seq > *cursor into buf (OLDEST-FIRST),
     * advance *cursor to the last copied seq, and return the count copied.
     *   - cursor==0 is a TAIL read: start at the newest sample minus `max`, so a
     *     fresh reader gets at most `max` newest samples (seq starts at 1, so 0
     *     is never a real sample — it is the sentinel for "start fresh").
     *   - Unknown channel_id (or null buf, or max==0) -> 0, *cursor UNTOUCHED.
     *   - Caught up (no seq > *cursor) -> 0, *cursor UNTOUCHED.
     *   - A GAP: if *cursor points below the oldest still-buffered sample (the
     *     ring overwrote it), the copy resumes at the oldest available sample,
     *     so the returned seqs JUMP past *cursor+1 — data was lost, honestly
     *     observable by the caller (returned seq > previous *cursor + 1).
     * Capacity: each channel keeps at most a fixed number of newest samples;
     * older samples are dropped (overflow drops oldest). Non-blocking. */
    uint32_t (*read)(const char* channel_id, CaliperFeedSample* buf,
                     uint32_t max, uint64_t* cursor);
    void*    reserved0;     /* future: applet-registered sources */
} CaliperFeedV1;

#ifdef __cplusplus
}
/* --- ABI freeze: sizes + offsets pinned (only C types cross the boundary) --- */
static_assert(sizeof(CaliperFeedSample) == 24,
              "CaliperFeedSample ABI size is frozen (24 B, 8-aligned)");
static_assert(offsetof(CaliperFeedSample, seq) == 0);
static_assert(offsetof(CaliperFeedSample, t_ns) == 8);
static_assert(offsetof(CaliperFeedSample, value) == 16);
static_assert(offsetof(CaliperFeedSample, reserved0) == 20);

static_assert(sizeof(CaliperFeedChannelInfo) == 152,
              "CaliperFeedChannelInfo ABI size is frozen");
static_assert(offsetof(CaliperFeedChannelInfo, struct_size) == 0);
static_assert(offsetof(CaliperFeedChannelInfo, id) == 4);
static_assert(offsetof(CaliperFeedChannelInfo, name) == 68);
static_assert(offsetof(CaliperFeedChannelInfo, units) == 132);
static_assert(offsetof(CaliperFeedChannelInfo, nominal_hz) == 148);

static_assert(offsetof(CaliperFeedV1, struct_size) == 0);
static_assert(offsetof(CaliperFeedV1, caps) == sizeof(void*));
static_assert(offsetof(CaliperFeedV1, channel_count) ==
              offsetof(CaliperFeedV1, caps) + sizeof(void*));
static_assert(offsetof(CaliperFeedV1, channel_info) ==
              offsetof(CaliperFeedV1, channel_count) + sizeof(void*));
static_assert(offsetof(CaliperFeedV1, read) ==
              offsetof(CaliperFeedV1, channel_info) + sizeof(void*));
static_assert(offsetof(CaliperFeedV1, reserved0) ==
              offsetof(CaliperFeedV1, read) + sizeof(void*));
static_assert(sizeof(CaliperFeedV1) ==
              offsetof(CaliperFeedV1, reserved0) + sizeof(void*),
              "CaliperFeedV1 vtable layout is frozen");
#endif

Semantics

A channel is one named f32 time series (id like "sys.cpu.util", a display name, units, a nominal rate hint). A sample is { seq, t_ns, value, reserved0 }seq is per-channel monotonic from 1; t_ns is the host steady clock (mach_absolute-derived ns), the same epoch across channels so a reader can align series on one timeline.

  • caps() — bit 0 (CALIPER_FEED_CAP_LIVE) is set iff at least one channel is registered on this host. No provider (Linux v0) or no channels → 0, and every read yields nothing. Honest degradation, never fake data.
  • channel_count() / channel_info(index, info) — enumerate the channels this host actually vends. Enumerate dynamically; never assume a fixed set — other machines vend fewer, and the honest ladder covers zero. The caller sets info->struct_size; a bad index or too-small size returns 0 and leaves *info untouched.
  • read(channel_id, buf, max, cursor) — copy up to max samples with seq > *cursor into buf (oldest-first), advance *cursor to the last copied seq, return the count. cursor == 0 is a tail read (start at the newest sample minus max), so a fresh reader gets at most max newest samples. Unknown id / null buf / max == 0 / caught-up → 0, cursor untouched.

The honest-loss contract

Each channel keeps a fixed-capacity ring (4096 samples ≈ 6+ minutes at 10 Hz). Overflow drops the oldest. A reader that falls behind sees its seq jump: if *cursor points below the oldest still-buffered sample, the copy resumes at the oldest available one, so the returned seqs skip past *cursor + 1. That jump is exactly the count of samples lost — visible, never silent. A dashboard surfaces it as a gap counter; a stale channel (no new samples) is visible through its timestamps, never interpolated.

Thread-callability

Every entry point is any-thread and non-blocking — one mutex per channel (writer = the provider thread; readers = anyone, any cadence). Applet job threads and embedder UI threads read the same surface concurrently and safely. No callbacks cross the ABI: no reentrancy or threading contract to defend, and no per-subscriber host state. Push can layer on later without breaking this surface (reserved0 holds the slot).

Platform status

Platform Provider caps Channels
macOS (Apple Silicon) live, verified LIVE 7 (below)
Windows live, verified (feed spec §6.2) LIVE 6 (below)
Linux none yet 0 (inert) none

The macOS provider is privilege-honest: every signal is sudo-free; a channel that would need root/entitlements does not exist rather than half-existing. Verified live on this box (M-series, macOS 26), guaranteed + best-effort tiers:

Channel id Name Units Notes
sys.cpu.util CPU Utilization % host_processor_info deltas
sys.mem.used Memory Used % host_statistics64 (used, not kernel pressure — the name must not overclaim)
sys.thermal.state Thermal State (unitless) NSProcessInfo code 0..3 = nominal / fair / serious / critical — a reader labels the number, the ABI carries a bare float
sys.gpu.util GPU Utilization % IOAccelerator PerformanceStatistics
sys.fan.rpm Fan Speed rpm userland AppleSMC
sys.temp.battery Battery Temperature degC userland AppleSMC
sys.power.battery Battery Power W IOPMPowerSource

The Windows provider is privilege-honest the same way: every signal reads without elevation; a channel that would need admin or a kernel driver does not exist rather than half-existing (so CPU temperature and fan RPM are absent — Windows has no unprivileged path to them). Verified live on this box (RTX 500 Ada laptop, Windows 11), guaranteed + probe tiers:

Channel id Name Units Notes
sys.cpu.util CPU Utilization % GetSystemTimes deltas
sys.mem.used Memory Used % GlobalMemoryStatusEx dwMemoryLoad (used, the same non-overclaiming name)
sys.gpu.util GPU Utilization % NVML, runtime-loaded from the driver's nvml.dll — RTD3-parked ticks fail and are skipped, loss visible
sys.gpu.temp GPU Temperature degC NVML — the box's only privilege-free temperature
sys.gpu.power GPU Power W NVML, gated against the device-reported enforced power limit (wake-transition junk reads)
sys.power.battery Battery Power W CallNtPowerInformation, negative = discharge (same convention as macOS); absent on desktop boxes

The NVML channels are this box's set: no NVIDIA driver → no GPU channels (fail-closed probe at start, never a linked SDK).

Whatever is unavailable is simply not a channel — never a faked value; a consumer displays whatever enumerates. On Linux in v0 every entry point is inert and consumers degrade honestly (one line, nothing drawn).

The exemplar

PulseScope (applets/pulse_scope/, tag Telemetry) is the dashboard that freezes this service: it enumerates the channels dynamically, polls each on a jobs worker at ~10 Hz into an applet-side ring copy, and draws one input-locked ImPlot strip chart per channel with the current value + units, a climbing "last sample N.N s ago" staleness label, and a cumulative "lost M samples" gap counter — the honest-loss contract, made visible. It is CPU + ImPlot only (no tensors), and it is host-neutral: it runs identically under the caliper exe and under an out-of-tree embedder. The machine-thermal twin — a model that learns this Mac's utilization → temperature response and consumes the same feed through tensors — is the named follow-up (feed spec §6).