@webex/internal-plugin-metrics 3.12.0-next.6 → 3.12.0-next.60

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.
Files changed (98) hide show
  1. package/.repo-context.json +466 -0
  2. package/.sdd/manifest.json +626 -0
  3. package/AGENTS.md +208 -0
  4. package/README.md +274 -0
  5. package/dist/automated-user.js +19 -0
  6. package/dist/automated-user.js.map +1 -0
  7. package/dist/batcher.js +3 -0
  8. package/dist/batcher.js.map +1 -1
  9. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js +613 -53
  10. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js.map +1 -1
  11. package/dist/call-diagnostic/call-diagnostic-metrics.js +240 -103
  12. package/dist/call-diagnostic/call-diagnostic-metrics.js.map +1 -1
  13. package/dist/call-diagnostic/call-diagnostic-metrics.util.js +5 -0
  14. package/dist/call-diagnostic/call-diagnostic-metrics.util.js.map +1 -1
  15. package/dist/call-diagnostic/config.js +19 -4
  16. package/dist/call-diagnostic/config.js.map +1 -1
  17. package/dist/config.js +8 -0
  18. package/dist/config.js.map +1 -1
  19. package/dist/index.js +25 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/metrics.js +118 -5
  22. package/dist/metrics.js.map +1 -1
  23. package/dist/metrics.types.js +4 -0
  24. package/dist/metrics.types.js.map +1 -1
  25. package/dist/network-telemetry.js +610 -0
  26. package/dist/network-telemetry.js.map +1 -0
  27. package/dist/new-metrics.js +63 -17
  28. package/dist/new-metrics.js.map +1 -1
  29. package/dist/privacy-and-security-permission-enricher/constants.js +18 -0
  30. package/dist/privacy-and-security-permission-enricher/constants.js.map +1 -0
  31. package/dist/privacy-and-security-permission-enricher/index.js +143 -0
  32. package/dist/privacy-and-security-permission-enricher/index.js.map +1 -0
  33. package/dist/privacy-and-security-permission-enricher/types.js +7 -0
  34. package/dist/privacy-and-security-permission-enricher/types.js.map +1 -0
  35. package/dist/privacy-and-security-permission-enricher/utils.js +71 -0
  36. package/dist/privacy-and-security-permission-enricher/utils.js.map +1 -0
  37. package/dist/types/automated-user.d.ts +2 -0
  38. package/dist/types/call-diagnostic/call-diagnostic-metrics-latencies.d.ts +200 -5
  39. package/dist/types/call-diagnostic/call-diagnostic-metrics.d.ts +77 -17
  40. package/dist/types/call-diagnostic/config.d.ts +6 -0
  41. package/dist/types/config.d.ts +9 -0
  42. package/dist/types/index.d.ts +5 -3
  43. package/dist/types/metrics.types.d.ts +11 -5
  44. package/dist/types/network-telemetry.d.ts +108 -0
  45. package/dist/types/new-metrics.d.ts +17 -1
  46. package/dist/types/privacy-and-security-permission-enricher/constants.d.ts +6 -0
  47. package/dist/types/privacy-and-security-permission-enricher/index.d.ts +40 -0
  48. package/dist/types/privacy-and-security-permission-enricher/types.d.ts +14 -0
  49. package/dist/types/privacy-and-security-permission-enricher/utils.d.ts +5 -0
  50. package/dist/types/unhandled-exception-telemetry/index.d.ts +60 -0
  51. package/dist/types/unhandled-exception-telemetry/utils.d.ts +6 -0
  52. package/dist/unhandled-exception-telemetry/index.js +331 -0
  53. package/dist/unhandled-exception-telemetry/index.js.map +1 -0
  54. package/dist/unhandled-exception-telemetry/utils.js +105 -0
  55. package/dist/unhandled-exception-telemetry/utils.js.map +1 -0
  56. package/docs/adr/index.md +27 -0
  57. package/docs/architecture.md +437 -0
  58. package/docs/getting-started.md +131 -0
  59. package/docs/index.md +52 -0
  60. package/docs/specs/README.md +88 -0
  61. package/package.json +12 -11
  62. package/schemas/repo-context.schema.json +796 -0
  63. package/src/automated-user.ts +16 -0
  64. package/src/batcher.js +4 -0
  65. package/src/call-diagnostic/call-diagnostic-metrics-latencies.ts +737 -60
  66. package/src/call-diagnostic/call-diagnostic-metrics.ts +184 -42
  67. package/src/call-diagnostic/call-diagnostic-metrics.util.ts +5 -0
  68. package/src/call-diagnostic/config.ts +16 -0
  69. package/src/call-diagnostic/docs/README.md +535 -0
  70. package/src/config.js +8 -0
  71. package/src/docs/README.md +703 -0
  72. package/src/index.ts +16 -0
  73. package/src/metrics.js +123 -5
  74. package/src/metrics.types.ts +29 -6
  75. package/src/network-telemetry.ts +757 -0
  76. package/src/new-metrics.ts +56 -6
  77. package/src/privacy-and-security-permission-enricher/constants.ts +33 -0
  78. package/src/privacy-and-security-permission-enricher/docs/README.md +356 -0
  79. package/src/privacy-and-security-permission-enricher/index.ts +142 -0
  80. package/src/privacy-and-security-permission-enricher/types.ts +21 -0
  81. package/src/privacy-and-security-permission-enricher/utils.ts +82 -0
  82. package/src/rtcMetrics/docs/README.md +447 -0
  83. package/src/unhandled-exception-telemetry/docs/README.md +536 -0
  84. package/src/unhandled-exception-telemetry/index.ts +404 -0
  85. package/src/unhandled-exception-telemetry/utils.ts +101 -0
  86. package/test/unit/spec/automated-user.ts +43 -0
  87. package/test/unit/spec/batcher.js +43 -0
  88. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-batcher.ts +10 -1
  89. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-latencies.ts +1336 -131
  90. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.ts +1265 -207
  91. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts +27 -0
  92. package/test/unit/spec/metrics.js +14 -5
  93. package/test/unit/spec/network-telemetry.ts +610 -0
  94. package/test/unit/spec/new-metrics.ts +203 -50
  95. package/test/unit/spec/prelogin-metrics-batcher.ts +71 -36
  96. package/test/unit/spec/privacy-and-security-permission-enricher.ts +318 -0
  97. package/test/unit/spec/unhandled-exception-telemetry/utils.ts +109 -0
  98. package/test/unit/spec/unhandled-exception-telemetry.ts +689 -0
