@moqtap/trace 0.1.0 → 0.1.2
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/LICENSE +21 -21
- package/README.md +33 -33
- package/dist/index.cjs +26 -22
- package/dist/index.d.cts +13 -11
- package/dist/index.d.ts +13 -11
- package/dist/index.js +27 -23
- package/package.json +3 -3
- package/src/binary.ts +117 -120
- package/src/index.ts +19 -20
- package/src/json.ts +16 -10
- package/src/recorder.ts +96 -79
- package/src/types.ts +60 -60
package/src/recorder.ts
CHANGED
|
@@ -1,95 +1,110 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
4
|
-
Trace,
|
|
5
|
-
TraceHeader,
|
|
6
|
-
TraceEvent,
|
|
7
|
-
RecorderOptions,
|
|
8
|
-
DetailLevel,
|
|
9
|
-
} from './types.js';
|
|
1
|
+
import type { SessionState } from '@moqtap/codec/session'
|
|
2
|
+
import type { DetailLevel, RecorderOptions, Trace, TraceEvent, TraceHeader } from './types.js'
|
|
10
3
|
|
|
11
4
|
export interface TraceRecorder {
|
|
12
5
|
/** Wrap a SessionState to auto-record control messages and state changes. */
|
|
13
|
-
wrapSession
|
|
6
|
+
wrapSession<M extends { type: string }, T extends string>(
|
|
7
|
+
session: SessionState<M, T>,
|
|
8
|
+
): SessionState<M, T>
|
|
14
9
|
|
|
15
10
|
/** Record an arbitrary event manually. */
|
|
16
|
-
record(event: TraceEvent): void
|
|
11
|
+
record(event: TraceEvent): void
|
|
17
12
|
|
|
18
13
|
/** Record a stream-opened event. Ignored at 'control' detail level. */
|
|
19
|
-
recordStreamOpened(streamId: bigint, direction: 0 | 1, streamType: 0 | 1 | 2): void
|
|
14
|
+
recordStreamOpened(streamId: bigint, direction: 0 | 1, streamType: 0 | 1 | 2): void
|
|
20
15
|
|
|
21
16
|
/** Record a stream-closed event. Ignored at 'control' detail level. */
|
|
22
|
-
recordStreamClosed(streamId: bigint, errorCode?: number): void
|
|
17
|
+
recordStreamClosed(streamId: bigint, errorCode?: number): void
|
|
23
18
|
|
|
24
19
|
/** Record an object header event. Ignored below 'headers' detail level. */
|
|
25
20
|
recordObjectHeader(
|
|
26
|
-
streamId: bigint,
|
|
27
|
-
|
|
28
|
-
|
|
21
|
+
streamId: bigint,
|
|
22
|
+
groupId: bigint,
|
|
23
|
+
objectId: bigint,
|
|
24
|
+
publisherPriority: number,
|
|
25
|
+
objectStatus: number,
|
|
26
|
+
): void
|
|
29
27
|
|
|
30
28
|
/** Record an object payload event. Ignored below 'headers+sizes' detail level. */
|
|
31
29
|
recordObjectPayload(
|
|
32
|
-
streamId: bigint,
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
streamId: bigint,
|
|
31
|
+
groupId: bigint,
|
|
32
|
+
objectId: bigint,
|
|
33
|
+
size: number,
|
|
34
|
+
payload?: Uint8Array,
|
|
35
|
+
): void
|
|
35
36
|
|
|
36
37
|
/** Record a protocol error. */
|
|
37
|
-
recordError(errorCode: number, reason: string): void
|
|
38
|
+
recordError(errorCode: number, reason: string): void
|
|
38
39
|
|
|
39
40
|
/** Record a user-defined annotation. */
|
|
40
|
-
annotate(label: string, data?: unknown): void
|
|
41
|
+
annotate(label: string, data?: unknown): void
|
|
41
42
|
|
|
42
43
|
/** Finalize the trace. Stops recording and returns the trace. */
|
|
43
|
-
finalize(): Trace
|
|
44
|
+
finalize(): Trace
|
|
44
45
|
|
|
45
46
|
/** Whether the recorder is still accepting events. */
|
|
46
|
-
readonly recording: boolean
|
|
47
|
+
readonly recording: boolean
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
const DETAIL_RANK: Record<DetailLevel, number> = {
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
control: 0,
|
|
52
|
+
headers: 1,
|
|
52
53
|
'headers+sizes': 2,
|
|
53
54
|
'headers+data': 3,
|
|
54
|
-
|
|
55
|
-
}
|
|
55
|
+
full: 4,
|
|
56
|
+
}
|
|
56
57
|
|
|
57
58
|
export function createRecorder(options: RecorderOptions): TraceRecorder {
|
|
58
|
-
const detail = options.detail
|
|
59
|
-
const detailRank = DETAIL_RANK[detail]
|
|
60
|
-
const maxEvents = options.maxEvents ?? 100_000
|
|
61
|
-
const clock = options.clock ?? (() => Math.round(performance.now() * 1000))
|
|
62
|
-
const messageTypeId = options.messageTypeId ?? (() => 0)
|
|
59
|
+
const detail = options.detail
|
|
60
|
+
const detailRank = DETAIL_RANK[detail]
|
|
61
|
+
const maxEvents = options.maxEvents ?? 100_000
|
|
62
|
+
const clock = options.clock ?? (() => Math.round(performance.now() * 1000))
|
|
63
|
+
const messageTypeId = options.messageTypeId ?? (() => 0)
|
|
63
64
|
|
|
64
|
-
const events: TraceEvent[] = []
|
|
65
|
-
let _recording = true
|
|
66
|
-
let _seq = 0
|
|
67
|
-
const startTime = Date.now()
|
|
65
|
+
const events: TraceEvent[] = []
|
|
66
|
+
let _recording = true
|
|
67
|
+
let _seq = 0
|
|
68
|
+
const startTime = Date.now()
|
|
68
69
|
|
|
69
70
|
function addEvent(event: TraceEvent): void {
|
|
70
|
-
if (!_recording) return
|
|
71
|
+
if (!_recording) return
|
|
71
72
|
if (events.length >= maxEvents) {
|
|
72
|
-
events.shift()
|
|
73
|
+
events.shift()
|
|
73
74
|
}
|
|
74
|
-
events.push(event)
|
|
75
|
+
events.push(event)
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
function nextSeq(): number {
|
|
78
|
-
return _seq
|
|
79
|
+
return _seq++
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
function wrapSession
|
|
82
|
+
function wrapSession<M extends { type: string }, T extends string>(
|
|
83
|
+
session: SessionState<M, T>,
|
|
84
|
+
): SessionState<M, T> {
|
|
82
85
|
return {
|
|
83
|
-
get phase() {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
get
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
get phase() {
|
|
87
|
+
return session.phase
|
|
88
|
+
},
|
|
89
|
+
get role() {
|
|
90
|
+
return session.role
|
|
91
|
+
},
|
|
92
|
+
get subscriptions() {
|
|
93
|
+
return session.subscriptions
|
|
94
|
+
},
|
|
95
|
+
get announces() {
|
|
96
|
+
return session.announces
|
|
97
|
+
},
|
|
98
|
+
get legalOutgoing() {
|
|
99
|
+
return session.legalOutgoing
|
|
100
|
+
},
|
|
101
|
+
get legalIncoming() {
|
|
102
|
+
return session.legalIncoming
|
|
103
|
+
},
|
|
89
104
|
|
|
90
|
-
receive(message:
|
|
91
|
-
const prevPhase = session.phase
|
|
92
|
-
const result = session.receive(message)
|
|
105
|
+
receive(message: M) {
|
|
106
|
+
const prevPhase = session.phase
|
|
107
|
+
const result = session.receive(message)
|
|
93
108
|
|
|
94
109
|
addEvent({
|
|
95
110
|
type: 'control',
|
|
@@ -98,7 +113,7 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
|
|
|
98
113
|
direction: 1, // rx
|
|
99
114
|
messageType: messageTypeId(message.type),
|
|
100
115
|
message: message as unknown as Record<string, unknown>,
|
|
101
|
-
})
|
|
116
|
+
})
|
|
102
117
|
|
|
103
118
|
if (result.ok && result.phase !== prevPhase) {
|
|
104
119
|
addEvent({
|
|
@@ -107,19 +122,19 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
|
|
|
107
122
|
timestamp: clock(),
|
|
108
123
|
from: prevPhase,
|
|
109
124
|
to: result.phase,
|
|
110
|
-
})
|
|
125
|
+
})
|
|
111
126
|
}
|
|
112
127
|
|
|
113
|
-
return result
|
|
128
|
+
return result
|
|
114
129
|
},
|
|
115
130
|
|
|
116
|
-
validateOutgoing(message:
|
|
117
|
-
return session.validateOutgoing(message)
|
|
131
|
+
validateOutgoing(message: M) {
|
|
132
|
+
return session.validateOutgoing(message)
|
|
118
133
|
},
|
|
119
134
|
|
|
120
|
-
send(message:
|
|
121
|
-
const prevPhase = session.phase
|
|
122
|
-
const result = session.send(message)
|
|
135
|
+
send(message: M) {
|
|
136
|
+
const prevPhase = session.phase
|
|
137
|
+
const result = session.send(message)
|
|
123
138
|
|
|
124
139
|
addEvent({
|
|
125
140
|
type: 'control',
|
|
@@ -128,7 +143,7 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
|
|
|
128
143
|
direction: 0, // tx
|
|
129
144
|
messageType: messageTypeId(message.type),
|
|
130
145
|
message: message as unknown as Record<string, unknown>,
|
|
131
|
-
})
|
|
146
|
+
})
|
|
132
147
|
|
|
133
148
|
if (result.ok && result.phase !== prevPhase) {
|
|
134
149
|
addEvent({
|
|
@@ -137,16 +152,16 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
|
|
|
137
152
|
timestamp: clock(),
|
|
138
153
|
from: prevPhase,
|
|
139
154
|
to: result.phase,
|
|
140
|
-
})
|
|
155
|
+
})
|
|
141
156
|
}
|
|
142
157
|
|
|
143
|
-
return result
|
|
158
|
+
return result
|
|
144
159
|
},
|
|
145
160
|
|
|
146
161
|
reset() {
|
|
147
|
-
session.reset()
|
|
162
|
+
session.reset()
|
|
148
163
|
},
|
|
149
|
-
}
|
|
164
|
+
}
|
|
150
165
|
}
|
|
151
166
|
|
|
152
167
|
return {
|
|
@@ -155,7 +170,7 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
|
|
|
155
170
|
record: addEvent,
|
|
156
171
|
|
|
157
172
|
recordStreamOpened(streamId, direction, streamType) {
|
|
158
|
-
if (detailRank < DETAIL_RANK
|
|
173
|
+
if (detailRank < DETAIL_RANK.headers) return
|
|
159
174
|
addEvent({
|
|
160
175
|
type: 'stream-opened',
|
|
161
176
|
seq: nextSeq(),
|
|
@@ -163,22 +178,22 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
|
|
|
163
178
|
streamId,
|
|
164
179
|
direction,
|
|
165
180
|
streamType,
|
|
166
|
-
})
|
|
181
|
+
})
|
|
167
182
|
},
|
|
168
183
|
|
|
169
184
|
recordStreamClosed(streamId, errorCode = 0) {
|
|
170
|
-
if (detailRank < DETAIL_RANK
|
|
185
|
+
if (detailRank < DETAIL_RANK.headers) return
|
|
171
186
|
addEvent({
|
|
172
187
|
type: 'stream-closed',
|
|
173
188
|
seq: nextSeq(),
|
|
174
189
|
timestamp: clock(),
|
|
175
190
|
streamId,
|
|
176
191
|
errorCode,
|
|
177
|
-
})
|
|
192
|
+
})
|
|
178
193
|
},
|
|
179
194
|
|
|
180
195
|
recordObjectHeader(streamId, groupId, objectId, publisherPriority, objectStatus) {
|
|
181
|
-
if (detailRank < DETAIL_RANK
|
|
196
|
+
if (detailRank < DETAIL_RANK.headers) return
|
|
182
197
|
addEvent({
|
|
183
198
|
type: 'object-header',
|
|
184
199
|
seq: nextSeq(),
|
|
@@ -188,11 +203,11 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
|
|
|
188
203
|
objectId,
|
|
189
204
|
publisherPriority,
|
|
190
205
|
objectStatus,
|
|
191
|
-
})
|
|
206
|
+
})
|
|
192
207
|
},
|
|
193
208
|
|
|
194
209
|
recordObjectPayload(streamId, groupId, objectId, size, payload) {
|
|
195
|
-
if (detailRank < DETAIL_RANK['headers+sizes']) return
|
|
210
|
+
if (detailRank < DETAIL_RANK['headers+sizes']) return
|
|
196
211
|
const event: TraceEvent = {
|
|
197
212
|
type: 'object-payload',
|
|
198
213
|
seq: nextSeq(),
|
|
@@ -202,8 +217,8 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
|
|
|
202
217
|
objectId,
|
|
203
218
|
size,
|
|
204
219
|
...(detailRank >= DETAIL_RANK['headers+data'] && payload != null ? { payload } : {}),
|
|
205
|
-
}
|
|
206
|
-
addEvent(event)
|
|
220
|
+
}
|
|
221
|
+
addEvent(event)
|
|
207
222
|
},
|
|
208
223
|
|
|
209
224
|
recordError(errorCode, reason) {
|
|
@@ -213,7 +228,7 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
|
|
|
213
228
|
timestamp: clock(),
|
|
214
229
|
errorCode,
|
|
215
230
|
reason,
|
|
216
|
-
})
|
|
231
|
+
})
|
|
217
232
|
},
|
|
218
233
|
|
|
219
234
|
annotate(label, data) {
|
|
@@ -223,11 +238,11 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
|
|
|
223
238
|
timestamp: clock(),
|
|
224
239
|
label,
|
|
225
240
|
data,
|
|
226
|
-
})
|
|
241
|
+
})
|
|
227
242
|
},
|
|
228
243
|
|
|
229
244
|
finalize(): Trace {
|
|
230
|
-
_recording = false
|
|
245
|
+
_recording = false
|
|
231
246
|
const header: TraceHeader = {
|
|
232
247
|
protocol: options.protocol,
|
|
233
248
|
perspective: options.perspective,
|
|
@@ -238,10 +253,12 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
|
|
|
238
253
|
...(options.source != null ? { source: options.source } : {}),
|
|
239
254
|
...(options.endpoint != null ? { endpoint: options.endpoint } : {}),
|
|
240
255
|
...(options.sessionId != null ? { sessionId: options.sessionId } : {}),
|
|
241
|
-
}
|
|
242
|
-
return { header, events: [...events] }
|
|
256
|
+
}
|
|
257
|
+
return { header, events: [...events] }
|
|
243
258
|
},
|
|
244
259
|
|
|
245
|
-
get recording() {
|
|
246
|
-
|
|
260
|
+
get recording() {
|
|
261
|
+
return _recording
|
|
262
|
+
},
|
|
263
|
+
}
|
|
247
264
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
export type DetailLevel = 'control' | 'headers' | 'headers+sizes' | 'headers+data' | 'full'
|
|
2
|
-
export type Perspective = 'client' | 'server' | 'observer'
|
|
1
|
+
export type DetailLevel = 'control' | 'headers' | 'headers+sizes' | 'headers+data' | 'full'
|
|
2
|
+
export type Perspective = 'client' | 'server' | 'observer'
|
|
3
3
|
|
|
4
4
|
export interface TraceHeader {
|
|
5
|
-
readonly protocol: string
|
|
6
|
-
readonly perspective: Perspective
|
|
7
|
-
readonly detail: DetailLevel
|
|
8
|
-
readonly startTime: number
|
|
9
|
-
readonly endTime?: number
|
|
10
|
-
readonly transport?: string
|
|
11
|
-
readonly source?: string
|
|
12
|
-
readonly endpoint?: string
|
|
13
|
-
readonly sessionId?: string
|
|
14
|
-
readonly custom?: Record<string, unknown
|
|
5
|
+
readonly protocol: string
|
|
6
|
+
readonly perspective: Perspective
|
|
7
|
+
readonly detail: DetailLevel
|
|
8
|
+
readonly startTime: number
|
|
9
|
+
readonly endTime?: number
|
|
10
|
+
readonly transport?: string
|
|
11
|
+
readonly source?: string
|
|
12
|
+
readonly endpoint?: string
|
|
13
|
+
readonly sessionId?: string
|
|
14
|
+
readonly custom?: Record<string, unknown>
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
interface BaseEvent {
|
|
18
|
-
readonly seq: number
|
|
19
|
-
readonly timestamp: number
|
|
18
|
+
readonly seq: number
|
|
19
|
+
readonly timestamp: number
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export interface ControlMessageEvent extends BaseEvent {
|
|
23
|
-
readonly type: 'control'
|
|
24
|
-
readonly direction: 0 | 1
|
|
25
|
-
readonly messageType: number
|
|
26
|
-
readonly message: Record<string, unknown
|
|
27
|
-
readonly raw?: Uint8Array
|
|
23
|
+
readonly type: 'control'
|
|
24
|
+
readonly direction: 0 | 1
|
|
25
|
+
readonly messageType: number
|
|
26
|
+
readonly message: Record<string, unknown>
|
|
27
|
+
readonly raw?: Uint8Array
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export interface StreamOpenedEvent extends BaseEvent {
|
|
31
|
-
readonly type: 'stream-opened'
|
|
32
|
-
readonly streamId: bigint
|
|
33
|
-
readonly direction: 0 | 1
|
|
34
|
-
readonly streamType: 0 | 1 | 2
|
|
31
|
+
readonly type: 'stream-opened'
|
|
32
|
+
readonly streamId: bigint
|
|
33
|
+
readonly direction: 0 | 1
|
|
34
|
+
readonly streamType: 0 | 1 | 2
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
export interface StreamClosedEvent extends BaseEvent {
|
|
38
|
-
readonly type: 'stream-closed'
|
|
39
|
-
readonly streamId: bigint
|
|
40
|
-
readonly errorCode: number
|
|
38
|
+
readonly type: 'stream-closed'
|
|
39
|
+
readonly streamId: bigint
|
|
40
|
+
readonly errorCode: number
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
export interface ObjectHeaderEvent extends BaseEvent {
|
|
44
|
-
readonly type: 'object-header'
|
|
45
|
-
readonly streamId: bigint
|
|
46
|
-
readonly groupId: bigint
|
|
47
|
-
readonly objectId: bigint
|
|
48
|
-
readonly publisherPriority: number
|
|
49
|
-
readonly objectStatus: number
|
|
44
|
+
readonly type: 'object-header'
|
|
45
|
+
readonly streamId: bigint
|
|
46
|
+
readonly groupId: bigint
|
|
47
|
+
readonly objectId: bigint
|
|
48
|
+
readonly publisherPriority: number
|
|
49
|
+
readonly objectStatus: number
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
export interface ObjectPayloadEvent extends BaseEvent {
|
|
53
|
-
readonly type: 'object-payload'
|
|
54
|
-
readonly streamId: bigint
|
|
55
|
-
readonly groupId: bigint
|
|
56
|
-
readonly objectId: bigint
|
|
57
|
-
readonly size: number
|
|
58
|
-
readonly payload?: Uint8Array
|
|
53
|
+
readonly type: 'object-payload'
|
|
54
|
+
readonly streamId: bigint
|
|
55
|
+
readonly groupId: bigint
|
|
56
|
+
readonly objectId: bigint
|
|
57
|
+
readonly size: number
|
|
58
|
+
readonly payload?: Uint8Array
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
export interface StateChangeEvent extends BaseEvent {
|
|
62
|
-
readonly type: 'state-change'
|
|
63
|
-
readonly from: string
|
|
64
|
-
readonly to: string
|
|
62
|
+
readonly type: 'state-change'
|
|
63
|
+
readonly from: string
|
|
64
|
+
readonly to: string
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
export interface TraceErrorEvent extends BaseEvent {
|
|
68
|
-
readonly type: 'error'
|
|
69
|
-
readonly errorCode: number
|
|
70
|
-
readonly reason: string
|
|
68
|
+
readonly type: 'error'
|
|
69
|
+
readonly errorCode: number
|
|
70
|
+
readonly reason: string
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
export interface AnnotationEvent extends BaseEvent {
|
|
74
|
-
readonly type: 'annotation'
|
|
75
|
-
readonly label: string
|
|
76
|
-
readonly data: unknown
|
|
74
|
+
readonly type: 'annotation'
|
|
75
|
+
readonly label: string
|
|
76
|
+
readonly data: unknown
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
export type TraceEvent =
|
|
@@ -84,23 +84,23 @@ export type TraceEvent =
|
|
|
84
84
|
| ObjectPayloadEvent
|
|
85
85
|
| StateChangeEvent
|
|
86
86
|
| TraceErrorEvent
|
|
87
|
-
| AnnotationEvent
|
|
87
|
+
| AnnotationEvent
|
|
88
88
|
|
|
89
89
|
export interface Trace {
|
|
90
|
-
readonly header: TraceHeader
|
|
91
|
-
readonly events: TraceEvent[]
|
|
90
|
+
readonly header: TraceHeader
|
|
91
|
+
readonly events: TraceEvent[]
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
export interface RecorderOptions {
|
|
95
|
-
readonly detail: DetailLevel
|
|
96
|
-
readonly protocol: string
|
|
97
|
-
readonly perspective: Perspective
|
|
98
|
-
readonly transport?: string
|
|
99
|
-
readonly source?: string
|
|
100
|
-
readonly endpoint?: string
|
|
101
|
-
readonly sessionId?: string
|
|
102
|
-
readonly maxEvents?: number
|
|
103
|
-
readonly clock?: () => number
|
|
95
|
+
readonly detail: DetailLevel
|
|
96
|
+
readonly protocol: string
|
|
97
|
+
readonly perspective: Perspective
|
|
98
|
+
readonly transport?: string
|
|
99
|
+
readonly source?: string
|
|
100
|
+
readonly endpoint?: string
|
|
101
|
+
readonly sessionId?: string
|
|
102
|
+
readonly maxEvents?: number
|
|
103
|
+
readonly clock?: () => number
|
|
104
104
|
/** Map message type name (e.g. 'subscribe') to wire ID (e.g. 0x03). Required for session-layer recording. */
|
|
105
|
-
readonly messageTypeId?: (name: string) => number
|
|
105
|
+
readonly messageTypeId?: (name: string) => number
|
|
106
106
|
}
|