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 setsinfo->struct_size; a bad index or too-small size returns0and leaves*infountouched.read(channel_id, buf, max, cursor)— copy up tomaxsamples withseq > *cursorintobuf(oldest-first), advance*cursorto the last copied seq, return the count.cursor == 0is a tail read (start at the newest sample minusmax), so a fresh reader gets at mostmaxnewest 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).