@moqtap/trace 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -0
- package/SPEC.md +378 -111
- package/dist/index.cjs +461 -54
- package/dist/index.d.cts +251 -5
- package/dist/index.d.ts +251 -5
- package/dist/index.js +460 -53
- package/package.json +2 -2
- package/src/binary.ts +467 -57
- package/src/cbor-scan.ts +185 -0
- package/src/index.ts +18 -1
- package/src/recorder.ts +10 -0
- package/src/types.ts +198 -2
package/dist/index.d.cts
CHANGED
|
@@ -1,22 +1,98 @@
|
|
|
1
1
|
import { SessionState } from '@moqtap/codec/session';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Any string other than the ones named beside it.
|
|
5
|
+
*
|
|
6
|
+
* Unions built with this keep editor completion for the values this version
|
|
7
|
+
* knows, while still accepting a value it does not. Nothing in the format
|
|
8
|
+
* rejects an unrecognised string: new perspectives, detail levels and drop
|
|
9
|
+
* policies may be added without a version bump, and a reader that refused one
|
|
10
|
+
* would refuse a file it can otherwise read in full.
|
|
11
|
+
*/
|
|
12
|
+
type OtherString = string & Record<never, never>;
|
|
13
|
+
type DetailLevel = 'control' | 'headers' | 'headers+sizes' | 'headers+data' | 'full' | OtherString;
|
|
14
|
+
type Perspective = 'client' | 'server' | 'observer' | 'relay-tap' | OtherString;
|
|
15
|
+
/** Drop strategy a sampled source applied when it could not keep up. */
|
|
16
|
+
type DropPolicy = 'head' | 'tail' | 'sampled' | OtherString;
|
|
17
|
+
/** Length of a trace ID, in bytes. Fixed by the format. */
|
|
18
|
+
declare const TRACE_ID_LENGTH = 16;
|
|
19
|
+
/**
|
|
20
|
+
* Per-segment metadata.
|
|
21
|
+
*
|
|
22
|
+
* Present only in segmented traces. A single-shot file must not carry it —
|
|
23
|
+
* its absence is what tells a reader that sequence numbers and timestamps are
|
|
24
|
+
* file-global rather than segment-local.
|
|
25
|
+
*/
|
|
26
|
+
interface SegmentInfo {
|
|
27
|
+
/** 0-based sequence number of this segment within the stream. */
|
|
28
|
+
readonly sequence: number;
|
|
29
|
+
/** Nominal segment duration in milliseconds. A hint; the real one may differ. */
|
|
30
|
+
readonly durationMs?: number;
|
|
31
|
+
/** Opaque identifier shared by every segment of the same logical stream. */
|
|
32
|
+
readonly streamId?: string;
|
|
33
|
+
/** True if this segment continues a previous one with the same `streamId`. */
|
|
34
|
+
readonly continues?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Sampling and filtering metadata. Present only when events were dropped or
|
|
38
|
+
* filtered at the source; its absence means the trace is complete relative to
|
|
39
|
+
* its detail level.
|
|
40
|
+
*/
|
|
41
|
+
interface SamplingInfo {
|
|
42
|
+
/** Fraction of source events retained, in (0, 1]. */
|
|
43
|
+
readonly effectiveRate?: number;
|
|
44
|
+
/** Per-segment cap that triggered drops, if rate-limited. */
|
|
45
|
+
readonly maxEventsPerSec?: number;
|
|
46
|
+
/** Drop strategy applied when the cap was exceeded. */
|
|
47
|
+
readonly dropPolicy?: DropPolicy;
|
|
48
|
+
/** Cumulative events dropped since `startTime`. */
|
|
49
|
+
readonly droppedTotal?: number;
|
|
50
|
+
/** Events dropped within this segment only. */
|
|
51
|
+
readonly droppedSegment?: number;
|
|
52
|
+
/** Source-side filter rule that selected events. */
|
|
53
|
+
readonly rule?: string;
|
|
54
|
+
/** Language the rule is written in ('prefix', 'glob', 'cel'). */
|
|
55
|
+
readonly ruleLang?: string;
|
|
56
|
+
/** Event type IDs the drop policy was applied to. */
|
|
57
|
+
readonly appliesTo?: number[];
|
|
58
|
+
}
|
|
5
59
|
interface TraceHeader {
|
|
6
60
|
readonly protocol: string;
|
|
7
61
|
readonly perspective: Perspective;
|
|
8
62
|
readonly detail: DetailLevel;
|
|
63
|
+
/**
|
|
64
|
+
* Recording start (Unix epoch milliseconds). In a segmented trace this is
|
|
65
|
+
* the segment's start, not the stream's.
|
|
66
|
+
*/
|
|
9
67
|
readonly startTime: number;
|
|
10
68
|
readonly endTime?: number;
|
|
11
69
|
readonly transport?: string;
|
|
12
70
|
readonly source?: string;
|
|
13
71
|
readonly endpoint?: string;
|
|
14
72
|
readonly sessionId?: string;
|
|
73
|
+
/** Present only when this header begins one segment of a segmented stream. */
|
|
74
|
+
readonly segment?: SegmentInfo;
|
|
75
|
+
/** Present only when events were dropped or filtered at the source. */
|
|
76
|
+
readonly sampling?: SamplingInfo;
|
|
15
77
|
readonly custom?: Record<string, unknown>;
|
|
16
78
|
}
|
|
17
79
|
interface BaseEvent {
|
|
80
|
+
/**
|
|
81
|
+
* Monotonically increasing sequence number. Segment-local in a segmented
|
|
82
|
+
* trace, so ordering across segments is `(segment.sequence, seq)`.
|
|
83
|
+
*/
|
|
18
84
|
readonly seq: number;
|
|
85
|
+
/** Microseconds since the containing segment's `startTime`. */
|
|
19
86
|
readonly timestamp: number;
|
|
87
|
+
/**
|
|
88
|
+
* Which peer this event pertains to.
|
|
89
|
+
*
|
|
90
|
+
* Required when the perspective is `'relay-tap'`, where one trace covers
|
|
91
|
+
* many concurrent sessions; omitted otherwise. The identifier is
|
|
92
|
+
* source-local: the same string in two traces from different sources does
|
|
93
|
+
* not name the same peer.
|
|
94
|
+
*/
|
|
95
|
+
readonly peer?: string;
|
|
20
96
|
}
|
|
21
97
|
interface ControlMessageEvent extends BaseEvent {
|
|
22
98
|
readonly type: 'control';
|
|
@@ -76,7 +152,103 @@ interface AnnotationEvent extends BaseEvent {
|
|
|
76
152
|
readonly label: string;
|
|
77
153
|
readonly data: unknown;
|
|
78
154
|
}
|
|
79
|
-
|
|
155
|
+
/** Role a peer is acting in at connection time. */
|
|
156
|
+
type PeerRole = 'publisher' | 'subscriber' | 'both' | OtherString;
|
|
157
|
+
/**
|
|
158
|
+
* Which end of a relay a peer sits on.
|
|
159
|
+
*
|
|
160
|
+
* This is the direction the connection was made in, not the direction
|
|
161
|
+
* subscriptions flow. For subscription causality see
|
|
162
|
+
* {@link SubscriptionDerivationEvent}.
|
|
163
|
+
*/
|
|
164
|
+
type Side = 'downstream' | 'upstream' | OtherString;
|
|
165
|
+
/**
|
|
166
|
+
* A new peer session was established. Emitted only by `'relay-tap'` sources;
|
|
167
|
+
* the peer it identifies is on the event's `peer` field.
|
|
168
|
+
*/
|
|
169
|
+
interface PeerConnectedEvent extends BaseEvent {
|
|
170
|
+
readonly type: 'peer-connected';
|
|
171
|
+
readonly endpoint?: string;
|
|
172
|
+
readonly transport?: string;
|
|
173
|
+
readonly role?: PeerRole;
|
|
174
|
+
readonly side?: Side;
|
|
175
|
+
}
|
|
176
|
+
/** A peer session ended. Emitted only by `'relay-tap'` sources. */
|
|
177
|
+
interface PeerDisconnectedEvent extends BaseEvent {
|
|
178
|
+
readonly type: 'peer-disconnected';
|
|
179
|
+
readonly errorCode: number;
|
|
180
|
+
readonly reason?: string;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* A `(peer, request id)` pair naming one subscription on one peer.
|
|
184
|
+
*
|
|
185
|
+
* Request ids are unique only within a peer's session, so neither half
|
|
186
|
+
* identifies a subscription on its own.
|
|
187
|
+
*/
|
|
188
|
+
interface SubscriptionRef {
|
|
189
|
+
readonly peer: string;
|
|
190
|
+
readonly requestId: bigint;
|
|
191
|
+
}
|
|
192
|
+
/** How an upstream subscription came to serve a downstream one. */
|
|
193
|
+
type DerivationKind = 'created' | 'shared' | OtherString;
|
|
194
|
+
/**
|
|
195
|
+
* An upstream subscription was created or extended in causal response to
|
|
196
|
+
* downstream ones. Emitted only by `'relay-tap'` sources.
|
|
197
|
+
*
|
|
198
|
+
* This is the primitive multi-hop correlation is built from: a collector
|
|
199
|
+
* reconstructs an end-to-end tree from these links plus the trace ids
|
|
200
|
+
* propagated along them.
|
|
201
|
+
*
|
|
202
|
+
* The four timestamps share the `timestamp` field's timebase — the emitting
|
|
203
|
+
* source's own clock. Differences between them are meaningful with no
|
|
204
|
+
* cross-hop clock agreement, which is the point: they measure how long one
|
|
205
|
+
* hop took. Never subtract one source's timestamp from another's.
|
|
206
|
+
*
|
|
207
|
+
* A source emits the event as soon as the downstream SUBSCRIBE arrives,
|
|
208
|
+
* carrying whichever timestamps it has, and may emit it again for the same
|
|
209
|
+
* pair as the rest arrive. Treat a later event for the same pair as an update
|
|
210
|
+
* to the earlier one, not as a second derivation.
|
|
211
|
+
*/
|
|
212
|
+
interface SubscriptionDerivationEvent extends BaseEvent {
|
|
213
|
+
readonly type: 'subscription-derivation';
|
|
214
|
+
readonly upstream: SubscriptionRef;
|
|
215
|
+
/** Every downstream subscription currently served by `upstream`. */
|
|
216
|
+
readonly downstream: SubscriptionRef[];
|
|
217
|
+
readonly kind: DerivationKind;
|
|
218
|
+
/**
|
|
219
|
+
* Trace id propagated along the subscription chain: exactly
|
|
220
|
+
* {@link TRACE_ID_LENGTH} raw bytes, never hex- or base64-encoded, so two
|
|
221
|
+
* implementations produce identical values for the same chain.
|
|
222
|
+
*/
|
|
223
|
+
readonly traceId?: Uint8Array;
|
|
224
|
+
/** Track namespace the subscription targets, one entry per field. */
|
|
225
|
+
readonly namespace?: Uint8Array[];
|
|
226
|
+
readonly trackName?: Uint8Array;
|
|
227
|
+
/** When the downstream SUBSCRIBE was received. */
|
|
228
|
+
readonly tDownstreamReceived?: number;
|
|
229
|
+
/** When the upstream SUBSCRIBE was sent. Absent for a terminal subscription. */
|
|
230
|
+
readonly tUpstreamSent?: number;
|
|
231
|
+
/** When SUBSCRIBE_OK arrived from upstream. */
|
|
232
|
+
readonly tUpstreamOkReceived?: number;
|
|
233
|
+
/** When SUBSCRIBE_OK was sent downstream. */
|
|
234
|
+
readonly tDownstreamOkSent?: number;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* An event whose type this version of the package does not know.
|
|
238
|
+
*
|
|
239
|
+
* New event types may be added without a format version bump, so a reader
|
|
240
|
+
* that dropped or relabelled them would make one tool's ignorance permanent
|
|
241
|
+
* for everything downstream of it. The fields are kept verbatim, so an
|
|
242
|
+
* unknown event survives a read-modify-write round trip intact.
|
|
243
|
+
*/
|
|
244
|
+
interface UnknownEvent extends BaseEvent {
|
|
245
|
+
readonly type: 'unknown';
|
|
246
|
+
/** The `"e"` discriminant that was read. */
|
|
247
|
+
readonly eventType: number;
|
|
248
|
+
/** Every key from the event map other than `n`, `t`, `p` and `e`. */
|
|
249
|
+
readonly fields: Record<string, unknown>;
|
|
250
|
+
}
|
|
251
|
+
type TraceEvent = ControlMessageEvent | StreamOpenedEvent | StreamClosedEvent | ObjectHeaderEvent | ObjectPayloadEvent | StateChangeEvent | TraceErrorEvent | AnnotationEvent | PeerConnectedEvent | PeerDisconnectedEvent | SubscriptionDerivationEvent | UnknownEvent;
|
|
80
252
|
interface Trace {
|
|
81
253
|
readonly header: TraceHeader;
|
|
82
254
|
readonly events: TraceEvent[];
|
|
@@ -95,16 +267,80 @@ interface RecorderOptions {
|
|
|
95
267
|
readonly messageTypeId?: (name: string) => number;
|
|
96
268
|
}
|
|
97
269
|
|
|
270
|
+
/** Format version this package writes. */
|
|
271
|
+
declare const FORMAT_VERSION = 2;
|
|
272
|
+
/**
|
|
273
|
+
* Format versions this package reads.
|
|
274
|
+
*
|
|
275
|
+
* Version 1 is a non-segmented version 2 trace carrying none of the keys
|
|
276
|
+
* version 2 added, so reading it costs nothing and keeps every capture taken
|
|
277
|
+
* before the bump openable.
|
|
278
|
+
*/
|
|
279
|
+
declare const SUPPORTED_VERSIONS: readonly number[];
|
|
280
|
+
/** The stream ended part-way through an item — the file was truncated. */
|
|
281
|
+
declare class TruncatedTraceError extends Error {
|
|
282
|
+
/** Byte offset at which the incomplete item begins. */
|
|
283
|
+
readonly offset: number;
|
|
284
|
+
/**
|
|
285
|
+
* Everything that decoded before the cut, one entry per segment. A
|
|
286
|
+
* truncated trace is still evidence, and the events before the cut are
|
|
287
|
+
* exactly as valid as they were.
|
|
288
|
+
*/
|
|
289
|
+
readonly segments: Trace[];
|
|
290
|
+
constructor(offset: number, segments: Trace[]);
|
|
291
|
+
/** What decoded, flattened as {@link readMoqtrace} would return it. */
|
|
292
|
+
get trace(): Trace | undefined;
|
|
293
|
+
}
|
|
294
|
+
/** Options shared by the readers. */
|
|
295
|
+
interface ReadOptions {
|
|
296
|
+
/**
|
|
297
|
+
* On a truncated or corrupt region, skip forward to the next segment and
|
|
298
|
+
* carry on instead of throwing.
|
|
299
|
+
*
|
|
300
|
+
* This is the recovery segmentation exists to offer: one damaged segment
|
|
301
|
+
* costs that segment rather than the rest of the capture. It can only help
|
|
302
|
+
* a segmented trace — there is nothing to resynchronize to in a single
|
|
303
|
+
* segment, so a damaged one still ends the read, just without an error.
|
|
304
|
+
*/
|
|
305
|
+
readonly recover?: boolean;
|
|
306
|
+
}
|
|
98
307
|
/**
|
|
99
308
|
* Write a complete trace to .moqtrace binary format.
|
|
100
309
|
*/
|
|
101
310
|
declare function writeMoqtrace(trace: Trace): Uint8Array;
|
|
311
|
+
/**
|
|
312
|
+
* Write several segments back to back as one segmented `.moqtrace` stream.
|
|
313
|
+
*
|
|
314
|
+
* Every segment header must carry {@link SegmentInfo}. That field is what
|
|
315
|
+
* tells a reader the sequence numbers and timestamps it is about to see
|
|
316
|
+
* restart at zero; a segmented stream written without it reads as a run of
|
|
317
|
+
* complete files whose timeline jumps backwards at every boundary, and
|
|
318
|
+
* nothing downstream can detect that it happened.
|
|
319
|
+
*/
|
|
320
|
+
declare function writeMoqtraceSegments(segments: Trace[]): Uint8Array;
|
|
102
321
|
/**
|
|
103
322
|
* Read a complete .moqtrace file from bytes.
|
|
323
|
+
*
|
|
324
|
+
* A segmented file is flattened: the returned header is the first segment's,
|
|
325
|
+
* and the events of every segment follow in order. Use
|
|
326
|
+
* {@link readMoqtraceSegments} to see the boundaries — you need them to order
|
|
327
|
+
* events across segments, since `seq` and `timestamp` restart in each.
|
|
328
|
+
*
|
|
329
|
+
* @throws {TruncatedTraceError} if the file stops part-way through an item.
|
|
330
|
+
* The error carries everything that decoded before the cut.
|
|
331
|
+
*/
|
|
332
|
+
declare function readMoqtrace(bytes: Uint8Array, options?: ReadOptions): Trace;
|
|
333
|
+
/**
|
|
334
|
+
* Read a .moqtrace file as its segments, one entry per segment.
|
|
335
|
+
*
|
|
336
|
+
* A non-segmented file yields exactly one entry, whose header carries no
|
|
337
|
+
* `segment` field.
|
|
104
338
|
*/
|
|
105
|
-
declare function
|
|
339
|
+
declare function readMoqtraceSegments(bytes: Uint8Array, options?: ReadOptions): Trace[];
|
|
106
340
|
/**
|
|
107
341
|
* Read only the header from a .moqtrace file (fast metadata peek).
|
|
342
|
+
*
|
|
343
|
+
* In a segmented file this is the first segment's header.
|
|
108
344
|
*/
|
|
109
345
|
declare function readMoqtraceHeader(bytes: Uint8Array): TraceHeader;
|
|
110
346
|
/**
|
|
@@ -115,6 +351,16 @@ interface MoqtraceWriter {
|
|
|
115
351
|
preamble(): Uint8Array;
|
|
116
352
|
/** Encode a single event. Append the returned bytes after the preamble. */
|
|
117
353
|
writeEvent(event: TraceEvent): Uint8Array;
|
|
354
|
+
/**
|
|
355
|
+
* Begin a new segment: returns a fresh preamble to append after the
|
|
356
|
+
* previous segment's last event.
|
|
357
|
+
*
|
|
358
|
+
* The header must carry {@link SegmentInfo} — see
|
|
359
|
+
* {@link writeMoqtraceSegments} for why. It should keep the same
|
|
360
|
+
* `protocol`, `perspective`, `detail` and `sessionId` as the segments
|
|
361
|
+
* before it, and increment `segment.sequence`.
|
|
362
|
+
*/
|
|
363
|
+
startSegment(header: TraceHeader): Uint8Array;
|
|
118
364
|
}
|
|
119
365
|
declare function createMoqtraceWriter(header: TraceHeader): MoqtraceWriter;
|
|
120
366
|
|
|
@@ -154,4 +400,4 @@ interface TraceRecorder {
|
|
|
154
400
|
}
|
|
155
401
|
declare function createRecorder(options: RecorderOptions): TraceRecorder;
|
|
156
402
|
|
|
157
|
-
export { type AnnotationEvent, type ControlMessageEvent, type DetailLevel, type MoqtraceWriter, type ObjectHeaderEvent, type ObjectPayloadEvent, type Perspective, type RecorderOptions, type StateChangeEvent, type StreamClosedEvent, type StreamOpenedEvent, type Trace, type TraceErrorEvent, type TraceEvent, type TraceHeader, type TraceRecorder, createMoqtraceWriter, createRecorder, readMoqtrace, readMoqtraceHeader, traceToJSON, writeMoqtrace };
|
|
403
|
+
export { type AnnotationEvent, type ControlMessageEvent, type DerivationKind, type DetailLevel, type DropPolicy, FORMAT_VERSION, type MoqtraceWriter, type ObjectHeaderEvent, type ObjectPayloadEvent, type PeerConnectedEvent, type PeerDisconnectedEvent, type PeerRole, type Perspective, type ReadOptions, type RecorderOptions, SUPPORTED_VERSIONS, type SamplingInfo, type SegmentInfo, type Side, type StateChangeEvent, type StreamClosedEvent, type StreamOpenedEvent, type SubscriptionDerivationEvent, type SubscriptionRef, TRACE_ID_LENGTH, type Trace, type TraceErrorEvent, type TraceEvent, type TraceHeader, type TraceRecorder, TruncatedTraceError, type UnknownEvent, createMoqtraceWriter, createRecorder, readMoqtrace, readMoqtraceHeader, readMoqtraceSegments, traceToJSON, writeMoqtrace, writeMoqtraceSegments };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,22 +1,98 @@
|
|
|
1
1
|
import { SessionState } from '@moqtap/codec/session';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Any string other than the ones named beside it.
|
|
5
|
+
*
|
|
6
|
+
* Unions built with this keep editor completion for the values this version
|
|
7
|
+
* knows, while still accepting a value it does not. Nothing in the format
|
|
8
|
+
* rejects an unrecognised string: new perspectives, detail levels and drop
|
|
9
|
+
* policies may be added without a version bump, and a reader that refused one
|
|
10
|
+
* would refuse a file it can otherwise read in full.
|
|
11
|
+
*/
|
|
12
|
+
type OtherString = string & Record<never, never>;
|
|
13
|
+
type DetailLevel = 'control' | 'headers' | 'headers+sizes' | 'headers+data' | 'full' | OtherString;
|
|
14
|
+
type Perspective = 'client' | 'server' | 'observer' | 'relay-tap' | OtherString;
|
|
15
|
+
/** Drop strategy a sampled source applied when it could not keep up. */
|
|
16
|
+
type DropPolicy = 'head' | 'tail' | 'sampled' | OtherString;
|
|
17
|
+
/** Length of a trace ID, in bytes. Fixed by the format. */
|
|
18
|
+
declare const TRACE_ID_LENGTH = 16;
|
|
19
|
+
/**
|
|
20
|
+
* Per-segment metadata.
|
|
21
|
+
*
|
|
22
|
+
* Present only in segmented traces. A single-shot file must not carry it —
|
|
23
|
+
* its absence is what tells a reader that sequence numbers and timestamps are
|
|
24
|
+
* file-global rather than segment-local.
|
|
25
|
+
*/
|
|
26
|
+
interface SegmentInfo {
|
|
27
|
+
/** 0-based sequence number of this segment within the stream. */
|
|
28
|
+
readonly sequence: number;
|
|
29
|
+
/** Nominal segment duration in milliseconds. A hint; the real one may differ. */
|
|
30
|
+
readonly durationMs?: number;
|
|
31
|
+
/** Opaque identifier shared by every segment of the same logical stream. */
|
|
32
|
+
readonly streamId?: string;
|
|
33
|
+
/** True if this segment continues a previous one with the same `streamId`. */
|
|
34
|
+
readonly continues?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Sampling and filtering metadata. Present only when events were dropped or
|
|
38
|
+
* filtered at the source; its absence means the trace is complete relative to
|
|
39
|
+
* its detail level.
|
|
40
|
+
*/
|
|
41
|
+
interface SamplingInfo {
|
|
42
|
+
/** Fraction of source events retained, in (0, 1]. */
|
|
43
|
+
readonly effectiveRate?: number;
|
|
44
|
+
/** Per-segment cap that triggered drops, if rate-limited. */
|
|
45
|
+
readonly maxEventsPerSec?: number;
|
|
46
|
+
/** Drop strategy applied when the cap was exceeded. */
|
|
47
|
+
readonly dropPolicy?: DropPolicy;
|
|
48
|
+
/** Cumulative events dropped since `startTime`. */
|
|
49
|
+
readonly droppedTotal?: number;
|
|
50
|
+
/** Events dropped within this segment only. */
|
|
51
|
+
readonly droppedSegment?: number;
|
|
52
|
+
/** Source-side filter rule that selected events. */
|
|
53
|
+
readonly rule?: string;
|
|
54
|
+
/** Language the rule is written in ('prefix', 'glob', 'cel'). */
|
|
55
|
+
readonly ruleLang?: string;
|
|
56
|
+
/** Event type IDs the drop policy was applied to. */
|
|
57
|
+
readonly appliesTo?: number[];
|
|
58
|
+
}
|
|
5
59
|
interface TraceHeader {
|
|
6
60
|
readonly protocol: string;
|
|
7
61
|
readonly perspective: Perspective;
|
|
8
62
|
readonly detail: DetailLevel;
|
|
63
|
+
/**
|
|
64
|
+
* Recording start (Unix epoch milliseconds). In a segmented trace this is
|
|
65
|
+
* the segment's start, not the stream's.
|
|
66
|
+
*/
|
|
9
67
|
readonly startTime: number;
|
|
10
68
|
readonly endTime?: number;
|
|
11
69
|
readonly transport?: string;
|
|
12
70
|
readonly source?: string;
|
|
13
71
|
readonly endpoint?: string;
|
|
14
72
|
readonly sessionId?: string;
|
|
73
|
+
/** Present only when this header begins one segment of a segmented stream. */
|
|
74
|
+
readonly segment?: SegmentInfo;
|
|
75
|
+
/** Present only when events were dropped or filtered at the source. */
|
|
76
|
+
readonly sampling?: SamplingInfo;
|
|
15
77
|
readonly custom?: Record<string, unknown>;
|
|
16
78
|
}
|
|
17
79
|
interface BaseEvent {
|
|
80
|
+
/**
|
|
81
|
+
* Monotonically increasing sequence number. Segment-local in a segmented
|
|
82
|
+
* trace, so ordering across segments is `(segment.sequence, seq)`.
|
|
83
|
+
*/
|
|
18
84
|
readonly seq: number;
|
|
85
|
+
/** Microseconds since the containing segment's `startTime`. */
|
|
19
86
|
readonly timestamp: number;
|
|
87
|
+
/**
|
|
88
|
+
* Which peer this event pertains to.
|
|
89
|
+
*
|
|
90
|
+
* Required when the perspective is `'relay-tap'`, where one trace covers
|
|
91
|
+
* many concurrent sessions; omitted otherwise. The identifier is
|
|
92
|
+
* source-local: the same string in two traces from different sources does
|
|
93
|
+
* not name the same peer.
|
|
94
|
+
*/
|
|
95
|
+
readonly peer?: string;
|
|
20
96
|
}
|
|
21
97
|
interface ControlMessageEvent extends BaseEvent {
|
|
22
98
|
readonly type: 'control';
|
|
@@ -76,7 +152,103 @@ interface AnnotationEvent extends BaseEvent {
|
|
|
76
152
|
readonly label: string;
|
|
77
153
|
readonly data: unknown;
|
|
78
154
|
}
|
|
79
|
-
|
|
155
|
+
/** Role a peer is acting in at connection time. */
|
|
156
|
+
type PeerRole = 'publisher' | 'subscriber' | 'both' | OtherString;
|
|
157
|
+
/**
|
|
158
|
+
* Which end of a relay a peer sits on.
|
|
159
|
+
*
|
|
160
|
+
* This is the direction the connection was made in, not the direction
|
|
161
|
+
* subscriptions flow. For subscription causality see
|
|
162
|
+
* {@link SubscriptionDerivationEvent}.
|
|
163
|
+
*/
|
|
164
|
+
type Side = 'downstream' | 'upstream' | OtherString;
|
|
165
|
+
/**
|
|
166
|
+
* A new peer session was established. Emitted only by `'relay-tap'` sources;
|
|
167
|
+
* the peer it identifies is on the event's `peer` field.
|
|
168
|
+
*/
|
|
169
|
+
interface PeerConnectedEvent extends BaseEvent {
|
|
170
|
+
readonly type: 'peer-connected';
|
|
171
|
+
readonly endpoint?: string;
|
|
172
|
+
readonly transport?: string;
|
|
173
|
+
readonly role?: PeerRole;
|
|
174
|
+
readonly side?: Side;
|
|
175
|
+
}
|
|
176
|
+
/** A peer session ended. Emitted only by `'relay-tap'` sources. */
|
|
177
|
+
interface PeerDisconnectedEvent extends BaseEvent {
|
|
178
|
+
readonly type: 'peer-disconnected';
|
|
179
|
+
readonly errorCode: number;
|
|
180
|
+
readonly reason?: string;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* A `(peer, request id)` pair naming one subscription on one peer.
|
|
184
|
+
*
|
|
185
|
+
* Request ids are unique only within a peer's session, so neither half
|
|
186
|
+
* identifies a subscription on its own.
|
|
187
|
+
*/
|
|
188
|
+
interface SubscriptionRef {
|
|
189
|
+
readonly peer: string;
|
|
190
|
+
readonly requestId: bigint;
|
|
191
|
+
}
|
|
192
|
+
/** How an upstream subscription came to serve a downstream one. */
|
|
193
|
+
type DerivationKind = 'created' | 'shared' | OtherString;
|
|
194
|
+
/**
|
|
195
|
+
* An upstream subscription was created or extended in causal response to
|
|
196
|
+
* downstream ones. Emitted only by `'relay-tap'` sources.
|
|
197
|
+
*
|
|
198
|
+
* This is the primitive multi-hop correlation is built from: a collector
|
|
199
|
+
* reconstructs an end-to-end tree from these links plus the trace ids
|
|
200
|
+
* propagated along them.
|
|
201
|
+
*
|
|
202
|
+
* The four timestamps share the `timestamp` field's timebase — the emitting
|
|
203
|
+
* source's own clock. Differences between them are meaningful with no
|
|
204
|
+
* cross-hop clock agreement, which is the point: they measure how long one
|
|
205
|
+
* hop took. Never subtract one source's timestamp from another's.
|
|
206
|
+
*
|
|
207
|
+
* A source emits the event as soon as the downstream SUBSCRIBE arrives,
|
|
208
|
+
* carrying whichever timestamps it has, and may emit it again for the same
|
|
209
|
+
* pair as the rest arrive. Treat a later event for the same pair as an update
|
|
210
|
+
* to the earlier one, not as a second derivation.
|
|
211
|
+
*/
|
|
212
|
+
interface SubscriptionDerivationEvent extends BaseEvent {
|
|
213
|
+
readonly type: 'subscription-derivation';
|
|
214
|
+
readonly upstream: SubscriptionRef;
|
|
215
|
+
/** Every downstream subscription currently served by `upstream`. */
|
|
216
|
+
readonly downstream: SubscriptionRef[];
|
|
217
|
+
readonly kind: DerivationKind;
|
|
218
|
+
/**
|
|
219
|
+
* Trace id propagated along the subscription chain: exactly
|
|
220
|
+
* {@link TRACE_ID_LENGTH} raw bytes, never hex- or base64-encoded, so two
|
|
221
|
+
* implementations produce identical values for the same chain.
|
|
222
|
+
*/
|
|
223
|
+
readonly traceId?: Uint8Array;
|
|
224
|
+
/** Track namespace the subscription targets, one entry per field. */
|
|
225
|
+
readonly namespace?: Uint8Array[];
|
|
226
|
+
readonly trackName?: Uint8Array;
|
|
227
|
+
/** When the downstream SUBSCRIBE was received. */
|
|
228
|
+
readonly tDownstreamReceived?: number;
|
|
229
|
+
/** When the upstream SUBSCRIBE was sent. Absent for a terminal subscription. */
|
|
230
|
+
readonly tUpstreamSent?: number;
|
|
231
|
+
/** When SUBSCRIBE_OK arrived from upstream. */
|
|
232
|
+
readonly tUpstreamOkReceived?: number;
|
|
233
|
+
/** When SUBSCRIBE_OK was sent downstream. */
|
|
234
|
+
readonly tDownstreamOkSent?: number;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* An event whose type this version of the package does not know.
|
|
238
|
+
*
|
|
239
|
+
* New event types may be added without a format version bump, so a reader
|
|
240
|
+
* that dropped or relabelled them would make one tool's ignorance permanent
|
|
241
|
+
* for everything downstream of it. The fields are kept verbatim, so an
|
|
242
|
+
* unknown event survives a read-modify-write round trip intact.
|
|
243
|
+
*/
|
|
244
|
+
interface UnknownEvent extends BaseEvent {
|
|
245
|
+
readonly type: 'unknown';
|
|
246
|
+
/** The `"e"` discriminant that was read. */
|
|
247
|
+
readonly eventType: number;
|
|
248
|
+
/** Every key from the event map other than `n`, `t`, `p` and `e`. */
|
|
249
|
+
readonly fields: Record<string, unknown>;
|
|
250
|
+
}
|
|
251
|
+
type TraceEvent = ControlMessageEvent | StreamOpenedEvent | StreamClosedEvent | ObjectHeaderEvent | ObjectPayloadEvent | StateChangeEvent | TraceErrorEvent | AnnotationEvent | PeerConnectedEvent | PeerDisconnectedEvent | SubscriptionDerivationEvent | UnknownEvent;
|
|
80
252
|
interface Trace {
|
|
81
253
|
readonly header: TraceHeader;
|
|
82
254
|
readonly events: TraceEvent[];
|
|
@@ -95,16 +267,80 @@ interface RecorderOptions {
|
|
|
95
267
|
readonly messageTypeId?: (name: string) => number;
|
|
96
268
|
}
|
|
97
269
|
|
|
270
|
+
/** Format version this package writes. */
|
|
271
|
+
declare const FORMAT_VERSION = 2;
|
|
272
|
+
/**
|
|
273
|
+
* Format versions this package reads.
|
|
274
|
+
*
|
|
275
|
+
* Version 1 is a non-segmented version 2 trace carrying none of the keys
|
|
276
|
+
* version 2 added, so reading it costs nothing and keeps every capture taken
|
|
277
|
+
* before the bump openable.
|
|
278
|
+
*/
|
|
279
|
+
declare const SUPPORTED_VERSIONS: readonly number[];
|
|
280
|
+
/** The stream ended part-way through an item — the file was truncated. */
|
|
281
|
+
declare class TruncatedTraceError extends Error {
|
|
282
|
+
/** Byte offset at which the incomplete item begins. */
|
|
283
|
+
readonly offset: number;
|
|
284
|
+
/**
|
|
285
|
+
* Everything that decoded before the cut, one entry per segment. A
|
|
286
|
+
* truncated trace is still evidence, and the events before the cut are
|
|
287
|
+
* exactly as valid as they were.
|
|
288
|
+
*/
|
|
289
|
+
readonly segments: Trace[];
|
|
290
|
+
constructor(offset: number, segments: Trace[]);
|
|
291
|
+
/** What decoded, flattened as {@link readMoqtrace} would return it. */
|
|
292
|
+
get trace(): Trace | undefined;
|
|
293
|
+
}
|
|
294
|
+
/** Options shared by the readers. */
|
|
295
|
+
interface ReadOptions {
|
|
296
|
+
/**
|
|
297
|
+
* On a truncated or corrupt region, skip forward to the next segment and
|
|
298
|
+
* carry on instead of throwing.
|
|
299
|
+
*
|
|
300
|
+
* This is the recovery segmentation exists to offer: one damaged segment
|
|
301
|
+
* costs that segment rather than the rest of the capture. It can only help
|
|
302
|
+
* a segmented trace — there is nothing to resynchronize to in a single
|
|
303
|
+
* segment, so a damaged one still ends the read, just without an error.
|
|
304
|
+
*/
|
|
305
|
+
readonly recover?: boolean;
|
|
306
|
+
}
|
|
98
307
|
/**
|
|
99
308
|
* Write a complete trace to .moqtrace binary format.
|
|
100
309
|
*/
|
|
101
310
|
declare function writeMoqtrace(trace: Trace): Uint8Array;
|
|
311
|
+
/**
|
|
312
|
+
* Write several segments back to back as one segmented `.moqtrace` stream.
|
|
313
|
+
*
|
|
314
|
+
* Every segment header must carry {@link SegmentInfo}. That field is what
|
|
315
|
+
* tells a reader the sequence numbers and timestamps it is about to see
|
|
316
|
+
* restart at zero; a segmented stream written without it reads as a run of
|
|
317
|
+
* complete files whose timeline jumps backwards at every boundary, and
|
|
318
|
+
* nothing downstream can detect that it happened.
|
|
319
|
+
*/
|
|
320
|
+
declare function writeMoqtraceSegments(segments: Trace[]): Uint8Array;
|
|
102
321
|
/**
|
|
103
322
|
* Read a complete .moqtrace file from bytes.
|
|
323
|
+
*
|
|
324
|
+
* A segmented file is flattened: the returned header is the first segment's,
|
|
325
|
+
* and the events of every segment follow in order. Use
|
|
326
|
+
* {@link readMoqtraceSegments} to see the boundaries — you need them to order
|
|
327
|
+
* events across segments, since `seq` and `timestamp` restart in each.
|
|
328
|
+
*
|
|
329
|
+
* @throws {TruncatedTraceError} if the file stops part-way through an item.
|
|
330
|
+
* The error carries everything that decoded before the cut.
|
|
331
|
+
*/
|
|
332
|
+
declare function readMoqtrace(bytes: Uint8Array, options?: ReadOptions): Trace;
|
|
333
|
+
/**
|
|
334
|
+
* Read a .moqtrace file as its segments, one entry per segment.
|
|
335
|
+
*
|
|
336
|
+
* A non-segmented file yields exactly one entry, whose header carries no
|
|
337
|
+
* `segment` field.
|
|
104
338
|
*/
|
|
105
|
-
declare function
|
|
339
|
+
declare function readMoqtraceSegments(bytes: Uint8Array, options?: ReadOptions): Trace[];
|
|
106
340
|
/**
|
|
107
341
|
* Read only the header from a .moqtrace file (fast metadata peek).
|
|
342
|
+
*
|
|
343
|
+
* In a segmented file this is the first segment's header.
|
|
108
344
|
*/
|
|
109
345
|
declare function readMoqtraceHeader(bytes: Uint8Array): TraceHeader;
|
|
110
346
|
/**
|
|
@@ -115,6 +351,16 @@ interface MoqtraceWriter {
|
|
|
115
351
|
preamble(): Uint8Array;
|
|
116
352
|
/** Encode a single event. Append the returned bytes after the preamble. */
|
|
117
353
|
writeEvent(event: TraceEvent): Uint8Array;
|
|
354
|
+
/**
|
|
355
|
+
* Begin a new segment: returns a fresh preamble to append after the
|
|
356
|
+
* previous segment's last event.
|
|
357
|
+
*
|
|
358
|
+
* The header must carry {@link SegmentInfo} — see
|
|
359
|
+
* {@link writeMoqtraceSegments} for why. It should keep the same
|
|
360
|
+
* `protocol`, `perspective`, `detail` and `sessionId` as the segments
|
|
361
|
+
* before it, and increment `segment.sequence`.
|
|
362
|
+
*/
|
|
363
|
+
startSegment(header: TraceHeader): Uint8Array;
|
|
118
364
|
}
|
|
119
365
|
declare function createMoqtraceWriter(header: TraceHeader): MoqtraceWriter;
|
|
120
366
|
|
|
@@ -154,4 +400,4 @@ interface TraceRecorder {
|
|
|
154
400
|
}
|
|
155
401
|
declare function createRecorder(options: RecorderOptions): TraceRecorder;
|
|
156
402
|
|
|
157
|
-
export { type AnnotationEvent, type ControlMessageEvent, type DetailLevel, type MoqtraceWriter, type ObjectHeaderEvent, type ObjectPayloadEvent, type Perspective, type RecorderOptions, type StateChangeEvent, type StreamClosedEvent, type StreamOpenedEvent, type Trace, type TraceErrorEvent, type TraceEvent, type TraceHeader, type TraceRecorder, createMoqtraceWriter, createRecorder, readMoqtrace, readMoqtraceHeader, traceToJSON, writeMoqtrace };
|
|
403
|
+
export { type AnnotationEvent, type ControlMessageEvent, type DerivationKind, type DetailLevel, type DropPolicy, FORMAT_VERSION, type MoqtraceWriter, type ObjectHeaderEvent, type ObjectPayloadEvent, type PeerConnectedEvent, type PeerDisconnectedEvent, type PeerRole, type Perspective, type ReadOptions, type RecorderOptions, SUPPORTED_VERSIONS, type SamplingInfo, type SegmentInfo, type Side, type StateChangeEvent, type StreamClosedEvent, type StreamOpenedEvent, type SubscriptionDerivationEvent, type SubscriptionRef, TRACE_ID_LENGTH, type Trace, type TraceErrorEvent, type TraceEvent, type TraceHeader, type TraceRecorder, TruncatedTraceError, type UnknownEvent, createMoqtraceWriter, createRecorder, readMoqtrace, readMoqtraceHeader, readMoqtraceSegments, traceToJSON, writeMoqtrace, writeMoqtraceSegments };
|