@@ -5,11 +5,12 @@ import sinon from 'sinon';
5
5
  describe('internal-plugin-metrics', () => {
6
6
  describe('CallDiagnosticLatencies', () => {
7
7
  let cdl: CallDiagnosticLatencies;
8
+ let clock: ReturnType<typeof sinon.useFakeTimers>;
8
9
  var now = new Date();
9
10
 
10
11
  beforeEach(() => {
11
12
  sinon.createSandbox();
12
- sinon.useFakeTimers(now.getTime());
13
+ clock = sinon.useFakeTimers(now.getTime());
13
14
  const webex = {
14
15
  meetings: {
15
16
  getBasicMeetingInformation: (id: string) => {
@@ -51,17 +52,17 @@ describe('internal-plugin-metrics', () => {
51
52
 
52
53
  it('should overwrite latency when accumulate is false', () => {
53
54
  assert.deepEqual(cdl.precomputedLatencies.size, 0);
54
- cdl.saveLatency('internal.client.pageJMT', 10, false);
55
+ cdl.saveLatency('internal.client.pageJMT', 10, {accumulate: false});
55
56
  assert.deepEqual(cdl.precomputedLatencies.size, 1);
56
57
  assert.deepEqual(cdl.precomputedLatencies.get('internal.client.pageJMT'), 10);
57
- cdl.saveLatency('internal.client.pageJMT', 20, false);
58
+ cdl.saveLatency('internal.client.pageJMT', 20, {accumulate: false});
58
59
  assert.deepEqual(cdl.precomputedLatencies.size, 1);
59
60
  assert.deepEqual(cdl.precomputedLatencies.get('internal.client.pageJMT'), 20);
60
61
  });
61
62
 
62
63
  it('should save latency correctly when accumulate is true', () => {
63
64
  assert.deepEqual(cdl.precomputedLatencies.size, 0);
64
- cdl.saveLatency('internal.client.pageJMT', 10, true);
65
+ cdl.saveLatency('internal.client.pageJMT', 10, {accumulate: true});
65
66
  assert.deepEqual(cdl.precomputedLatencies.size, 1);
66
67
  assert.deepEqual(cdl.precomputedLatencies.get('internal.client.pageJMT'), 10);
67
68
  });
@@ -71,11 +72,31 @@ describe('internal-plugin-metrics', () => {
71
72
  cdl.saveLatency('internal.client.pageJMT', 10);
72
73
  assert.deepEqual(cdl.precomputedLatencies.size, 1);
73
74
  assert.deepEqual(cdl.precomputedLatencies.get('internal.client.pageJMT'), 10);
74
- cdl.saveLatency('internal.client.pageJMT', 10, true);
75
+ cdl.saveLatency('internal.client.pageJMT', 10, {accumulate: true});
75
76
  assert.deepEqual(cdl.precomputedLatencies.size, 1);
76
77
  assert.deepEqual(cdl.precomputedLatencies.get('internal.client.pageJMT'), 20);
77
78
  });
78
79
 
80
+ it('accumulates when accumulate is passed as the legacy boolean argument', () => {
81
+ assert.deepEqual(cdl.precomputedLatencies.size, 0);
82
+ // @ts-ignore - legacy boolean third argument from older (untyped) SDK/plugin consumers
83
+ cdl.saveLatency('internal.client.pageJMT', 10, true);
84
+ // @ts-ignore - legacy boolean third argument from older (untyped) SDK/plugin consumers
85
+ cdl.saveLatency('internal.client.pageJMT', 15, true);
86
+ assert.deepEqual(cdl.precomputedLatencies.size, 1);
87
+ assert.deepEqual(cdl.precomputedLatencies.get('internal.client.pageJMT'), 25);
88
+ });
89
+
90
+ it('overwrites when the legacy boolean argument is false', () => {
91
+ assert.deepEqual(cdl.precomputedLatencies.size, 0);
92
+ // @ts-ignore - legacy boolean third argument from older (untyped) SDK/plugin consumers
93
+ cdl.saveLatency('internal.client.pageJMT', 10, false);
94
+ // @ts-ignore - legacy boolean third argument from older (untyped) SDK/plugin consumers
95
+ cdl.saveLatency('internal.client.pageJMT', 15, false);
96
+ assert.deepEqual(cdl.precomputedLatencies.size, 1);
97
+ assert.deepEqual(cdl.precomputedLatencies.get('internal.client.pageJMT'), 15);
98
+ });
99
+
79
100
  it('should save only first timestamp correctly', () => {
80
101
  assert.deepEqual(cdl.latencyTimestamps.size, 0);
81
102
  cdl.saveFirstTimestampOnly('client.alert.displayed', 10);
@@ -108,11 +129,1174 @@ describe('internal-plugin-metrics', () => {
108
129
  assert.deepEqual(cdl.latencyTimestamps.size, 2);
109
130
  cdl.saveLatency('internal.api.fetch.intelligence.models', 42);
110
131
  assert.deepEqual(cdl.precomputedLatencies.size, 1);
132
+ cdl.saveTimestamp({
133
+ key: 'internal.client.locus.sync.start',
134
+ value: 1,
135
+ options: {
136
+ meetingId: 'meeting-1',
137
+ dataSetName: 'main',
138
+ randomBackoffTime: 10,
139
+ trackingId: 'sync-tracking-id',
140
+ },
141
+ });
142
+ assert.deepEqual(cdl.meetingLatencies.size, 1);
111
143
 
112
144
  cdl.clearTimestamps();
113
145
 
114
146
  assert.deepEqual(cdl.latencyTimestamps.size, 0);
115
147
  assert.deepEqual(cdl.precomputedLatencies.size, 0);
148
+ assert.deepEqual(cdl.meetingLatencies.size, 0);
149
+ });
150
+
151
+ describe('locus sync latencies', () => {
152
+ it('uses pending sync backoff latency when sync starts', () => {
153
+ cdl.saveLatency('internal.client.locus.sync.random.backoff', 10.4, {
154
+ meetingId: 'meeting-1',
155
+ dataSetName: 'main',
156
+ });
157
+ cdl.saveTimestamp({
158
+ key: 'internal.client.locus.sync.start',
159
+ value: 100,
160
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-id'},
161
+ });
162
+ cdl.saveTimestamp({
163
+ key: 'internal.client.locus.sync.request',
164
+ value: 110,
165
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-id'},
166
+ });
167
+ cdl.saveTimestamp({
168
+ key: 'internal.client.locus.sync.response',
169
+ value: 130,
170
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-id'},
171
+ });
172
+ cdl.saveTimestamp({
173
+ key: 'internal.client.locus.sync.message.received',
174
+ value: 140,
175
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-id'},
176
+ });
177
+
178
+ assert.deepEqual(cdl.getLocusSyncLatency('meeting-1', 'sync-tracking-id'), {
179
+ randomBackoffTime: 10,
180
+ hashtreePrepTime: 0,
181
+ hashtreeResponseTime: 0,
182
+ syncPrepTime: 10,
183
+ syncResponseTime: 20,
184
+ syncMessageReceiveTime: 30,
185
+ totalTime: 40,
186
+ });
187
+ });
188
+
189
+ it('calculates sync latency values from milestones', () => {
190
+ cdl.saveLatency('internal.client.locus.sync.random.backoff', 10.4, {
191
+ meetingId: 'meeting-1',
192
+ dataSetName: 'main',
193
+ });
194
+ cdl.saveTimestamp({
195
+ key: 'internal.client.locus.sync.start',
196
+ value: 100,
197
+ options: {
198
+ meetingId: 'meeting-1',
199
+ dataSetName: 'main',
200
+ trackingId: 'sync-tracking-id',
201
+ },
202
+ });
203
+ cdl.saveTimestamp({
204
+ key: 'internal.client.locus.hashtree.request',
205
+ value: 105,
206
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-id'},
207
+ });
208
+ cdl.saveTimestamp({
209
+ key: 'internal.client.locus.hashtree.response',
210
+ value: 125,
211
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-id'},
212
+ });
213
+ cdl.saveTimestamp({
214
+ key: 'internal.client.locus.sync.request',
215
+ value: 128,
216
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-id'},
217
+ });
218
+ cdl.saveTimestamp({
219
+ key: 'internal.client.locus.sync.response',
220
+ value: 143,
221
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-id'},
222
+ });
223
+ cdl.saveTimestamp({
224
+ key: 'internal.client.locus.sync.message.received',
225
+ value: 150,
226
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-id'},
227
+ });
228
+
229
+ assert.deepEqual(cdl.getLocusSyncLatency('meeting-1', 'sync-tracking-id'), {
230
+ randomBackoffTime: 10,
231
+ hashtreePrepTime: 5,
232
+ hashtreeResponseTime: 20,
233
+ syncPrepTime: 3,
234
+ syncResponseTime: 15,
235
+ syncMessageReceiveTime: 22,
236
+ totalTime: 50,
237
+ });
238
+ });
239
+
240
+ it('calculates sync latency values when hash tree request is skipped', () => {
241
+ cdl.saveTimestamp({
242
+ key: 'internal.client.locus.sync.start',
243
+ value: 100,
244
+ options: {
245
+ meetingId: 'meeting-1',
246
+ dataSetName: 'main',
247
+ randomBackoffTime: 0,
248
+ trackingId: 'sync-tracking-1',
249
+ },
250
+ });
251
+ cdl.saveTimestamp({
252
+ key: 'internal.client.locus.sync.request',
253
+ value: 110,
254
+ options: {
255
+ meetingId: 'meeting-1',
256
+ dataSetName: 'main',
257
+ trackingId: 'sync-tracking-1',
258
+ },
259
+ });
260
+ cdl.saveTimestamp({
261
+ key: 'internal.client.locus.sync.response',
262
+ value: 130,
263
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-1'},
264
+ });
265
+ cdl.saveTimestamp({
266
+ key: 'internal.client.locus.sync.message.received',
267
+ value: 140,
268
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-1'},
269
+ });
270
+
271
+ assert.deepEqual(cdl.getLocusSyncLatency('meeting-1', 'sync-tracking-1'), {
272
+ randomBackoffTime: 0,
273
+ hashtreePrepTime: 0,
274
+ hashtreeResponseTime: 0,
275
+ syncPrepTime: 10,
276
+ syncResponseTime: 20,
277
+ syncMessageReceiveTime: 30,
278
+ totalTime: 40,
279
+ });
280
+ });
281
+
282
+ it('associates sync response tracking id and completes the matching meeting record', () => {
283
+ clock.setSystemTime(150);
284
+
285
+ try {
286
+ cdl.saveTimestamp({
287
+ key: 'internal.client.locus.sync.start',
288
+ value: 100,
289
+ options: {
290
+ meetingId: 'meeting-1',
291
+ dataSetName: 'main',
292
+ randomBackoffTime: 0,
293
+ trackingId: 'our-sync-tracking-id',
294
+ },
295
+ });
296
+ cdl.saveTimestamp({
297
+ key: 'internal.client.locus.sync.request',
298
+ value: 110,
299
+ options: {
300
+ meetingId: 'meeting-1',
301
+ dataSetName: 'main',
302
+ trackingId: 'our-sync-tracking-id',
303
+ },
304
+ });
305
+ cdl.saveTimestamp({
306
+ key: 'internal.client.locus.sync.response',
307
+ value: 130,
308
+ options: {
309
+ meetingId: 'meeting-1',
310
+ dataSetName: 'main',
311
+ trackingId: 'our-sync-tracking-id',
312
+ },
313
+ });
314
+ // the LLM state-update message that gates the metric arrives for this sync
315
+ cdl.saveTimestamp({
316
+ key: 'internal.client.locus.sync.message.received',
317
+ value: 150,
318
+ options: {
319
+ meetingId: 'meeting-1',
320
+ dataSetName: 'main',
321
+ trackingId: 'our-sync-tracking-id',
322
+ },
323
+ });
324
+ cdl.saveLatency('internal.client.locus.sync.random.backoff', 99, {
325
+ meetingId: 'meeting-1',
326
+ dataSetName: 'main',
327
+ });
328
+
329
+ // eager completion: both the /sync response and the LLM message are present
330
+ assert.deepEqual(cdl.completeLocusSyncLatency('meeting-1', 'our-sync-tracking-id'), {
331
+ dataSet: 'main',
332
+ syncLatency: {
333
+ randomBackoffTime: 0,
334
+ hashtreePrepTime: 0,
335
+ hashtreeResponseTime: 0,
336
+ syncPrepTime: 10,
337
+ syncResponseTime: 20,
338
+ syncMessageReceiveTime: 40,
339
+ totalTime: 50,
340
+ },
341
+ });
342
+ // completing the metric removes the matching record, leaving only the pending
343
+ // backoff record created above for the next sync to consume
344
+ assert.lengthOf(cdl.meetingLatencies.get('meeting-1'), 1);
345
+
346
+ cdl.saveTimestamp({
347
+ key: 'internal.client.locus.sync.start',
348
+ value: 200,
349
+ options: {
350
+ meetingId: 'meeting-1',
351
+ dataSetName: 'main',
352
+ trackingId: 'next-sync-tracking-id',
353
+ },
354
+ });
355
+ assert.deepInclude(cdl.meetingLatencies.get('meeting-1'), {
356
+ locusSync: {
357
+ meetingId: 'meeting-1',
358
+ dataSetName: 'main',
359
+ randomBackoffTime: 99,
360
+ trackingId: 'next-sync-tracking-id',
361
+ syncStart: 200,
362
+ },
363
+ });
364
+ } finally {
365
+ clock.setSystemTime(now.getTime());
366
+ }
367
+ });
368
+
369
+ it('does not bind message.received when tracking id does not match any record', () => {
370
+ clock.setSystemTime(500);
371
+
372
+ try {
373
+ cdl.saveTimestamp({
374
+ key: 'internal.client.locus.sync.start',
375
+ value: 100,
376
+ options: {
377
+ meetingId: 'meeting-1',
378
+ dataSetName: 'main',
379
+ randomBackoffTime: 0,
380
+ trackingId: 'sync-tracking-id',
381
+ },
382
+ });
383
+ cdl.saveTimestamp({
384
+ key: 'internal.client.locus.sync.request',
385
+ value: 110,
386
+ options: {
387
+ meetingId: 'meeting-1',
388
+ dataSetName: 'main',
389
+ trackingId: 'sync-tracking-id',
390
+ },
391
+ });
392
+ cdl.saveTimestamp({
393
+ key: 'internal.client.locus.sync.response',
394
+ value: 130,
395
+ options: {
396
+ meetingId: 'meeting-1',
397
+ dataSetName: 'main',
398
+ trackingId: 'sync-tracking-id',
399
+ },
400
+ });
401
+ cdl.saveTimestamp({
402
+ key: 'internal.client.locus.sync.message.received',
403
+ value: 140,
404
+ options: {
405
+ meetingId: 'meeting-1',
406
+ dataSetName: 'main',
407
+ trackingId: 'llm-envelope-id',
408
+ },
409
+ });
410
+
411
+ assert.isUndefined(cdl.completeLocusSyncLatency('meeting-1', 'llm-envelope-id'));
412
+ assert.isUndefined(cdl.meetingLatencies.get('meeting-1')?.[0].locusSync.messageReceived);
413
+ } finally {
414
+ clock.setSystemTime(now.getTime());
415
+ }
416
+ });
417
+
418
+ it('does not allow totalTime to drop below sync response duration when message.received is stale', () => {
419
+ cdl.saveTimestamp({
420
+ key: 'internal.client.locus.sync.start',
421
+ value: 100,
422
+ options: {
423
+ meetingId: 'meeting-1',
424
+ dataSetName: 'main',
425
+ randomBackoffTime: 0,
426
+ trackingId: 'sync-tracking-id',
427
+ },
428
+ });
429
+ cdl.saveTimestamp({
430
+ key: 'internal.client.locus.sync.request',
431
+ value: 101,
432
+ options: {
433
+ meetingId: 'meeting-1',
434
+ dataSetName: 'main',
435
+ trackingId: 'sync-tracking-id',
436
+ },
437
+ });
438
+ cdl.saveTimestamp({
439
+ key: 'internal.client.locus.sync.response',
440
+ value: 475,
441
+ options: {
442
+ meetingId: 'meeting-1',
443
+ dataSetName: 'main',
444
+ trackingId: 'sync-tracking-id',
445
+ },
446
+ });
447
+ cdl.saveTimestamp({
448
+ key: 'internal.client.locus.sync.message.received',
449
+ value: 90,
450
+ options: {
451
+ meetingId: 'meeting-1',
452
+ dataSetName: 'main',
453
+ trackingId: 'sync-tracking-id',
454
+ },
455
+ });
456
+
457
+ assert.deepEqual(cdl.completeLocusSyncLatency('meeting-1', 'sync-tracking-id'), {
458
+ dataSet: 'main',
459
+ syncLatency: {
460
+ randomBackoffTime: 0,
461
+ hashtreePrepTime: 0,
462
+ hashtreeResponseTime: 0,
463
+ syncPrepTime: 1,
464
+ syncResponseTime: 374,
465
+ syncMessageReceiveTime: 0,
466
+ totalTime: 375,
467
+ },
468
+ });
469
+ });
470
+
471
+ it('drops and consumes a completed sync latency record when a segment exceeds the clock skew threshold', () => {
472
+ cdl.saveTimestamp({
473
+ key: 'internal.client.locus.sync.start',
474
+ value: 100,
475
+ options: {
476
+ meetingId: 'meeting-1',
477
+ dataSetName: 'main',
478
+ trackingId: 'sync-tracking-id',
479
+ },
480
+ });
481
+ cdl.saveTimestamp({
482
+ key: 'internal.client.locus.sync.request',
483
+ value: 110,
484
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-id'},
485
+ });
486
+ cdl.saveTimestamp({
487
+ key: 'internal.client.locus.sync.response',
488
+ value: 600111,
489
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-id'},
490
+ });
491
+ cdl.saveTimestamp({
492
+ key: 'internal.client.locus.sync.message.received',
493
+ value: 600112,
494
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-id'},
495
+ });
496
+
497
+ assert.isUndefined(cdl.completeLocusSyncLatency('meeting-1', 'sync-tracking-id'));
498
+ assert.isFalse(cdl.meetingLatencies.has('meeting-1'));
499
+ });
500
+
501
+ it('allows a completed sync latency record when the largest segment equals the clock skew threshold', () => {
502
+ cdl.saveTimestamp({
503
+ key: 'internal.client.locus.sync.start',
504
+ value: 100,
505
+ options: {
506
+ meetingId: 'meeting-1',
507
+ dataSetName: 'main',
508
+ trackingId: 'sync-tracking-id',
509
+ },
510
+ });
511
+ cdl.saveTimestamp({
512
+ key: 'internal.client.locus.sync.request',
513
+ value: 110,
514
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-id'},
515
+ });
516
+ cdl.saveTimestamp({
517
+ key: 'internal.client.locus.sync.response',
518
+ value: 600100,
519
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-id'},
520
+ });
521
+ cdl.saveTimestamp({
522
+ key: 'internal.client.locus.sync.message.received',
523
+ value: 600100,
524
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-id'},
525
+ });
526
+
527
+ assert.deepEqual(cdl.completeLocusSyncLatency('meeting-1', 'sync-tracking-id'), {
528
+ dataSet: 'main',
529
+ syncLatency: {
530
+ randomBackoffTime: 0,
531
+ hashtreePrepTime: 0,
532
+ hashtreeResponseTime: 0,
533
+ syncPrepTime: 10,
534
+ syncResponseTime: 599990,
535
+ syncMessageReceiveTime: 599990,
536
+ totalTime: 600000,
537
+ },
538
+ });
539
+ assert.isFalse(cdl.meetingLatencies.has('meeting-1'));
540
+ });
541
+
542
+ it('matches exact record by tracking id on first pass', () => {
543
+ clock.setSystemTime(300);
544
+
545
+ try {
546
+ cdl.saveTimestamp({
547
+ key: 'internal.client.locus.sync.start',
548
+ value: 100,
549
+ options: {
550
+ meetingId: 'meeting-1',
551
+ dataSetName: 'main',
552
+ randomBackoffTime: 0,
553
+ trackingId: 'sync-tracking-1',
554
+ },
555
+ });
556
+ cdl.saveTimestamp({
557
+ key: 'internal.client.locus.sync.request',
558
+ value: 110,
559
+ options: {
560
+ meetingId: 'meeting-1',
561
+ dataSetName: 'main',
562
+ trackingId: 'sync-tracking-1',
563
+ },
564
+ });
565
+ cdl.saveTimestamp({
566
+ key: 'internal.client.locus.sync.response',
567
+ value: 130,
568
+ options: {
569
+ meetingId: 'meeting-1',
570
+ dataSetName: 'main',
571
+ trackingId: 'sync-tracking-1',
572
+ },
573
+ });
574
+
575
+ cdl.saveTimestamp({
576
+ key: 'internal.client.locus.sync.start',
577
+ value: 200,
578
+ options: {
579
+ meetingId: 'meeting-1',
580
+ dataSetName: 'main',
581
+ randomBackoffTime: 0,
582
+ trackingId: 'sync-tracking-2',
583
+ },
584
+ });
585
+ cdl.saveTimestamp({
586
+ key: 'internal.client.locus.sync.request',
587
+ value: 210,
588
+ options: {
589
+ meetingId: 'meeting-1',
590
+ dataSetName: 'main',
591
+ trackingId: 'sync-tracking-2',
592
+ },
593
+ });
594
+ cdl.saveTimestamp({
595
+ key: 'internal.client.locus.sync.response',
596
+ value: 230,
597
+ options: {
598
+ meetingId: 'meeting-1',
599
+ dataSetName: 'main',
600
+ trackingId: 'sync-tracking-2',
601
+ },
602
+ });
603
+ cdl.saveTimestamp({
604
+ key: 'internal.client.locus.sync.message.received',
605
+ value: 240,
606
+ options: {
607
+ meetingId: 'meeting-1',
608
+ dataSetName: 'main',
609
+ trackingId: 'sync-tracking-2',
610
+ },
611
+ });
612
+
613
+ assert.deepEqual(cdl.completeLocusSyncLatency('meeting-1', 'sync-tracking-2'), {
614
+ dataSet: 'main',
615
+ syncLatency: {
616
+ randomBackoffTime: 0,
617
+ hashtreePrepTime: 0,
618
+ hashtreeResponseTime: 0,
619
+ syncPrepTime: 10,
620
+ syncResponseTime: 20,
621
+ syncMessageReceiveTime: 30,
622
+ totalTime: 40,
623
+ },
624
+ });
625
+ } finally {
626
+ clock.setSystemTime(now.getTime());
627
+ }
628
+ });
629
+
630
+ it('does not bind message.received when tracking id does not match any record', () => {
631
+ cdl.saveTimestamp({
632
+ key: 'internal.client.locus.sync.start',
633
+ value: 100,
634
+ options: {
635
+ meetingId: 'meeting-1',
636
+ dataSetName: 'main',
637
+ randomBackoffTime: 0,
638
+ trackingId: 'sync-tracking-1',
639
+ },
640
+ });
641
+ cdl.saveTimestamp({
642
+ key: 'internal.client.locus.sync.request',
643
+ value: 110,
644
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-1'},
645
+ });
646
+ cdl.saveTimestamp({
647
+ key: 'internal.client.locus.sync.response',
648
+ value: 130,
649
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-1'},
650
+ });
651
+
652
+ cdl.saveTimestamp({
653
+ key: 'internal.client.locus.sync.start',
654
+ value: 200,
655
+ options: {
656
+ meetingId: 'meeting-1',
657
+ dataSetName: 'main',
658
+ randomBackoffTime: 0,
659
+ trackingId: 'sync-tracking-2',
660
+ },
661
+ });
662
+ cdl.saveTimestamp({
663
+ key: 'internal.client.locus.sync.request',
664
+ value: 210,
665
+ options: {
666
+ meetingId: 'meeting-1',
667
+ dataSetName: 'main',
668
+ trackingId: 'sync-tracking-2',
669
+ },
670
+ });
671
+ cdl.saveTimestamp({
672
+ key: 'internal.client.locus.sync.response',
673
+ value: 230,
674
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-2'},
675
+ });
676
+
677
+ cdl.saveTimestamp({
678
+ key: 'internal.client.locus.sync.message.received',
679
+ value: 120,
680
+ options: {
681
+ meetingId: 'meeting-1',
682
+ dataSetName: 'main',
683
+ trackingId: 'llm-unknown-id',
684
+ },
685
+ });
686
+
687
+ const records = cdl.meetingLatencies.get('meeting-1');
688
+
689
+ assert.isDefined(records);
690
+ assert.lengthOf(records!, 2);
691
+ assert.isUndefined(records![0].locusSync.messageReceived);
692
+ assert.isUndefined(records![1].locusSync.messageReceived);
693
+ });
694
+
695
+ it('binds message.received when the tracking id matches the only record', () => {
696
+ cdl.saveTimestamp({
697
+ key: 'internal.client.locus.sync.start',
698
+ value: 100,
699
+ options: {
700
+ meetingId: 'meeting-1',
701
+ dataSetName: 'main',
702
+ randomBackoffTime: 0,
703
+ trackingId: 'sync-tracking-id',
704
+ },
705
+ });
706
+ cdl.saveTimestamp({
707
+ key: 'internal.client.locus.sync.request',
708
+ value: 110,
709
+ options: {
710
+ meetingId: 'meeting-1',
711
+ dataSetName: 'main',
712
+ trackingId: 'sync-tracking-id',
713
+ },
714
+ });
715
+ cdl.saveTimestamp({
716
+ key: 'internal.client.locus.sync.response',
717
+ value: 130,
718
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-id'},
719
+ });
720
+
721
+ cdl.saveTimestamp({
722
+ key: 'internal.client.locus.sync.message.received',
723
+ value: 140,
724
+ options: {
725
+ meetingId: 'meeting-1',
726
+ dataSetName: 'main',
727
+ trackingId: 'sync-tracking-id',
728
+ },
729
+ });
730
+
731
+ const records = cdl.meetingLatencies.get('meeting-1');
732
+
733
+ assert.isDefined(records);
734
+ assert.lengthOf(records!, 1);
735
+ assert.equal(records![0].locusSync.messageReceived, 140);
736
+ });
737
+
738
+ it('returns undefined when the tracking id does not match', () => {
739
+ clock.setSystemTime(150);
740
+
741
+ try {
742
+ cdl.saveTimestamp({
743
+ key: 'internal.client.locus.sync.start',
744
+ value: 100,
745
+ options: {
746
+ meetingId: 'meeting-1',
747
+ dataSetName: 'main',
748
+ randomBackoffTime: 0,
749
+ trackingId: 'our-sync-tracking-id',
750
+ },
751
+ });
752
+ cdl.saveTimestamp({
753
+ key: 'internal.client.locus.sync.request',
754
+ value: 110,
755
+ options: {
756
+ meetingId: 'meeting-1',
757
+ dataSetName: 'main',
758
+ trackingId: 'our-sync-tracking-id',
759
+ },
760
+ });
761
+ cdl.saveTimestamp({
762
+ key: 'internal.client.locus.sync.response',
763
+ value: 130,
764
+ options: {
765
+ meetingId: 'meeting-1',
766
+ dataSetName: 'main',
767
+ trackingId: 'our-sync-tracking-id',
768
+ },
769
+ });
770
+
771
+ assert.isUndefined(cdl.completeLocusSyncLatency('meeting-1', 'llm-envelope-tracking-id'));
772
+ } finally {
773
+ clock.setSystemTime(now.getTime());
774
+ }
775
+ });
776
+
777
+ it('does not clean up older never-completed record for the same dataset when newer sync completes', () => {
778
+ // first sync: all sync milestones recorded, but its completing LLM event never arrives
779
+ cdl.saveTimestamp({
780
+ key: 'internal.client.locus.sync.start',
781
+ value: 100,
782
+ options: {
783
+ meetingId: 'meeting-1',
784
+ dataSetName: 'main',
785
+ randomBackoffTime: 0,
786
+ trackingId: 'sync-tracking-1',
787
+ },
788
+ });
789
+ cdl.saveTimestamp({
790
+ key: 'internal.client.locus.sync.request',
791
+ value: 110,
792
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-1'},
793
+ });
794
+ cdl.saveTimestamp({
795
+ key: 'internal.client.locus.sync.response',
796
+ value: 130,
797
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-1'},
798
+ });
799
+
800
+ // a second sync for the same dataset starts before the first one completed.
801
+ // because the first record already has syncStart set, it is not reused and a
802
+ // brand new record is created for the second sync
803
+ cdl.saveTimestamp({
804
+ key: 'internal.client.locus.sync.start',
805
+ value: 200,
806
+ options: {
807
+ meetingId: 'meeting-1',
808
+ dataSetName: 'main',
809
+ randomBackoffTime: 0,
810
+ trackingId: 'sync-tracking-2',
811
+ },
812
+ });
813
+ cdl.saveTimestamp({
814
+ key: 'internal.client.locus.sync.request',
815
+ value: 210,
816
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-2'},
817
+ });
818
+ cdl.saveTimestamp({
819
+ key: 'internal.client.locus.sync.response',
820
+ value: 230,
821
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-2'},
822
+ });
823
+ cdl.saveTimestamp({
824
+ key: 'internal.client.locus.sync.message.received',
825
+ value: 240,
826
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-2'},
827
+ });
828
+
829
+ // both records exist before completion
830
+ assert.lengthOf(cdl.meetingLatencies.get('meeting-1')!, 2);
831
+
832
+ // completing the second sync removes only its own record; the older pending record is left
833
+ // in place because a late LLM message could still complete it (stale cleanup is handled by
834
+ // the TTL / capacity pruning, not by completion).
835
+ const completed = cdl.completeLocusSyncLatency('meeting-1', 'sync-tracking-2');
836
+
837
+ assert.isDefined(completed);
838
+ assert.equal(completed!.dataSet, 'main');
839
+
840
+ const remaining = cdl.meetingLatencies.get('meeting-1');
841
+
842
+ assert.isDefined(remaining);
843
+ assert.lengthOf(remaining!, 1);
844
+ assert.equal(remaining![0].locusSync.trackingId, 'sync-tracking-1');
845
+ });
846
+
847
+ it('prunes an incomplete record once it is older than the pending TTL', () => {
848
+ // an incomplete sync record that never received its LLM message
849
+ cdl.saveTimestamp({
850
+ key: 'internal.client.locus.sync.start',
851
+ value: 100,
852
+ options: {
853
+ meetingId: 'meeting-1',
854
+ dataSetName: 'main',
855
+ trackingId: 'stale-tracking-id',
856
+ },
857
+ });
858
+
859
+ // a brand new sync for the same dataset starts more than the TTL later
860
+ cdl.saveTimestamp({
861
+ key: 'internal.client.locus.sync.start',
862
+ value: 100 + 5 * 60 * 1000 + 1,
863
+ options: {
864
+ meetingId: 'meeting-1',
865
+ dataSetName: 'main',
866
+ trackingId: 'fresh-tracking-id',
867
+ },
868
+ });
869
+
870
+ const records = cdl.meetingLatencies.get('meeting-1');
871
+
872
+ assert.isDefined(records);
873
+ assert.lengthOf(records!, 1);
874
+ assert.equal(records![0].locusSync.trackingId, 'fresh-tracking-id');
875
+ });
876
+
877
+ it('keeps an incomplete record that is still within the pending TTL', () => {
878
+ cdl.saveTimestamp({
879
+ key: 'internal.client.locus.sync.start',
880
+ value: 100,
881
+ options: {
882
+ meetingId: 'meeting-1',
883
+ dataSetName: 'main',
884
+ trackingId: 'pending-tracking-id',
885
+ },
886
+ });
887
+
888
+ // a new sync for the same dataset starts well within the TTL window
889
+ cdl.saveTimestamp({
890
+ key: 'internal.client.locus.sync.start',
891
+ value: 100 + 1000,
892
+ options: {
893
+ meetingId: 'meeting-1',
894
+ dataSetName: 'main',
895
+ trackingId: 'fresh-tracking-id',
896
+ },
897
+ });
898
+
899
+ const records = cdl.meetingLatencies.get('meeting-1');
900
+
901
+ assert.isDefined(records);
902
+ assert.lengthOf(records!, 2);
903
+ assert.equal(records![0].locusSync.trackingId, 'pending-tracking-id');
904
+ assert.equal(records![1].locusSync.trackingId, 'fresh-tracking-id');
905
+ });
906
+
907
+ it('does not prune incomplete records from a different dataset', () => {
908
+ cdl.saveTimestamp({
909
+ key: 'internal.client.locus.sync.start',
910
+ value: 100,
911
+ options: {
912
+ meetingId: 'meeting-1',
913
+ dataSetName: 'other',
914
+ trackingId: 'other-tracking-id',
915
+ },
916
+ });
917
+
918
+ // a much later sync for a different dataset must not touch the "other" record
919
+ cdl.saveTimestamp({
920
+ key: 'internal.client.locus.sync.start',
921
+ value: 100 + 5 * 60 * 1000 + 1,
922
+ options: {
923
+ meetingId: 'meeting-1',
924
+ dataSetName: 'main',
925
+ trackingId: 'main-tracking-id',
926
+ },
927
+ });
928
+
929
+ const records = cdl.meetingLatencies.get('meeting-1');
930
+
931
+ assert.isDefined(records);
932
+ assert.lengthOf(records!, 2);
933
+ assert.equal(records![0].locusSync.trackingId, 'other-tracking-id');
934
+ assert.equal(records![1].locusSync.trackingId, 'main-tracking-id');
935
+ });
936
+
937
+ it('caps the number of incomplete records kept per dataset', () => {
938
+ // create more incomplete records than the per-dataset cap, all within the TTL window
939
+ for (let i = 0; i <= 20; i += 1) {
940
+ cdl.saveTimestamp({
941
+ key: 'internal.client.locus.sync.start',
942
+ value: 100 + i,
943
+ options: {
944
+ meetingId: 'meeting-1',
945
+ dataSetName: 'main',
946
+ trackingId: `sync-tracking-${i}`,
947
+ },
948
+ });
949
+ }
950
+
951
+ const records = cdl.meetingLatencies.get('meeting-1');
952
+ const trackingIds = records!.map(({locusSync}) => locusSync.trackingId);
953
+
954
+ assert.isDefined(records);
955
+ assert.lengthOf(records!, 20);
956
+ // the oldest record is dropped, the newest is kept
957
+ assert.notInclude(trackingIds, 'sync-tracking-0');
958
+ assert.include(trackingIds, 'sync-tracking-20');
959
+ });
960
+
961
+ it('does not clean up never-completed records for a different dataset', () => {
962
+ // never-completed sync for dataset "other"
963
+ cdl.saveTimestamp({
964
+ key: 'internal.client.locus.sync.start',
965
+ value: 100,
966
+ options: {
967
+ meetingId: 'meeting-1',
968
+ dataSetName: 'other',
969
+ randomBackoffTime: 0,
970
+ trackingId: 'other-tracking-1',
971
+ },
972
+ });
973
+ cdl.saveTimestamp({
974
+ key: 'internal.client.locus.sync.request',
975
+ value: 110,
976
+ options: {meetingId: 'meeting-1', dataSetName: 'other', trackingId: 'other-tracking-1'},
977
+ });
978
+ cdl.saveTimestamp({
979
+ key: 'internal.client.locus.sync.response',
980
+ value: 130,
981
+ options: {meetingId: 'meeting-1', dataSetName: 'other', trackingId: 'other-tracking-1'},
982
+ });
983
+
984
+ // completed sync for dataset "main"
985
+ cdl.saveTimestamp({
986
+ key: 'internal.client.locus.sync.start',
987
+ value: 200,
988
+ options: {
989
+ meetingId: 'meeting-1',
990
+ dataSetName: 'main',
991
+ randomBackoffTime: 0,
992
+ trackingId: 'main-tracking-1',
993
+ },
994
+ });
995
+ cdl.saveTimestamp({
996
+ key: 'internal.client.locus.sync.request',
997
+ value: 210,
998
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'main-tracking-1'},
999
+ });
1000
+ cdl.saveTimestamp({
1001
+ key: 'internal.client.locus.sync.response',
1002
+ value: 230,
1003
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'main-tracking-1'},
1004
+ });
1005
+ cdl.saveTimestamp({
1006
+ key: 'internal.client.locus.sync.message.received',
1007
+ value: 240,
1008
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'main-tracking-1'},
1009
+ });
1010
+
1011
+ const completed = cdl.completeLocusSyncLatency('meeting-1', 'main-tracking-1');
1012
+
1013
+ assert.isDefined(completed);
1014
+
1015
+ // the "other" dataset record must remain untouched
1016
+ const records = cdl.meetingLatencies.get('meeting-1');
1017
+
1018
+ assert.isDefined(records);
1019
+ assert.lengthOf(records!, 1);
1020
+ assert.equal(records![0].locusSync.dataSetName, 'other');
1021
+ assert.equal(records![0].locusSync.trackingId, 'other-tracking-1');
1022
+ });
1023
+
1024
+ it('keys sync latency records by meeting id', () => {
1025
+ cdl.saveTimestamp({
1026
+ key: 'internal.client.locus.sync.start',
1027
+ value: 100,
1028
+ options: {
1029
+ meetingId: 'meeting-1',
1030
+ dataSetName: 'main',
1031
+ randomBackoffTime: 10,
1032
+ trackingId: 'sync-tracking-1',
1033
+ },
1034
+ });
1035
+ cdl.saveTimestamp({
1036
+ key: 'internal.client.locus.sync.request',
1037
+ value: 110,
1038
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-1'},
1039
+ });
1040
+
1041
+ cdl.saveTimestamp({
1042
+ key: 'internal.client.locus.sync.start',
1043
+ value: 200,
1044
+ options: {
1045
+ meetingId: 'meeting-2',
1046
+ dataSetName: 'main',
1047
+ randomBackoffTime: 23,
1048
+ trackingId: 'sync-tracking-2',
1049
+ },
1050
+ });
1051
+ cdl.saveTimestamp({
1052
+ key: 'internal.client.locus.sync.request',
1053
+ value: 217,
1054
+ options: {meetingId: 'meeting-2', dataSetName: 'main', trackingId: 'sync-tracking-2'},
1055
+ });
1056
+ cdl.saveTimestamp({
1057
+ key: 'internal.client.locus.sync.response',
1058
+ value: 241,
1059
+ options: {meetingId: 'meeting-2', dataSetName: 'main', trackingId: 'sync-tracking-2'},
1060
+ });
1061
+ cdl.saveTimestamp({
1062
+ key: 'internal.client.locus.sync.message.received',
1063
+ value: 268,
1064
+ options: {meetingId: 'meeting-2', dataSetName: 'main', trackingId: 'sync-tracking-2'},
1065
+ });
1066
+
1067
+ cdl.saveTimestamp({
1068
+ key: 'internal.client.locus.sync.response',
1069
+ value: 125,
1070
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-1'},
1071
+ });
1072
+ cdl.saveTimestamp({
1073
+ key: 'internal.client.locus.sync.message.received',
1074
+ value: 150,
1075
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-1'},
1076
+ });
1077
+
1078
+ assert.deepEqual(cdl.getLocusSyncLatency('meeting-1', 'sync-tracking-1'), {
1079
+ randomBackoffTime: 0,
1080
+ hashtreePrepTime: 0,
1081
+ hashtreeResponseTime: 0,
1082
+ syncPrepTime: 10,
1083
+ syncResponseTime: 15,
1084
+ syncMessageReceiveTime: 40,
1085
+ totalTime: 50,
1086
+ });
1087
+ assert.deepEqual(cdl.getLocusSyncLatency('meeting-2', 'sync-tracking-2'), {
1088
+ randomBackoffTime: 0,
1089
+ hashtreePrepTime: 0,
1090
+ hashtreeResponseTime: 0,
1091
+ syncPrepTime: 17,
1092
+ syncResponseTime: 24,
1093
+ syncMessageReceiveTime: 51,
1094
+ totalTime: 68,
1095
+ });
1096
+
1097
+ cdl.clearLocusSyncLatency('main', 'meeting-1');
1098
+
1099
+ assert.isUndefined(cdl.getLocusSyncLatency('meeting-1', 'sync-tracking-1'));
1100
+ assert.isDefined(cdl.getLocusSyncLatency('meeting-2', 'sync-tracking-2'));
1101
+ });
1102
+
1103
+ it('returns undefined and clears state when required milestones are missing', () => {
1104
+ cdl.saveTimestamp({
1105
+ key: 'internal.client.locus.sync.start',
1106
+ value: 100,
1107
+ options: {
1108
+ meetingId: 'meeting-1',
1109
+ dataSetName: 'main',
1110
+ randomBackoffTime: 0,
1111
+ trackingId: 'sync-tracking-id',
1112
+ },
1113
+ });
1114
+ cdl.saveTimestamp({
1115
+ key: 'internal.client.locus.sync.message.received',
1116
+ value: 140,
1117
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-id'},
1118
+ });
1119
+
1120
+ assert.isUndefined(cdl.getLocusSyncLatency('meeting-1', 'sync-tracking-id'));
1121
+ cdl.clearLocusSyncLatency('main', 'meeting-1');
1122
+ assert.isFalse(cdl.meetingLatencies.has('meeting-1'));
1123
+ });
1124
+
1125
+ it('clears only the record matching the tracking id when one is supplied', () => {
1126
+ const stampFullRecord = (trackingId: string, base: number) => {
1127
+ cdl.saveTimestamp({
1128
+ key: 'internal.client.locus.sync.start',
1129
+ value: base,
1130
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId},
1131
+ });
1132
+ cdl.saveTimestamp({
1133
+ key: 'internal.client.locus.sync.request',
1134
+ value: base + 10,
1135
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId},
1136
+ });
1137
+ cdl.saveTimestamp({
1138
+ key: 'internal.client.locus.sync.response',
1139
+ value: base + 25,
1140
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId},
1141
+ });
1142
+ cdl.saveTimestamp({
1143
+ key: 'internal.client.locus.sync.message.received',
1144
+ value: base + 50,
1145
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId},
1146
+ });
1147
+ };
1148
+
1149
+ // Two records for the same dataset/meeting but different tracking ids.
1150
+ stampFullRecord('sync-tracking-1', 100);
1151
+ stampFullRecord('sync-tracking-2', 200);
1152
+
1153
+ assert.isDefined(cdl.getLocusSyncLatency('meeting-1', 'sync-tracking-1'));
1154
+ assert.isDefined(cdl.getLocusSyncLatency('meeting-1', 'sync-tracking-2'));
1155
+
1156
+ // Clearing with a tracking id drops only that record, leaving the other intact.
1157
+ cdl.clearLocusSyncLatency('main', 'meeting-1', 'sync-tracking-1');
1158
+
1159
+ assert.isUndefined(cdl.getLocusSyncLatency('meeting-1', 'sync-tracking-1'));
1160
+ assert.isDefined(cdl.getLocusSyncLatency('meeting-1', 'sync-tracking-2'));
1161
+ });
1162
+
1163
+ describe('recordLocusSyncMessageReceived', () => {
1164
+ const startPendingSync = (trackingId: string) => {
1165
+ cdl.saveTimestamp({
1166
+ key: 'internal.client.locus.sync.start',
1167
+ value: 100,
1168
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId},
1169
+ });
1170
+ };
1171
+
1172
+ it('stamps messageReceived on the matching pending record using the current time', () => {
1173
+ clock.setSystemTime(500);
1174
+
1175
+ try {
1176
+ startPendingSync('sync-tracking-id');
1177
+ cdl.recordLocusSyncMessageReceived('meeting-1', 'sync-tracking-id');
1178
+
1179
+ assert.equal(cdl.meetingLatencies.get('meeting-1')![0].locusSync.messageReceived, 500);
1180
+ } finally {
1181
+ clock.setSystemTime(now.getTime());
1182
+ }
1183
+ });
1184
+
1185
+ it('does not overwrite an already recorded messageReceived', () => {
1186
+ clock.setSystemTime(999);
1187
+
1188
+ try {
1189
+ startPendingSync('sync-tracking-id');
1190
+ cdl.saveTimestamp({
1191
+ key: 'internal.client.locus.sync.message.received',
1192
+ value: 140,
1193
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId: 'sync-tracking-id'},
1194
+ });
1195
+ cdl.recordLocusSyncMessageReceived('meeting-1', 'sync-tracking-id');
1196
+
1197
+ assert.equal(cdl.meetingLatencies.get('meeting-1')![0].locusSync.messageReceived, 140);
1198
+ } finally {
1199
+ clock.setSystemTime(now.getTime());
1200
+ }
1201
+ });
1202
+
1203
+ it('is a no-op when no record matches the tracking id', () => {
1204
+ startPendingSync('sync-tracking-id');
1205
+ cdl.recordLocusSyncMessageReceived('meeting-1', 'some-other-tracking-id');
1206
+
1207
+ assert.isUndefined(cdl.meetingLatencies.get('meeting-1')![0].locusSync.messageReceived);
1208
+ });
1209
+
1210
+ it('is a no-op when trackingId is empty', () => {
1211
+ startPendingSync('sync-tracking-id');
1212
+
1213
+ assert.doesNotThrow(() => cdl.recordLocusSyncMessageReceived('meeting-1', ''));
1214
+ assert.isUndefined(cdl.meetingLatencies.get('meeting-1')![0].locusSync.messageReceived);
1215
+ });
1216
+ });
1217
+
1218
+ describe('completeLocusSyncLatency requires both milestones', () => {
1219
+ const recordSyncRequest = (trackingId: string) => {
1220
+ cdl.saveTimestamp({
1221
+ key: 'internal.client.locus.sync.start',
1222
+ value: 100,
1223
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId},
1224
+ });
1225
+ cdl.saveTimestamp({
1226
+ key: 'internal.client.locus.sync.request',
1227
+ value: 110,
1228
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId},
1229
+ });
1230
+ };
1231
+ const recordSyncResponse = (trackingId: string) =>
1232
+ cdl.saveTimestamp({
1233
+ key: 'internal.client.locus.sync.response',
1234
+ value: 130,
1235
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId},
1236
+ });
1237
+ const recordMessageReceived = (trackingId: string) =>
1238
+ cdl.saveTimestamp({
1239
+ key: 'internal.client.locus.sync.message.received',
1240
+ value: 150,
1241
+ options: {meetingId: 'meeting-1', dataSetName: 'main', trackingId},
1242
+ });
1243
+
1244
+ it('returns undefined and keeps the record when only the /sync response is present', () => {
1245
+ recordSyncRequest('sync-tracking-id');
1246
+ recordSyncResponse('sync-tracking-id');
1247
+
1248
+ assert.isUndefined(cdl.completeLocusSyncLatency('meeting-1', 'sync-tracking-id'));
1249
+ assert.lengthOf(cdl.meetingLatencies.get('meeting-1')!, 1);
1250
+ });
1251
+
1252
+ it('returns undefined and keeps the record when only the LLM message is present', () => {
1253
+ recordSyncRequest('sync-tracking-id');
1254
+ recordMessageReceived('sync-tracking-id');
1255
+
1256
+ assert.isUndefined(cdl.completeLocusSyncLatency('meeting-1', 'sync-tracking-id'));
1257
+ assert.lengthOf(cdl.meetingLatencies.get('meeting-1')!, 1);
1258
+ });
1259
+
1260
+ it('emits and consumes the record when the /sync response arrives after the LLM message', () => {
1261
+ recordSyncRequest('sync-tracking-id');
1262
+ // LLM message arrives first, before the HTTP response
1263
+ recordMessageReceived('sync-tracking-id');
1264
+ assert.isUndefined(cdl.completeLocusSyncLatency('meeting-1', 'sync-tracking-id'));
1265
+
1266
+ // /sync response lands second -> now both milestones are in
1267
+ recordSyncResponse('sync-tracking-id');
1268
+
1269
+ assert.deepEqual(cdl.completeLocusSyncLatency('meeting-1', 'sync-tracking-id'), {
1270
+ dataSet: 'main',
1271
+ syncLatency: {
1272
+ randomBackoffTime: 0,
1273
+ hashtreePrepTime: 0,
1274
+ hashtreeResponseTime: 0,
1275
+ syncPrepTime: 10,
1276
+ syncResponseTime: 20,
1277
+ syncMessageReceiveTime: 40,
1278
+ totalTime: 50,
1279
+ },
1280
+ });
1281
+ assert.isUndefined(cdl.meetingLatencies.get('meeting-1'));
1282
+ });
1283
+
1284
+ it('emits and consumes the record when the LLM message arrives after the /sync response', () => {
1285
+ recordSyncRequest('sync-tracking-id');
1286
+ // HTTP response arrives first
1287
+ recordSyncResponse('sync-tracking-id');
1288
+ assert.isUndefined(cdl.completeLocusSyncLatency('meeting-1', 'sync-tracking-id'));
1289
+
1290
+ // LLM message lands second
1291
+ recordMessageReceived('sync-tracking-id');
1292
+
1293
+ const completed = cdl.completeLocusSyncLatency('meeting-1', 'sync-tracking-id');
1294
+
1295
+ assert.isDefined(completed);
1296
+ assert.equal(completed!.dataSet, 'main');
1297
+ assert.isUndefined(cdl.meetingLatencies.get('meeting-1'));
1298
+ });
1299
+ });
116
1300
  });
117
1301
 
118
1302
  it('should calculate diff between timestamps correctly', () => {
@@ -235,20 +1419,12 @@ describe('internal-plugin-metrics', () => {
235
1419
  });
236
1420
 
237
1421
  describe('measureLatency', () => {
238
- let clock;
239
1422
  let saveLatencySpy;
240
1423
 
241
1424
  beforeEach(() => {
242
- clock = sinon.useFakeTimers();
243
-
244
1425
  saveLatencySpy = sinon.stub(cdl, 'saveLatency');
245
1426
  });
246
1427
 
247
- afterEach(() => {
248
- clock.restore();
249
- sinon.restore();
250
- });
251
-
252
1428
  it('checks measureLatency with accumulate false', async () => {
253
1429
  const key = 'internal.client.pageJMT';
254
1430
  const accumulate = false;
@@ -264,7 +1440,7 @@ describe('internal-plugin-metrics', () => {
264
1440
  const resolvedValue = await promise;
265
1441
  assert.deepEqual(resolvedValue, 'test');
266
1442
  assert.calledOnceWithExactly(callbackStub);
267
- assert.calledOnceWithExactly(saveLatencySpy, key, 50, accumulate);
1443
+ assert.calledOnceWithExactly(saveLatencySpy, key, 50, {accumulate});
268
1444
  });
269
1445
 
270
1446
  it('checks measureLatency with accumulate true', async () => {
@@ -280,7 +1456,7 @@ describe('internal-plugin-metrics', () => {
280
1456
  const resolvedValue = await promise;
281
1457
  assert.deepEqual(resolvedValue, 'test123');
282
1458
  assert.calledOnceWithExactly(callbackStub);
283
- assert.calledOnceWithExactly(saveLatencySpy, key, 20, accumulate);
1459
+ assert.calledOnceWithExactly(saveLatencySpy, key, 20, {accumulate});
284
1460
  });
285
1461
 
286
1462
  it('checks measureLatency when callBack rejects', async () => {
@@ -297,7 +1473,7 @@ describe('internal-plugin-metrics', () => {
297
1473
  const rejectedValue = await assert.isRejected(promise);
298
1474
  assert.deepEqual(rejectedValue, error);
299
1475
  assert.calledOnceWithExactly(callbackStub);
300
- assert.calledOnceWithExactly(saveLatencySpy, key, 50, accumulate);
1476
+ assert.calledOnceWithExactly(saveLatencySpy, key, 50, {accumulate});
301
1477
  });
302
1478
  });
303
1479
 
@@ -412,8 +1588,10 @@ describe('internal-plugin-metrics', () => {
412
1588
  });
413
1589
 
414
1590
  it('calculates getCallInitJoinReq correctly', () => {
1591
+ cdl.saveTimestamp({key: 'internal.client.meeting.interstitial-window.showed', value: 5});
415
1592
  cdl.saveTimestamp({key: 'internal.client.interstitial-window.click.joinbutton', value: 10});
416
1593
  cdl.saveTimestamp({key: 'client.locus.join.request', value: 20});
1594
+ // showedToJoinReq = 20-5 = 15, showInterstitialTime = 10-5 = 5, result = 15-5 = 10
417
1595
  assert.deepEqual(cdl.getCallInitJoinReq(), 10);
418
1596
  });
419
1597
 
@@ -509,7 +1687,7 @@ describe('internal-plugin-metrics', () => {
509
1687
 
510
1688
  it('calculates getStayLobbyTime correctly', () => {
511
1689
  cdl.saveTimestamp({
512
- key: 'client.locus.join.response',
1690
+ key: 'client.lobby.entered',
513
1691
  value: 10,
514
1692
  });
515
1693
  cdl.saveTimestamp({
@@ -520,44 +1698,44 @@ describe('internal-plugin-metrics', () => {
520
1698
  });
521
1699
 
522
1700
  describe('getStayLobbyTimeCappedBy', () => {
523
- it('returns undefined when lobbyStartTimestamp is missing', () => {
1701
+ it('returns 0 when lobbyStartTimestamp is missing', () => {
524
1702
  cdl.saveTimestamp({key: 'client.media-engine.ready', value: 100});
525
- assert.deepEqual(cdl.getStayLobbyTimeCappedBy('client.media-engine.ready'), undefined);
1703
+ assert.deepEqual(cdl.getStayLobbyTimeCappedBy('client.media-engine.ready'), 0);
526
1704
  });
527
1705
 
528
1706
  it('returns undefined when endTimestampKey is missing', () => {
529
- cdl.saveTimestamp({key: 'client.locus.join.response', value: 10});
1707
+ cdl.saveTimestamp({key: 'client.lobby.entered', value: 10});
530
1708
  assert.deepEqual(cdl.getStayLobbyTimeCappedBy('client.media-engine.ready'), undefined);
531
1709
  });
532
1710
 
533
1711
  it('uses maximumEndTimestamp when lobby end does not exist', () => {
534
- cdl.saveTimestamp({key: 'client.locus.join.response', value: 10});
1712
+ cdl.saveTimestamp({key: 'client.lobby.entered', value: 10});
535
1713
  cdl.saveTimestamp({key: 'client.media-engine.ready', value: 50});
536
1714
  assert.deepEqual(cdl.getStayLobbyTimeCappedBy('client.media-engine.ready'), 40);
537
1715
  });
538
1716
 
539
1717
  it('uses lobby end when it is before maximumEndTimestamp', () => {
540
- cdl.saveTimestamp({key: 'client.locus.join.response', value: 10});
1718
+ cdl.saveTimestamp({key: 'client.lobby.entered', value: 10});
541
1719
  cdl.saveTimestamp({key: 'client.lobby.exited', value: 30});
542
1720
  cdl.saveTimestamp({key: 'client.media-engine.ready', value: 50});
543
1721
  assert.deepEqual(cdl.getStayLobbyTimeCappedBy('client.media-engine.ready'), 20);
544
1722
  });
545
1723
 
546
1724
  it('uses maximumEndTimestamp when lobby end is after it', () => {
547
- cdl.saveTimestamp({key: 'client.locus.join.response', value: 10});
1725
+ cdl.saveTimestamp({key: 'client.lobby.entered', value: 10});
548
1726
  cdl.saveTimestamp({key: 'client.lobby.exited', value: 60});
549
1727
  cdl.saveTimestamp({key: 'client.media-engine.ready', value: 50});
550
1728
  assert.deepEqual(cdl.getStayLobbyTimeCappedBy('client.media-engine.ready'), 40);
551
1729
  });
552
1730
 
553
1731
  it('clamps to 0 when result would be negative', () => {
554
- cdl.saveTimestamp({key: 'client.locus.join.response', value: 100});
1732
+ cdl.saveTimestamp({key: 'client.lobby.entered', value: 100});
555
1733
  cdl.saveTimestamp({key: 'client.media-engine.ready', value: 50});
556
1734
  assert.deepEqual(cdl.getStayLobbyTimeCappedBy('client.media-engine.ready'), 0);
557
1735
  });
558
1736
 
559
1737
  it('clamps to MAX_INTEGER when result is very large', () => {
560
- cdl.saveTimestamp({key: 'client.locus.join.response', value: 0});
1738
+ cdl.saveTimestamp({key: 'client.lobby.entered', value: 0});
561
1739
  cdl.saveTimestamp({key: 'client.media-engine.ready', value: 2147483648});
562
1740
  assert.deepEqual(cdl.getStayLobbyTimeCappedBy('client.media-engine.ready'), 2147483647);
563
1741
  });
@@ -574,76 +1752,40 @@ describe('internal-plugin-metrics', () => {
574
1752
  });
575
1753
 
576
1754
  it('calculates getClickToInterstitial correctly', () => {
577
- cdl.saveTimestamp({
578
- key: 'internal.client.meeting.click.joinbutton',
579
- value: 10,
580
- });
581
- cdl.saveTimestamp({
582
- key: 'internal.client.meeting.interstitial-window.showed',
583
- value: 20,
584
- });
585
- assert.deepEqual(cdl.getClickToInterstitial(), 10);
586
- });
587
-
588
- it('calculates getClickToInterstitial without join button timestamp', () => {
589
1755
  cdl.saveLatency('internal.click.to.interstitial', 5);
590
- cdl.saveTimestamp({
591
- key: 'internal.client.meeting.interstitial-window.showed',
592
- value: 20,
593
- });
594
1756
  assert.deepEqual(cdl.getClickToInterstitial(), 5);
595
1757
  });
596
1758
 
597
- it('calculates getClickToInterstitial without join button timestamp when it is 0', () => {
1759
+ it('calculates getClickToInterstitial correctly when it is 0', () => {
598
1760
  cdl.saveLatency('internal.click.to.interstitial', 0);
599
- cdl.saveTimestamp({
600
- key: 'internal.client.meeting.interstitial-window.showed',
601
- value: 20,
602
- });
603
1761
  assert.deepEqual(cdl.getClickToInterstitial(), 0);
604
1762
  });
605
1763
 
606
- it('calculates getClickToInterstitial without join button timestamp when it is greater than MAX_INTEGER', () => {
1764
+ it('calculates getClickToInterstitial correctly when it is greater than MAX_INTEGER', () => {
607
1765
  cdl.saveLatency('internal.click.to.interstitial', 2147483648);
608
1766
  assert.deepEqual(cdl.getClickToInterstitial(), 2147483647);
609
1767
  });
610
1768
 
611
1769
  it('calculates getClickToInterstitialWithUserDelay correctly', () => {
612
- cdl.saveTimestamp({
613
- key: 'internal.client.meeting.click.joinbutton',
614
- value: 10,
615
- });
616
- cdl.saveTimestamp({
617
- key: 'internal.client.meeting.interstitial-window.showed',
618
- value: 20,
619
- });
620
- assert.deepEqual(cdl.getClickToInterstitialWithUserDelay(), 10);
621
- });
622
-
623
- it('calculates getClickToInterstitialWithUserDelay without join button timestamp', () => {
624
1770
  cdl.saveLatency('internal.click.to.interstitial.with.user.delay', 5);
625
- cdl.saveTimestamp({
626
- key: 'internal.client.meeting.interstitial-window.showed',
627
- value: 20,
628
- });
629
1771
  assert.deepEqual(cdl.getClickToInterstitialWithUserDelay(), 5);
630
1772
  });
631
1773
 
632
- it('calculates getClickToInterstitialWithUserDelay without join button timestamp when it is 0', () => {
1774
+ it('calculates getClickToInterstitialWithUserDelay correctly when it is 0', () => {
633
1775
  cdl.saveLatency('internal.click.to.interstitial.with.user.delay', 0);
634
- cdl.saveTimestamp({
635
- key: 'internal.client.meeting.interstitial-window.showed',
636
- value: 20,
637
- });
638
1776
  assert.deepEqual(cdl.getClickToInterstitialWithUserDelay(), 0);
639
1777
  });
640
1778
 
641
- it('calculates getClickToInterstitialWithUserDelay without join button timestamp when it is greater than MAX_INTEGER', () => {
1779
+ it('calculates getClickToInterstitialWithUserDelay correctly when it is greater than MAX_INTEGER', () => {
642
1780
  cdl.saveLatency('internal.click.to.interstitial.with.user.delay', 2147483648);
643
1781
  assert.deepEqual(cdl.getClickToInterstitialWithUserDelay(), 2147483647);
644
1782
  });
645
1783
 
646
1784
  it('calculates getInterstitialToJoinOK correctly', () => {
1785
+ cdl.saveTimestamp({
1786
+ key: 'internal.client.meeting.interstitial-window.showed',
1787
+ value: 5,
1788
+ });
647
1789
  cdl.saveTimestamp({
648
1790
  key: 'internal.client.interstitial-window.click.joinbutton',
649
1791
  value: 10,
@@ -652,12 +1794,13 @@ describe('internal-plugin-metrics', () => {
652
1794
  key: 'client.locus.join.response',
653
1795
  value: 20,
654
1796
  });
1797
+ // showedToJoinResp = 20-5 = 15, showInterstitialTime = 10-5 = 5, result = 15-5 = 10
655
1798
  assert.deepEqual(cdl.getInterstitialToJoinOK(), 10);
656
1799
  });
657
1800
 
658
1801
  it('calculates getInterstitialToJoinOK correctly when one value is not a number', () => {
659
1802
  cdl.saveTimestamp({
660
- key: 'internal.client.interstitial-window.click.joinbutton',
1803
+ key: 'internal.client.meeting.interstitial-window.showed',
661
1804
  value: 'ten' as unknown as number,
662
1805
  });
663
1806
  cdl.saveTimestamp({
@@ -668,22 +1811,12 @@ describe('internal-plugin-metrics', () => {
668
1811
  });
669
1812
 
670
1813
  it('calculates getCallInitMediaEngineReady correctly', () => {
671
- cdl.saveTimestamp({
672
- key: 'internal.client.interstitial-window.click.joinbutton',
673
- value: 10,
674
- });
675
- cdl.saveTimestamp({
676
- key: 'client.media-engine.ready',
677
- value: 20,
678
- });
679
- assert.deepEqual(cdl.getCallInitMediaEngineReady(), 10);
1814
+ sinon.stub(cdl, 'getInterstitialToMediaOKJMT').returns(42);
1815
+ assert.deepEqual(cdl.getCallInitMediaEngineReady(), 42);
680
1816
  });
681
1817
 
682
1818
  it('calculates getTotalJMT correctly', () => {
683
- cdl.saveTimestamp({
684
- key: 'internal.client.interstitial-window.click.joinbutton',
685
- value: 5,
686
- });
1819
+ cdl.saveLatency('internal.click.to.interstitial', 10);
687
1820
  cdl.saveTimestamp({
688
1821
  key: 'internal.client.meeting.click.joinbutton',
689
1822
  value: 10,
@@ -692,29 +1825,39 @@ describe('internal-plugin-metrics', () => {
692
1825
  key: 'internal.client.meeting.interstitial-window.showed',
693
1826
  value: 20,
694
1827
  });
1828
+ cdl.saveTimestamp({
1829
+ key: 'internal.client.interstitial-window.click.joinbutton',
1830
+ value: 25,
1831
+ });
695
1832
  cdl.saveTimestamp({
696
1833
  key: 'client.locus.join.response',
697
1834
  value: 40,
698
1835
  });
699
- assert.deepEqual(cdl.getTotalJMT(), 45);
1836
+ // clickToInterstitial = 20-10 = 10
1837
+ // showedToJoinLocusResponse = 40-20 = 20
1838
+ // showInterstitialTime = 25-20 = 5
1839
+ // total = 10 + 20 - 5 = 25
1840
+ assert.deepEqual(cdl.getTotalJMT(), 25);
700
1841
  });
701
1842
 
702
1843
  it('calculates getTotalJMT correctly when clickToInterstitial is 0', () => {
703
1844
  cdl.saveLatency('internal.click.to.interstitial', 0);
704
1845
  cdl.saveTimestamp({
705
- key: 'internal.client.interstitial-window.click.joinbutton',
1846
+ key: 'internal.client.meeting.interstitial-window.showed',
706
1847
  value: 20,
707
1848
  });
708
1849
  cdl.saveTimestamp({
709
1850
  key: 'client.locus.join.response',
710
1851
  value: 40,
711
1852
  });
1853
+ // showedToJoinLocusResponse = 40-20 = 20, showInterstitialTime = 0
1854
+ // total = 0 + 20 - 0 = 20
712
1855
  assert.deepEqual(cdl.getTotalJMT(), 20);
713
1856
  });
714
1857
 
715
1858
  it('calculates getTotalJMT correctly when interstitialClickJoinToJoinLocusResponse is 0', () => {
716
1859
  cdl.saveTimestamp({
717
- key: 'internal.client.interstitial-window.click.joinbutton',
1860
+ key: 'internal.client.meeting.interstitial-window.showed',
718
1861
  value: 40,
719
1862
  });
720
1863
  cdl.saveLatency('internal.click.to.interstitial', 12);
@@ -722,12 +1865,14 @@ describe('internal-plugin-metrics', () => {
722
1865
  key: 'client.locus.join.response',
723
1866
  value: 40,
724
1867
  });
1868
+ // showedToJoinLocusResponse = 0, showInterstitialTime = 0
1869
+ // total = 12 + 0 - 0 = 12
725
1870
  assert.deepEqual(cdl.getTotalJMT(), 12);
726
1871
  });
727
1872
 
728
1873
  it('calculates getTotalJMT correctly when both clickToInterstitial and interstitialClickJoinToJoinLocusResponse are 0', () => {
729
1874
  cdl.saveTimestamp({
730
- key: 'internal.client.interstitial-window.click.joinbutton',
1875
+ key: 'internal.client.meeting.interstitial-window.showed',
731
1876
  value: 40,
732
1877
  });
733
1878
  cdl.saveLatency('internal.click.to.interstitial', 0);
@@ -740,7 +1885,7 @@ describe('internal-plugin-metrics', () => {
740
1885
 
741
1886
  it('calculates getTotalJMT correctly when both clickToInterstitial is not a number', () => {
742
1887
  cdl.saveTimestamp({
743
- key: 'internal.client.interstitial-window.click.joinbutton',
1888
+ key: 'internal.client.meeting.interstitial-window.showed',
744
1889
  value: 40,
745
1890
  });
746
1891
  cdl.saveLatency('internal.click.to.interstitial', 'eleven' as unknown as number);
@@ -753,7 +1898,7 @@ describe('internal-plugin-metrics', () => {
753
1898
 
754
1899
  it('calculates getTotalJMT correctly when it is greater than MAX_INTEGER', () => {
755
1900
  cdl.saveTimestamp({
756
- key: 'internal.client.interstitial-window.click.joinbutton',
1901
+ key: 'internal.client.meeting.interstitial-window.showed',
757
1902
  value: 5,
758
1903
  });
759
1904
  cdl.saveTimestamp({
@@ -765,14 +1910,7 @@ describe('internal-plugin-metrics', () => {
765
1910
  });
766
1911
 
767
1912
  it('calculates getTotalJMTWithUserDelay correctly', () => {
768
- cdl.saveTimestamp({
769
- key: 'internal.client.interstitial-window.click.joinbutton',
770
- value: 5,
771
- });
772
- cdl.saveTimestamp({
773
- key: 'internal.client.meeting.click.joinbutton',
774
- value: 10,
775
- });
1913
+ cdl.saveLatency('internal.click.to.interstitial.with.user.delay', 10);
776
1914
  cdl.saveTimestamp({
777
1915
  key: 'internal.client.meeting.interstitial-window.showed',
778
1916
  value: 20,
@@ -853,65 +1991,79 @@ describe('internal-plugin-metrics', () => {
853
1991
  });
854
1992
 
855
1993
  it('calculates getTotalMediaJMT correctly with lobby exiting before media-engine.ready', () => {
856
- cdl.saveTimestamp({key: 'internal.client.meeting.click.joinbutton', value: 5});
1994
+ cdl.saveLatency('internal.click.to.interstitial', 3);
1995
+ // clickToInterstitial = 3
857
1996
  cdl.saveTimestamp({key: 'internal.client.meeting.interstitial-window.showed', value: 8});
858
- // clickToInterstitial = 8 - 5 = 3
859
1997
  cdl.saveTimestamp({key: 'internal.client.interstitial-window.click.joinbutton', value: 10});
860
1998
  cdl.saveTimestamp({key: 'client.media-engine.ready', value: 50});
861
- // interstitialClickJoinToMediaEngineReady = 50 - 10 = 40
862
- cdl.saveTimestamp({key: 'client.locus.join.response', value: 20});
1999
+ // interstitialShowedToMediaEngineReady = 50 - 8 = 42
2000
+ // showInterstitialTime = 10 - 8 = 2
2001
+ cdl.saveTimestamp({key: 'client.lobby.entered', value: 20});
863
2002
  cdl.saveTimestamp({key: 'client.lobby.exited', value: 30});
864
2003
  // stayLobbyTimeCappedByMediaEngineReady = min(30, 50) - 20 = 10
865
- // total = 3 + 40 - 10 = 33
2004
+ // total = 3 + 42 - 2 - 10 = 33
866
2005
  assert.deepEqual(cdl.getTotalMediaJMT(), 33);
867
2006
  });
868
2007
 
869
2008
  it('calculates getTotalMediaJMT correctly without lobby', () => {
870
- cdl.saveTimestamp({key: 'internal.client.meeting.click.joinbutton', value: 5});
871
- cdl.saveTimestamp({key: 'internal.client.meeting.interstitial-window.showed', value: 8});
2009
+ cdl.saveLatency('internal.click.to.interstitial', 3);
872
2010
  // clickToInterstitial = 3
2011
+ cdl.saveTimestamp({key: 'internal.client.meeting.interstitial-window.showed', value: 8});
873
2012
  cdl.saveTimestamp({key: 'internal.client.interstitial-window.click.joinbutton', value: 10});
874
2013
  cdl.saveTimestamp({key: 'client.media-engine.ready', value: 50});
875
- // interstitialClickJoinToMediaEngineReady = 40
876
- cdl.saveTimestamp({key: 'client.locus.join.response', value: 20});
877
- // no client.lobby.exited
878
- // stayLobbyTimeCappedByMediaEngineReady = 50 - 20 = 30
879
- // total = 3 + 40 - 30 = 13
880
- assert.deepEqual(cdl.getTotalMediaJMT(), 13);
2014
+ // interstitialShowedToMediaEngineReady = 50 - 8 = 42
2015
+ // showInterstitialTime = 10 - 8 = 2
2016
+ // no client.lobby.entered → stayLobbyTimeCappedByMediaEngineReady = 0
2017
+ // total = 3 + 42 - 2 - 0 = 43
2018
+ assert.deepEqual(cdl.getTotalMediaJMT(), 43);
881
2019
  });
882
2020
 
883
2021
  it('calculates getTotalMediaJMT correctly with lobby exiting after media-engine.ready', () => {
884
- cdl.saveTimestamp({key: 'internal.client.meeting.click.joinbutton', value: 5});
885
- cdl.saveTimestamp({key: 'internal.client.meeting.interstitial-window.showed', value: 8});
2022
+ cdl.saveLatency('internal.click.to.interstitial', 3);
886
2023
  // clickToInterstitial = 3
2024
+ cdl.saveTimestamp({key: 'internal.client.meeting.interstitial-window.showed', value: 8});
887
2025
  cdl.saveTimestamp({key: 'internal.client.interstitial-window.click.joinbutton', value: 10});
888
2026
  cdl.saveTimestamp({key: 'client.media-engine.ready', value: 50});
889
- // interstitialClickJoinToMediaEngineReady = 40
890
- cdl.saveTimestamp({key: 'client.locus.join.response', value: 20});
2027
+ // interstitialShowedToMediaEngineReady = 50 - 8 = 42
2028
+ // showInterstitialTime = 10 - 8 = 2
2029
+ cdl.saveTimestamp({key: 'client.lobby.entered', value: 20});
891
2030
  cdl.saveTimestamp({key: 'client.lobby.exited', value: 60});
892
2031
  // stayLobbyTimeCappedByMediaEngineReady = min(60, 50) - 20 = 30
893
- // total = 3 + 40 - 30 = 13
2032
+ // total = 3 + 42 - 2 - 30 = 13
894
2033
  assert.deepEqual(cdl.getTotalMediaJMT(), 13);
895
2034
  });
896
2035
 
897
2036
  it('calculates getTotalMediaJMT correctly when it is greater than MAX_INTEGER', () => {
898
- cdl.saveTimestamp({key: 'internal.client.meeting.click.joinbutton', value: 5});
899
- cdl.saveTimestamp({key: 'internal.client.meeting.interstitial-window.showed', value: 8});
2037
+ cdl.saveLatency('internal.click.to.interstitial', 5);
2038
+ cdl.saveTimestamp({key: 'internal.client.meeting.interstitial-window.showed', value: 10});
900
2039
  cdl.saveTimestamp({key: 'internal.client.interstitial-window.click.joinbutton', value: 10});
901
2040
  cdl.saveTimestamp({key: 'client.media-engine.ready', value: 4294967400});
902
- cdl.saveTimestamp({key: 'client.locus.join.response', value: 28});
2041
+ cdl.saveTimestamp({key: 'client.lobby.entered', value: 28});
903
2042
  cdl.saveTimestamp({key: 'client.lobby.exited', value: 30});
904
2043
  assert.deepEqual(cdl.getTotalMediaJMT(), 2147483647);
905
2044
  });
906
2045
 
907
2046
  it('returns undefined for getTotalMediaJMT when media-engine.ready is missing', () => {
908
- cdl.saveTimestamp({key: 'internal.client.meeting.click.joinbutton', value: 5});
2047
+ cdl.saveLatency('internal.click.to.interstitial', 3);
909
2048
  cdl.saveTimestamp({key: 'internal.client.meeting.interstitial-window.showed', value: 8});
910
2049
  cdl.saveTimestamp({key: 'internal.client.interstitial-window.click.joinbutton', value: 10});
911
2050
  cdl.saveTimestamp({key: 'client.locus.join.response', value: 20});
912
2051
  assert.deepEqual(cdl.getTotalMediaJMT(), undefined);
913
2052
  });
914
2053
 
2054
+ it('calculates getTotalMediaJMT correctly when there is no lobby and stayLobbyTime defaults to 0', () => {
2055
+ cdl.saveLatency('internal.click.to.interstitial', 3);
2056
+ // clickToInterstitial = 3
2057
+ cdl.saveTimestamp({key: 'internal.client.meeting.interstitial-window.showed', value: 8});
2058
+ cdl.saveTimestamp({key: 'internal.client.interstitial-window.click.joinbutton', value: 10});
2059
+ cdl.saveTimestamp({key: 'client.media-engine.ready', value: 50});
2060
+ // interstitialShowedToMediaEngineReady = 50 - 8 = 42
2061
+ // showInterstitialTime = 10 - 8 = 2
2062
+ // no client.lobby.entered → stayLobbyTimeCappedByMediaEngineReady = 0
2063
+ // total = 3 + 42 - 2 - 0 = 43
2064
+ assert.deepEqual(cdl.getTotalMediaJMT(), 43);
2065
+ });
2066
+
915
2067
  it('calculates getTotalMediaJMTWithUserDelay correctly', () => {
916
2068
  cdl.saveLatency('internal.click.to.interstitial.with.user.delay', 7);
917
2069
  // clickToInterstitialWithUserDelay = 7
@@ -923,9 +2075,9 @@ describe('internal-plugin-metrics', () => {
923
2075
  });
924
2076
 
925
2077
  it('calculates getTotalMediaJMTWithUserDelay correctly for guest join', () => {
926
- cdl.saveTimestamp({key: 'internal.client.meeting.click.joinbutton', value: 5});
2078
+ cdl.saveLatency('internal.click.to.interstitial.with.user.delay', 3);
2079
+ // clickToInterstitialWithUserDelay = 3
927
2080
  cdl.saveTimestamp({key: 'internal.client.meeting.interstitial-window.showed', value: 8});
928
- // clickToInterstitialWithUserDelay = 8 - 5 = 3
929
2081
  cdl.saveTimestamp({key: 'client.media-engine.ready', value: 50});
930
2082
  // interstitialShowedToMediaEngineReady = 50 - 8 = 42
931
2083
  // total = 3 + 42 = 45
@@ -988,6 +2140,11 @@ describe('internal-plugin-metrics', () => {
988
2140
  });
989
2141
 
990
2142
  it('calculates getClientJMT correctly', () => {
2143
+ cdl.saveLatency('internal.click.to.interstitial.for.client.jmt', 5);
2144
+ cdl.saveTimestamp({
2145
+ key: 'internal.client.meeting.interstitial-window.showed',
2146
+ value: 1,
2147
+ });
991
2148
  cdl.saveTimestamp({
992
2149
  key: 'internal.client.interstitial-window.click.joinbutton',
993
2150
  value: 2,
@@ -996,19 +2153,30 @@ describe('internal-plugin-metrics', () => {
996
2153
  key: 'client.locus.join.request',
997
2154
  value: 6,
998
2155
  });
2156
+ // showedToLocusJoinRequest = 6-1 = 5, showInterstitialTime = 2-1 = 1
2157
+ // clickToInterstitialForClientJmt (5) + 5 - 1 = 9
2158
+ assert.deepEqual(cdl.getClientJMT(), 9);
2159
+ });
2160
+
2161
+ it('returns undefined for getClientJMT when clickToInterstitialForClientJmt is missing', () => {
999
2162
  cdl.saveTimestamp({
1000
- key: 'client.locus.join.response',
1001
- value: 8,
2163
+ key: 'internal.client.meeting.interstitial-window.showed',
2164
+ value: 1,
1002
2165
  });
1003
2166
  cdl.saveTimestamp({
1004
- key: 'client.ice.start',
1005
- value: 10,
2167
+ key: 'internal.client.interstitial-window.click.joinbutton',
2168
+ value: 2,
1006
2169
  });
1007
2170
  cdl.saveTimestamp({
1008
- key: 'client.ice.end',
1009
- value: 11,
2171
+ key: 'client.locus.join.request',
2172
+ value: 6,
1010
2173
  });
1011
- assert.deepEqual(cdl.getClientJMT(), 3);
2174
+ assert.deepEqual(cdl.getClientJMT(), undefined);
2175
+ });
2176
+
2177
+ it('returns undefined for getClientJMT when interstitialJoinToLocusJoinRequest is missing', () => {
2178
+ cdl.saveLatency('internal.click.to.interstitial.for.client.jmt', 5);
2179
+ assert.deepEqual(cdl.getClientJMT(), undefined);
1012
2180
  });
1013
2181
 
1014
2182
  it('calculates getAudioJoinRespRxStart correctly', () => {
@@ -1060,12 +2228,16 @@ describe('internal-plugin-metrics', () => {
1060
2228
  });
1061
2229
 
1062
2230
  it('calculates getInterstitialToMediaOKJMT correctly', () => {
2231
+ cdl.saveTimestamp({
2232
+ key: 'internal.client.meeting.interstitial-window.showed',
2233
+ value: 2,
2234
+ });
1063
2235
  cdl.saveTimestamp({
1064
2236
  key: 'internal.client.interstitial-window.click.joinbutton',
1065
2237
  value: 4,
1066
2238
  });
1067
2239
  cdl.saveTimestamp({
1068
- key: 'client.locus.join.response',
2240
+ key: 'client.lobby.entered',
1069
2241
  value: 10,
1070
2242
  });
1071
2243
  cdl.saveTimestamp({
@@ -1076,21 +2248,28 @@ describe('internal-plugin-metrics', () => {
1076
2248
  key: 'client.ice.end',
1077
2249
  value: 14,
1078
2250
  });
2251
+ // showedToIceEnd = 14-2 = 12, showInterstitialTime = 4-2 = 2
2252
+ // stayLobbyTimeCappedByIceEnd = min(12,14)-10 = 2
2253
+ // result = 12 - 2 - 2 = 8
1079
2254
  assert.deepEqual(cdl.getInterstitialToMediaOKJMT(), 8);
1080
2255
  });
1081
2256
 
1082
2257
  it('calculates getInterstitialToMediaOKJMT correctly when it is greater than MAX_INTEGER', () => {
2258
+ cdl.saveTimestamp({
2259
+ key: 'internal.client.meeting.interstitial-window.showed',
2260
+ value: 4,
2261
+ });
1083
2262
  cdl.saveTimestamp({
1084
2263
  key: 'internal.client.interstitial-window.click.joinbutton',
1085
2264
  value: 4,
1086
2265
  });
1087
2266
  cdl.saveTimestamp({
1088
- key: 'client.locus.join.response',
2267
+ key: 'client.lobby.entered',
1089
2268
  value: 10,
1090
2269
  });
1091
2270
  cdl.saveTimestamp({
1092
2271
  key: 'client.lobby.exited',
1093
- value: 12,
2272
+ value: 10,
1094
2273
  });
1095
2274
  cdl.saveTimestamp({
1096
2275
  key: 'client.ice.end',
@@ -1100,6 +2279,29 @@ describe('internal-plugin-metrics', () => {
1100
2279
  });
1101
2280
 
1102
2281
  it('calculates getInterstitialToMediaOKJMT correctly without lobby', () => {
2282
+ cdl.saveTimestamp({
2283
+ key: 'internal.client.meeting.interstitial-window.showed',
2284
+ value: 2,
2285
+ });
2286
+ cdl.saveTimestamp({
2287
+ key: 'internal.client.interstitial-window.click.joinbutton',
2288
+ value: 4,
2289
+ });
2290
+ cdl.saveTimestamp({
2291
+ key: 'client.ice.end',
2292
+ value: 14,
2293
+ });
2294
+ // showedToIceEnd = 14-2 = 12, showInterstitialTime = 4-2 = 2
2295
+ // stayLobbyTimeCappedByIceEnd = 0 (no lobby)
2296
+ // result = 12 - 2 - 0 = 10
2297
+ assert.deepEqual(cdl.getInterstitialToMediaOKJMT(), 10);
2298
+ });
2299
+
2300
+ it('calculates getInterstitialToMediaOKJMT correctly when there is no lobby and stayLobbyTime defaults to 0', () => {
2301
+ cdl.saveTimestamp({
2302
+ key: 'internal.client.meeting.interstitial-window.showed',
2303
+ value: 2,
2304
+ });
1103
2305
  cdl.saveTimestamp({
1104
2306
  key: 'internal.client.interstitial-window.click.joinbutton',
1105
2307
  value: 4,
@@ -1108,6 +2310,9 @@ describe('internal-plugin-metrics', () => {
1108
2310
  key: 'client.ice.end',
1109
2311
  value: 14,
1110
2312
  });
2313
+ // showedToIceEnd = 14-2 = 12, showInterstitialTime = 4-2 = 2
2314
+ // stayLobbyTimeCappedByIceEnd = 0 (no lobby)
2315
+ // result = 12 - 2 - 0 = 10
1111
2316
  assert.deepEqual(cdl.getInterstitialToMediaOKJMT(), 10);
1112
2317
  });
1113
2318