@moqtap/trace 0.1.1 → 0.1.3
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 +13 -7
- package/dist/index.d.cts +13 -11
- package/dist/index.d.ts +13 -11
- package/dist/index.js +7 -1
- package/package.json +3 -3
- package/src/binary.ts +170 -153
- package/src/index.ts +6 -6
- package/src/json.ts +7 -7
- package/src/recorder.ts +82 -79
- package/src/types.ts +60 -60
package/src/recorder.ts
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import type {
|
|
3
|
-
import type { DetailLevel, RecorderOptions, Trace, TraceEvent, TraceHeader } from "./types.js";
|
|
1
|
+
import type { SessionState } from '@moqtap/codec/session'
|
|
2
|
+
import type { DetailLevel, RecorderOptions, Trace, TraceEvent, TraceHeader } from './types.js'
|
|
4
3
|
|
|
5
4
|
export interface TraceRecorder {
|
|
6
5
|
/** Wrap a SessionState to auto-record control messages and state changes. */
|
|
7
|
-
wrapSession
|
|
6
|
+
wrapSession<M extends { type: string }, T extends string>(
|
|
7
|
+
session: SessionState<M, T>,
|
|
8
|
+
): SessionState<M, T>
|
|
8
9
|
|
|
9
10
|
/** Record an arbitrary event manually. */
|
|
10
|
-
record(event: TraceEvent): void
|
|
11
|
+
record(event: TraceEvent): void
|
|
11
12
|
|
|
12
13
|
/** Record a stream-opened event. Ignored at 'control' detail level. */
|
|
13
|
-
recordStreamOpened(streamId: bigint, direction: 0 | 1, streamType: 0 | 1 | 2): void
|
|
14
|
+
recordStreamOpened(streamId: bigint, direction: 0 | 1, streamType: 0 | 1 | 2): void
|
|
14
15
|
|
|
15
16
|
/** Record a stream-closed event. Ignored at 'control' detail level. */
|
|
16
|
-
recordStreamClosed(streamId: bigint, errorCode?: number): void
|
|
17
|
+
recordStreamClosed(streamId: bigint, errorCode?: number): void
|
|
17
18
|
|
|
18
19
|
/** Record an object header event. Ignored below 'headers' detail level. */
|
|
19
20
|
recordObjectHeader(
|
|
@@ -22,7 +23,7 @@ export interface TraceRecorder {
|
|
|
22
23
|
objectId: bigint,
|
|
23
24
|
publisherPriority: number,
|
|
24
25
|
objectStatus: number,
|
|
25
|
-
): void
|
|
26
|
+
): void
|
|
26
27
|
|
|
27
28
|
/** Record an object payload event. Ignored below 'headers+sizes' detail level. */
|
|
28
29
|
recordObjectPayload(
|
|
@@ -31,134 +32,136 @@ export interface TraceRecorder {
|
|
|
31
32
|
objectId: bigint,
|
|
32
33
|
size: number,
|
|
33
34
|
payload?: Uint8Array,
|
|
34
|
-
): void
|
|
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
|
control: 0,
|
|
51
52
|
headers: 1,
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
'headers+sizes': 2,
|
|
54
|
+
'headers+data': 3,
|
|
54
55
|
full: 4,
|
|
55
|
-
}
|
|
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
86
|
get phase() {
|
|
84
|
-
return session.phase
|
|
87
|
+
return session.phase
|
|
85
88
|
},
|
|
86
89
|
get role() {
|
|
87
|
-
return session.role
|
|
90
|
+
return session.role
|
|
88
91
|
},
|
|
89
92
|
get subscriptions() {
|
|
90
|
-
return session.subscriptions
|
|
93
|
+
return session.subscriptions
|
|
91
94
|
},
|
|
92
95
|
get announces() {
|
|
93
|
-
return session.announces
|
|
96
|
+
return session.announces
|
|
94
97
|
},
|
|
95
98
|
get legalOutgoing() {
|
|
96
|
-
return session.legalOutgoing
|
|
99
|
+
return session.legalOutgoing
|
|
97
100
|
},
|
|
98
101
|
get legalIncoming() {
|
|
99
|
-
return session.legalIncoming
|
|
102
|
+
return session.legalIncoming
|
|
100
103
|
},
|
|
101
104
|
|
|
102
|
-
receive(message:
|
|
103
|
-
const prevPhase = session.phase
|
|
104
|
-
const result = session.receive(message)
|
|
105
|
+
receive(message: M) {
|
|
106
|
+
const prevPhase = session.phase
|
|
107
|
+
const result = session.receive(message)
|
|
105
108
|
|
|
106
109
|
addEvent({
|
|
107
|
-
type:
|
|
110
|
+
type: 'control',
|
|
108
111
|
seq: nextSeq(),
|
|
109
112
|
timestamp: clock(),
|
|
110
113
|
direction: 1, // rx
|
|
111
114
|
messageType: messageTypeId(message.type),
|
|
112
115
|
message: message as unknown as Record<string, unknown>,
|
|
113
|
-
})
|
|
116
|
+
})
|
|
114
117
|
|
|
115
118
|
if (result.ok && result.phase !== prevPhase) {
|
|
116
119
|
addEvent({
|
|
117
|
-
type:
|
|
120
|
+
type: 'state-change',
|
|
118
121
|
seq: nextSeq(),
|
|
119
122
|
timestamp: clock(),
|
|
120
123
|
from: prevPhase,
|
|
121
124
|
to: result.phase,
|
|
122
|
-
})
|
|
125
|
+
})
|
|
123
126
|
}
|
|
124
127
|
|
|
125
|
-
return result
|
|
128
|
+
return result
|
|
126
129
|
},
|
|
127
130
|
|
|
128
|
-
validateOutgoing(message:
|
|
129
|
-
return session.validateOutgoing(message)
|
|
131
|
+
validateOutgoing(message: M) {
|
|
132
|
+
return session.validateOutgoing(message)
|
|
130
133
|
},
|
|
131
134
|
|
|
132
|
-
send(message:
|
|
133
|
-
const prevPhase = session.phase
|
|
134
|
-
const result = session.send(message)
|
|
135
|
+
send(message: M) {
|
|
136
|
+
const prevPhase = session.phase
|
|
137
|
+
const result = session.send(message)
|
|
135
138
|
|
|
136
139
|
addEvent({
|
|
137
|
-
type:
|
|
140
|
+
type: 'control',
|
|
138
141
|
seq: nextSeq(),
|
|
139
142
|
timestamp: clock(),
|
|
140
143
|
direction: 0, // tx
|
|
141
144
|
messageType: messageTypeId(message.type),
|
|
142
145
|
message: message as unknown as Record<string, unknown>,
|
|
143
|
-
})
|
|
146
|
+
})
|
|
144
147
|
|
|
145
148
|
if (result.ok && result.phase !== prevPhase) {
|
|
146
149
|
addEvent({
|
|
147
|
-
type:
|
|
150
|
+
type: 'state-change',
|
|
148
151
|
seq: nextSeq(),
|
|
149
152
|
timestamp: clock(),
|
|
150
153
|
from: prevPhase,
|
|
151
154
|
to: result.phase,
|
|
152
|
-
})
|
|
155
|
+
})
|
|
153
156
|
}
|
|
154
157
|
|
|
155
|
-
return result
|
|
158
|
+
return result
|
|
156
159
|
},
|
|
157
160
|
|
|
158
161
|
reset() {
|
|
159
|
-
session.reset()
|
|
162
|
+
session.reset()
|
|
160
163
|
},
|
|
161
|
-
}
|
|
164
|
+
}
|
|
162
165
|
}
|
|
163
166
|
|
|
164
167
|
return {
|
|
@@ -167,32 +170,32 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
|
|
|
167
170
|
record: addEvent,
|
|
168
171
|
|
|
169
172
|
recordStreamOpened(streamId, direction, streamType) {
|
|
170
|
-
if (detailRank < DETAIL_RANK.headers) return
|
|
173
|
+
if (detailRank < DETAIL_RANK.headers) return
|
|
171
174
|
addEvent({
|
|
172
|
-
type:
|
|
175
|
+
type: 'stream-opened',
|
|
173
176
|
seq: nextSeq(),
|
|
174
177
|
timestamp: clock(),
|
|
175
178
|
streamId,
|
|
176
179
|
direction,
|
|
177
180
|
streamType,
|
|
178
|
-
})
|
|
181
|
+
})
|
|
179
182
|
},
|
|
180
183
|
|
|
181
184
|
recordStreamClosed(streamId, errorCode = 0) {
|
|
182
|
-
if (detailRank < DETAIL_RANK.headers) return
|
|
185
|
+
if (detailRank < DETAIL_RANK.headers) return
|
|
183
186
|
addEvent({
|
|
184
|
-
type:
|
|
187
|
+
type: 'stream-closed',
|
|
185
188
|
seq: nextSeq(),
|
|
186
189
|
timestamp: clock(),
|
|
187
190
|
streamId,
|
|
188
191
|
errorCode,
|
|
189
|
-
})
|
|
192
|
+
})
|
|
190
193
|
},
|
|
191
194
|
|
|
192
195
|
recordObjectHeader(streamId, groupId, objectId, publisherPriority, objectStatus) {
|
|
193
|
-
if (detailRank < DETAIL_RANK.headers) return
|
|
196
|
+
if (detailRank < DETAIL_RANK.headers) return
|
|
194
197
|
addEvent({
|
|
195
|
-
type:
|
|
198
|
+
type: 'object-header',
|
|
196
199
|
seq: nextSeq(),
|
|
197
200
|
timestamp: clock(),
|
|
198
201
|
streamId,
|
|
@@ -200,46 +203,46 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
|
|
|
200
203
|
objectId,
|
|
201
204
|
publisherPriority,
|
|
202
205
|
objectStatus,
|
|
203
|
-
})
|
|
206
|
+
})
|
|
204
207
|
},
|
|
205
208
|
|
|
206
209
|
recordObjectPayload(streamId, groupId, objectId, size, payload) {
|
|
207
|
-
if (detailRank < DETAIL_RANK[
|
|
210
|
+
if (detailRank < DETAIL_RANK['headers+sizes']) return
|
|
208
211
|
const event: TraceEvent = {
|
|
209
|
-
type:
|
|
212
|
+
type: 'object-payload',
|
|
210
213
|
seq: nextSeq(),
|
|
211
214
|
timestamp: clock(),
|
|
212
215
|
streamId,
|
|
213
216
|
groupId,
|
|
214
217
|
objectId,
|
|
215
218
|
size,
|
|
216
|
-
...(detailRank >= DETAIL_RANK[
|
|
217
|
-
}
|
|
218
|
-
addEvent(event)
|
|
219
|
+
...(detailRank >= DETAIL_RANK['headers+data'] && payload != null ? { payload } : {}),
|
|
220
|
+
}
|
|
221
|
+
addEvent(event)
|
|
219
222
|
},
|
|
220
223
|
|
|
221
224
|
recordError(errorCode, reason) {
|
|
222
225
|
addEvent({
|
|
223
|
-
type:
|
|
226
|
+
type: 'error',
|
|
224
227
|
seq: nextSeq(),
|
|
225
228
|
timestamp: clock(),
|
|
226
229
|
errorCode,
|
|
227
230
|
reason,
|
|
228
|
-
})
|
|
231
|
+
})
|
|
229
232
|
},
|
|
230
233
|
|
|
231
234
|
annotate(label, data) {
|
|
232
235
|
addEvent({
|
|
233
|
-
type:
|
|
236
|
+
type: 'annotation',
|
|
234
237
|
seq: nextSeq(),
|
|
235
238
|
timestamp: clock(),
|
|
236
239
|
label,
|
|
237
240
|
data,
|
|
238
|
-
})
|
|
241
|
+
})
|
|
239
242
|
},
|
|
240
243
|
|
|
241
244
|
finalize(): Trace {
|
|
242
|
-
_recording = false
|
|
245
|
+
_recording = false
|
|
243
246
|
const header: TraceHeader = {
|
|
244
247
|
protocol: options.protocol,
|
|
245
248
|
perspective: options.perspective,
|
|
@@ -250,12 +253,12 @@ export function createRecorder(options: RecorderOptions): TraceRecorder {
|
|
|
250
253
|
...(options.source != null ? { source: options.source } : {}),
|
|
251
254
|
...(options.endpoint != null ? { endpoint: options.endpoint } : {}),
|
|
252
255
|
...(options.sessionId != null ? { sessionId: options.sessionId } : {}),
|
|
253
|
-
}
|
|
254
|
-
return { header, events: [...events] }
|
|
256
|
+
}
|
|
257
|
+
return { header, events: [...events] }
|
|
255
258
|
},
|
|
256
259
|
|
|
257
260
|
get recording() {
|
|
258
|
-
return _recording
|
|
261
|
+
return _recording
|
|
259
262
|
},
|
|
260
|
-
}
|
|
263
|
+
}
|
|
261
264
|
}
|
package/src/types.ts
CHANGED
|
@@ -1,79 +1,79 @@
|
|
|
1
|
-
export type DetailLevel =
|
|
2
|
-
export type Perspective =
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
}
|