libxtc 0.4.0
Async concurrency for C: Tokio + Seastar + BEAM, in one library
Loading...
Searching...
No Matches
xtc_tail.h
1/*-
2 * Copyright (c) 2026, The XTC Project
3 * Use of this source code is governed by the ISC License.
4 *
5 * src/inc/xtc_tail.h
6 * A runtime microscope: cheap, high-volume recording of what the
7 * scheduler and runtime are actually doing -- "tail -f a system" --
8 * so hard async bugs (lost/late wakeups, long polls, mailbox backups,
9 * scheduler imbalance) become obvious after the fact. Inspired by
10 * dial9 ("a microscope for Tokio").
11 *
12 * Unlike xtc_stats (aggregate counters) and xtc_trace (the causal
13 * message trace), xtc_tail records every INDIVIDUAL runtime event
14 * tied to a precise instant, so a degraded window can be diffed
15 * against a normal one. It is OFF by default and one branch when
16 * disabled -- observability must not tax production.
17 *
18 * Phase 1 (this): the SCHED source (proc spawn / exit / wake / run)
19 * over a bounded per-process ring, an in-process read callback, and a
20 * versioned binary dump to an fd. The IO/OS sources, on-disk spill
21 * with rotation, and the offline viewer are staged follow-ons (see
22 * the roadmap); the record format and API are designed to accept them
23 * without a break.
24 */
25
26#ifndef XTC_TAIL_H
27#define XTC_TAIL_H
28
29#include <stddef.h>
30#include <stdint.h>
31
32#include "xtc.h"
33#include "xtc_proc.h"
34
35/* Event sources, an enable mask (dial9-style: pay only for what you turn
36 * on). Phase 1 implements SCHED; the others are reserved so the mask is
37 * stable across the staged build-out. */
38#define XTC_TAIL_SCHED (1u << 0) /* proc spawn/exit/wake/run */
39#define XTC_TAIL_MSG (1u << 1) /* reserved: send/recv/mailbox depth */
40#define XTC_TAIL_IO (1u << 2) /* reserved: fd reg/del/completion */
41#define XTC_TAIL_OS (1u << 3) /* reserved: per-loop CPU/RSS sampling */
42#define XTC_TAIL_ALL (XTC_TAIL_SCHED | XTC_TAIL_MSG | XTC_TAIL_IO | XTC_TAIL_OS)
43
44/* Event kinds recorded by the SCHED source. */
45enum xtc_tail_kind {
46 XTC_TAIL_SPAWN = 0, /* a proc was spawned */
47 XTC_TAIL_EXIT = 1, /* a proc exited (detail = reason) */
48 XTC_TAIL_WAKE = 2, /* a parked proc was woken (armed) */
49 XTC_TAIL_RUN = 3, /* a proc began running after a wake
50 * (detail = wake-to-run latency, ns) */
51 XTC_TAIL_PARK = 4, /* a proc parked (blocked on recv/timer/fd) */
52 /* MSG source: */
53 XTC_TAIL_SEND = 5, /* pid sent a message (detail = payload bytes) */
54 XTC_TAIL_RECV = 6, /* pid received a message (detail = bytes) */
55 XTC_TAIL_MBOX_HWM = 7 /* pid's mailbox depth reached a new high-water
56 * (detail = the new peak depth) */
57};
58
59/* One recorded event. Fixed layout; the binary dump writes it verbatim
60 * behind a versioned header, so a reader across the wire/disk decodes it
61 * without guessing. */
62typedef struct xtc_tail_rec {
63 uint64_t ts_ns; /* monotonic timestamp (ns) */
64 uint32_t source; /* which XTC_TAIL_* source produced it */
65 uint32_t kind; /* enum xtc_tail_kind */
66 xtc_pid_t pid; /* the proc the event concerns */
67 uint64_t detail; /* kind-specific (EXIT reason / RUN latency ns) */
69
70/* Visit callback for xtc_tail_read: return 0 to continue, nonzero stops. */
71typedef int (*xtc_tail_fn)(const xtc_tail_rec_t *rec, void *user);
72
73/*
74 * PUBLIC: unsigned xtc_tail_enable __P((unsigned));
75 * PUBLIC: void xtc_tail_disable __P((void));
76 * PUBLIC: int xtc_tail_reset __P((void));
77 * PUBLIC: int xtc_tail_read __P((xtc_tail_fn, void *));
78 * PUBLIC: int xtc_tail_dump __P((int));
79 * PUBLIC: size_t xtc_tail_count __P((void));
80 */
81
82/* Enable the named sources (a bitwise-OR of XTC_TAIL_*). Returns the
83 * previously enabled mask. Enabling is idempotent; call with the full
84 * mask you want each time (it replaces, not ORs). */
85unsigned xtc_tail_enable(unsigned source_mask);
86
87/* Disable all recording (equivalent to xtc_tail_enable(0)). */
88void xtc_tail_disable(void);
89
90/* Drop all buffered records. Returns XTC_OK. */
91int xtc_tail_reset(void);
92
93/* Visit every buffered record oldest-first (a stable snapshot). */
94int xtc_tail_read(xtc_tail_fn cb, void *user);
95
96/* Write the buffered records to `fd` as a versioned binary trace:
97 * a small header (magic, version, record count, record size) followed
98 * by the records verbatim. A separate offline tool renders it. */
99int xtc_tail_dump(int fd);
100
101/* Number of records currently buffered. */
102size_t xtc_tail_count(void);
103
104/* Internal: record one event from a runtime hook point (proc spawn/exit,
105 * etc.). Not part of the public API -- callers use xtc_tail_enable to
106 * turn recording on and the read/dump/count functions to consume it.
107 * A no-op fast path (one branch) when `source` is disabled. */
108void __xtc_tail_emit(unsigned source, unsigned kind, xtc_pid_t pid,
109 uint64_t detail);
110
111/* Internal: 1 if `source` is currently enabled. A hook point uses this
112 * to guard extra work (e.g. a clock read) so a disabled tail costs one
113 * branch and has no side effects. */
114int __xtc_tail_on(unsigned source);
115
116/* On-disk binary dump header (also used by the offline reader).
117 *
118 * Format v2 is COMPACT and PORTABLE (dial9-style): all header fields are
119 * written as explicit little-endian bytes (no struct memcpy, so it is
120 * byte-identical across endianness and padding), and each event is
121 * varint/delta encoded rather than a fixed 32-byte record:
122 *
123 * header: magic[4]="XTCL" version(LE u32)=2 flags(LE u32)
124 * count(LE u32) base_ts_ns(LE u64)
125 * flags bit0 = 1 -> little-endian canonical stream (always set today)
126 * per event (oldest first):
127 * kind : 1 byte
128 * source : 1 byte
129 * ts_delta: LEB128 varint, ns since the previous event (base for the
130 * first) -- monotonic timestamps make this 1-2 bytes
131 * loop_id : LEB128 varint (pid.loop_id)
132 * local_id: LEB128 varint (pid.local_id)
133 * gen : LEB128 varint (pid.gen)
134 * detail : LEB128 varint (EXIT reason / RUN latency ns)
135 *
136 * Typical ~6-12 bytes/event vs 32 for the raw struct, and portable.
137 * xtc_tail_read (in-process) still hands back the fixed xtc_tail_rec_t. */
138#define XTC_TAIL_MAGIC 0x5854434Cu /* "XTCL" */
139#define XTC_TAIL_VERSION 2u
140#define XTC_TAIL_FLAG_LE 1u /* little-endian canonical stream */
141typedef struct xtc_tail_hdr {
142 uint32_t magic; /* XTC_TAIL_MAGIC */
143 uint32_t version; /* XTC_TAIL_VERSION */
144 uint32_t flags; /* XTC_TAIL_FLAG_* (endianness marker) */
145 uint32_t count; /* number of events that follow */
146 uint64_t base_ts_ns; /* timestamp of the first event (deltas from here) */
148
149#endif /* XTC_TAIL_H */