lib/alloc/observe/src/root.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 //! ## Layered allocation and operation hierarchy
  2 //!
  3 //! Observation of memory behavior across allocator layers needs to distinguish
  4 //! a caller's logical request from the underlying acquisition of backing
  5 //! memory. For example, a high-level allocator such as an arena or memory pool
  6 //! satisfies requests by carving space out of larger regions that it already
  7 //! holds. When an arena exhausts its available capacity, it requests an
  8 //! additional chunk from a backing allocator. In that situation, a single
  9 //! logical allocation at the arena layer can cause a second, nested allocation
 10 //! underneath it.
 11 //!
 12 //! If an observer records only high-level requests, backing chunk acquisitions
 13 //! remain invisible. If an observer records only backing calls, individual
 14 //! logical objects cannot be tracked. Recording both layers without
 15 //! distinguishing their roles can also mislead analysis, because summing
 16 //! requested lengths across both layers as one memory total can count the same
 17 //! storage relationship twice.
 18 //!
 19 //! The package connects instrumented allocators through one shared callback.
 20 //! Within this system, a tracking interval is called a span. A span becomes
 21 //! active only when observation is enabled, not suppressed on this thread, and
 22 //! a sink is installed. Each active span reserves its own operation ID, saves
 23 //! the current thread operation ID as parent, becomes current, and restores the
 24 //! saved parent on finish. It has no duration bound, timeout, or automatic
 25 //! finish.
 26 //!
 27 //! Spans finish in reverse order of their initiation. When an arena allocation
 28 //! triggers a backing allocation, the inner backing operation finishes and
 29 //! delivers its completion notice, or event, before the outer arena operation
 30 //! finishes. Because operation identifiers are reserved at the start of an
 31 //! operation, callback delivery order need not follow increasing identifier
 32 //! order.
 33 //!
 34 //! Thread nesting is strictly local, with no global event ordering or
 35 //! cross-thread context propagation. Callers must finish spans and silent
 36 //! contexts in reverse initiation order on their originating thread. While
 37 //! invoking finish on the same token twice is harmless, copying an active token
 38 //! does not produce independent ownership, so callers must finish only one
 39 //! authoritative token.
 40 //!
 41 //! Callers must not infer physical bytes released, total heap use, or complete
 42 //! process coverage from logical allocation events. A free outcome marked as
 43 //! succeeded indicates that the allocator operation completed, not that backing
 44 //! pages were released to the operating system. An arena or pool can retain
 45 //! freed memory internally for subsequent reuse.
 46 //!
 47 //! In `lib/chic/src/profiling/workloads/invocation.zig`, the `Counts` helper
 48 //! filters only successful `.arena` allocation events, distinguishing root
 49 //! requests with a parent ID of zero from nested arena requests with a non-zero
 50 //! parent ID. In `lib/choir/src/core/operation/storage.zig`, test assertions
 51 //! confirm that reusing a pool slot emits a logical allocation event even when
 52 //! the pool requires no backing expansion.
 53 //!
 54 //! ## Synchronous notification and `Session` lifecycle
 55 //!
 56 //! Observation connects instrumented allocators to one caller-installed
 57 //! callback. The package records no history itself, owns no event buffer, and
 58 //! provides no export format or capacity policy. Observation relies on a
 59 //! caller-provided destination, or sink, comprising an opaque context pointer
 60 //! and a function pointer accepting an `Event` structure by value. The callback
 61 //! returns `void`. Consequently, error recovery, bounded buffer allocation,
 62 //! sample dropping policies, data aggregation, and thread synchronization
 63 //! belong entirely to the sink implementation.
 64 //!
 65 //! Notification occurs synchronously when an active span finishes. In the
 66 //! provided allocator adapters, the observed memory operation has already
 67 //! returned before event delivery begins. Callback work participates directly
 68 //! in the caller's execution path, without an asynchronous queue. When multiple
 69 //! application threads allocate concurrently, event delivery from different
 70 //! threads can overlap. A single installed sink serves the shared protocol
 71 //! instance.
 72 //!
 73 //! An observation run, or session, borrows the caller's sink and its context
 74 //! pointer. The `install` function registers the sink and returns
 75 //! `error.AlreadyInstalled` if another session is already active. `Sink`
 76 //! storage and callback context memory must remain valid and at stable
 77 //! addresses until `Session.deinit` returns. This lifetime requirement applies
 78 //! immediately during installation, because an emitter on another thread can
 79 //! invoke the callback before `install` returns. Allocators can exist before
 80 //! observation begins, and their subsequent operations are delivered once a
 81 //! sink is active.
 82 //!
 83 //! Tearing down an enabled observation session requires calling
 84 //! `Session.deinit` exactly once. The shutdown sequence first uninstalls the
 85 //! sink and then spins until the shared count of active spans and contexts
 86 //! reaches zero, without providing any timeout or cancellation mechanism.
 87 //! Because outstanding tokens on the thread invoking shutdown cannot finish
 88 //! while that thread spins, callers must finish all thread-local tokens before
 89 //! initiating `deinit`. Callers must also invoke `Session.deinit` outside of
 90 //! active callbacks and suppression scopes to satisfy internal assertions, and
 91 //! without holding locks that concurrent callbacks require to make progress. In
 92 //! addition, callers must serialize the complete shutdown of an old session
 93 //! before installing a new one, as the lifecycle rules do not accommodate
 94 //! overlapping transitions.
 95 //!
 96 //! To prevent infinite recursion, the protocol automatically suppresses
 97 //! observation on the current thread while executing a sink callback. Callers
 98 //! can also initiate manual suppression using `suppress`, which returns a token
 99 //! whose `deinit` restores the previous suppression depth. `Suppression` skips
