@webex/internal-plugin-metrics 3.12.0-next.34 → 3.12.0-next.36
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/dist/call-diagnostic/call-diagnostic-metrics-latencies.js +571 -15
- package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/metrics.js +1 -1
- package/dist/metrics.types.js +4 -0
- package/dist/metrics.types.js.map +1 -1
- package/dist/types/call-diagnostic/call-diagnostic-metrics-latencies.d.ts +199 -5
- package/dist/types/index.d.ts +2 -2
- package/dist/types/metrics.types.d.ts +4 -2
- package/package.json +2 -2
- package/src/call-diagnostic/call-diagnostic-metrics-latencies.ts +661 -5
- package/src/index.ts +2 -0
- package/src/metrics.types.ts +15 -2
- package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-latencies.ts +1192 -16
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
var _Reflect$construct = require("@babel/runtime-corejs2/core-js/reflect/construct");
|
|
4
|
+
var _Object$keys = require("@babel/runtime-corejs2/core-js/object/keys");
|
|
5
|
+
var _Object$getOwnPropertySymbols = require("@babel/runtime-corejs2/core-js/object/get-own-property-symbols");
|
|
6
|
+
var _Object$getOwnPropertyDescriptor = require("@babel/runtime-corejs2/core-js/object/get-own-property-descriptor");
|
|
7
|
+
var _Object$getOwnPropertyDescriptors = require("@babel/runtime-corejs2/core-js/object/get-own-property-descriptors");
|
|
8
|
+
var _Object$defineProperties = require("@babel/runtime-corejs2/core-js/object/define-properties");
|
|
4
9
|
var _Object$defineProperty = require("@babel/runtime-corejs2/core-js/object/define-property");
|
|
5
10
|
var _interopRequireDefault = require("@babel/runtime-corejs2/helpers/interopRequireDefault");
|
|
6
11
|
_Object$defineProperty(exports, "__esModule", {
|
|
@@ -8,6 +13,7 @@ _Object$defineProperty(exports, "__esModule", {
|
|
|
8
13
|
});
|
|
9
14
|
exports.default = void 0;
|
|
10
15
|
var _map = _interopRequireDefault(require("@babel/runtime-corejs2/core-js/map"));
|
|
16
|
+
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/toConsumableArray"));
|
|
11
17
|
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/classCallCheck"));
|
|
12
18
|
var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/createClass"));
|
|
13
19
|
var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/possibleConstructorReturn"));
|
|
@@ -16,9 +22,17 @@ var _inherits2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/
|
|
|
16
22
|
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime-corejs2/helpers/defineProperty"));
|
|
17
23
|
var _webexCore = require("@webex/webex-core");
|
|
18
24
|
var _lodash = require("lodash");
|
|
25
|
+
var _metrics = require("../metrics.types");
|
|
26
|
+
function ownKeys(e, r) { var t = _Object$keys(e); if (_Object$getOwnPropertySymbols) { var o = _Object$getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return _Object$getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
27
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : _Object$getOwnPropertyDescriptors ? _Object$defineProperties(e, _Object$getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { _Object$defineProperty(e, r, _Object$getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
19
28
|
function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0, _possibleConstructorReturn2.default)(t, _isNativeReflectConstruct() ? _Reflect$construct(o, e || [], (0, _getPrototypeOf2.default)(t).constructor) : o.apply(t, e)); }
|
|
20
29
|
function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(_Reflect$construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } /* eslint-disable class-methods-use-this */ /* eslint-disable valid-jsdoc */
|
|
21
30
|
// we only care about client event and feature event for now
|
|
31
|
+
/**
|
|
32
|
+
* Container for the latencies tracked for a single meeting. Locus sync is currently the only
|
|
33
|
+
* meeting-specific latency we track, but this wrapper lets us add other latency records in the
|
|
34
|
+
* future without changing the shape of `meetingLatencies`.
|
|
35
|
+
*/
|
|
22
36
|
/**
|
|
23
37
|
* @description Helper class to store latencies timestamp and to calculate various latencies for CA.
|
|
24
38
|
* @exports
|
|
@@ -37,11 +51,23 @@ var CallDiagnosticLatencies = exports.default = /*#__PURE__*/function (_WebexPlu
|
|
|
37
51
|
_this = _callSuper(this, CallDiagnosticLatencies, [].concat(args));
|
|
38
52
|
(0, _defineProperty2.default)(_this, "latencyTimestamps", void 0);
|
|
39
53
|
(0, _defineProperty2.default)(_this, "precomputedLatencies", void 0);
|
|
54
|
+
(0, _defineProperty2.default)(_this, "meetingLatencies", void 0);
|
|
40
55
|
// meetingId that the current latencies are for
|
|
41
56
|
(0, _defineProperty2.default)(_this, "meetingId", void 0);
|
|
42
57
|
(0, _defineProperty2.default)(_this, "MAX_INTEGER", 2147483647);
|
|
58
|
+
// Aligned with UCF desktop (MekleFlowSyncMetricsTracker::kSleepWakeSkewThresholdMs): if any
|
|
59
|
+
// measured Locus sync segment exceeds this threshold the record is assumed to be corrupted by a
|
|
60
|
+
// clock jump (sleep/wake) and is discarded instead of being reported.
|
|
61
|
+
(0, _defineProperty2.default)(_this, "LOCUS_SYNC_SKEW_THRESHOLD_MS", 10 * 60 * 1000);
|
|
62
|
+
// Incomplete Locus sync records (missing the /sync response and/or the LLM message) are kept for
|
|
63
|
+
// this long so a late milestone can still complete them, then pruned as stale.
|
|
64
|
+
(0, _defineProperty2.default)(_this, "LOCUS_SYNC_PENDING_RECORD_TTL_MS", 5 * 60 * 1000);
|
|
65
|
+
// Safety cap on the number of records kept per meeting + dataset, to bound memory if syncs churn
|
|
66
|
+
// faster than the TTL can clean them up.
|
|
67
|
+
(0, _defineProperty2.default)(_this, "LOCUS_SYNC_MAX_RECORDS_PER_DATASET", 20);
|
|
43
68
|
_this.latencyTimestamps = new _map.default();
|
|
44
69
|
_this.precomputedLatencies = new _map.default();
|
|
70
|
+
_this.meetingLatencies = new _map.default();
|
|
45
71
|
return _this;
|
|
46
72
|
}
|
|
47
73
|
|
|
@@ -54,6 +80,502 @@ var CallDiagnosticLatencies = exports.default = /*#__PURE__*/function (_WebexPlu
|
|
|
54
80
|
value: function clearTimestamps() {
|
|
55
81
|
this.latencyTimestamps.clear();
|
|
56
82
|
this.precomputedLatencies.clear();
|
|
83
|
+
this.meetingLatencies.clear();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Clear tracked Locus sync latency state for a dataset.
|
|
88
|
+
*
|
|
89
|
+
* When a `trackingId` is supplied only the single record matching it is dropped, mirroring the
|
|
90
|
+
* native client which discards the individual in-progress record on a sync error (by syncId).
|
|
91
|
+
* When no `trackingId` is supplied every record for the dataset is dropped (legacy behavior).
|
|
92
|
+
*
|
|
93
|
+
* @param dataSetName dataset name
|
|
94
|
+
* @param meetingId meeting id
|
|
95
|
+
* @param trackingId optional sync tracking id to drop a single record instead of the whole dataset
|
|
96
|
+
*/
|
|
97
|
+
}, {
|
|
98
|
+
key: "clearLocusSyncLatency",
|
|
99
|
+
value: function clearLocusSyncLatency(dataSetName, meetingId, trackingId) {
|
|
100
|
+
var records = this.meetingLatencies.get(meetingId);
|
|
101
|
+
if (!records) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
var remainingRecords = trackingId ? records.filter(function (record) {
|
|
105
|
+
return record.locusSync.trackingId !== trackingId;
|
|
106
|
+
}) : records.filter(function (record) {
|
|
107
|
+
return record.locusSync.dataSetName !== dataSetName;
|
|
108
|
+
});
|
|
109
|
+
this.setMeetingLatencyRecords(meetingId, remainingRecords);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Calculates Locus sync latency values from stored milestone timestamps.
|
|
114
|
+
* @param meetingId meeting id
|
|
115
|
+
* @param trackingId sync tracking id used to match the record
|
|
116
|
+
* @returns sync latency metrics
|
|
117
|
+
*/
|
|
118
|
+
}, {
|
|
119
|
+
key: "getLocusSyncLatency",
|
|
120
|
+
value: function getLocusSyncLatency(meetingId, trackingId) {
|
|
121
|
+
var _this$getDiffBetweenL,
|
|
122
|
+
_this$getDiffBetweenL2,
|
|
123
|
+
_this2 = this;
|
|
124
|
+
if (!trackingId) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
var record = this.getLocusSyncLatencyRecord(meetingId, trackingId);
|
|
128
|
+
if (!record) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Some sync flows skip the /hashtree request, for example single-leaf data sets or cases
|
|
133
|
+
// where we already know which leaves to sync. Treat the missing hashtree segment as 0,
|
|
134
|
+
// while still requiring the sync request/response/message milestones below.
|
|
135
|
+
var hashtreePrepTime = (_this$getDiffBetweenL = this.getDiffBetweenLocusSyncTimestamps(record, 'syncStart', 'hashTreeRequest')) !== null && _this$getDiffBetweenL !== void 0 ? _this$getDiffBetweenL : 0;
|
|
136
|
+
var hashtreeResponseTime = (_this$getDiffBetweenL2 = this.getDiffBetweenLocusSyncTimestamps(record, 'hashTreeRequest', 'hashTreeResponse')) !== null && _this$getDiffBetweenL2 !== void 0 ? _this$getDiffBetweenL2 : 0;
|
|
137
|
+
var syncPrepStart = this.getLocusSyncPrepStart(record);
|
|
138
|
+
var syncPrepTime = this.getDiffBetweenLocusSyncTimestamps(record, syncPrepStart, 'syncRequest');
|
|
139
|
+
var syncResponseTime = this.getDiffBetweenLocusSyncTimestamps(record, 'syncRequest', 'syncResponse');
|
|
140
|
+
// Aligned with UCF: syncMessageReceiveTime is measured from the sync request start (not the
|
|
141
|
+
// sync response), i.e. how long after we asked Locus to sync the resulting state update arrived.
|
|
142
|
+
var syncMessageReceiveTime = this.getDiffBetweenLocusSyncTimestamps(record, 'syncRequest', 'messageReceived');
|
|
143
|
+
var totalTimeFromMessageReceived = this.getDiffBetweenLocusSyncTimestamps(record, 'syncStart', 'messageReceived');
|
|
144
|
+
var totalTimeFromSyncResponse = this.getDiffBetweenLocusSyncTimestamps(record, 'syncStart', 'syncResponse');
|
|
145
|
+
|
|
146
|
+
// messageReceived can occasionally be out of order due to race conditions relative to the
|
|
147
|
+
// /sync response. Use the larger of the two so totalTime is never less than syncStart->syncResponse.
|
|
148
|
+
var totalTime = typeof totalTimeFromMessageReceived === 'number' && typeof totalTimeFromSyncResponse === 'number' ? Math.max(totalTimeFromMessageReceived, totalTimeFromSyncResponse) : totalTimeFromMessageReceived !== null && totalTimeFromMessageReceived !== void 0 ? totalTimeFromMessageReceived : totalTimeFromSyncResponse;
|
|
149
|
+
if (typeof syncPrepTime !== 'number' || typeof totalTime !== 'number') {
|
|
150
|
+
return undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Aligned with UCF desktop (MekleFlowSyncMetricsTracker kSleepWakeSkewThresholdMs): if any
|
|
154
|
+
// individual segment is larger than the skew threshold the timestamps were almost certainly
|
|
155
|
+
// distorted by a clock jump (device sleep/wake), so discard the whole record.
|
|
156
|
+
var skewedSegment = [record.randomBackoffTime, hashtreePrepTime, hashtreeResponseTime, syncPrepTime, syncResponseTime, syncMessageReceiveTime, totalTime].some(function (segment) {
|
|
157
|
+
return typeof segment === 'number' && segment > _this2.LOCUS_SYNC_SKEW_THRESHOLD_MS;
|
|
158
|
+
});
|
|
159
|
+
if (skewedSegment) {
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Emit whenever at least one measured segment is non-zero. The LLM broadcast itself is gated
|
|
164
|
+
// in completeLocusSyncLatency(); this only guards against an all-zero (unmeasurable) record.
|
|
165
|
+
var hasMeaningfulSegment = hashtreeResponseTime > 0 || typeof syncResponseTime === 'number' && syncResponseTime > 0 || typeof syncMessageReceiveTime === 'number' && syncMessageReceiveTime > 0;
|
|
166
|
+
if (!hasMeaningfulSegment) {
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
return _objectSpread(_objectSpread(_objectSpread({
|
|
170
|
+
randomBackoffTime: this.getClampedLocusSyncLatency(record.randomBackoffTime),
|
|
171
|
+
hashtreePrepTime: hashtreePrepTime,
|
|
172
|
+
hashtreeResponseTime: hashtreeResponseTime,
|
|
173
|
+
syncPrepTime: syncPrepTime
|
|
174
|
+
}, typeof syncResponseTime === 'number' ? {
|
|
175
|
+
syncResponseTime: syncResponseTime
|
|
176
|
+
} : {}), typeof syncMessageReceiveTime === 'number' ? {
|
|
177
|
+
syncMessageReceiveTime: syncMessageReceiveTime
|
|
178
|
+
} : {}), {}, {
|
|
179
|
+
totalTime: totalTime
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Records the time the LLM state-update message for a Locus sync arrived. This is the milestone
|
|
185
|
+
* that gates the client.locus.sync.complete metric: the metric is emitted only for flows where
|
|
186
|
+
* the update came over LLM (aligned with the agreed requirement that body/http-only syncs do not
|
|
187
|
+
* emit). The record is matched by meeting id + tracking id, regardless of which other milestones
|
|
188
|
+
* are present yet, so the real arrival time is captured even if the /sync response has not landed.
|
|
189
|
+
* @param meetingId meeting id
|
|
190
|
+
* @param trackingId sync tracking id used to match the pending record
|
|
191
|
+
* @returns void
|
|
192
|
+
*/
|
|
193
|
+
}, {
|
|
194
|
+
key: "recordLocusSyncMessageReceived",
|
|
195
|
+
value: function recordLocusSyncMessageReceived(meetingId, trackingId) {
|
|
196
|
+
if (!trackingId) {
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
var record = this.getLocusSyncLatencyRecord(meetingId, trackingId);
|
|
200
|
+
if (record && typeof record.messageReceived !== 'number') {
|
|
201
|
+
record.messageReceived = new Date().getTime();
|
|
202
|
+
var records = this.meetingLatencies.get(meetingId);
|
|
203
|
+
if (records) {
|
|
204
|
+
this.setMeetingLatencyRecords(meetingId, records, {
|
|
205
|
+
changedRecord: record
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Complete and remove the Locus sync latency record for a meeting + tracking id.
|
|
213
|
+
*
|
|
214
|
+
* Both milestones are required, in any arrival order: the metric is emitted only once BOTH the
|
|
215
|
+
* matching LLM broadcast (messageReceived) AND the /sync response have arrived. The LLM broadcast
|
|
216
|
+
* confirms the client that issued the /sync received the resulting state update for its tracking
|
|
217
|
+
* id (so sync cancel, or a sync storm where the final broadcast echoed another client's tracking
|
|
218
|
+
* id, never emit); the /sync response is always waited for too, since the LLM message can arrive
|
|
219
|
+
* before the HTTP response. If either milestone is missing the record is kept so the other can
|
|
220
|
+
* still arrive; it is only consumed when the metric is actually emitted.
|
|
221
|
+
* @param meetingId meeting id
|
|
222
|
+
* @param trackingId sync tracking id used to match the pending record
|
|
223
|
+
* @returns completed sync latency metric payload, or undefined when not completed
|
|
224
|
+
*/
|
|
225
|
+
}, {
|
|
226
|
+
key: "completeLocusSyncLatency",
|
|
227
|
+
value: function completeLocusSyncLatency(meetingId, trackingId) {
|
|
228
|
+
if (!trackingId) {
|
|
229
|
+
return undefined;
|
|
230
|
+
}
|
|
231
|
+
var record = this.getLocusSyncLatencyRecord(meetingId, trackingId);
|
|
232
|
+
if (!record) {
|
|
233
|
+
return undefined;
|
|
234
|
+
}
|
|
235
|
+
var hasMessageReceived = typeof record.messageReceived === 'number';
|
|
236
|
+
var hasSyncResponse = typeof record.syncResponse === 'number';
|
|
237
|
+
|
|
238
|
+
// Wait until both milestones are present before emitting. The LLM broadcast and the /sync
|
|
239
|
+
// response can arrive in either order, so we keep the record until the second one lands.
|
|
240
|
+
if (!hasMessageReceived || !hasSyncResponse) {
|
|
241
|
+
return undefined;
|
|
242
|
+
}
|
|
243
|
+
var syncLatency = this.getLocusSyncLatency(meetingId, trackingId);
|
|
244
|
+
this.removeLocusSyncLatencyRecord(meetingId, trackingId);
|
|
245
|
+
if (!syncLatency) {
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
var completed = {
|
|
249
|
+
dataSet: record.dataSetName,
|
|
250
|
+
syncLatency: syncLatency
|
|
251
|
+
};
|
|
252
|
+
return completed;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Helper to calculate end - start for Locus sync milestones.
|
|
257
|
+
* @param record tracked milestone timestamps
|
|
258
|
+
* @param a start milestone
|
|
259
|
+
* @param b end milestone
|
|
260
|
+
* @returns latency
|
|
261
|
+
*/
|
|
262
|
+
}, {
|
|
263
|
+
key: "getDiffBetweenLocusSyncTimestamps",
|
|
264
|
+
value: function getDiffBetweenLocusSyncTimestamps(record, a, b) {
|
|
265
|
+
var start = record[a];
|
|
266
|
+
var end = record[b];
|
|
267
|
+
if (typeof start !== 'number' || typeof end !== 'number') {
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
return this.getClampedLocusSyncLatency(end - start);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get the timestamp that starts the sync prep segment.
|
|
275
|
+
* @param record tracked milestone timestamps
|
|
276
|
+
* @returns sync prep start milestone
|
|
277
|
+
*/
|
|
278
|
+
}, {
|
|
279
|
+
key: "getLocusSyncPrepStart",
|
|
280
|
+
value: function getLocusSyncPrepStart(record) {
|
|
281
|
+
return typeof record.hashTreeResponse === 'number' ? 'hashTreeResponse' : 'syncStart';
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Round and clamp Locus sync latency values.
|
|
286
|
+
* @param latency latency value
|
|
287
|
+
* @returns rounded latency
|
|
288
|
+
*/
|
|
289
|
+
}, {
|
|
290
|
+
key: "getClampedLocusSyncLatency",
|
|
291
|
+
value: function getClampedLocusSyncLatency(latency) {
|
|
292
|
+
return Math.round((0, _lodash.clamp)(latency, 0, this.MAX_INTEGER));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Finds a Locus sync latency record for a meeting using a predicate run against each stored
|
|
297
|
+
* locusSync record.
|
|
298
|
+
* @param meetingId meeting id
|
|
299
|
+
* @param predicate matcher run against each locusSync record
|
|
300
|
+
* @param options.searchFromLatest when true, iterate from the most recently added record first
|
|
301
|
+
* @returns matching Locus sync latency record
|
|
302
|
+
*/
|
|
303
|
+
}, {
|
|
304
|
+
key: "findLocusSyncRecord",
|
|
305
|
+
value: function findLocusSyncRecord(meetingId, predicate) {
|
|
306
|
+
var _orderedRecords$find;
|
|
307
|
+
var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
|
|
308
|
+
_ref$searchFromLatest = _ref.searchFromLatest,
|
|
309
|
+
searchFromLatest = _ref$searchFromLatest === void 0 ? false : _ref$searchFromLatest;
|
|
310
|
+
var records = this.meetingLatencies.get(meetingId);
|
|
311
|
+
if (!records) {
|
|
312
|
+
return undefined;
|
|
313
|
+
}
|
|
314
|
+
var orderedRecords = searchFromLatest ? (0, _toConsumableArray2.default)(records).reverse() : records;
|
|
315
|
+
return (_orderedRecords$find = orderedRecords.find(function (record) {
|
|
316
|
+
return predicate(record.locusSync);
|
|
317
|
+
})) === null || _orderedRecords$find === void 0 ? void 0 : _orderedRecords$find.locusSync;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Get the Locus sync latency record for a meeting and tracking id.
|
|
322
|
+
* @param meetingId meeting id
|
|
323
|
+
* @param trackingId /sync response tracking id
|
|
324
|
+
* @returns matching Locus sync latency record
|
|
325
|
+
*/
|
|
326
|
+
}, {
|
|
327
|
+
key: "getLocusSyncLatencyRecord",
|
|
328
|
+
value: function getLocusSyncLatencyRecord(meetingId, trackingId) {
|
|
329
|
+
return this.findLocusSyncRecord(meetingId, function (locusSync) {
|
|
330
|
+
return locusSync.trackingId === trackingId;
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Remove the Locus sync latency record for a meeting and tracking id.
|
|
336
|
+
* @param meetingId meeting id
|
|
337
|
+
* @param trackingId sync tracking id used to match the record
|
|
338
|
+
* @returns void
|
|
339
|
+
*/
|
|
340
|
+
}, {
|
|
341
|
+
key: "removeLocusSyncLatencyRecord",
|
|
342
|
+
value: function removeLocusSyncLatencyRecord(meetingId, trackingId) {
|
|
343
|
+
var records = this.meetingLatencies.get(meetingId);
|
|
344
|
+
if (!records) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
var remainingRecords = records.filter(function (_ref2) {
|
|
348
|
+
var locusSync = _ref2.locusSync;
|
|
349
|
+
return locusSync.trackingId !== trackingId;
|
|
350
|
+
});
|
|
351
|
+
this.setMeetingLatencyRecords(meetingId, remainingRecords);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Prune stale never-completed Locus sync latency records for the same dataset as the record that
|
|
356
|
+
* just changed. Records are dropped when they have been pending longer than the TTL, or to keep
|
|
357
|
+
* the number of records per dataset under the safety cap (oldest pending candidates first).
|
|
358
|
+
* @param records current records for the meeting
|
|
359
|
+
* @param changedRecord the record that was just updated, used as the reference point
|
|
360
|
+
* @returns the records to keep
|
|
361
|
+
*/
|
|
362
|
+
}, {
|
|
363
|
+
key: "pruneStaleLocusSyncLatencyRecords",
|
|
364
|
+
value: function pruneStaleLocusSyncLatencyRecords(records, changedRecord) {
|
|
365
|
+
var _this3 = this;
|
|
366
|
+
var currentTimestamp = this.getLatestLocusSyncTimestamp(changedRecord);
|
|
367
|
+
if (typeof currentTimestamp !== 'number') {
|
|
368
|
+
return records;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// A record is only a cleanup candidate when it belongs to the same dataset as the record that
|
|
372
|
+
// just changed, is not that record itself, and has never completed. Everything else is kept.
|
|
373
|
+
var isPrunableCandidate = function isPrunableCandidate(locusSync) {
|
|
374
|
+
if (locusSync.dataSetName !== changedRecord.dataSetName) {
|
|
375
|
+
return false;
|
|
376
|
+
}
|
|
377
|
+
if (changedRecord.trackingId && locusSync.trackingId === changedRecord.trackingId) {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
var hasCompleted = typeof locusSync.syncResponse === 'number' && typeof locusSync.messageReceived === 'number';
|
|
381
|
+
return !hasCompleted;
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
// 1) Drop candidates that have been pending longer than the TTL.
|
|
385
|
+
var recordsAfterTtl = records.filter(function (_ref3) {
|
|
386
|
+
var locusSync = _ref3.locusSync;
|
|
387
|
+
if (!isPrunableCandidate(locusSync)) {
|
|
388
|
+
return true;
|
|
389
|
+
}
|
|
390
|
+
var lastTimestamp = _this3.getLatestLocusSyncTimestamp(locusSync);
|
|
391
|
+
if (typeof lastTimestamp !== 'number') {
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
return currentTimestamp - lastTimestamp < _this3.LOCUS_SYNC_PENDING_RECORD_TTL_MS;
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
// 2) Cap the number of records kept per dataset, dropping the oldest pending candidates first.
|
|
398
|
+
var overflowCount = Math.max(0, recordsAfterTtl.filter(function (_ref4) {
|
|
399
|
+
var locusSync = _ref4.locusSync;
|
|
400
|
+
return locusSync.dataSetName === changedRecord.dataSetName;
|
|
401
|
+
}).length - this.LOCUS_SYNC_MAX_RECORDS_PER_DATASET);
|
|
402
|
+
if (overflowCount === 0) {
|
|
403
|
+
return recordsAfterTtl;
|
|
404
|
+
}
|
|
405
|
+
return recordsAfterTtl.filter(function (_ref5) {
|
|
406
|
+
var locusSync = _ref5.locusSync;
|
|
407
|
+
if (overflowCount <= 0 || !isPrunableCandidate(locusSync)) {
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
overflowCount -= 1;
|
|
411
|
+
return false;
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Get the most recent milestone timestamp stored on a Locus sync latency record, checking
|
|
417
|
+
* milestones from latest to earliest so the newest available one is returned.
|
|
418
|
+
* @param record tracked milestone timestamps
|
|
419
|
+
* @returns latest milestone timestamp, or undefined when none are set
|
|
420
|
+
*/
|
|
421
|
+
}, {
|
|
422
|
+
key: "getLatestLocusSyncTimestamp",
|
|
423
|
+
value: function getLatestLocusSyncTimestamp(record) {
|
|
424
|
+
var _ref6, _ref7, _ref8, _ref9, _record$messageReceiv;
|
|
425
|
+
return (_ref6 = (_ref7 = (_ref8 = (_ref9 = (_record$messageReceiv = record.messageReceived) !== null && _record$messageReceiv !== void 0 ? _record$messageReceiv : record.syncResponse) !== null && _ref9 !== void 0 ? _ref9 : record.syncRequest) !== null && _ref8 !== void 0 ? _ref8 : record.hashTreeResponse) !== null && _ref7 !== void 0 ? _ref7 : record.hashTreeRequest) !== null && _ref6 !== void 0 ? _ref6 : record.syncStart;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Persist the Locus sync latency records for a meeting, deleting the meeting entry when no
|
|
430
|
+
* records remain. When pruneOptions is provided, stale records are pruned before saving.
|
|
431
|
+
* @param meetingId meeting id
|
|
432
|
+
* @param records records to save
|
|
433
|
+
* @param pruneOptions when provided, triggers stale record pruning relative to changedRecord
|
|
434
|
+
* @returns void
|
|
435
|
+
*/
|
|
436
|
+
}, {
|
|
437
|
+
key: "setMeetingLatencyRecords",
|
|
438
|
+
value: function setMeetingLatencyRecords(meetingId, records, pruneOptions) {
|
|
439
|
+
var recordsToSave = pruneOptions ? this.pruneStaleLocusSyncLatencyRecords(records, pruneOptions.changedRecord) : records;
|
|
440
|
+
if (recordsToSave.length > 0) {
|
|
441
|
+
this.meetingLatencies.set(meetingId, recordsToSave);
|
|
442
|
+
} else {
|
|
443
|
+
this.meetingLatencies.delete(meetingId);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Get the latest Locus sync latency record waiting for sync.start.
|
|
449
|
+
* @param meetingId meeting id
|
|
450
|
+
* @param dataSetName dataset name
|
|
451
|
+
* @returns latest pending Locus sync latency record
|
|
452
|
+
*/
|
|
453
|
+
}, {
|
|
454
|
+
key: "getLatestPendingLocusSyncLatencyRecord",
|
|
455
|
+
value: function getLatestPendingLocusSyncLatencyRecord(meetingId, dataSetName) {
|
|
456
|
+
return this.findLocusSyncRecord(meetingId, function (locusSync) {
|
|
457
|
+
return locusSync.dataSetName === dataSetName && locusSync.syncStart === undefined;
|
|
458
|
+
}, {
|
|
459
|
+
searchFromLatest: true
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Store random backoff latency for the current or next Locus sync latency record.
|
|
465
|
+
* @param meetingId meeting id
|
|
466
|
+
* @param dataSetName dataset name
|
|
467
|
+
* @param randomBackoffTime random backoff latency value
|
|
468
|
+
* @returns void
|
|
469
|
+
*/
|
|
470
|
+
}, {
|
|
471
|
+
key: "saveLocusSyncBackoffLatency",
|
|
472
|
+
value: function saveLocusSyncBackoffLatency(_ref0) {
|
|
473
|
+
var _this$meetingLatencie;
|
|
474
|
+
var meetingId = _ref0.meetingId,
|
|
475
|
+
dataSetName = _ref0.dataSetName,
|
|
476
|
+
randomBackoffTime = _ref0.randomBackoffTime;
|
|
477
|
+
var pendingRecord = this.getLatestPendingLocusSyncLatencyRecord(meetingId, dataSetName);
|
|
478
|
+
if (pendingRecord) {
|
|
479
|
+
pendingRecord.randomBackoffTime = randomBackoffTime;
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
var records = (_this$meetingLatencie = this.meetingLatencies.get(meetingId)) !== null && _this$meetingLatencie !== void 0 ? _this$meetingLatencie : [];
|
|
483
|
+
records.push({
|
|
484
|
+
locusSync: {
|
|
485
|
+
meetingId: meetingId,
|
|
486
|
+
dataSetName: dataSetName,
|
|
487
|
+
randomBackoffTime: randomBackoffTime
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
this.meetingLatencies.set(meetingId, records);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Checks if metric event name is a Locus sync latency milestone.
|
|
495
|
+
* @param key event name
|
|
496
|
+
* @returns whether event is Locus sync latency milestone
|
|
497
|
+
*/
|
|
498
|
+
}, {
|
|
499
|
+
key: "isLocusSyncLatencyEvent",
|
|
500
|
+
value: function isLocusSyncLatencyEvent(key) {
|
|
501
|
+
return _metrics.LOCUS_SYNC_LATENCY_EVENT_NAMES.includes(key);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Stores a Locus sync latency milestone timestamp.
|
|
506
|
+
* @param options options
|
|
507
|
+
*/
|
|
508
|
+
}, {
|
|
509
|
+
key: "saveLocusSyncLatencyTimestamp",
|
|
510
|
+
value: function saveLocusSyncLatencyTimestamp(_ref1) {
|
|
511
|
+
var _this$meetingLatencie4;
|
|
512
|
+
var meetingId = _ref1.meetingId,
|
|
513
|
+
dataSetName = _ref1.dataSetName,
|
|
514
|
+
key = _ref1.key,
|
|
515
|
+
value = _ref1.value,
|
|
516
|
+
trackingId = _ref1.trackingId;
|
|
517
|
+
if (!trackingId) {
|
|
518
|
+
// @ts-ignore
|
|
519
|
+
this.webex.logger.warn("CallDiagnosticLatencies: saveLocusSyncLatencyTimestamp called without a trackingId for key \"".concat(key, "\"; skipping Locus sync milestone"));
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (key === 'internal.client.locus.sync.start') {
|
|
523
|
+
var _this$meetingLatencie3;
|
|
524
|
+
var pendingRecord = this.getLatestPendingLocusSyncLatencyRecord(meetingId, dataSetName);
|
|
525
|
+
if (pendingRecord) {
|
|
526
|
+
var _this$meetingLatencie2;
|
|
527
|
+
pendingRecord.trackingId = trackingId;
|
|
528
|
+
pendingRecord.syncStart = value;
|
|
529
|
+
this.setMeetingLatencyRecords(meetingId, (_this$meetingLatencie2 = this.meetingLatencies.get(meetingId)) !== null && _this$meetingLatencie2 !== void 0 ? _this$meetingLatencie2 : [], {
|
|
530
|
+
changedRecord: pendingRecord
|
|
531
|
+
});
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
var records = (_this$meetingLatencie3 = this.meetingLatencies.get(meetingId)) !== null && _this$meetingLatencie3 !== void 0 ? _this$meetingLatencie3 : [];
|
|
535
|
+
records.push({
|
|
536
|
+
locusSync: {
|
|
537
|
+
meetingId: meetingId,
|
|
538
|
+
dataSetName: dataSetName,
|
|
539
|
+
randomBackoffTime: 0,
|
|
540
|
+
trackingId: trackingId,
|
|
541
|
+
syncStart: value
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
this.setMeetingLatencyRecords(meetingId, records, {
|
|
545
|
+
changedRecord: records[records.length - 1].locusSync
|
|
546
|
+
});
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Every sync milestone after sync.start is stamped with the same tracking id that was
|
|
551
|
+
// generated up-front and forced onto the /sync request, so the record is located by
|
|
552
|
+
// meeting id + tracking id.
|
|
553
|
+
var record = this.getLocusSyncLatencyRecord(meetingId, trackingId);
|
|
554
|
+
if (!record) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
switch (key) {
|
|
558
|
+
case 'internal.client.locus.hashtree.request':
|
|
559
|
+
record.hashTreeRequest = value;
|
|
560
|
+
break;
|
|
561
|
+
case 'internal.client.locus.hashtree.response':
|
|
562
|
+
record.hashTreeResponse = value;
|
|
563
|
+
break;
|
|
564
|
+
case 'internal.client.locus.sync.request':
|
|
565
|
+
record.syncRequest = value;
|
|
566
|
+
break;
|
|
567
|
+
case 'internal.client.locus.sync.response':
|
|
568
|
+
record.syncResponse = value;
|
|
569
|
+
break;
|
|
570
|
+
case 'internal.client.locus.sync.message.received':
|
|
571
|
+
record.messageReceived = value;
|
|
572
|
+
break;
|
|
573
|
+
default:
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
this.setMeetingLatencyRecords(meetingId, (_this$meetingLatencie4 = this.meetingLatencies.get(meetingId)) !== null && _this$meetingLatencie4 !== void 0 ? _this$meetingLatencie4 : [], {
|
|
577
|
+
changedRecord: record
|
|
578
|
+
});
|
|
57
579
|
}
|
|
58
580
|
|
|
59
581
|
/**
|
|
@@ -90,17 +612,28 @@ var CallDiagnosticLatencies = exports.default = /*#__PURE__*/function (_WebexPlu
|
|
|
90
612
|
*/
|
|
91
613
|
}, {
|
|
92
614
|
key: "saveTimestamp",
|
|
93
|
-
value: function saveTimestamp(
|
|
94
|
-
var key =
|
|
95
|
-
|
|
96
|
-
value =
|
|
97
|
-
|
|
98
|
-
options =
|
|
615
|
+
value: function saveTimestamp(_ref10) {
|
|
616
|
+
var key = _ref10.key,
|
|
617
|
+
_ref10$value = _ref10.value,
|
|
618
|
+
value = _ref10$value === void 0 ? new Date().getTime() : _ref10$value,
|
|
619
|
+
_ref10$options = _ref10.options,
|
|
620
|
+
options = _ref10$options === void 0 ? {} : _ref10$options;
|
|
99
621
|
// save the meetingId so we can use the meeting object in latency calculations if needed
|
|
100
622
|
var meetingId = options.meetingId;
|
|
101
623
|
if (meetingId) {
|
|
102
624
|
this.setMeetingId(meetingId);
|
|
103
625
|
}
|
|
626
|
+
if (this.isLocusSyncLatencyEvent(key) && options.dataSetName && options.meetingId) {
|
|
627
|
+
this.saveLocusSyncLatencyTimestamp({
|
|
628
|
+
meetingId: options.meetingId,
|
|
629
|
+
dataSetName: options.dataSetName,
|
|
630
|
+
key: key,
|
|
631
|
+
value: value,
|
|
632
|
+
trackingId: options.trackingId
|
|
633
|
+
});
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
|
|
104
637
|
// for some events we're only interested in the first timestamp not last
|
|
105
638
|
// as these events can happen multiple times
|
|
106
639
|
if (key === 'client.media.rx.start' || key === 'client.media.tx.start' || key === 'internal.client.meetinginfo.request' || key === 'internal.client.meetinginfo.response' || key === 'client.media-engine.remote-sdp-received') {
|
|
@@ -118,14 +651,35 @@ var CallDiagnosticLatencies = exports.default = /*#__PURE__*/function (_WebexPlu
|
|
|
118
651
|
* Store precomputed latency value
|
|
119
652
|
* @param key - key
|
|
120
653
|
* @param value - value
|
|
121
|
-
* @param
|
|
654
|
+
* @param options - store options (a legacy boolean `accumulate` flag is also accepted)
|
|
655
|
+
* @param options.accumulate - when it is true, it overwrites existing value with sum of the current value and the new measurement otherwise just store the new measurement
|
|
656
|
+
* @param options.meetingId - meeting id, only used for Locus sync latency records
|
|
657
|
+
* @param options.dataSetName - dataset name, only used for Locus sync latency records
|
|
122
658
|
* @throws
|
|
123
659
|
* @returns
|
|
124
660
|
*/
|
|
125
661
|
}, {
|
|
126
662
|
key: "saveLatency",
|
|
127
663
|
value: function saveLatency(key, value) {
|
|
128
|
-
var
|
|
664
|
+
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
|
|
665
|
+
// Older (untyped) callers may still pass a boolean `accumulate` as the third argument
|
|
666
|
+
// (saveLatency(key, value, true)). Normalize it to the options object before destructuring,
|
|
667
|
+
// otherwise accumulation is silently lost and legacy SDK/plugin consumers underreport latencies.
|
|
668
|
+
var normalizedOptions = typeof options === 'boolean' ? {
|
|
669
|
+
accumulate: options
|
|
670
|
+
} : options;
|
|
671
|
+
var _normalizedOptions$ac = normalizedOptions.accumulate,
|
|
672
|
+
accumulate = _normalizedOptions$ac === void 0 ? false : _normalizedOptions$ac,
|
|
673
|
+
meetingId = normalizedOptions.meetingId,
|
|
674
|
+
dataSetName = normalizedOptions.dataSetName;
|
|
675
|
+
if (key === 'internal.client.locus.sync.random.backoff' && meetingId && dataSetName) {
|
|
676
|
+
this.saveLocusSyncBackoffLatency({
|
|
677
|
+
meetingId: meetingId,
|
|
678
|
+
dataSetName: dataSetName,
|
|
679
|
+
randomBackoffTime: value
|
|
680
|
+
});
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
129
683
|
var existingValue = accumulate ? this.precomputedLatencies.get(key) || 0 : 0;
|
|
130
684
|
this.precomputedLatencies.set(key, value + existingValue);
|
|
131
685
|
}
|
|
@@ -140,11 +694,13 @@ var CallDiagnosticLatencies = exports.default = /*#__PURE__*/function (_WebexPlu
|
|
|
140
694
|
}, {
|
|
141
695
|
key: "measureLatency",
|
|
142
696
|
value: function measureLatency(callback, key) {
|
|
143
|
-
var
|
|
697
|
+
var _this4 = this;
|
|
144
698
|
var accumulate = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
|
|
145
699
|
var start = performance.now();
|
|
146
700
|
return callback().finally(function () {
|
|
147
|
-
|
|
701
|
+
_this4.saveLatency(key, performance.now() - start, {
|
|
702
|
+
accumulate: accumulate
|
|
703
|
+
});
|
|
148
704
|
});
|
|
149
705
|
}
|
|
150
706
|
|
|
@@ -180,11 +736,11 @@ var CallDiagnosticLatencies = exports.default = /*#__PURE__*/function (_WebexPlu
|
|
|
180
736
|
return undefined;
|
|
181
737
|
}
|
|
182
738
|
var diff = end - start;
|
|
183
|
-
var
|
|
184
|
-
|
|
185
|
-
minimum =
|
|
186
|
-
|
|
187
|
-
maximum =
|
|
739
|
+
var _ref11 = clampValues || {},
|
|
740
|
+
_ref11$minimum = _ref11.minimum,
|
|
741
|
+
minimum = _ref11$minimum === void 0 ? 0 : _ref11$minimum,
|
|
742
|
+
_ref11$maximum = _ref11.maximum,
|
|
743
|
+
maximum = _ref11$maximum === void 0 ? this.MAX_INTEGER : _ref11$maximum;
|
|
188
744
|
return (0, _lodash.clamp)(diff, minimum, maximum);
|
|
189
745
|
}
|
|
190
746
|
|