@observertc/observer-js 1.2.0 → 1.3.0-rc.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/README.md +149 -86
- package/dist/index.d.mts +237 -92
- package/dist/index.d.ts +237 -92
- package/dist/index.js +342 -150
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +340 -148
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -15
package/dist/index.js
CHANGED
|
@@ -42,9 +42,9 @@ __export(src_exports, {
|
|
|
42
42
|
ClientMetaTypes: () => ClientMetaTypes,
|
|
43
43
|
ClientPopulationIssueDetector: () => ClientPopulationIssueDetector,
|
|
44
44
|
ClientPopulationIssueTypes: () => ClientPopulationIssueTypes,
|
|
45
|
-
ClientSampleSink: () => ClientSampleSink,
|
|
46
45
|
CodecConsistencyValidator: () => CodecConsistencyValidator,
|
|
47
46
|
Detectors: () => Detectors,
|
|
47
|
+
ExtensionStatsRegistry: () => ExtensionStatsRegistry,
|
|
48
48
|
GEOHASH_CELL_SIZES: () => GEOHASH_CELL_SIZES,
|
|
49
49
|
InMemorySink: () => InMemorySink,
|
|
50
50
|
IssueFanOutDetector: () => IssueFanOutDetector,
|
|
@@ -88,6 +88,7 @@ __export(src_exports, {
|
|
|
88
88
|
SCORE_HISTOGRAM_NUMBER_OF_BUCKETS: () => SCORE_HISTOGRAM_NUMBER_OF_BUCKETS,
|
|
89
89
|
SfuCongestionDetector: () => SfuCongestionDetector,
|
|
90
90
|
SimulcastReceiverValidator: () => SimulcastReceiverValidator,
|
|
91
|
+
SinkRegistry: () => SinkRegistry,
|
|
91
92
|
SlidingWindow: () => SlidingWindow,
|
|
92
93
|
TrackDeliveryMismatchDetector: () => TrackDeliveryMismatchDetector,
|
|
93
94
|
TrackDeliveryMismatchTypes: () => TrackDeliveryMismatchTypes,
|
|
@@ -108,7 +109,6 @@ __export(src_exports, {
|
|
|
108
109
|
createDefaultMediasoupRemoteTrackResolverFactory: () => createDefaultMediasoupRemoteTrackResolverFactory,
|
|
109
110
|
createInMemorySink: () => createInMemorySink,
|
|
110
111
|
createJsonlFileSink: () => createJsonlFileSink,
|
|
111
|
-
createJsonlFileSinkFactory: () => createJsonlFileSinkFactory,
|
|
112
112
|
createLogger: () => createLogger,
|
|
113
113
|
createP2pRemoteTrackResolverFactory: () => createP2pRemoteTrackResolverFactory,
|
|
114
114
|
defaultCallSummaryConfig: () => defaultCallSummaryConfig,
|
|
@@ -171,18 +171,189 @@ function createLogger(moduleName) {
|
|
|
171
171
|
}
|
|
172
172
|
}();
|
|
173
173
|
}
|
|
174
|
-
function setObserverLogger(
|
|
175
|
-
mainLogger =
|
|
174
|
+
function setObserverLogger(logger13) {
|
|
175
|
+
mainLogger = logger13;
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
// src/ObservedCall.ts
|
|
179
|
-
var
|
|
179
|
+
var import_node_events4 = require("events");
|
|
180
|
+
|
|
181
|
+
// src/sinks/SinkRegistry.ts
|
|
182
|
+
var import_uuid = require("uuid");
|
|
183
|
+
var logger = createLogger("SinkRegistry");
|
|
184
|
+
var SinkRegistry = class {
|
|
185
|
+
constructor(parent) {
|
|
186
|
+
this.parent = parent;
|
|
187
|
+
}
|
|
188
|
+
parent;
|
|
189
|
+
// This registry's sinks and its descendants'.
|
|
190
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
191
|
+
_sinks = /* @__PURE__ */ new Map();
|
|
192
|
+
/** Number of open sinks in this registry and its descendants. */
|
|
193
|
+
get size() {
|
|
194
|
+
return this._sinks.size;
|
|
195
|
+
}
|
|
196
|
+
keys() {
|
|
197
|
+
return this._sinks.keys();
|
|
198
|
+
}
|
|
199
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
200
|
+
values() {
|
|
201
|
+
return this._sinks.values();
|
|
202
|
+
}
|
|
203
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
204
|
+
get(id) {
|
|
205
|
+
return this._sinks.get(id);
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Construct `new SinkClass(settings)` and register it. The id (`settings.id`, generated when
|
|
209
|
+
* omitted) is checked first, so nothing is constructed — no file is opened — when it is taken:
|
|
210
|
+
* `undefined` is returned and a warning logged.
|
|
211
|
+
*/
|
|
212
|
+
create(SinkClass, settings) {
|
|
213
|
+
const lineage = this._lineage();
|
|
214
|
+
const id = settings.id ?? (0, import_uuid.v4)();
|
|
215
|
+
if (lineage[lineage.length - 1]._sinks.has(id)) {
|
|
216
|
+
logger.warn("Sink id %s is already in use; sink ids must be unique per observer. No sink was created", id);
|
|
217
|
+
return void 0;
|
|
218
|
+
}
|
|
219
|
+
const sink = new SinkClass({ ...settings, id });
|
|
220
|
+
const onError = (err) => logger.warn("Sink %s failed: %o", id, err);
|
|
221
|
+
sink.on("error", onError);
|
|
222
|
+
sink.once("close", () => sink.off("error", onError));
|
|
223
|
+
sink.once("ended", () => {
|
|
224
|
+
for (const registry of lineage) {
|
|
225
|
+
if (registry._sinks.get(id) === sink) registry._sinks.delete(id);
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
for (const registry of lineage) registry._sinks.set(id, sink);
|
|
229
|
+
return sink;
|
|
230
|
+
}
|
|
231
|
+
/** Write `record` to the sink `id`. `false` when there is no such sink, or on backpressure. */
|
|
232
|
+
write(id, record) {
|
|
233
|
+
const sink = this._sinks.get(id);
|
|
234
|
+
if (!sink) {
|
|
235
|
+
logger.debug("No sink %s; record dropped", id);
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
return sink.write(record);
|
|
239
|
+
}
|
|
240
|
+
/** End every sink of this registry and of its descendants. */
|
|
241
|
+
clear() {
|
|
242
|
+
for (const sink of [...this._sinks.values()]) {
|
|
243
|
+
try {
|
|
244
|
+
sink.end();
|
|
245
|
+
} catch (err) {
|
|
246
|
+
logger.warn("Sink %s failed to end: %o", sink.id, err);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/** This registry and its ancestors, up to the root. */
|
|
251
|
+
_lineage() {
|
|
252
|
+
return [this, ...this.parent?._lineage() ?? []];
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// src/sinks/ExtensionStatsRegistry.ts
|
|
257
|
+
var logger2 = createLogger("ExtensionStats");
|
|
258
|
+
var ExtensionStatsRegistry = class {
|
|
259
|
+
constructor(id, _sinks) {
|
|
260
|
+
this.id = id;
|
|
261
|
+
this._sinks = _sinks;
|
|
262
|
+
}
|
|
263
|
+
id;
|
|
264
|
+
_sinks;
|
|
265
|
+
/** The last data accepted for each extension stats id. */
|
|
266
|
+
values = /* @__PURE__ */ new Map();
|
|
267
|
+
closed = false;
|
|
268
|
+
/**
|
|
269
|
+
* Refuse further `create` / `accept`, and end the extension stats' sinks (the sinks whose id is
|
|
270
|
+
* an extension stats id). Other sinks of the owner, e.g. client sample sinks, are left to the
|
|
271
|
+
* owner's `sinks.clear()`. Called by the owner when it closes.
|
|
272
|
+
*/
|
|
273
|
+
close() {
|
|
274
|
+
if (this.closed) return;
|
|
275
|
+
this.closed = true;
|
|
276
|
+
for (const id of this.values.keys()) {
|
|
277
|
+
this._sinks.get(id)?.end();
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
create(settings) {
|
|
281
|
+
if (this.closed) {
|
|
282
|
+
logger2.warn("Attempted to create extension stats %s on closed %s", settings.id, this.id);
|
|
283
|
+
return void 0;
|
|
284
|
+
}
|
|
285
|
+
if (this.values.has(settings.id)) {
|
|
286
|
+
logger2.warn("Extension stats with id %s already exists on %s", settings.id, this.id);
|
|
287
|
+
return void 0;
|
|
288
|
+
}
|
|
289
|
+
this.values.set(settings.id, void 0);
|
|
290
|
+
if (!settings.createSink) return void 0;
|
|
291
|
+
const [SinkClass, sinkSettings] = settings.createSink;
|
|
292
|
+
this._sinks.get(settings.id)?.end();
|
|
293
|
+
return this._sinks.create(SinkClass, { ...sinkSettings, id: settings.id });
|
|
294
|
+
}
|
|
295
|
+
accept(id, data) {
|
|
296
|
+
if (this.closed) {
|
|
297
|
+
logger2.debug("Extension stats %s dropped: %s is closed", id, this.id);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
this.values.set(id, data);
|
|
301
|
+
try {
|
|
302
|
+
this._sinks.get(id)?.write(data);
|
|
303
|
+
} catch (err) {
|
|
304
|
+
logger2.warn("Failed to write extension stats %s to sink: %o", id, err);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
get(id) {
|
|
308
|
+
return this.values.get(id);
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
// src/sinks/JsonlFileSink.ts
|
|
313
|
+
var fs = __toESM(require("fs"));
|
|
314
|
+
var import_node_events = require("events");
|
|
315
|
+
var JsonlFileSink = class extends import_node_events.EventEmitter {
|
|
316
|
+
id;
|
|
317
|
+
/** The file this sink writes to. Read it (e.g. in a `close` handler) to upload/move the file. */
|
|
318
|
+
path;
|
|
319
|
+
_stream;
|
|
320
|
+
_serializeSample;
|
|
321
|
+
_ended = false;
|
|
322
|
+
constructor(options) {
|
|
323
|
+
super();
|
|
324
|
+
this.id = options.id ?? options.path;
|
|
325
|
+
this.path = options.path;
|
|
326
|
+
this._stream = fs.createWriteStream(this.path, { flags: "a" });
|
|
327
|
+
this._stream.on("close", () => this.emit("close"));
|
|
328
|
+
this._stream.on("finish", () => this.emit("finish"));
|
|
329
|
+
this._stream.on("drain", () => this.emit("drain"));
|
|
330
|
+
this._stream.on("error", (err) => this.emit("error", err));
|
|
331
|
+
this._serializeSample = options.serializeSample ?? ((record) => JSON.stringify(record));
|
|
332
|
+
}
|
|
333
|
+
get ended() {
|
|
334
|
+
return this._ended;
|
|
335
|
+
}
|
|
336
|
+
write(record) {
|
|
337
|
+
if (this._ended) return false;
|
|
338
|
+
return this._stream.write(`${this._serializeSample(record)}
|
|
339
|
+
`);
|
|
340
|
+
}
|
|
341
|
+
end() {
|
|
342
|
+
if (this._ended) return;
|
|
343
|
+
this._ended = true;
|
|
344
|
+
this.emit("ended");
|
|
345
|
+
this._stream.end();
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
function createJsonlFileSink(options) {
|
|
349
|
+
return new JsonlFileSink(options);
|
|
350
|
+
}
|
|
180
351
|
|
|
181
352
|
// src/ObservedClient.ts
|
|
182
|
-
var
|
|
353
|
+
var import_node_events3 = require("events");
|
|
183
354
|
|
|
184
355
|
// src/ObservedPeerConnection.ts
|
|
185
|
-
var
|
|
356
|
+
var import_node_events2 = require("events");
|
|
186
357
|
|
|
187
358
|
// src/utils/stats.ts
|
|
188
359
|
function percentile(values, p) {
|
|
@@ -339,8 +510,9 @@ function percentileOfHistogram(histogram, bucketWidth, p, options = {}) {
|
|
|
339
510
|
continue;
|
|
340
511
|
}
|
|
341
512
|
if (options.openEnded && index === histogram.length - 1 && options.max !== void 0) return options.max;
|
|
342
|
-
const
|
|
343
|
-
|
|
513
|
+
const lower = Math.max(index * bucketWidth, options.min ?? -Infinity);
|
|
514
|
+
const upper = Math.min((index + 1) * bucketWidth, options.max ?? Infinity);
|
|
515
|
+
return lower + (rank - cumulative) / count * Math.max(0, upper - lower);
|
|
344
516
|
}
|
|
345
517
|
return options.max;
|
|
346
518
|
}
|
|
@@ -1874,14 +2046,14 @@ var ObservedOutboundTrack = class {
|
|
|
1874
2046
|
|
|
1875
2047
|
// src/ObservedPeerConnection.ts
|
|
1876
2048
|
var ascending2 = (a, b) => a - b;
|
|
1877
|
-
var
|
|
2049
|
+
var logger3 = createLogger("ObservedPeerConnection");
|
|
1878
2050
|
function publicAddressOfLocalCandidate(candidate) {
|
|
1879
2051
|
if (!candidate) return void 0;
|
|
1880
2052
|
if (candidate.candidateType !== "relay" && isPublicIpAddress(candidate.address)) return candidate.address;
|
|
1881
2053
|
if (isPublicIpAddress(candidate.relatedAddress)) return candidate.relatedAddress;
|
|
1882
2054
|
return void 0;
|
|
1883
2055
|
}
|
|
1884
|
-
var ObservedPeerConnection = class extends
|
|
2056
|
+
var ObservedPeerConnection = class extends import_node_events2.EventEmitter {
|
|
1885
2057
|
constructor(peerConnectionId, client) {
|
|
1886
2058
|
super();
|
|
1887
2059
|
this.peerConnectionId = peerConnectionId;
|
|
@@ -2463,7 +2635,7 @@ var ObservedPeerConnection = class extends import_node_events.EventEmitter {
|
|
|
2463
2635
|
let observedCertificate = this.observedCertificates.get(stats.id);
|
|
2464
2636
|
if (!observedCertificate) {
|
|
2465
2637
|
if (!stats.timestamp || !stats.id || !stats.fingerprint) {
|
|
2466
|
-
return
|
|
2638
|
+
return logger3.warn(
|
|
2467
2639
|
`ObservedPeerConnection received an invalid CertificateStats (missing timestamp OR id OR fingerprint field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2468
2640
|
stats
|
|
2469
2641
|
);
|
|
@@ -2482,7 +2654,7 @@ var ObservedPeerConnection = class extends import_node_events.EventEmitter {
|
|
|
2482
2654
|
let observedCodec = this.observedCodecs.get(stats.id);
|
|
2483
2655
|
if (!observedCodec) {
|
|
2484
2656
|
if (!stats.timestamp || !stats.id || !stats.mimeType) {
|
|
2485
|
-
return
|
|
2657
|
+
return logger3.warn(
|
|
2486
2658
|
`ObservedPeerConnection received an invalid CodecStats (missing timestamp OR id OR mimeType field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2487
2659
|
stats
|
|
2488
2660
|
);
|
|
@@ -2501,7 +2673,7 @@ var ObservedPeerConnection = class extends import_node_events.EventEmitter {
|
|
|
2501
2673
|
let observedDataChannel = this.observedDataChannels.get(stats.id);
|
|
2502
2674
|
if (!observedDataChannel) {
|
|
2503
2675
|
if (!stats.timestamp || !stats.id) {
|
|
2504
|
-
return
|
|
2676
|
+
return logger3.warn(
|
|
2505
2677
|
`ObservedPeerConnection received an invalid DataChannelStats (missing timestamp OR id field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2506
2678
|
stats
|
|
2507
2679
|
);
|
|
@@ -2520,7 +2692,7 @@ var ObservedPeerConnection = class extends import_node_events.EventEmitter {
|
|
|
2520
2692
|
let observedIceCandidate = this.observedIceCandidates.get(stats.id);
|
|
2521
2693
|
if (!observedIceCandidate) {
|
|
2522
2694
|
if (!stats.timestamp || !stats.id) {
|
|
2523
|
-
return
|
|
2695
|
+
return logger3.warn(
|
|
2524
2696
|
`ObservedPeerConnection received an invalid IceCandidateStats (missing timestamp OR id field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2525
2697
|
stats
|
|
2526
2698
|
);
|
|
@@ -2539,7 +2711,7 @@ var ObservedPeerConnection = class extends import_node_events.EventEmitter {
|
|
|
2539
2711
|
let observedIceCandidatePair = this.observedIceCandidatesPair.get(stats.id);
|
|
2540
2712
|
if (!observedIceCandidatePair) {
|
|
2541
2713
|
if (!stats.timestamp || !stats.id) {
|
|
2542
|
-
return
|
|
2714
|
+
return logger3.warn(
|
|
2543
2715
|
`ObservedPeerConnection received an invalid IceCandidateStats (missing timestamp OR id field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2544
2716
|
stats
|
|
2545
2717
|
);
|
|
@@ -2558,7 +2730,7 @@ var ObservedPeerConnection = class extends import_node_events.EventEmitter {
|
|
|
2558
2730
|
let observedIceTransport = this.observedIceTransports.get(stats.id);
|
|
2559
2731
|
if (!observedIceTransport) {
|
|
2560
2732
|
if (!stats.timestamp || !stats.id) {
|
|
2561
|
-
return
|
|
2733
|
+
return logger3.warn(
|
|
2562
2734
|
`ObservedPeerConnection received an invalid IceCandidateStats (missing timestamp OR id field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2563
2735
|
stats
|
|
2564
2736
|
);
|
|
@@ -2577,7 +2749,7 @@ var ObservedPeerConnection = class extends import_node_events.EventEmitter {
|
|
|
2577
2749
|
let observedInboundRtp = this.observedInboundRtps.get(stats.ssrc);
|
|
2578
2750
|
if (!observedInboundRtp) {
|
|
2579
2751
|
if (!stats.timestamp || !stats.id || !stats.ssrc || !stats.kind || !stats.trackIdentifier) {
|
|
2580
|
-
return
|
|
2752
|
+
return logger3.warn(
|
|
2581
2753
|
`ObservedPeerConnection received an invalid InboundRtpStats (missing timestamp OR id OR ssrc OR kind OR trackIdentifier field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2582
2754
|
stats
|
|
2583
2755
|
);
|
|
@@ -2603,7 +2775,7 @@ var ObservedPeerConnection = class extends import_node_events.EventEmitter {
|
|
|
2603
2775
|
let observedInboundTrack = this.observedInboundTracks.get(stats.id);
|
|
2604
2776
|
if (!observedInboundTrack) {
|
|
2605
2777
|
if (!stats.timestamp || !stats.id || !stats.kind) {
|
|
2606
|
-
return
|
|
2778
|
+
return logger3.warn(
|
|
2607
2779
|
`ObservedPeerConnection received an invalid InboundTrackSample (missing timestamp OR id OR kind field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2608
2780
|
stats
|
|
2609
2781
|
);
|
|
@@ -2631,7 +2803,7 @@ var ObservedPeerConnection = class extends import_node_events.EventEmitter {
|
|
|
2631
2803
|
let observedMediaPlayout = this.observedMediaPlayouts.get(stats.id);
|
|
2632
2804
|
if (!observedMediaPlayout) {
|
|
2633
2805
|
if (!stats.timestamp || !stats.id || !stats.kind) {
|
|
2634
|
-
return
|
|
2806
|
+
return logger3.warn(
|
|
2635
2807
|
`ObservedPeerConnection received an invalid InboundRtpStats (missing timestamp OR id OR kind OR trackIdentifier field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2636
2808
|
stats
|
|
2637
2809
|
);
|
|
@@ -2655,7 +2827,7 @@ var ObservedPeerConnection = class extends import_node_events.EventEmitter {
|
|
|
2655
2827
|
let observedMediaSource = this.observedMediaSources.get(stats.id);
|
|
2656
2828
|
if (!observedMediaSource) {
|
|
2657
2829
|
if (!stats.timestamp || !stats.id || !stats.kind) {
|
|
2658
|
-
return
|
|
2830
|
+
return logger3.warn(
|
|
2659
2831
|
`ObservedPeerConnection received an invalid InboundRtpStats (missing timestamp OR id OR kind field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2660
2832
|
stats
|
|
2661
2833
|
);
|
|
@@ -2679,7 +2851,7 @@ var ObservedPeerConnection = class extends import_node_events.EventEmitter {
|
|
|
2679
2851
|
let observedOutboundRtp = this.observedOutboundRtps.get(stats.ssrc);
|
|
2680
2852
|
if (!observedOutboundRtp) {
|
|
2681
2853
|
if (!stats.timestamp || !stats.id || !stats.ssrc || !stats.kind) {
|
|
2682
|
-
return
|
|
2854
|
+
return logger3.warn(
|
|
2683
2855
|
`ObservedPeerConnection received an invalid OutboundRtpStats (missing timestamp OR id OR ssrc OR kind field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2684
2856
|
stats
|
|
2685
2857
|
);
|
|
@@ -2704,7 +2876,7 @@ var ObservedPeerConnection = class extends import_node_events.EventEmitter {
|
|
|
2704
2876
|
let observedOutboundTrack = this.observedOutboundTracks.get(stats.id);
|
|
2705
2877
|
if (!observedOutboundTrack) {
|
|
2706
2878
|
if (!stats.timestamp || !stats.id || !stats.kind) {
|
|
2707
|
-
return
|
|
2879
|
+
return logger3.warn(
|
|
2708
2880
|
`ObservedPeerConnection received an invalid OutboundTrackSample (missing timestamp OR id OR kind field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2709
2881
|
stats
|
|
2710
2882
|
);
|
|
@@ -2732,7 +2904,7 @@ var ObservedPeerConnection = class extends import_node_events.EventEmitter {
|
|
|
2732
2904
|
let observedPeerConnectionTransport = this.observedPeerConnectionTransports.get(stats.id);
|
|
2733
2905
|
if (!observedPeerConnectionTransport) {
|
|
2734
2906
|
if (!stats.timestamp || !stats.id) {
|
|
2735
|
-
return
|
|
2907
|
+
return logger3.warn(
|
|
2736
2908
|
`ObservedPeerConnection received an invalid PeerConnectionTransportStats (missing timestamp OR id field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2737
2909
|
stats
|
|
2738
2910
|
);
|
|
@@ -2751,7 +2923,7 @@ var ObservedPeerConnection = class extends import_node_events.EventEmitter {
|
|
|
2751
2923
|
let observedRemoteInboundRtp = this.observedRemoteInboundRtps.get(stats.ssrc);
|
|
2752
2924
|
if (!observedRemoteInboundRtp) {
|
|
2753
2925
|
if (!stats.timestamp || !stats.id || !stats.ssrc || !stats.kind) {
|
|
2754
|
-
return
|
|
2926
|
+
return logger3.warn(
|
|
2755
2927
|
`ObservedPeerConnection received an invalid RemoteInboundRtpStats (missing timestamp OR id OR ssrc OR kind field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2756
2928
|
stats
|
|
2757
2929
|
);
|
|
@@ -2776,7 +2948,7 @@ var ObservedPeerConnection = class extends import_node_events.EventEmitter {
|
|
|
2776
2948
|
let observedRemoteOutboundRtp = this.observedRemoteOutboundRtps.get(stats.ssrc);
|
|
2777
2949
|
if (!observedRemoteOutboundRtp) {
|
|
2778
2950
|
if (!stats.timestamp || !stats.id || !stats.ssrc || !stats.kind) {
|
|
2779
|
-
return
|
|
2951
|
+
return logger3.warn(
|
|
2780
2952
|
`ObservedPeerConnection received an invalid RemoteOutboundRtpStats (missing timestamp OR id OR ssrc OR kind field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2781
2953
|
stats
|
|
2782
2954
|
);
|
|
@@ -2876,35 +3048,27 @@ function baseIssueType(type) {
|
|
|
2876
3048
|
}
|
|
2877
3049
|
|
|
2878
3050
|
// src/ObservedClient.ts
|
|
2879
|
-
var
|
|
3051
|
+
var logger4 = createLogger("ObservedClient");
|
|
2880
3052
|
var RTT_HISTOGRAM_BUCKET_WIDTH_IN_MS = 20;
|
|
2881
3053
|
var RTT_HISTOGRAM_NUMBER_OF_BUCKETS = 50;
|
|
2882
3054
|
var SCORE_HISTOGRAM_BUCKET_WIDTH = 1;
|
|
2883
3055
|
var SCORE_HISTOGRAM_NUMBER_OF_BUCKETS = 5;
|
|
2884
|
-
var ObservedClient = class extends
|
|
3056
|
+
var ObservedClient = class extends import_node_events3.EventEmitter {
|
|
2885
3057
|
constructor(settings, call, activeIssues) {
|
|
2886
3058
|
super();
|
|
2887
3059
|
this.call = call;
|
|
2888
3060
|
this.setMaxListeners(Infinity);
|
|
2889
3061
|
this.eventScope = { observer: call.observer, observedCall: call, observedClient: this };
|
|
2890
|
-
this.sink = call.observer.config.createClientSink?.({ clientId: settings.clientId, observedCall: call });
|
|
2891
3062
|
this.clientId = settings.clientId;
|
|
2892
3063
|
this.appData = settings.appData ?? {};
|
|
2893
3064
|
this.settings = {
|
|
2894
3065
|
closeClientIfIdleForMs: settings.closeClientIfIdleForMs
|
|
2895
3066
|
};
|
|
2896
|
-
if (this.sink) {
|
|
2897
|
-
const onError = (err) => logger2.warn("ClientSampleSink error for client %s: %o", this.clientId, err);
|
|
2898
|
-
this.sink.once("close", () => this.sink?.off("error", onError));
|
|
2899
|
-
this.sink.on("error", onError);
|
|
2900
|
-
}
|
|
2901
3067
|
this.activeIssues = activeIssues;
|
|
2902
3068
|
}
|
|
2903
3069
|
call;
|
|
2904
3070
|
clientId;
|
|
2905
3071
|
observedPeerConnections = /* @__PURE__ */ new Map();
|
|
2906
|
-
/** Per-client sink (from `ObserverConfig.createClientSink`), if any. */
|
|
2907
|
-
sink;
|
|
2908
3072
|
/** Ancestry base shared by all Observer-bus events originating at this client. */
|
|
2909
3073
|
eventScope;
|
|
2910
3074
|
calculatedScore = {
|
|
@@ -3043,6 +3207,13 @@ var ObservedClient = class extends import_node_events2.EventEmitter {
|
|
|
3043
3207
|
_closedPeerConnectionIds = /* @__PURE__ */ new Set();
|
|
3044
3208
|
closeTimer;
|
|
3045
3209
|
/** The distinct `publicAddress`es currently known by this client's open peer connections. */
|
|
3210
|
+
/**
|
|
3211
|
+
* The sink this client's samples are written to: whatever is registered under its client id in
|
|
3212
|
+
* `call.sinks` (the `createClientJsonlSink` file, or a sink the application added), if any.
|
|
3213
|
+
*/
|
|
3214
|
+
get sink() {
|
|
3215
|
+
return this.call.sinks.get(this.clientId);
|
|
3216
|
+
}
|
|
3046
3217
|
get publicAddresses() {
|
|
3047
3218
|
const result = /* @__PURE__ */ new Set();
|
|
3048
3219
|
for (const observedPeerConnection of this.observedPeerConnections.values()) {
|
|
@@ -3137,12 +3308,12 @@ var ObservedClient = class extends import_node_events2.EventEmitter {
|
|
|
3137
3308
|
try {
|
|
3138
3309
|
this.sink?.end();
|
|
3139
3310
|
} catch (err) {
|
|
3140
|
-
|
|
3311
|
+
logger4.warn("Sink end failed for client %s: %o", this.clientId, err);
|
|
3141
3312
|
}
|
|
3142
3313
|
}
|
|
3143
3314
|
accept(sample, context) {
|
|
3144
3315
|
if (this.closed) {
|
|
3145
|
-
|
|
3316
|
+
logger4.warn("Attempted to accept a sample on a closed client %s", this.clientId);
|
|
3146
3317
|
return;
|
|
3147
3318
|
}
|
|
3148
3319
|
if (this.closeTimer) {
|
|
@@ -3332,7 +3503,7 @@ var ObservedClient = class extends import_node_events2.EventEmitter {
|
|
|
3332
3503
|
try {
|
|
3333
3504
|
this.sink?.write(sample);
|
|
3334
3505
|
} catch (err) {
|
|
3335
|
-
|
|
3506
|
+
logger4.warn("Sink write failed for client %s: %o", this.clientId, err);
|
|
3336
3507
|
}
|
|
3337
3508
|
}
|
|
3338
3509
|
injectMetaData(metaData) {
|
|
@@ -3515,10 +3686,10 @@ var ObservedClient = class extends import_node_events2.EventEmitter {
|
|
|
3515
3686
|
this._notify("client-rejoined", { ...this.eventScope, timestamp: event.timestamp });
|
|
3516
3687
|
} else {
|
|
3517
3688
|
this.joinedAt = event.timestamp;
|
|
3518
|
-
|
|
3689
|
+
logger4.warn(`Client ${this.clientId} joinedAt timestamp was updated to ${event.timestamp}. the joined event will not be emitted.`);
|
|
3519
3690
|
}
|
|
3520
3691
|
}
|
|
3521
|
-
|
|
3692
|
+
logger4.debug("Client %s joined at %o", this.clientId, event);
|
|
3522
3693
|
break;
|
|
3523
3694
|
}
|
|
3524
3695
|
case "CLIENT_LEFT" /* CLIENT_LEFT */: {
|
|
@@ -3528,10 +3699,10 @@ var ObservedClient = class extends import_node_events2.EventEmitter {
|
|
|
3528
3699
|
this.emit("left");
|
|
3529
3700
|
this._notify("client-left", { ...this.eventScope });
|
|
3530
3701
|
} else {
|
|
3531
|
-
|
|
3702
|
+
logger4.warn(`Client ${this.clientId} leftAt timestamp was already set`);
|
|
3532
3703
|
}
|
|
3533
3704
|
}
|
|
3534
|
-
|
|
3705
|
+
logger4.debug("Client %s left at %o", this.clientId, event);
|
|
3535
3706
|
break;
|
|
3536
3707
|
}
|
|
3537
3708
|
case "PEER_CONNECTION_OPENED" /* PEER_CONNECTION_OPENED */: {
|
|
@@ -3543,7 +3714,7 @@ var ObservedClient = class extends import_node_events2.EventEmitter {
|
|
|
3543
3714
|
} else if (postBuffer) {
|
|
3544
3715
|
postBuffer.push(event);
|
|
3545
3716
|
} else {
|
|
3546
|
-
|
|
3717
|
+
logger4.warn("Received PEER_CONNECTION_OPENED event without a corresponding observedPeerConnection: %o", event);
|
|
3547
3718
|
}
|
|
3548
3719
|
}
|
|
3549
3720
|
break;
|
|
@@ -3560,7 +3731,7 @@ var ObservedClient = class extends import_node_events2.EventEmitter {
|
|
|
3560
3731
|
} else if (postBuffer) {
|
|
3561
3732
|
postBuffer.push(event);
|
|
3562
3733
|
} else {
|
|
3563
|
-
|
|
3734
|
+
logger4.warn("Received PEER_CONNECTION_CLOSED event without a corresponding observedPeerConnection: %o", event);
|
|
3564
3735
|
}
|
|
3565
3736
|
}
|
|
3566
3737
|
break;
|
|
@@ -3575,7 +3746,7 @@ var ObservedClient = class extends import_node_events2.EventEmitter {
|
|
|
3575
3746
|
} else if (postBuffer) {
|
|
3576
3747
|
postBuffer.push(event);
|
|
3577
3748
|
} else {
|
|
3578
|
-
|
|
3749
|
+
logger4.warn("Received MEDIA_TRACK_ADDED event without a corresponding observedPeerConnection or observedTrack: %o", event);
|
|
3579
3750
|
}
|
|
3580
3751
|
}
|
|
3581
3752
|
break;
|
|
@@ -3590,7 +3761,7 @@ var ObservedClient = class extends import_node_events2.EventEmitter {
|
|
|
3590
3761
|
} else if (postBuffer) {
|
|
3591
3762
|
postBuffer.push(event);
|
|
3592
3763
|
} else {
|
|
3593
|
-
|
|
3764
|
+
logger4.warn("Received MEDIA_TRACK_REMOVED event without a corresponding observedPeerConnection or observedTrack: %o", event);
|
|
3594
3765
|
}
|
|
3595
3766
|
}
|
|
3596
3767
|
break;
|
|
@@ -3605,7 +3776,7 @@ var ObservedClient = class extends import_node_events2.EventEmitter {
|
|
|
3605
3776
|
} else if (postBuffer) {
|
|
3606
3777
|
postBuffer.push(event);
|
|
3607
3778
|
} else {
|
|
3608
|
-
|
|
3779
|
+
logger4.warn("Received DATA_CHANNEL_OPENED event without a corresponding observedPeerConnection or observedDataChannel: %o", event);
|
|
3609
3780
|
}
|
|
3610
3781
|
}
|
|
3611
3782
|
break;
|
|
@@ -3620,7 +3791,7 @@ var ObservedClient = class extends import_node_events2.EventEmitter {
|
|
|
3620
3791
|
} else if (postBuffer) {
|
|
3621
3792
|
postBuffer.push(event);
|
|
3622
3793
|
} else {
|
|
3623
|
-
|
|
3794
|
+
logger4.warn("Received DATA_CHANNEL_CLOSE event without a corresponding observedPeerConnection or observedDataChannel: %o", event);
|
|
3624
3795
|
}
|
|
3625
3796
|
}
|
|
3626
3797
|
break;
|
|
@@ -3640,7 +3811,7 @@ var ObservedClient = class extends import_node_events2.EventEmitter {
|
|
|
3640
3811
|
} else if (postBuffer) {
|
|
3641
3812
|
postBuffer.push(event);
|
|
3642
3813
|
} else {
|
|
3643
|
-
|
|
3814
|
+
logger4.warn("Received MEDIA_TRACK_MUTED event without a corresponding observedPeerConnection or observedTrack: %o", event);
|
|
3644
3815
|
}
|
|
3645
3816
|
}
|
|
3646
3817
|
break;
|
|
@@ -3660,7 +3831,7 @@ var ObservedClient = class extends import_node_events2.EventEmitter {
|
|
|
3660
3831
|
} else if (postBuffer) {
|
|
3661
3832
|
postBuffer.push(event);
|
|
3662
3833
|
} else {
|
|
3663
|
-
|
|
3834
|
+
logger4.warn("Received MEDIA_TRACK_UNMUTED event without a corresponding observedPeerConnection or observedTrack: %o", event);
|
|
3664
3835
|
}
|
|
3665
3836
|
}
|
|
3666
3837
|
break;
|
|
@@ -3675,7 +3846,7 @@ var ObservedClient = class extends import_node_events2.EventEmitter {
|
|
|
3675
3846
|
} else if (postBuffer) {
|
|
3676
3847
|
postBuffer.push(event);
|
|
3677
3848
|
} else {
|
|
3678
|
-
|
|
3849
|
+
logger4.warn("Received ICE_CONNECTION_STATE_CHANGED event without a corresponding observedPeerConnection: %o", event);
|
|
3679
3850
|
}
|
|
3680
3851
|
}
|
|
3681
3852
|
break;
|
|
@@ -3690,7 +3861,7 @@ var ObservedClient = class extends import_node_events2.EventEmitter {
|
|
|
3690
3861
|
} else if (postBuffer) {
|
|
3691
3862
|
postBuffer.push(event);
|
|
3692
3863
|
} else {
|
|
3693
|
-
|
|
3864
|
+
logger4.warn("Received ICE_GATHERING_STATE_CHANGED event without a corresponding observedPeerConnection: %o", event);
|
|
3694
3865
|
}
|
|
3695
3866
|
}
|
|
3696
3867
|
break;
|
|
@@ -3705,7 +3876,7 @@ var ObservedClient = class extends import_node_events2.EventEmitter {
|
|
|
3705
3876
|
} else if (postBuffer) {
|
|
3706
3877
|
postBuffer.push(event);
|
|
3707
3878
|
} else {
|
|
3708
|
-
|
|
3879
|
+
logger4.warn("Received PEER_CONNECTION_STATE_CHANGED event without a corresponding observedPeerConnection: %o", event);
|
|
3709
3880
|
}
|
|
3710
3881
|
}
|
|
3711
3882
|
break;
|
|
@@ -3724,11 +3895,11 @@ var ObservedClient = class extends import_node_events2.EventEmitter {
|
|
|
3724
3895
|
let observedPeerConnection = this.observedPeerConnections.get(sample.peerConnectionId);
|
|
3725
3896
|
if (!observedPeerConnection) {
|
|
3726
3897
|
if (this._closedPeerConnectionIds.has(sample.peerConnectionId)) {
|
|
3727
|
-
|
|
3898
|
+
logger4.debug("Ignoring stats of peer connection %s, already closed by client %s", sample.peerConnectionId, this.clientId);
|
|
3728
3899
|
return void 0;
|
|
3729
3900
|
}
|
|
3730
3901
|
if (!sample.peerConnectionId) {
|
|
3731
|
-
return
|
|
3902
|
+
return logger4.warn(
|
|
3732
3903
|
`ObservedClient received an invalid PeerConnectionSample (missing peerConnectionId field). ClientId: ${this.clientId}, CallId: ${this.call.callId}`,
|
|
3733
3904
|
sample
|
|
3734
3905
|
), void 0;
|
|
@@ -3788,7 +3959,7 @@ var ObservedClient = class extends import_node_events2.EventEmitter {
|
|
|
3788
3959
|
try {
|
|
3789
3960
|
this.sink?.write(finalSample);
|
|
3790
3961
|
} catch (err) {
|
|
3791
|
-
|
|
3962
|
+
logger4.warn("Sink write failed during injection flush for client %s: %o", this.clientId, err);
|
|
3792
3963
|
}
|
|
3793
3964
|
}
|
|
3794
3965
|
/** Emit an Observer-bus event scoped to this client (or a peer connection under it). */
|
|
@@ -3816,7 +3987,7 @@ var DefaultCallScoreCalculator = class {
|
|
|
3816
3987
|
};
|
|
3817
3988
|
|
|
3818
3989
|
// src/detectors/Detectors.ts
|
|
3819
|
-
var
|
|
3990
|
+
var logger5 = createLogger("Detectors");
|
|
3820
3991
|
var Detectors = class {
|
|
3821
3992
|
_detectors;
|
|
3822
3993
|
constructor(...detectors) {
|
|
@@ -3897,7 +4068,7 @@ var Detectors = class {
|
|
|
3897
4068
|
try {
|
|
3898
4069
|
detector.update();
|
|
3899
4070
|
} catch (err) {
|
|
3900
|
-
|
|
4071
|
+
logger5.warn(`Error updating detector ${detector?.name}`, err);
|
|
3901
4072
|
}
|
|
3902
4073
|
}
|
|
3903
4074
|
}
|
|
@@ -3910,7 +4081,7 @@ var Detectors = class {
|
|
|
3910
4081
|
try {
|
|
3911
4082
|
detector.close?.();
|
|
3912
4083
|
} catch (err) {
|
|
3913
|
-
|
|
4084
|
+
logger5.warn(`Error closing detector ${detector?.name}`, err);
|
|
3914
4085
|
}
|
|
3915
4086
|
}
|
|
3916
4087
|
};
|
|
@@ -4877,8 +5048,8 @@ var AudioEchoDetector = class _AudioEchoDetector {
|
|
|
4877
5048
|
};
|
|
4878
5049
|
|
|
4879
5050
|
// src/ObservedCall.ts
|
|
4880
|
-
var
|
|
4881
|
-
var ObservedCall = class extends
|
|
5051
|
+
var logger6 = createLogger("ObservedCall");
|
|
5052
|
+
var ObservedCall = class extends import_node_events4.EventEmitter {
|
|
4882
5053
|
constructor(settings, observer, activeIssuesRegistry) {
|
|
4883
5054
|
super();
|
|
4884
5055
|
this.observer = observer;
|
|
@@ -4886,6 +5057,8 @@ var ObservedCall = class extends import_node_events3.EventEmitter {
|
|
|
4886
5057
|
this.setMaxListeners(Infinity);
|
|
4887
5058
|
this.eventScope = { observer: this.observer, observedCall: this };
|
|
4888
5059
|
this.callId = settings.callId;
|
|
5060
|
+
this.sinks = new SinkRegistry(this.observer.sinks);
|
|
5061
|
+
this.extensionStats = new ExtensionStatsRegistry(this.callId, this.sinks);
|
|
4889
5062
|
this.appData = settings.appData ?? {};
|
|
4890
5063
|
this.scoreCalculator = new DefaultCallScoreCalculator(this);
|
|
4891
5064
|
this.detectors = new Detectors();
|
|
@@ -4942,6 +5115,8 @@ var ObservedCall = class extends import_node_events3.EventEmitter {
|
|
|
4942
5115
|
settings;
|
|
4943
5116
|
/** Ancestry base shared by all Observer-bus events originating at this call. */
|
|
4944
5117
|
eventScope;
|
|
5118
|
+
sinks;
|
|
5119
|
+
extensionStats;
|
|
4945
5120
|
closeTimer;
|
|
4946
5121
|
get numberOfClients() {
|
|
4947
5122
|
return this.observedClients.size;
|
|
@@ -4984,7 +5159,7 @@ var ObservedCall = class extends import_node_events3.EventEmitter {
|
|
|
4984
5159
|
break;
|
|
4985
5160
|
}
|
|
4986
5161
|
default: {
|
|
4987
|
-
|
|
5162
|
+
logger6.warn("Unknown detector name %s; skipping", name);
|
|
4988
5163
|
return this;
|
|
4989
5164
|
}
|
|
4990
5165
|
}
|
|
@@ -5054,6 +5229,8 @@ var ObservedCall = class extends import_node_events3.EventEmitter {
|
|
|
5054
5229
|
if (client.joinedAt) minSampleTimestamps = Math.min(minSampleTimestamps ?? client.joinedAt, client.joinedAt);
|
|
5055
5230
|
if (client.leftAt) maxSampleTimestamps = Math.max(maxSampleTimestamps ?? client.leftAt, client.leftAt);
|
|
5056
5231
|
}
|
|
5232
|
+
this.extensionStats.close();
|
|
5233
|
+
this.sinks.clear();
|
|
5057
5234
|
if (this.startedAt === void 0) this.startedAt = minSampleTimestamps;
|
|
5058
5235
|
if (this.endedAt === void 0) this.endedAt = maxSampleTimestamps;
|
|
5059
5236
|
this.closedAt = Date.now();
|
|
@@ -5066,17 +5243,26 @@ var ObservedCall = class extends import_node_events3.EventEmitter {
|
|
|
5066
5243
|
this.emit("close");
|
|
5067
5244
|
this._notify("call-closed", { ...this.eventScope });
|
|
5068
5245
|
}
|
|
5246
|
+
createExtensionStats(settings) {
|
|
5247
|
+
return this.extensionStats.create(settings);
|
|
5248
|
+
}
|
|
5249
|
+
acceptExtensionStats(id, data) {
|
|
5250
|
+
this.extensionStats.accept(id, data);
|
|
5251
|
+
}
|
|
5252
|
+
getExtensionStats(id) {
|
|
5253
|
+
return this.extensionStats.get(id);
|
|
5254
|
+
}
|
|
5069
5255
|
getObservedClient(clientId) {
|
|
5070
5256
|
if (this.closed || !this.observedClients.has(clientId)) return;
|
|
5071
5257
|
return this.observedClients.get(clientId);
|
|
5072
5258
|
}
|
|
5073
5259
|
createObservedClient(settings, acceptCtx) {
|
|
5074
5260
|
if (this.closed) {
|
|
5075
|
-
|
|
5261
|
+
logger6.warn("Attempted to create a client (clientId: %s) on a closed call %s", settings.clientId, this.callId);
|
|
5076
5262
|
return void 0;
|
|
5077
5263
|
}
|
|
5078
5264
|
if (this.observedClients.has(settings.clientId)) {
|
|
5079
|
-
|
|
5265
|
+
logger6.warn("Client with id %s already exists in call %s; returning the existing instance", settings.clientId, this.callId);
|
|
5080
5266
|
return this.observedClients.get(settings.clientId);
|
|
5081
5267
|
}
|
|
5082
5268
|
const clientSettings = {
|
|
@@ -5126,10 +5312,17 @@ var ObservedCall = class extends import_node_events3.EventEmitter {
|
|
|
5126
5312
|
clearTimeout(this.closeTimer);
|
|
5127
5313
|
this.closeTimer = void 0;
|
|
5128
5314
|
}
|
|
5315
|
+
const jsonlPath = this.observer.config.createClientJsonlSink?.({ observedCall: this, clientId: settings.clientId });
|
|
5316
|
+
let jsonlSink;
|
|
5317
|
+
if (jsonlPath !== void 0 && this.observer.sinks.get(settings.clientId)) {
|
|
5318
|
+
logger6.warn("No sample file for client %s in call %s: sink id %s is already in use", settings.clientId, this.callId, settings.clientId);
|
|
5319
|
+
} else if (jsonlPath !== void 0) {
|
|
5320
|
+
jsonlSink = this.sinks.create(JsonlFileSink, { id: settings.clientId, path: jsonlPath });
|
|
5321
|
+
}
|
|
5129
5322
|
this.emit("newclient", result);
|
|
5130
5323
|
this._notify("client-added", { ...this.eventScope, observedClient: result });
|
|
5131
|
-
if (
|
|
5132
|
-
this._notify("client-sink-created", { ...this.eventScope, observedClient: result, sink:
|
|
5324
|
+
if (jsonlSink) {
|
|
5325
|
+
this._notify("client-sink-created", { ...this.eventScope, observedClient: result, sink: jsonlSink });
|
|
5133
5326
|
}
|
|
5134
5327
|
if (wasEmpty) {
|
|
5135
5328
|
this.emit("not-empty");
|
|
@@ -5182,10 +5375,10 @@ var ObservedCall = class extends import_node_events3.EventEmitter {
|
|
|
5182
5375
|
};
|
|
5183
5376
|
|
|
5184
5377
|
// src/Observer.ts
|
|
5185
|
-
var
|
|
5378
|
+
var import_node_events7 = require("events");
|
|
5186
5379
|
|
|
5187
5380
|
// src/ObservedTURN.ts
|
|
5188
|
-
var
|
|
5381
|
+
var import_node_events5 = require("events");
|
|
5189
5382
|
|
|
5190
5383
|
// src/ObservedTurnServer.ts
|
|
5191
5384
|
var ObservedTurnServer = class {
|
|
@@ -5245,8 +5438,8 @@ var ObservedTurnServer = class {
|
|
|
5245
5438
|
};
|
|
5246
5439
|
|
|
5247
5440
|
// src/ObservedTURN.ts
|
|
5248
|
-
var
|
|
5249
|
-
var ObservedTURN = class extends
|
|
5441
|
+
var logger7 = createLogger("ObservedTURN");
|
|
5442
|
+
var ObservedTURN = class extends import_node_events5.EventEmitter {
|
|
5250
5443
|
totalBytesSent = 0;
|
|
5251
5444
|
totalBytesReceived = 0;
|
|
5252
5445
|
totalPacketsSent = 0;
|
|
@@ -5275,12 +5468,12 @@ var ObservedTURN = class extends import_node_events4.EventEmitter {
|
|
|
5275
5468
|
addPeerConnection(peerConnection) {
|
|
5276
5469
|
const turnPairs = peerConnection.selectedIceCandiadtePairForTurn;
|
|
5277
5470
|
if (turnPairs.length !== 1) {
|
|
5278
|
-
return
|
|
5471
|
+
return logger7.warn(`Expected exactly one TURN pair, but found for peerconnection ${peerConnection.peerConnectionId}`, turnPairs.length), void 0;
|
|
5279
5472
|
}
|
|
5280
5473
|
const candidatePair = turnPairs[0];
|
|
5281
5474
|
const rawUrl = candidatePair.getLocalCandidate()?.url;
|
|
5282
5475
|
if (!rawUrl) {
|
|
5283
|
-
return
|
|
5476
|
+
return logger7.warn(`No local candidate URL found for peerconnection ${peerConnection.peerConnectionId}`), void 0;
|
|
5284
5477
|
}
|
|
5285
5478
|
const turnServerUrl = rawUrl.split("?")[0];
|
|
5286
5479
|
let turnServer = this.servers.get(turnServerUrl);
|
|
@@ -5356,14 +5549,16 @@ var MiddlewareProcessor = class {
|
|
|
5356
5549
|
};
|
|
5357
5550
|
|
|
5358
5551
|
// src/ObservedMediasoupRouter.ts
|
|
5359
|
-
var
|
|
5360
|
-
var
|
|
5361
|
-
var ObservedMediasoupRouter = class extends
|
|
5552
|
+
var import_node_events6 = require("events");
|
|
5553
|
+
var logger8 = createLogger("ObservedMediasoupRouter");
|
|
5554
|
+
var ObservedMediasoupRouter = class extends import_node_events6.EventEmitter {
|
|
5362
5555
|
router;
|
|
5363
5556
|
appData;
|
|
5364
5557
|
sample;
|
|
5365
5558
|
webrtcTransportIds = /* @__PURE__ */ new Set();
|
|
5366
5559
|
closed = false;
|
|
5560
|
+
sinks;
|
|
5561
|
+
extensionStats;
|
|
5367
5562
|
// Id -> sample indexes over the arrays in `this.sample`, so an entity can be reached in O(1)
|
|
5368
5563
|
// instead of scanning (`sample.producers.find(...)`). They hold the *same* objects as the arrays.
|
|
5369
5564
|
_transportSamples = /* @__PURE__ */ new Map();
|
|
@@ -5372,11 +5567,17 @@ var ObservedMediasoupRouter = class extends import_node_events5.EventEmitter {
|
|
|
5372
5567
|
_dataProducerSamples = /* @__PURE__ */ new Map();
|
|
5373
5568
|
_dataConsumerSamples = /* @__PURE__ */ new Map();
|
|
5374
5569
|
_enrich;
|
|
5375
|
-
|
|
5570
|
+
/**
|
|
5571
|
+
* @param parentSinks the parent of `sinks`: `observer.sinks`, passed by
|
|
5572
|
+
* `Observer.createObservedMediasoupRouter`. Internal; a router constructed directly has none.
|
|
5573
|
+
*/
|
|
5574
|
+
constructor(settings, parentSinks) {
|
|
5376
5575
|
super();
|
|
5377
5576
|
this.router = settings.router;
|
|
5378
5577
|
this.appData = settings.appData ?? {};
|
|
5379
5578
|
this._enrich = settings.enrich;
|
|
5579
|
+
this.sinks = new SinkRegistry(parentSinks);
|
|
5580
|
+
this.extensionStats = new ExtensionStatsRegistry(this.router.id, this.sinks);
|
|
5380
5581
|
this.sample = {
|
|
5381
5582
|
routerId: this.router.id,
|
|
5382
5583
|
attachments: settings.attachments ?? {},
|
|
@@ -5445,8 +5646,19 @@ var ObservedMediasoupRouter = class extends import_node_events5.EventEmitter {
|
|
|
5445
5646
|
if (this.closed) return;
|
|
5446
5647
|
this.closed = true;
|
|
5447
5648
|
if (this.sample.closedAt === void 0) this.sample.closedAt = Date.now();
|
|
5649
|
+
this.extensionStats.close();
|
|
5650
|
+
this.sinks.clear();
|
|
5448
5651
|
this.emit("close");
|
|
5449
5652
|
}
|
|
5653
|
+
createExtensionStats(settings) {
|
|
5654
|
+
return this.extensionStats.create(settings);
|
|
5655
|
+
}
|
|
5656
|
+
acceptExtensionStats(id, data) {
|
|
5657
|
+
this.extensionStats.accept(id, data);
|
|
5658
|
+
}
|
|
5659
|
+
getExtensionStats(id) {
|
|
5660
|
+
return this.extensionStats.get(id);
|
|
5661
|
+
}
|
|
5450
5662
|
addTransport = (transport) => {
|
|
5451
5663
|
let transportType = transport.type;
|
|
5452
5664
|
if (!transportType) {
|
|
@@ -5455,7 +5667,7 @@ var ObservedMediasoupRouter = class extends import_node_events5.EventEmitter {
|
|
|
5455
5667
|
else if (transport.constructor.name === "PipeTransport") transportType = "pipe";
|
|
5456
5668
|
else if (transport.constructor.name === "DirectTransport") transportType = "direct";
|
|
5457
5669
|
}
|
|
5458
|
-
if (!transportType) return
|
|
5670
|
+
if (!transportType) return logger8.warn(`Unknown transport type for transport ${transport.id}: ${transport.constructor.name}`);
|
|
5459
5671
|
switch (transportType) {
|
|
5460
5672
|
case "webrtc":
|
|
5461
5673
|
return this.addWebRtcTransport(transport);
|
|
@@ -5466,7 +5678,7 @@ var ObservedMediasoupRouter = class extends import_node_events5.EventEmitter {
|
|
|
5466
5678
|
case "direct":
|
|
5467
5679
|
return this.addDirectTransport(transport);
|
|
5468
5680
|
default:
|
|
5469
|
-
return
|
|
5681
|
+
return logger8.warn(`Unsupported transport type for transport ${transport.id}: ${transportType}`);
|
|
5470
5682
|
}
|
|
5471
5683
|
};
|
|
5472
5684
|
addWebRtcTransport(transport) {
|
|
@@ -5708,7 +5920,7 @@ var ObservedMediasoupRouter = class extends import_node_events5.EventEmitter {
|
|
|
5708
5920
|
const attachments = enrich();
|
|
5709
5921
|
if (attachments) sample.attachments = { ...sample.attachments, ...attachments };
|
|
5710
5922
|
} catch (err) {
|
|
5711
|
-
|
|
5923
|
+
logger8.warn("Mediasoup sample enricher threw for router %s: %o", this.id, err);
|
|
5712
5924
|
}
|
|
5713
5925
|
}
|
|
5714
5926
|
_attachTransportObserverListeners(transport, transportSample, onClose) {
|
|
@@ -5733,7 +5945,7 @@ var ObservedMediasoupRouter = class extends import_node_events5.EventEmitter {
|
|
|
5733
5945
|
};
|
|
5734
5946
|
|
|
5735
5947
|
// src/issues/ActiveIssuesRegistry.ts
|
|
5736
|
-
var
|
|
5948
|
+
var logger9 = createLogger("ActiveIssuesRegistry");
|
|
5737
5949
|
var ActiveIssuesRegistry = class {
|
|
5738
5950
|
constructor(parent) {
|
|
5739
5951
|
this.parent = parent;
|
|
@@ -5819,7 +6031,7 @@ var ActiveIssuesRegistry = class {
|
|
|
5819
6031
|
try {
|
|
5820
6032
|
apply(target);
|
|
5821
6033
|
} catch (err) {
|
|
5822
|
-
|
|
6034
|
+
logger9.warn("An issue tracker threw while being notified; continuing with the rest. %o", err);
|
|
5823
6035
|
}
|
|
5824
6036
|
}
|
|
5825
6037
|
};
|
|
@@ -5848,7 +6060,7 @@ function createCallSummary(callId, config) {
|
|
|
5848
6060
|
}
|
|
5849
6061
|
|
|
5850
6062
|
// src/summaries/CallSummaryCollector.ts
|
|
5851
|
-
var
|
|
6063
|
+
var logger10 = createLogger("CallSummaryCollector");
|
|
5852
6064
|
var CallSummaryCollector = class {
|
|
5853
6065
|
constructor(_observer, _config) {
|
|
5854
6066
|
this._observer = _observer;
|
|
@@ -5923,7 +6135,7 @@ var CallSummaryCollector = class {
|
|
|
5923
6135
|
try {
|
|
5924
6136
|
handler(summary, scratch, ...args);
|
|
5925
6137
|
} catch (err) {
|
|
5926
|
-
|
|
6138
|
+
logger10.warn("A call-summary handler for %s threw; continuing. %o", event, err);
|
|
5927
6139
|
}
|
|
5928
6140
|
};
|
|
5929
6141
|
this._observer.on(event, listener);
|
|
@@ -6348,7 +6560,7 @@ function geohash(location, precision = 3) {
|
|
|
6348
6560
|
}
|
|
6349
6561
|
|
|
6350
6562
|
// src/detectors/ClientPopulationIssueDetector.ts
|
|
6351
|
-
var
|
|
6563
|
+
var logger11 = createLogger("ClientPopulationIssueDetector");
|
|
6352
6564
|
var ClientPopulationIssueTypes = {
|
|
6353
6565
|
/** One issue type is concentrated on one client population while the rest of the fleet is fine. */
|
|
6354
6566
|
clientPopulationIssue: "CLIENT_POPULATION_ISSUE"
|
|
@@ -6370,7 +6582,7 @@ var ClientPopulationIssueDetector = class _ClientPopulationIssueDetector {
|
|
|
6370
6582
|
...config
|
|
6371
6583
|
};
|
|
6372
6584
|
if (this._config.groupBy === "location" && !this._config.resolveClientLocation) {
|
|
6373
|
-
|
|
6585
|
+
logger11.warn(
|
|
6374
6586
|
"%s is configured with groupBy: 'location' but no resolveClientLocation, so no client can be placed and nothing will ever be detected",
|
|
6375
6587
|
this.name
|
|
6376
6588
|
);
|
|
@@ -7204,11 +7416,13 @@ function mediaKindOf(mimeType) {
|
|
|
7204
7416
|
}
|
|
7205
7417
|
|
|
7206
7418
|
// src/Observer.ts
|
|
7207
|
-
var
|
|
7208
|
-
var Observer = class extends
|
|
7419
|
+
var logger12 = createLogger("Observer");
|
|
7420
|
+
var Observer = class extends import_node_events7.EventEmitter {
|
|
7209
7421
|
observedTURN = new ObservedTURN();
|
|
7210
7422
|
observedCalls = /* @__PURE__ */ new Map();
|
|
7211
7423
|
observedMediasoupRouters = /* @__PURE__ */ new Map();
|
|
7424
|
+
sinks = new SinkRegistry();
|
|
7425
|
+
extensionStats = new ExtensionStatsRegistry("observer", this.sinks);
|
|
7212
7426
|
/** Ancestry base shared by all Observer-bus events originating at the observer. */
|
|
7213
7427
|
eventScope = { observer: this };
|
|
7214
7428
|
config;
|
|
@@ -7323,7 +7537,7 @@ var Observer = class extends import_node_events6.EventEmitter {
|
|
|
7323
7537
|
break;
|
|
7324
7538
|
}
|
|
7325
7539
|
default: {
|
|
7326
|
-
|
|
7540
|
+
logger12.warn("Unknown detector name %s; skipping", name);
|
|
7327
7541
|
return this;
|
|
7328
7542
|
}
|
|
7329
7543
|
}
|
|
@@ -7408,7 +7622,7 @@ var Observer = class extends import_node_events6.EventEmitter {
|
|
|
7408
7622
|
break;
|
|
7409
7623
|
}
|
|
7410
7624
|
if (!validator) {
|
|
7411
|
-
|
|
7625
|
+
logger12.warn("Unknown validator name %s; skipping", name);
|
|
7412
7626
|
return this;
|
|
7413
7627
|
}
|
|
7414
7628
|
this.validators.add(validator);
|
|
@@ -7439,7 +7653,7 @@ var Observer = class extends import_node_events6.EventEmitter {
|
|
|
7439
7653
|
try {
|
|
7440
7654
|
validator.cancel(reason);
|
|
7441
7655
|
} catch (err) {
|
|
7442
|
-
|
|
7656
|
+
logger12.warn("Error cancelling validator %s: %o", validator.name, err);
|
|
7443
7657
|
}
|
|
7444
7658
|
}
|
|
7445
7659
|
return matching.length;
|
|
@@ -7455,11 +7669,11 @@ var Observer = class extends import_node_events6.EventEmitter {
|
|
|
7455
7669
|
*/
|
|
7456
7670
|
createObservedCall(settings, acceptCtx) {
|
|
7457
7671
|
if (this.closed) {
|
|
7458
|
-
|
|
7672
|
+
logger12.warn("Attempted to create a call (callId: %s) on a closed observer", settings.callId);
|
|
7459
7673
|
return void 0;
|
|
7460
7674
|
}
|
|
7461
7675
|
if (this.observedCalls.has(settings.callId)) {
|
|
7462
|
-
|
|
7676
|
+
logger12.warn("Observed Call with id %s already exists; returning the existing instance", settings.callId);
|
|
7463
7677
|
return this.observedCalls.get(settings.callId);
|
|
7464
7678
|
}
|
|
7465
7679
|
const callSettings = {
|
|
@@ -7497,14 +7711,14 @@ var Observer = class extends import_node_events6.EventEmitter {
|
|
|
7497
7711
|
}
|
|
7498
7712
|
createObservedMediasoupRouter(settings) {
|
|
7499
7713
|
if (this.closed) {
|
|
7500
|
-
|
|
7714
|
+
logger12.warn("Attempted to create mediasoup router (id: %d) on a closed observer", settings.router.id);
|
|
7501
7715
|
return void 0;
|
|
7502
7716
|
}
|
|
7503
7717
|
if (this.observedMediasoupRouters.has(settings.router.id)) {
|
|
7504
|
-
|
|
7718
|
+
logger12.warn("Observed Mediasoup Router (id %s) already exists; returning the existing instance", settings.router.id);
|
|
7505
7719
|
return this.observedMediasoupRouters.get(settings.router.id);
|
|
7506
7720
|
}
|
|
7507
|
-
const observedMediasoupRouter = new ObservedMediasoupRouter(settings);
|
|
7721
|
+
const observedMediasoupRouter = new ObservedMediasoupRouter(settings, this.sinks);
|
|
7508
7722
|
const observedMediasoupRouterScope = {
|
|
7509
7723
|
observedMediasoupRouter,
|
|
7510
7724
|
observer: this
|
|
@@ -7532,7 +7746,7 @@ var Observer = class extends import_node_events6.EventEmitter {
|
|
|
7532
7746
|
}
|
|
7533
7747
|
close() {
|
|
7534
7748
|
if (this.closed) {
|
|
7535
|
-
return
|
|
7749
|
+
return logger12.debug("Attempted to close twice");
|
|
7536
7750
|
}
|
|
7537
7751
|
this.closed = true;
|
|
7538
7752
|
for (const call of [...this.observedCalls.values()]) call.close();
|
|
@@ -7541,8 +7755,19 @@ var Observer = class extends import_node_events6.EventEmitter {
|
|
|
7541
7755
|
this.validators.clear();
|
|
7542
7756
|
this.callSummaryCollector?.close();
|
|
7543
7757
|
this.activeIssuesRegistry.clear();
|
|
7758
|
+
this.extensionStats.close();
|
|
7759
|
+
this.sinks.clear();
|
|
7544
7760
|
this._notify("observer-closed", { ...this.eventScope });
|
|
7545
7761
|
}
|
|
7762
|
+
createExtensionStats(settings) {
|
|
7763
|
+
return this.extensionStats.create(settings);
|
|
7764
|
+
}
|
|
7765
|
+
acceptExtensionStats(id, data) {
|
|
7766
|
+
this.extensionStats.accept(id, data);
|
|
7767
|
+
}
|
|
7768
|
+
getExtensionStats(id) {
|
|
7769
|
+
return this.extensionStats.get(id);
|
|
7770
|
+
}
|
|
7546
7771
|
accept(sample, context) {
|
|
7547
7772
|
if (this.closed) {
|
|
7548
7773
|
return this._notify("sample-rejected", { ...this.eventScope, reason: "observer-closed", sample });
|
|
@@ -7550,7 +7775,7 @@ var Observer = class extends import_node_events6.EventEmitter {
|
|
|
7550
7775
|
try {
|
|
7551
7776
|
this.acceptMiddlewares.process({ sample, context });
|
|
7552
7777
|
} catch (err) {
|
|
7553
|
-
|
|
7778
|
+
logger12.warn("An accept middleware threw; dropping the sample. %o", err);
|
|
7554
7779
|
}
|
|
7555
7780
|
if (!sample.callId) {
|
|
7556
7781
|
this._notify("sample-rejected", { ...this.eventScope, reason: "missing-callId", sample });
|
|
@@ -7590,7 +7815,7 @@ var Observer = class extends import_node_events6.EventEmitter {
|
|
|
7590
7815
|
try {
|
|
7591
7816
|
validator.update();
|
|
7592
7817
|
} catch (err) {
|
|
7593
|
-
|
|
7818
|
+
logger12.warn("Error running validator %s: %o", validator.name, err);
|
|
7594
7819
|
}
|
|
7595
7820
|
}
|
|
7596
7821
|
this._notify("observer-updated", { ...this.eventScope });
|
|
@@ -7826,67 +8051,34 @@ var TrendTester = class {
|
|
|
7826
8051
|
}
|
|
7827
8052
|
};
|
|
7828
8053
|
|
|
7829
|
-
// src/sinks/ClientSampleSink.ts
|
|
7830
|
-
var import_node_events7 = require("events");
|
|
7831
|
-
var ClientSampleSink = class extends import_node_events7.EventEmitter {
|
|
7832
|
-
};
|
|
7833
|
-
|
|
7834
|
-
// src/sinks/JsonlFileSink.ts
|
|
7835
|
-
var fs = __toESM(require("fs"));
|
|
7836
|
-
var path = __toESM(require("path"));
|
|
7837
|
-
var JsonlFileSink = class extends ClientSampleSink {
|
|
7838
|
-
/** The file this sink writes to. Read it (e.g. in a `close` handler) to upload/move the file. */
|
|
7839
|
-
path;
|
|
7840
|
-
_stream;
|
|
7841
|
-
_serializeSample;
|
|
7842
|
-
constructor(options) {
|
|
7843
|
-
super();
|
|
7844
|
-
this.path = options.path;
|
|
7845
|
-
this._stream = fs.createWriteStream(this.path, { flags: "a" });
|
|
7846
|
-
this._stream.on("close", () => this.emit("close"));
|
|
7847
|
-
this._stream.on("finish", () => this.emit("finish"));
|
|
7848
|
-
this._stream.on("drain", () => this.emit("drain"));
|
|
7849
|
-
this._stream.on("error", (err) => this.emit("error", err));
|
|
7850
|
-
this._serializeSample = options.serializeSample ?? ((sample) => JSON.stringify(sample));
|
|
7851
|
-
}
|
|
7852
|
-
write(sample) {
|
|
7853
|
-
return this._stream.write(`${this._serializeSample(sample)}
|
|
7854
|
-
`);
|
|
7855
|
-
}
|
|
7856
|
-
end() {
|
|
7857
|
-
this._stream.end();
|
|
7858
|
-
}
|
|
7859
|
-
};
|
|
7860
|
-
function createJsonlFileSink(options) {
|
|
7861
|
-
return new JsonlFileSink(options);
|
|
7862
|
-
}
|
|
7863
|
-
function createJsonlFileSinkFactory(options) {
|
|
7864
|
-
return ({ clientId, observedCall }) => {
|
|
7865
|
-
const filename = options.getFileName?.({ callId: observedCall.callId, clientId }) ?? `${observedCall.callId}__${clientId}.jsonl`;
|
|
7866
|
-
return createJsonlFileSink({
|
|
7867
|
-
path: path.join(options.directory, filename),
|
|
7868
|
-
serializeSample: options.serializeSample
|
|
7869
|
-
});
|
|
7870
|
-
};
|
|
7871
|
-
}
|
|
7872
|
-
|
|
7873
8054
|
// src/sinks/InMemorySink.ts
|
|
7874
|
-
var
|
|
7875
|
-
|
|
8055
|
+
var import_node_events8 = require("events");
|
|
8056
|
+
var InMemorySink = class extends import_node_events8.EventEmitter {
|
|
8057
|
+
id;
|
|
8058
|
+
samples;
|
|
8059
|
+
_ended = false;
|
|
8060
|
+
constructor(settings = {}) {
|
|
7876
8061
|
super();
|
|
7877
|
-
this.
|
|
8062
|
+
this.id = settings.id ?? "in-memory";
|
|
8063
|
+
this.samples = settings.samples ?? [];
|
|
8064
|
+
}
|
|
8065
|
+
get ended() {
|
|
8066
|
+
return this._ended;
|
|
7878
8067
|
}
|
|
7879
|
-
samples;
|
|
7880
8068
|
write(sample) {
|
|
8069
|
+
if (this._ended) return false;
|
|
7881
8070
|
this.samples.push(sample);
|
|
7882
8071
|
return true;
|
|
7883
8072
|
}
|
|
7884
8073
|
end() {
|
|
8074
|
+
if (this._ended) return;
|
|
8075
|
+
this._ended = true;
|
|
8076
|
+
this.emit("ended");
|
|
7885
8077
|
this.emit("close");
|
|
7886
8078
|
}
|
|
7887
8079
|
};
|
|
7888
8080
|
function createInMemorySink(samples) {
|
|
7889
|
-
return new InMemorySink(samples);
|
|
8081
|
+
return new InMemorySink({ samples });
|
|
7890
8082
|
}
|
|
7891
8083
|
|
|
7892
8084
|
// src/resolvers/RemoteTrackResolver.ts
|
|
@@ -8075,9 +8267,9 @@ function createP2pRemoteTrackResolverFactory() {
|
|
|
8075
8267
|
ClientMetaTypes,
|
|
8076
8268
|
ClientPopulationIssueDetector,
|
|
8077
8269
|
ClientPopulationIssueTypes,
|
|
8078
|
-
ClientSampleSink,
|
|
8079
8270
|
CodecConsistencyValidator,
|
|
8080
8271
|
Detectors,
|
|
8272
|
+
ExtensionStatsRegistry,
|
|
8081
8273
|
GEOHASH_CELL_SIZES,
|
|
8082
8274
|
InMemorySink,
|
|
8083
8275
|
IssueFanOutDetector,
|
|
@@ -8121,6 +8313,7 @@ function createP2pRemoteTrackResolverFactory() {
|
|
|
8121
8313
|
SCORE_HISTOGRAM_NUMBER_OF_BUCKETS,
|
|
8122
8314
|
SfuCongestionDetector,
|
|
8123
8315
|
SimulcastReceiverValidator,
|
|
8316
|
+
SinkRegistry,
|
|
8124
8317
|
SlidingWindow,
|
|
8125
8318
|
TrackDeliveryMismatchDetector,
|
|
8126
8319
|
TrackDeliveryMismatchTypes,
|
|
@@ -8141,7 +8334,6 @@ function createP2pRemoteTrackResolverFactory() {
|
|
|
8141
8334
|
createDefaultMediasoupRemoteTrackResolverFactory,
|
|
8142
8335
|
createInMemorySink,
|
|
8143
8336
|
createJsonlFileSink,
|
|
8144
|
-
createJsonlFileSinkFactory,
|
|
8145
8337
|
createLogger,
|
|
8146
8338
|
createP2pRemoteTrackResolverFactory,
|
|
8147
8339
|
defaultCallSummaryConfig,
|