100 //! new spans and contexts without preventing the underlying allocation.
101 //! `Suppression` does not cancel already active spans, mute other threads, or
102 //! automatically suppress the separate `sys.memory` observer. Spans and
103 //! contexts initiated when no sink is installed remain inactive.
104 //!
105 //! Subsystems can track allocation boundaries through private event
106 //! representations rather than standard observer events. For example, the
107 //! `memtrace` tracing allocator assigns an identifier around a backing call but
108 //! records that boundary inside its own event structure. To accommodate this
109 //! pattern, `beginContext` reserves an identifier and participates in
110 //! same-thread nesting without emitting an `Event`. Consequently, a child event
111 //! may specify a `parent_operation_id` that has no matching event
112 //! `operation_id` in the observer stream. Downstream consumers can read
113 //! `Context.id` and `parentId` to connect that externally recorded boundary
114 //! with nested operations. On any context that was initially active, both
115 //! stored getter functions remain readable after `finish`.
116 //!
117 //! ## Producer `Identity` and lifecycle tracking
118 //!
119 //! Correlating allocation activity with specific program components requires
120 //! stable identifiers that persist across individual operations. The protocol
121 //! identifies each allocation source through a producer identifier, or producer
122 //! ID, allocated independently of whether an active observation session exists.
123 //!
124 //! When initializing a session, the system records an identifier boundary by
125 //! sampling the next producer identifier under the same registry lock that
126 //! serializes `producerId` allocations. Identifiers assigned prior to this
127 //! boundary fall strictly below the sampled value, whereas identifiers assigned
128 //! after installation are at or above it. This boundary does not filter
129 //! incoming events emitted by older producers. Instead, downstream consumers
130 //! such as `memtrace` use the boundary to qualify histories that may lack
131 //! allocation records from before observation began. A producer registered at
132 //! or above the boundary still does not guarantee a complete event history,
133 //! because active suppression scopes and sink recording policies can omit
134 //! events.
135 //!
136 //! Full emitter identity comprises four components: `producer_id`, a producer
137 //! kind (`Producer`), a generation number, and an owner cookie. The generation
138 //! is an owner-supplied lifetime number rather than a counter advanced by this
139 //! module, and the owner cookie provides an owner-supplied association value.
140 //! Callers use `beginOwned` to forward these attributes. The protocol forwards
141 //! owner-supplied values directly without advancing generations or validating
142 //! lifetime transitions. Standard `begin` calls supply generation zero and
143 //! cookie zero.
144 //!
145 //! The `Identity.movable` helper derives a non-zero owner cookie from a
146 //! non-zero producer ID rather than an owner memory address, preserving
147 //! association when the owner struct moves. However, this derivation does not
148 //! update allocator handles borrowing old owner addresses, and folding 64-bit
149 //! identifiers into `usize` gives no cookie uniqueness guarantee.
150 //!
151 //! Allocator invalidations and teardown phases are communicated through
152 //! lifecycle spans created via `beginLifecycle`. These spans record events with
153 //! operations set to `.lifecycle` and capture reasons such as `reset` or
154 //! `deinit`. The allocator owner supplies the outcome and actual lifetime
155 //! behavior. For example, the arena allocator wrapper emits a reset lifecycle
156 //! span using its current generation and increments the generation afterward,
157 //! even when reset returns false. Callers must not assume every adapter emits
158 //! lifecycle events, as generic `ObservedAllocator`, `debug.Allocator`, and
159 //! `buffer.First` do not emit teardown lifecycle records.
160 //!
161 //! ## Build configurations and allocator adapters
162 //!
163 //! The package module name is `alloc_observe`, an internal implementation
164 //! package rather than `tiny.alloc_observe`. In standalone builds,
165 //! `-Denabled=true` chooses the active implementation, which defaults to false.
166 //! Consumer build scripts across the repository commonly qualify observation
167 //! with `-Dobserve-allocations=true`, though individual packages do not enable
168 //! it identically. The test root compiles both enabled and disabled variants in
169 //! a single invocation.
170 //!
171 //! In disabled builds, session installation succeeds as an inert operation,
172 //! while calls to `begin`, `finish`, and `suppress` perform no work. Internal
173 //! data structures also adopt different memory layouts compared to enabled
174 //! builds. State queries reflect this disabled status: `producerId`,
175 //! `currentOperationId`, and context identifiers evaluate to zero, while
176 //! `suppressed` returns false. Because generic `ObservedAllocator.init` always
177 //! asserts a non-zero producer identifier, callers must not invoke it
178 //! unconditionally with `producerId()` in disabled configurations.
179 //!
180 //! An allocator type that tries a supplied buffer, then a fallback allocator,
181 //! is buffer.First. This buffer stores allocations, not recorded events. When
182 //! observation is disabled, the type aliases `std.heap.BufferFirstAllocator`,
183 //! while enabled observation wraps it. To obtain a diagnostic allocator,
184 //! invoking `debug.Allocator(options)` returns a type with init(backing),
185 //! allocator(), and deinit(), where deinit returns std.heap.Check. Around
186 //! backing raw calls, enabled adapters use `ObservedAllocator`, preserve
187 //! backing outcome, and add no lifetime policy. Allocator handles borrow stable
188 //! live owners and backing allocators. Sinks called from concurrent threads
189 //! must synchronize shared state themselves.
190 //!
191 //! ## Direct protocol execution
192 //!
193 //! Driving spans directly demonstrates how the protocol registers operations,
194 //! establishes parentage, and delivers completion events.
195 //!
196 //! In this test, the operation IDs and producer IDs are real identifiers
197 //! allocated by the protocol. Only the addresses and lengths are simulated
198 //! operation data rather than real memory allocations, and callers must never
199 //! dereference these synthetic addresses.
200 //!
201 //! The demonstration runs on a single thread. A local `Recorder` structure
202 //! provides bounded storage using a fixed array of two events, recording events
203 //! until capacity is reached and incrementing a dropped counter with saturating
204 //! addition thereafter. This recorder contains no synchronization. A sink
205 //! receiving events from concurrent producers requires synchronization to
206 //! protect its state.
207 //!
208 //! ```zig
209 //! const std = @import("std");
210 //! const observe = @import("alloc_observe");
211 //!
212 //! test "direct protocol event demonstration" {
213 //!     if (comptime !observe.enabled) return error.SkipZigTest;
214 //!
215 //!     const Recorder = struct {
216 //!         events: [2]observe.Event = undefined,
217 //!         count: usize = 0,
218 //!         dropped: usize = 0,
219 //!
220 //!         fn record(context: *anyopaque, event: observe.Event) void {
221 //!             const self: *@This() = @ptrCast(@alignCast(context));
222 //!             if (self.count < self.events.len) {
223 //!                 self.events[self.count] = event;
224 //!                 self.count += 1;
225 //!             } else {
226 //!                 self.dropped = self.dropped +| 1;
227 //!             }
228 //!         }
229 //!     };
230 //!
231 //!     var recorder = Recorder{};
232 //!     var sink = observe.Sink{
233 //!         .context = &recorder,
234 //!         .record = Recorder.record,
235 //!     };
236 //!
237 //!     var session = try observe.install(&sink);
238 //!     defer session.deinit();
239 //!
240 //!     const arena_id = observe.producerId();
241 //!     const backing_id = observe.producerId();
242 //!
243 //!     var outer = observe.begin(
244 //!         arena_id,
245 //!         .arena,
246 //!         .alloc,
247 //!         0,
248 //!         0,
249 //!         24,
250 //!         8,
251 //!         101,
252 //!     );
253 //!     defer outer.finish(.{ .address = 0, .succeeded = false });
254 //!
255 //!     var inner = observe.begin(
256 //!         backing_id,
257 //!         .boundary,
258 //!         .alloc,
259 //!         0,
260 //!         0,
261 //!         4096,
262 //!         16,
263 //!         202,
264 //!     );
265 //!     defer inner.finish(.{ .address = 0, .succeeded = false });
266 //!
267 //!     inner.finish(.{ .address = 0x2000, .succeeded = true });
268 //!     outer.finish(.{ .address = 0x2010, .succeeded = true });
269 //!
270 //!     try std.testing.expectEqual(@as(usize, 2), recorder.count);
271 //!     try std.testing.expectEqual(@as(usize, 0), recorder.dropped);
272 //!     try std.testing.expectEqual(
273 //!         recorder.events[1].operation_id,
274 //!         recorder.events[0].parent_operation_id,
275 //!     );
276 //!     try std.testing.expectEqual(
277 //!         @as(u64, 0),
278 //!         recorder.events[1].parent_operation_id,
279 //!     );
280 //!     try std.testing.expectEqual(@as(u64, 0), observe.currentOperationId());
281 //! }
282 //! ```
283 
284 const protocol = @import("protocol.zig");
285 
286 pub const buffer = @import("buffer.zig");
287 pub const debug = @import("debug.zig");
288 
289 /// A generic adapter wrapping a supplied backing allocator, instrumenting
290 /// allocation, resizing, remapping, and deallocation virtual table calls while
291 /// returning backing allocator outcomes unchanged. The adapter emits no
292 /// lifecycle records and enforces no independent memory reclamation policy.
293 /// Callers must keep both the adapter and its backing allocator at stable
294 /// memory addresses throughout use, and must ensure the installed sink can
295 /// handle concurrent invocations when the backing allocator is accessed across
296 /// multiple threads.
297 pub const ObservedAllocator = @import("allocator.zig").ObservedAllocator;
298 /// A compile-time boolean indicating whether allocation observation is active.
299 /// Build options determine this setting: standalone builds default to `false`
300 /// unless configured with `-Denabled=true`, while monorepo consumers typically
301 /// forward `-Dobserve-allocations=true` through build scripts. When this flag
302 /// is `false`, installation succeeds inertly, identifier queries return zero,
303 /// suppression checks return `false`, and operations such as span tracking or
304 /// suppression perform no work. Public data structures and types differ in
305 /// concrete field layout between enabled and disabled builds, so they offer no
306 /// layout equivalence guarantee across build configurations.
307 pub const enabled = protocol.enabled;
308 /// An `Event` is a by-value notification delivered at span finish. This
309 /// notification conveys caller-supplied data rather than proof of actual
310 /// storage. Numeric addresses are diagnostic, providing no ownership,
311 /// capability, or dereference guarantee after an allocation expires. Ordinary
312 /// spans set lifecycle tags to none.
313 ///
314 /// The exact fields and their meanings are:
315 ///
316 /// - `operation_id`: operation identifier assigned at begin.
317 /// - `parent_operation_id`: identifier of the enclosing span or context on the
318 ///   current thread, or zero if none exists.
319 /// - `producer_id`: numeric identifier of the producer.
320 /// - `producer`: kind of producer.
321 /// - `generation`: owner-supplied lifetime number.
322 /// - `owner_cookie`: owner association value.
323 /// - `operation`: executed allocation or lifecycle operation.
324 /// - `lifecycle_disposition`: lifecycle disposition tag, set to none for
325 ///   ordinary spans.
326 /// - `lifecycle_reason`: lifecycle reason tag, set to none for ordinary spans.
327 /// - `address`: result address supplied to finish.
328 /// - `old_address`: prior address before the operation.
329 /// - `len`: requested or new length.
330 /// - `old_len`: prior length before the operation.
331 /// - `alignment`: alignment in bytes.
332 /// - `return_address`: caller instruction address.
333 /// - `succeeded`: producer result flag indicating success or failure.
334 ///
335 /// When `beginLifecycle` opens a span, it sets old_address, old_len, and len to
336 /// zero, and alignment to one byte. That initialization does not dictate the
337 /// final result address supplied to finish. Nested spans on the same thread
338 /// deliver inner spans before outer spans, so operation identifiers do not
339 /// increase in delivery order. Concurrent callbacks have no globally serialized
340 /// order across threads. A silent context reserves an operation identifier but
341 /// emits no event, so a parent operation identifier can lack a matching event.
342 pub const Event = protocol.Event;
343 /// A silent thread-bound nesting token that reserves an operation identifier
344 /// and participates in thread hierarchy tracking without publishing an event
345 /// upon completion. An active context updates the thread current operation ID,
346 /// links to the prior parent, and increments the shared active operation
347 /// counter. Callers must finish contexts in last-in, first-out order on the
348 /// originating thread, treating each token as having a single authoritative
349 /// owner. When observation is disabled, suppressed, or executed without an
350 /// installed sink, the returned context is inactive and reports an identifier
351 /// of zero.
352 pub const Context = protocol.Context;
353 /// Identifies an allocation source by recording a producer identifier, a
354 /// producer kind, an owner-supplied generation number, and an owner-supplied
355 /// cookie value. When observation is enabled, starting a span requires a
356 /// nonzero producer identifier, and specifying a nonzero generation number
357 /// requires a nonzero cookie. The observation layer stores and forwards these
358 /// values as provided by the caller without automatically tracking lifetime
359 /// state or advancing generation numbers.
360 pub const Identity = protocol.Identity;
361 /// Specifies how a lifecycle event affects allocations belonging to the
362 /// emitting producer: none indicates a standard allocator operation, invalidate
363 /// marks earlier allocations as having lost validity, and end signals that the
364 /// producer owner lifetime has completed. The emitting allocator supplies this
365 /// disposition directly. The observation protocol forwards the value to the
366 /// installed sink without validating owner state or enforcing lifetime
367 /// guarantees.
368 pub const LifecycleDisposition = protocol.LifecycleDisposition;
369 /// Describes the motive for an allocator lifecycle transition: none, reset,
370 /// clear_and_free, clear_retaining_current, clear_retaining_largest,
371 /// clear_retaining_capacity, clear_retaining_capacity_limit, and deinit. Each
372 /// value represents an owner-supplied description of its action. The
373 /// enumeration conveys descriptive intent rather than implementing memory
374 /// retention, so actual buffer preservation or release depends entirely on the
375 /// owner allocator implementation.
376 pub const LifecycleReason = protocol.LifecycleReason;
377 /// Identifies raw allocator virtual table operations (alloc, resize, remap, and
378 /// free) alongside lifecycle transitions. Values represent logical actions
379 /// reported by an allocator interface rather than physical memory changes in
380 /// the operating system. A successful free event records that an allocator
381 /// finished its logical deallocation procedure, which does not guarantee that
382 /// underlying virtual address pages or physical bytes were returned to the
383 /// kernel.
384 pub const Operation = protocol.Operation;
385 /// An `Outcome` carries the result of an operation as supplied by the producer.
386 /// It contains a boolean succeeded flag and a usize address that defaults to
387 /// zero. This value represents an operation-specific result rather than a
388 /// simple return without an exception, and it provides no proof of physical
389 /// reclamation. Within the generic `ObservedAllocator`, a successful allocation
390 /// or remap produces the resulting address, a successful resize produces the
391 /// old address, a deallocation produces the old address, and an operation
392 /// failure produces zero. A caller invoking a lifecycle span may omit the
393 /// address so that it defaults to zero, but the protocol does not enforce that
394 /// a lifecycle result address must be zero. The `beginLifecycle` function
395 /// zeroes the old address and length metadata when opening the span, while
396 /// finish accepts the address supplied in the final Outcome.
397 pub const Outcome = protocol.Outcome;
398 /// Classifies the origin of an observed allocation event through tags: arena,
399 /// bump, buffer_first, fixed_buffer, debug, pool, process, phase, limit,
400 /// boundary, sys_memory, and custom. Each tag labels the reporting component
401 /// rather than guaranteeing how that component manages memory or whether its
402 /// reported operations cover all process allocations. For instance, an
403 /// allocator tagged as an arena describes its own interface layer rather than
404 /// certifying backing page reclamation or absence of uninstrumented memory
405 /// traffic.
406 pub const Producer = protocol.Producer;
407 /// Represents an installed observation interval, managing session duration
408 /// while borrowing caller-owned sink and context storage. Callers must ensure
409 /// sink and context storage remain allocated at stable memory addresses
410 /// throughout the session. The session must be explicitly terminated by calling
411 /// deinit exactly once, as no automatic teardown is performed on drop. In
412 /// disabled builds, the session is inert. Callers must serialize session
413 /// lifecycle transitions externally, because the protocol does not support
414 /// overlapping the shutdown of an old session with the installation of a new
415 /// one.
416 pub const Session = protocol.Session;
417 /// A delivery target holding a type-erased context pointer and a function
418 /// pointer that accepts an event by value and returns `void`. Delivery occurs
419 /// synchronously on the thread finishing an observed span, directly in the
420 /// caller execution path with no intermediate queue. Because events from
421 /// distinct threads can arrive concurrently, the callback implementation must
422 /// synchronize access to shared state. The sink provides no internal buffer,
423 /// capacity management, dropping strategy, or error reporting mechanism. The
424 /// caller must guarantee that the `Sink` struct storage itself, the callback
425 /// context, and the callback code remain stable and valid in memory until
426 /// session shutdown returns.
427 pub const Sink = protocol.Sink;
428 /// A thread-bound tracking token representing an active observation interval.
429 /// When created while observation is enabled, unsuppressed, and backed by an
430 /// installed sink, an active span records the current thread parent operation
431 /// ID, updates the thread current operation ID, increments the active operation
432 /// count, and captures the sink reference. Active spans must be finished
433 /// strictly in last-in, first-out order on their originating thread, and each
434 /// token must be owned and finished by a single authoritative holder. When
435 /// observation is disabled, suppressed, or running without an installed sink,
436 /// the token is inert. Concrete field layouts differ between enabled and
437 /// disabled builds.
438 pub const Span = protocol.Span;
439 /// A nesting depth token that suspends the creation of new spans and silent
440 /// contexts on the current thread while active. `Suppression` does not prevent
441 /// underlying memory allocations from proceeding, does not mute observation on
442 /// other threads, and does not cancel or modify spans that were already
443 /// initiated prior to suppression.
444 pub const Suppression = protocol.Suppression;
445 
446 /// Initiates an ordinary observation span with generation and owner cookie
447 /// defaulted to zero, forwarding previous address, previous length, requested
448 /// length, byte alignment, and caller return address to the resulting token.
449 /// When observation is enabled, the caller must supply a nonzero producer
450 /// identifier even if observation is currently suppressed or no sink is
451 /// installed. The caller must finish the returned token with an outcome to
452 /// conclude the operation.
453 pub const begin = protocol.begin;
454 /// Reserves an operation identifier and establishes thread nesting without
455 /// publishing an event when finished. In enabled builds with an installed sink
456 /// and no active suppression, the context increments the active operation count
457 /// and links into the thread parent stack. In all other circumstances, it
458 /// returns an inert token with an identifier of zero. Callers must finish the
459 /// returned context on its originating thread, as active contexts contribute to
460 /// the session drain count during shutdown.
461 pub const beginContext = protocol.beginContext;
462 /// Initiates a lifecycle observation span for an allocator transition,
463 /// requiring disposition and reason values other than none even though disabled
464 /// builds ultimately discard the event. The span records a lifecycle operation
465 /// kind, sets prior address, prior length, and requested length to zero, sets
466 /// alignment to one, and forwards the supplied identity and return address. The
467 /// caller supplies the eventual outcome upon finishing the span. The
468 /// observation layer records this transition without modifying or managing
469 /// owner lifetime state.
470 pub const beginLifecycle = protocol.beginLifecycle;
471 /// Initiates an ordinary observation span using an explicit caller identity,
472 /// forwarding prior and requested buffer sizes, addresses, alignment, and
473 /// return address arguments. In enabled builds, this function asserts identity
474 /// validity rules by requiring a nonzero producer identifier and requiring a
475 /// nonzero cookie whenever the generation number is nonzero, but it performs no
476 /// automatic generation advancement or lifecycle state validation. If
477 /// observation is suppressed or no sink is present, the function returns an
478 /// inactive span. In disabled builds, it returns an inert span.
479 pub const beginOwned = protocol.beginOwned;
480 /// Returns the operation identifier currently active on the calling thread,
481 /// which belongs to either an active span or a silent context, or zero if no
482 /// operation is active or observation is disabled. Active suppression does not
483 /// clear an already established identifier.
484 pub const currentOperationId = protocol.currentOperationId;
485 /// Installs a borrowed sink and context as the active observation target,
486 /// returning an active session handle. If an enabled sink is already installed,
487 /// the function returns AlreadyInstalled. In disabled builds, installation
488 /// succeeds inertly. The caller must guarantee that sink storage and context
489 /// pointers remain valid and stationary, because registered producers on other
490 /// threads may deliver callbacks before install returns. The function must be
491 /// called outside existing callbacks and suppression scopes. Callers must
492 /// serialize session lifecycles externally, ensuring that prior session
493 /// teardown completes fully before a new session is installed.
494 pub const install = protocol.install;
495 /// Allocates a nonzero, monotonically increasing identifier for an allocator
496 /// producer when observation is enabled, or returns zero in disabled builds.
497 /// Identifiers are allocated independently of active sessions, protected by a
498 /// registry lock that serializes allocation with session floor sampling.
499 /// Exhaustion of the identifier space causes a panic rather than a recoverable
500 /// error. Generated values are unique only within the linked module instance,
501 /// providing no global uniqueness across separate processes or independently
502 /// loaded module binaries.
503 pub const producerId = protocol.producerId;
504 /// Increments the observation suppression depth counter on the current thread,
505 /// asserting that depth does not overflow an eight-bit integer, and returns a
506 /// suppression token that decrements the counter upon deinitialization. In
507 /// disabled builds, the call returns an inert token. Active suppression
508 /// prevents the creation of new spans and silent contexts on the calling thread
509 /// without altering spans that are already in progress.
510 pub const suppress = protocol.suppress;
511 /// Reports whether observation is currently suspended on the calling thread due
512 /// to an active callback execution or an explicit suppression scope, returning
513 /// false in disabled builds. This query reflects thread suppression depth
514 /// rather than indicating whether a sink is installed.
515 pub const suppressed = protocol.suppressed;