@splitsoftware/splitio-commons 1.3.2-rc.3 → 1.3.2-rc.6
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/CHANGES.txt +5 -0
- package/cjs/listeners/browser.js +6 -1
- package/cjs/storages/KeyBuilderSS.js +9 -5
- package/cjs/storages/inRedis/RedisAdapter.js +1 -1
- package/cjs/storages/inRedis/TelemetryCacheInRedis.js +7 -0
- package/cjs/storages/inRedis/index.js +4 -1
- package/cjs/storages/pluggable/index.js +2 -1
- package/cjs/sync/submitters/telemetrySubmitter.js +31 -17
- package/cjs/trackers/telemetryTracker.js +2 -2
- package/cjs/utils/timeTracker/now/browser.js +1 -1
- package/cjs/utils/timeTracker/now/node.js +1 -2
- package/esm/listeners/browser.js +6 -1
- package/esm/storages/KeyBuilderSS.js +9 -5
- package/esm/storages/inRedis/RedisAdapter.js +1 -1
- package/esm/storages/inRedis/TelemetryCacheInRedis.js +7 -0
- package/esm/storages/inRedis/index.js +4 -1
- package/esm/storages/pluggable/index.js +2 -1
- package/esm/sync/submitters/telemetrySubmitter.js +30 -17
- package/esm/trackers/telemetryTracker.js +2 -2
- package/esm/utils/timeTracker/now/browser.js +1 -1
- package/esm/utils/timeTracker/now/node.js +1 -2
- package/package.json +1 -1
- package/src/listeners/browser.ts +6 -1
- package/src/sdkFactory/types.ts +16 -2
- package/src/storages/KeyBuilderSS.ts +12 -6
- package/src/storages/inRedis/RedisAdapter.ts +1 -1
- package/src/storages/inRedis/TelemetryCacheInRedis.ts +7 -0
- package/src/storages/inRedis/index.ts +5 -1
- package/src/storages/pluggable/index.ts +2 -1
- package/src/sync/submitters/telemetrySubmitter.ts +33 -18
- package/src/sync/submitters/types.ts +13 -7
- package/src/trackers/telemetryTracker.ts +2 -2
- package/src/utils/murmur3/utfx.ts +1 -2
- package/src/utils/timeTracker/now/browser.ts +1 -1
- package/src/utils/timeTracker/now/node.ts +1 -2
- package/types/sdkFactory/types.d.ts +16 -2
- package/types/storages/KeyBuilderSS.d.ts +3 -2
- package/types/storages/inRedis/TelemetryCacheInRedis.d.ts +1 -0
- package/types/sync/submitters/telemetrySubmitter.d.ts +3 -2
- package/types/sync/submitters/types.d.ts +10 -6
- package/types/utils/timeTracker/index.d.ts +1 -70
- package/cjs/utils/timeTracker/index.js +0 -197
- package/esm/utils/timeTracker/index.js +0 -194
- package/src/utils/timeTracker/index.ts +0 -226
package/CHANGES.txt
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
1.4.0 (May 20, 2022)
|
|
2
|
+
- Added `scheduler.telemetryRefreshRate` property to SDK configuration, and deprecated `scheduler.metricsRefreshRate` property.
|
|
3
|
+
- Updated SDK telemetry storage, metrics and updater to be more effective and send less often.
|
|
4
|
+
- Bugfixing - Updated default values for `scheduler.impressionsRefreshRate` config parameter: 300s for OPTIMIZED impression mode and 60s for DEBUG impression mode.
|
|
5
|
+
|
|
1
6
|
1.3.1 (April 19, 2022)
|
|
2
7
|
- Bugfixing - Added peer dependencies to avoid issues when requiring some third-party dependencies used by modules of the package (Related to issue https://github.com/splitio/javascript-client/issues/662).
|
|
3
8
|
- Bugfixing - Updated `ready` method to rejects the promise with an Error object instead of a string value (Related to issue https://github.com/splitio/javascript-client/issues/654).
|
package/cjs/listeners/browser.js
CHANGED
|
@@ -7,6 +7,7 @@ var constants_1 = require("../utils/constants");
|
|
|
7
7
|
var objectAssign_1 = require("../utils/lang/objectAssign");
|
|
8
8
|
var constants_2 = require("../logger/constants");
|
|
9
9
|
var consent_1 = require("../consent");
|
|
10
|
+
var telemetrySubmitter_1 = require("../sync/submitters/telemetrySubmitter");
|
|
10
11
|
// 'unload' event is used instead of 'beforeunload', since 'unload' is not a cancelable event, so no other listeners can stop the event from occurring.
|
|
11
12
|
var UNLOAD_DOM_EVENT = 'unload';
|
|
12
13
|
var EVENT_NAME = 'for unload page event.';
|
|
@@ -63,7 +64,11 @@ var BrowserSignalListener = /** @class */ (function () {
|
|
|
63
64
|
this._flushData(eventsUrl + '/events/beacon', this.storage.events, this.serviceApi.postEventsBulk);
|
|
64
65
|
if (this.storage.impressionCounts)
|
|
65
66
|
this._flushData(eventsUrl + '/testImpressions/count/beacon', this.storage.impressionCounts, this.serviceApi.postTestImpressionsCount, impressionCountsSubmitter_1.fromImpressionCountsCollector);
|
|
66
|
-
|
|
67
|
+
if (this.storage.telemetry) {
|
|
68
|
+
var telemetryUrl = this.settings.urls.telemetry;
|
|
69
|
+
var telemetryCacheAdapter = (0, telemetrySubmitter_1.telemetryCacheStatsAdapter)(this.storage.telemetry, this.storage.splits, this.storage.segments);
|
|
70
|
+
this._flushData(telemetryUrl + '/v1/metrics/usage/beacon', telemetryCacheAdapter, this.serviceApi.postMetricsUsage);
|
|
71
|
+
}
|
|
67
72
|
}
|
|
68
73
|
// Close streaming connection
|
|
69
74
|
if (this.syncManager.pushManager)
|
|
@@ -20,23 +20,27 @@ var KeyBuilderSS = /** @class */ (function (_super) {
|
|
|
20
20
|
KeyBuilderSS.prototype.buildRegisteredSegmentsKey = function () {
|
|
21
21
|
return this.prefix + ".segments.registered";
|
|
22
22
|
};
|
|
23
|
-
KeyBuilderSS.prototype.buildVersionablePrefix = function () {
|
|
24
|
-
return this.metadata.s + "/" + this.metadata.n + "/" + this.metadata.i;
|
|
25
|
-
};
|
|
26
23
|
KeyBuilderSS.prototype.buildImpressionsKey = function () {
|
|
27
24
|
return this.prefix + ".impressions";
|
|
28
25
|
};
|
|
29
26
|
KeyBuilderSS.prototype.buildEventsKey = function () {
|
|
30
27
|
return this.prefix + ".events";
|
|
31
28
|
};
|
|
29
|
+
KeyBuilderSS.prototype.searchPatternForSplitKeys = function () {
|
|
30
|
+
return this.buildSplitKeyPrefix() + "*";
|
|
31
|
+
};
|
|
32
|
+
/* Telemetry keys */
|
|
32
33
|
KeyBuilderSS.prototype.buildLatencyKey = function (method, bucket) {
|
|
33
34
|
return this.prefix + ".telemetry.latencies::" + this.buildVersionablePrefix() + "/" + methodNames[method] + "/" + bucket;
|
|
34
35
|
};
|
|
35
36
|
KeyBuilderSS.prototype.buildExceptionKey = function (method) {
|
|
36
37
|
return this.prefix + ".telemetry.exceptions::" + this.buildVersionablePrefix() + "/" + methodNames[method];
|
|
37
38
|
};
|
|
38
|
-
KeyBuilderSS.prototype.
|
|
39
|
-
return this.
|
|
39
|
+
KeyBuilderSS.prototype.buildInitKey = function () {
|
|
40
|
+
return this.prefix + ".telemetry.init::" + this.buildVersionablePrefix();
|
|
41
|
+
};
|
|
42
|
+
KeyBuilderSS.prototype.buildVersionablePrefix = function () {
|
|
43
|
+
return this.metadata.s + "/" + this.metadata.n + "/" + this.metadata.i;
|
|
40
44
|
};
|
|
41
45
|
return KeyBuilderSS;
|
|
42
46
|
}(KeyBuilder_1.KeyBuilder));
|
|
@@ -9,7 +9,7 @@ var thenable_1 = require("../../utils/promise/thenable");
|
|
|
9
9
|
var timeout_1 = require("../../utils/promise/timeout");
|
|
10
10
|
var LOG_PREFIX = 'storage:redis-adapter: ';
|
|
11
11
|
// If we ever decide to fully wrap every method, there's a Commander.getBuiltinCommands from ioredis.
|
|
12
|
-
var METHODS_TO_PROMISE_WRAP = ['set', 'exec', 'del', 'get', 'keys', 'sadd', 'srem', 'sismember', 'smembers', 'incr', 'rpush', 'pipeline', 'expire', 'mget', 'lrange', 'ltrim'];
|
|
12
|
+
var METHODS_TO_PROMISE_WRAP = ['set', 'exec', 'del', 'get', 'keys', 'sadd', 'srem', 'sismember', 'smembers', 'incr', 'rpush', 'pipeline', 'expire', 'mget', 'lrange', 'ltrim', 'hset'];
|
|
13
13
|
// Not part of the settings since it'll vary on each storage. We should be removing storage specific logic from elsewhere.
|
|
14
14
|
var DEFAULT_OPTIONS = {
|
|
15
15
|
connectionTimeout: 10000,
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.TelemetryCacheInRedis = void 0;
|
|
4
4
|
var findLatencyIndex_1 = require("../findLatencyIndex");
|
|
5
|
+
var telemetrySubmitter_1 = require("../../sync/submitters/telemetrySubmitter");
|
|
6
|
+
var constants_1 = require("../../utils/constants");
|
|
5
7
|
var TelemetryCacheInRedis = /** @class */ (function () {
|
|
6
8
|
/**
|
|
7
9
|
* Create a Telemetry cache that uses Redis as storage.
|
|
@@ -24,6 +26,11 @@ var TelemetryCacheInRedis = /** @class */ (function () {
|
|
|
24
26
|
return this.redis.hincrby(key, field, 1)
|
|
25
27
|
.catch(function () { });
|
|
26
28
|
};
|
|
29
|
+
TelemetryCacheInRedis.prototype.recordConfig = function () {
|
|
30
|
+
var _a = this.keys.buildInitKey().split('::'), key = _a[0], field = _a[1];
|
|
31
|
+
var value = JSON.stringify((0, telemetrySubmitter_1.getTelemetryConfigStats)(constants_1.CONSUMER_MODE, constants_1.STORAGE_REDIS));
|
|
32
|
+
return this.redis.hset(key, field, value).catch(function () { });
|
|
33
|
+
};
|
|
27
34
|
return TelemetryCacheInRedis;
|
|
28
35
|
}());
|
|
29
36
|
exports.TelemetryCacheInRedis = TelemetryCacheInRedis;
|
|
@@ -21,16 +21,19 @@ function InRedisStorage(options) {
|
|
|
21
21
|
var log = _a.log, metadata = _a.metadata, onReadyCb = _a.onReadyCb;
|
|
22
22
|
var keys = new KeyBuilderSS_1.KeyBuilderSS(prefix, metadata);
|
|
23
23
|
var redisClient = new RedisAdapter_1.RedisAdapter(log, options.options || {});
|
|
24
|
+
var telemetry = new TelemetryCacheInRedis_1.TelemetryCacheInRedis(log, keys, redisClient);
|
|
24
25
|
// subscription to Redis connect event in order to emit SDK_READY event on consumer mode
|
|
25
26
|
redisClient.on('connect', function () {
|
|
26
27
|
onReadyCb();
|
|
28
|
+
// Synchronize config
|
|
29
|
+
telemetry.recordConfig();
|
|
27
30
|
});
|
|
28
31
|
return {
|
|
29
32
|
splits: new SplitsCacheInRedis_1.SplitsCacheInRedis(log, keys, redisClient),
|
|
30
33
|
segments: new SegmentsCacheInRedis_1.SegmentsCacheInRedis(log, keys, redisClient),
|
|
31
34
|
impressions: new ImpressionsCacheInRedis_1.ImpressionsCacheInRedis(log, keys.buildImpressionsKey(), redisClient, metadata),
|
|
32
35
|
events: new EventsCacheInRedis_1.EventsCacheInRedis(log, keys.buildEventsKey(), redisClient, metadata),
|
|
33
|
-
telemetry:
|
|
36
|
+
telemetry: telemetry,
|
|
34
37
|
// When using REDIS we should:
|
|
35
38
|
// 1- Disconnect from the storage
|
|
36
39
|
destroy: function () {
|
|
@@ -34,6 +34,7 @@ function validatePluggableStorageOptions(options) {
|
|
|
34
34
|
function wrapperConnect(wrapper, onReadyCb) {
|
|
35
35
|
wrapper.connect().then(function () {
|
|
36
36
|
onReadyCb();
|
|
37
|
+
// At the moment, we don't synchronize config with pluggable storage
|
|
37
38
|
}).catch(function (e) {
|
|
38
39
|
onReadyCb(e || new Error('Error connecting wrapper'));
|
|
39
40
|
});
|
|
@@ -66,7 +67,7 @@ function PluggableStorage(options) {
|
|
|
66
67
|
impressions: isPartialConsumer ? new ImpressionsCacheInMemory_1.ImpressionsCacheInMemory(impressionsQueueSize) : new ImpressionsCachePluggable_1.ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata),
|
|
67
68
|
impressionCounts: optimize ? new ImpressionCountsCacheInMemory_1.ImpressionCountsCacheInMemory() : undefined,
|
|
68
69
|
events: isPartialConsumer ? promisifyEventsTrack(new EventsCacheInMemory_1.EventsCacheInMemory(eventsQueueSize)) : new EventsCachePluggable_1.EventsCachePluggable(log, keys.buildEventsKey(), wrapper, metadata),
|
|
69
|
-
// @TODO Not using TelemetryCachePluggable yet
|
|
70
|
+
// @TODO Not using TelemetryCachePluggable yet because it's not supported by the Split Synchronizer, and needs to drop or queue operations while the wrapper is not ready
|
|
70
71
|
// telemetry: isPartialConsumer ? new TelemetryCacheInMemory() : new TelemetryCachePluggable(log, keys, wrapper),
|
|
71
72
|
// Disconnect the underlying storage
|
|
72
73
|
destroy: function () {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var _a, _b;
|
|
2
|
+
var _a, _b, _c;
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.telemetrySubmitterFactory = exports.telemetryCacheConfigAdapter = exports.telemetryCacheStatsAdapter = void 0;
|
|
4
|
+
exports.telemetrySubmitterFactory = exports.telemetryCacheConfigAdapter = exports.getTelemetryConfigStats = exports.telemetryCacheStatsAdapter = void 0;
|
|
5
5
|
var submitter_1 = require("./submitter");
|
|
6
6
|
var constants_1 = require("../../utils/constants");
|
|
7
7
|
var constants_2 = require("../../readiness/constants");
|
|
8
8
|
var settingsValidation_1 = require("../../utils/settingsValidation");
|
|
9
9
|
var apiKey_1 = require("../../utils/inputValidation/apiKey");
|
|
10
10
|
var timer_1 = require("../../utils/timeTracker/timer");
|
|
11
|
+
var objectAssign_1 = require("../../utils/lang/objectAssign");
|
|
11
12
|
/**
|
|
12
13
|
* Converts data from telemetry cache into /metrics/usage request payload.
|
|
13
14
|
*/
|
|
@@ -50,6 +51,11 @@ var IMPRESSIONS_MODE_MAP = (_b = {},
|
|
|
50
51
|
_b[constants_1.OPTIMIZED] = constants_1.OPTIMIZED_ENUM,
|
|
51
52
|
_b[constants_1.DEBUG] = constants_1.DEBUG_ENUM,
|
|
52
53
|
_b);
|
|
54
|
+
var USER_CONSENT_MAP = (_c = {},
|
|
55
|
+
_c[constants_1.CONSENT_UNKNOWN] = 1,
|
|
56
|
+
_c[constants_1.CONSENT_GRANTED] = 2,
|
|
57
|
+
_c[constants_1.CONSENT_DECLINED] = 3,
|
|
58
|
+
_c);
|
|
53
59
|
function getActiveFactories() {
|
|
54
60
|
return Object.keys(apiKey_1.usedKeysMap).length;
|
|
55
61
|
}
|
|
@@ -58,6 +64,15 @@ function getRedundantActiveFactories() {
|
|
|
58
64
|
return acum + apiKey_1.usedKeysMap[apiKey] - 1;
|
|
59
65
|
}, 0);
|
|
60
66
|
}
|
|
67
|
+
function getTelemetryConfigStats(mode, storageType) {
|
|
68
|
+
return {
|
|
69
|
+
oM: OPERATION_MODE_MAP[mode],
|
|
70
|
+
st: storageType.toLowerCase(),
|
|
71
|
+
aF: getActiveFactories(),
|
|
72
|
+
rF: getRedundantActiveFactories(),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
exports.getTelemetryConfigStats = getTelemetryConfigStats;
|
|
61
76
|
/**
|
|
62
77
|
* Converts data from telemetry cache and settings into /metrics/config request payload.
|
|
63
78
|
*/
|
|
@@ -67,16 +82,16 @@ function telemetryCacheConfigAdapter(telemetry, settings) {
|
|
|
67
82
|
clear: function () { },
|
|
68
83
|
state: function () {
|
|
69
84
|
var urls = settings.urls, scheduler = settings.scheduler;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
st: settings.storage.type.toLowerCase(),
|
|
85
|
+
var isClientSide = settings.core.key !== undefined;
|
|
86
|
+
return (0, objectAssign_1.objectAssign)(getTelemetryConfigStats(settings.mode, settings.storage.type), {
|
|
73
87
|
sE: settings.streamingEnabled,
|
|
74
88
|
rR: {
|
|
75
|
-
sp: scheduler.featuresRefreshRate,
|
|
76
|
-
se: scheduler.segmentsRefreshRate,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
89
|
+
sp: scheduler.featuresRefreshRate / 1000,
|
|
90
|
+
se: isClientSide ? undefined : scheduler.segmentsRefreshRate / 1000,
|
|
91
|
+
ms: isClientSide ? scheduler.segmentsRefreshRate / 1000 : undefined,
|
|
92
|
+
im: scheduler.impressionsRefreshRate / 1000,
|
|
93
|
+
ev: scheduler.eventsPushRate / 1000,
|
|
94
|
+
te: scheduler.telemetryRefreshRate / 1000,
|
|
80
95
|
},
|
|
81
96
|
uO: {
|
|
82
97
|
s: urls.sdk !== settingsValidation_1.base.urls.sdk,
|
|
@@ -90,14 +105,13 @@ function telemetryCacheConfigAdapter(telemetry, settings) {
|
|
|
90
105
|
iM: IMPRESSIONS_MODE_MAP[settings.sync.impressionsMode],
|
|
91
106
|
iL: settings.impressionListener ? true : false,
|
|
92
107
|
hP: false,
|
|
93
|
-
aF: getActiveFactories(),
|
|
94
|
-
rF: getRedundantActiveFactories(),
|
|
95
108
|
tR: telemetry.getTimeUntilReady(),
|
|
96
109
|
tC: telemetry.getTimeUntilReadyFromCache(),
|
|
97
110
|
nR: telemetry.getNonReadyUsage(),
|
|
98
111
|
t: telemetry.popTags(),
|
|
99
112
|
i: settings.integrations && settings.integrations.map(function (int) { return int.type; }),
|
|
100
|
-
|
|
113
|
+
uC: settings.userConsent ? USER_CONSENT_MAP[settings.userConsent] : 0
|
|
114
|
+
});
|
|
101
115
|
}
|
|
102
116
|
};
|
|
103
117
|
}
|
|
@@ -106,11 +120,11 @@ exports.telemetryCacheConfigAdapter = telemetryCacheConfigAdapter;
|
|
|
106
120
|
* Submitter that periodically posts telemetry data
|
|
107
121
|
*/
|
|
108
122
|
function telemetrySubmitterFactory(params) {
|
|
109
|
-
var _a = params.storage, splits = _a.splits, segments = _a.segments, telemetry = _a.telemetry;
|
|
110
|
-
if (!telemetry)
|
|
123
|
+
var _a = params.storage, splits = _a.splits, segments = _a.segments, telemetry = _a.telemetry, now = params.platform.now;
|
|
124
|
+
if (!telemetry || !now)
|
|
111
125
|
return; // No submitter created if telemetry cache is not defined
|
|
112
|
-
var settings = params.settings, _b = params.settings, log = _b.log, telemetryRefreshRate = _b.scheduler.telemetryRefreshRate, splitApi = params.splitApi,
|
|
113
|
-
var startTime = (0, timer_1.timer)(now
|
|
126
|
+
var settings = params.settings, _b = params.settings, log = _b.log, telemetryRefreshRate = _b.scheduler.telemetryRefreshRate, splitApi = params.splitApi, readiness = params.readiness, sdkReadinessManager = params.sdkReadinessManager;
|
|
127
|
+
var startTime = (0, timer_1.timer)(now);
|
|
114
128
|
var submitter = (0, submitter_1.firstPushWindowDecorator)((0, submitter_1.submitterFactory)(log, splitApi.postMetricsUsage, telemetryCacheStatsAdapter(telemetry, splits, segments), telemetryRefreshRate, 'telemetry stats', undefined, 0, true), telemetryRefreshRate);
|
|
115
129
|
readiness.gate.once(constants_2.SDK_READY_FROM_CACHE, function () {
|
|
116
130
|
telemetry.recordTimeUntilReadyFromCache(startTime());
|
|
@@ -29,7 +29,7 @@ function telemetryTrackerFactory(telemetryCache, now) {
|
|
|
29
29
|
if (error && error.statusCode)
|
|
30
30
|
telemetryCache.recordHttpError(operation, error.statusCode);
|
|
31
31
|
else
|
|
32
|
-
telemetryCache.recordSuccessfulSync(operation, now());
|
|
32
|
+
telemetryCache.recordSuccessfulSync(operation, Date.now());
|
|
33
33
|
};
|
|
34
34
|
},
|
|
35
35
|
sessionLength: function () {
|
|
@@ -44,7 +44,7 @@ function telemetryTrackerFactory(telemetryCache, now) {
|
|
|
44
44
|
telemetryCache.recordStreamingEvents({
|
|
45
45
|
e: e,
|
|
46
46
|
d: d,
|
|
47
|
-
t: now()
|
|
47
|
+
t: Date.now()
|
|
48
48
|
});
|
|
49
49
|
if (e === constants_1.TOKEN_REFRESH)
|
|
50
50
|
telemetryCache.recordTokenRefreshes();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.now = void 0;
|
|
4
|
-
//
|
|
4
|
+
// Can be used on any runtime, since it fallbacks to `Date.now` if `performance.now` is not available
|
|
5
5
|
function nowFactory() {
|
|
6
6
|
// eslint-disable-next-line
|
|
7
7
|
if (typeof performance === 'object' && typeof performance.now === 'function') {
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.now = void 0;
|
|
4
|
-
// @TODO migrate to Node SDK package eventually
|
|
5
4
|
function now() {
|
|
6
5
|
// eslint-disable-next-line no-undef
|
|
7
6
|
var time = process.hrtime();
|
|
8
|
-
return time[0] * 1e3 + time[1] * 1e-6; // convert it to
|
|
7
|
+
return time[0] * 1e3 + time[1] * 1e-6; // convert it to millis
|
|
9
8
|
}
|
|
10
9
|
exports.now = now;
|
package/esm/listeners/browser.js
CHANGED
|
@@ -4,6 +4,7 @@ import { OPTIMIZED, DEBUG } from '../utils/constants';
|
|
|
4
4
|
import { objectAssign } from '../utils/lang/objectAssign';
|
|
5
5
|
import { CLEANUP_REGISTERING, CLEANUP_DEREGISTERING } from '../logger/constants';
|
|
6
6
|
import { isConsentGranted } from '../consent';
|
|
7
|
+
import { telemetryCacheStatsAdapter } from '../sync/submitters/telemetrySubmitter';
|
|
7
8
|
// 'unload' event is used instead of 'beforeunload', since 'unload' is not a cancelable event, so no other listeners can stop the event from occurring.
|
|
8
9
|
var UNLOAD_DOM_EVENT = 'unload';
|
|
9
10
|
var EVENT_NAME = 'for unload page event.';
|
|
@@ -60,7 +61,11 @@ var BrowserSignalListener = /** @class */ (function () {
|
|
|
60
61
|
this._flushData(eventsUrl + '/events/beacon', this.storage.events, this.serviceApi.postEventsBulk);
|
|
61
62
|
if (this.storage.impressionCounts)
|
|
62
63
|
this._flushData(eventsUrl + '/testImpressions/count/beacon', this.storage.impressionCounts, this.serviceApi.postTestImpressionsCount, fromImpressionCountsCollector);
|
|
63
|
-
|
|
64
|
+
if (this.storage.telemetry) {
|
|
65
|
+
var telemetryUrl = this.settings.urls.telemetry;
|
|
66
|
+
var telemetryCacheAdapter = telemetryCacheStatsAdapter(this.storage.telemetry, this.storage.splits, this.storage.segments);
|
|
67
|
+
this._flushData(telemetryUrl + '/v1/metrics/usage/beacon', telemetryCacheAdapter, this.serviceApi.postMetricsUsage);
|
|
68
|
+
}
|
|
64
69
|
}
|
|
65
70
|
// Close streaming connection
|
|
66
71
|
if (this.syncManager.pushManager)
|
|
@@ -17,23 +17,27 @@ var KeyBuilderSS = /** @class */ (function (_super) {
|
|
|
17
17
|
KeyBuilderSS.prototype.buildRegisteredSegmentsKey = function () {
|
|
18
18
|
return this.prefix + ".segments.registered";
|
|
19
19
|
};
|
|
20
|
-
KeyBuilderSS.prototype.buildVersionablePrefix = function () {
|
|
21
|
-
return this.metadata.s + "/" + this.metadata.n + "/" + this.metadata.i;
|
|
22
|
-
};
|
|
23
20
|
KeyBuilderSS.prototype.buildImpressionsKey = function () {
|
|
24
21
|
return this.prefix + ".impressions";
|
|
25
22
|
};
|
|
26
23
|
KeyBuilderSS.prototype.buildEventsKey = function () {
|
|
27
24
|
return this.prefix + ".events";
|
|
28
25
|
};
|
|
26
|
+
KeyBuilderSS.prototype.searchPatternForSplitKeys = function () {
|
|
27
|
+
return this.buildSplitKeyPrefix() + "*";
|
|
28
|
+
};
|
|
29
|
+
/* Telemetry keys */
|
|
29
30
|
KeyBuilderSS.prototype.buildLatencyKey = function (method, bucket) {
|
|
30
31
|
return this.prefix + ".telemetry.latencies::" + this.buildVersionablePrefix() + "/" + methodNames[method] + "/" + bucket;
|
|
31
32
|
};
|
|
32
33
|
KeyBuilderSS.prototype.buildExceptionKey = function (method) {
|
|
33
34
|
return this.prefix + ".telemetry.exceptions::" + this.buildVersionablePrefix() + "/" + methodNames[method];
|
|
34
35
|
};
|
|
35
|
-
KeyBuilderSS.prototype.
|
|
36
|
-
return this.
|
|
36
|
+
KeyBuilderSS.prototype.buildInitKey = function () {
|
|
37
|
+
return this.prefix + ".telemetry.init::" + this.buildVersionablePrefix();
|
|
38
|
+
};
|
|
39
|
+
KeyBuilderSS.prototype.buildVersionablePrefix = function () {
|
|
40
|
+
return this.metadata.s + "/" + this.metadata.n + "/" + this.metadata.i;
|
|
37
41
|
};
|
|
38
42
|
return KeyBuilderSS;
|
|
39
43
|
}(KeyBuilder));
|
|
@@ -6,7 +6,7 @@ import { thenable } from '../../utils/promise/thenable';
|
|
|
6
6
|
import { timeout } from '../../utils/promise/timeout';
|
|
7
7
|
var LOG_PREFIX = 'storage:redis-adapter: ';
|
|
8
8
|
// If we ever decide to fully wrap every method, there's a Commander.getBuiltinCommands from ioredis.
|
|
9
|
-
var METHODS_TO_PROMISE_WRAP = ['set', 'exec', 'del', 'get', 'keys', 'sadd', 'srem', 'sismember', 'smembers', 'incr', 'rpush', 'pipeline', 'expire', 'mget', 'lrange', 'ltrim'];
|
|
9
|
+
var METHODS_TO_PROMISE_WRAP = ['set', 'exec', 'del', 'get', 'keys', 'sadd', 'srem', 'sismember', 'smembers', 'incr', 'rpush', 'pipeline', 'expire', 'mget', 'lrange', 'ltrim', 'hset'];
|
|
10
10
|
// Not part of the settings since it'll vary on each storage. We should be removing storage specific logic from elsewhere.
|
|
11
11
|
var DEFAULT_OPTIONS = {
|
|
12
12
|
connectionTimeout: 10000,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { findLatencyIndex } from '../findLatencyIndex';
|
|
2
|
+
import { getTelemetryConfigStats } from '../../sync/submitters/telemetrySubmitter';
|
|
3
|
+
import { CONSUMER_MODE, STORAGE_REDIS } from '../../utils/constants';
|
|
2
4
|
var TelemetryCacheInRedis = /** @class */ (function () {
|
|
3
5
|
/**
|
|
4
6
|
* Create a Telemetry cache that uses Redis as storage.
|
|
@@ -21,6 +23,11 @@ var TelemetryCacheInRedis = /** @class */ (function () {
|
|
|
21
23
|
return this.redis.hincrby(key, field, 1)
|
|
22
24
|
.catch(function () { });
|
|
23
25
|
};
|
|
26
|
+
TelemetryCacheInRedis.prototype.recordConfig = function () {
|
|
27
|
+
var _a = this.keys.buildInitKey().split('::'), key = _a[0], field = _a[1];
|
|
28
|
+
var value = JSON.stringify(getTelemetryConfigStats(CONSUMER_MODE, STORAGE_REDIS));
|
|
29
|
+
return this.redis.hset(key, field, value).catch(function () { });
|
|
30
|
+
};
|
|
24
31
|
return TelemetryCacheInRedis;
|
|
25
32
|
}());
|
|
26
33
|
export { TelemetryCacheInRedis };
|
|
@@ -18,16 +18,19 @@ export function InRedisStorage(options) {
|
|
|
18
18
|
var log = _a.log, metadata = _a.metadata, onReadyCb = _a.onReadyCb;
|
|
19
19
|
var keys = new KeyBuilderSS(prefix, metadata);
|
|
20
20
|
var redisClient = new RedisAdapter(log, options.options || {});
|
|
21
|
+
var telemetry = new TelemetryCacheInRedis(log, keys, redisClient);
|
|
21
22
|
// subscription to Redis connect event in order to emit SDK_READY event on consumer mode
|
|
22
23
|
redisClient.on('connect', function () {
|
|
23
24
|
onReadyCb();
|
|
25
|
+
// Synchronize config
|
|
26
|
+
telemetry.recordConfig();
|
|
24
27
|
});
|
|
25
28
|
return {
|
|
26
29
|
splits: new SplitsCacheInRedis(log, keys, redisClient),
|
|
27
30
|
segments: new SegmentsCacheInRedis(log, keys, redisClient),
|
|
28
31
|
impressions: new ImpressionsCacheInRedis(log, keys.buildImpressionsKey(), redisClient, metadata),
|
|
29
32
|
events: new EventsCacheInRedis(log, keys.buildEventsKey(), redisClient, metadata),
|
|
30
|
-
telemetry:
|
|
33
|
+
telemetry: telemetry,
|
|
31
34
|
// When using REDIS we should:
|
|
32
35
|
// 1- Disconnect from the storage
|
|
33
36
|
destroy: function () {
|
|
@@ -31,6 +31,7 @@ function validatePluggableStorageOptions(options) {
|
|
|
31
31
|
function wrapperConnect(wrapper, onReadyCb) {
|
|
32
32
|
wrapper.connect().then(function () {
|
|
33
33
|
onReadyCb();
|
|
34
|
+
// At the moment, we don't synchronize config with pluggable storage
|
|
34
35
|
}).catch(function (e) {
|
|
35
36
|
onReadyCb(e || new Error('Error connecting wrapper'));
|
|
36
37
|
});
|
|
@@ -63,7 +64,7 @@ export function PluggableStorage(options) {
|
|
|
63
64
|
impressions: isPartialConsumer ? new ImpressionsCacheInMemory(impressionsQueueSize) : new ImpressionsCachePluggable(log, keys.buildImpressionsKey(), wrapper, metadata),
|
|
64
65
|
impressionCounts: optimize ? new ImpressionCountsCacheInMemory() : undefined,
|
|
65
66
|
events: isPartialConsumer ? promisifyEventsTrack(new EventsCacheInMemory(eventsQueueSize)) : new EventsCachePluggable(log, keys.buildEventsKey(), wrapper, metadata),
|
|
66
|
-
// @TODO Not using TelemetryCachePluggable yet
|
|
67
|
+
// @TODO Not using TelemetryCachePluggable yet because it's not supported by the Split Synchronizer, and needs to drop or queue operations while the wrapper is not ready
|
|
67
68
|
// telemetry: isPartialConsumer ? new TelemetryCacheInMemory() : new TelemetryCachePluggable(log, keys, wrapper),
|
|
68
69
|
// Disconnect the underlying storage
|
|
69
70
|
destroy: function () {
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
var _a, _b;
|
|
1
|
+
var _a, _b, _c;
|
|
2
2
|
import { submitterFactory, firstPushWindowDecorator } from './submitter';
|
|
3
|
-
import { QUEUED, DEDUPED, DROPPED, CONSUMER_MODE, CONSUMER_ENUM, STANDALONE_MODE, CONSUMER_PARTIAL_MODE, STANDALONE_ENUM, CONSUMER_PARTIAL_ENUM, OPTIMIZED, DEBUG, DEBUG_ENUM, OPTIMIZED_ENUM } from '../../utils/constants';
|
|
3
|
+
import { QUEUED, DEDUPED, DROPPED, CONSUMER_MODE, CONSUMER_ENUM, STANDALONE_MODE, CONSUMER_PARTIAL_MODE, STANDALONE_ENUM, CONSUMER_PARTIAL_ENUM, OPTIMIZED, DEBUG, DEBUG_ENUM, OPTIMIZED_ENUM, CONSENT_GRANTED, CONSENT_DECLINED, CONSENT_UNKNOWN } from '../../utils/constants';
|
|
4
4
|
import { SDK_READY, SDK_READY_FROM_CACHE } from '../../readiness/constants';
|
|
5
5
|
import { base } from '../../utils/settingsValidation';
|
|
6
6
|
import { usedKeysMap } from '../../utils/inputValidation/apiKey';
|
|
7
7
|
import { timer } from '../../utils/timeTracker/timer';
|
|
8
|
+
import { objectAssign } from '../../utils/lang/objectAssign';
|
|
8
9
|
/**
|
|
9
10
|
* Converts data from telemetry cache into /metrics/usage request payload.
|
|
10
11
|
*/
|
|
@@ -46,6 +47,11 @@ var IMPRESSIONS_MODE_MAP = (_b = {},
|
|
|
46
47
|
_b[OPTIMIZED] = OPTIMIZED_ENUM,
|
|
47
48
|
_b[DEBUG] = DEBUG_ENUM,
|
|
48
49
|
_b);
|
|
50
|
+
var USER_CONSENT_MAP = (_c = {},
|
|
51
|
+
_c[CONSENT_UNKNOWN] = 1,
|
|
52
|
+
_c[CONSENT_GRANTED] = 2,
|
|
53
|
+
_c[CONSENT_DECLINED] = 3,
|
|
54
|
+
_c);
|
|
49
55
|
function getActiveFactories() {
|
|
50
56
|
return Object.keys(usedKeysMap).length;
|
|
51
57
|
}
|
|
@@ -54,6 +60,14 @@ function getRedundantActiveFactories() {
|
|
|
54
60
|
return acum + usedKeysMap[apiKey] - 1;
|
|
55
61
|
}, 0);
|
|
56
62
|
}
|
|
63
|
+
export function getTelemetryConfigStats(mode, storageType) {
|
|
64
|
+
return {
|
|
65
|
+
oM: OPERATION_MODE_MAP[mode],
|
|
66
|
+
st: storageType.toLowerCase(),
|
|
67
|
+
aF: getActiveFactories(),
|
|
68
|
+
rF: getRedundantActiveFactories(),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
57
71
|
/**
|
|
58
72
|
* Converts data from telemetry cache and settings into /metrics/config request payload.
|
|
59
73
|
*/
|
|
@@ -63,16 +77,16 @@ export function telemetryCacheConfigAdapter(telemetry, settings) {
|
|
|
63
77
|
clear: function () { },
|
|
64
78
|
state: function () {
|
|
65
79
|
var urls = settings.urls, scheduler = settings.scheduler;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
st: settings.storage.type.toLowerCase(),
|
|
80
|
+
var isClientSide = settings.core.key !== undefined;
|
|
81
|
+
return objectAssign(getTelemetryConfigStats(settings.mode, settings.storage.type), {
|
|
69
82
|
sE: settings.streamingEnabled,
|
|
70
83
|
rR: {
|
|
71
|
-
sp: scheduler.featuresRefreshRate,
|
|
72
|
-
se: scheduler.segmentsRefreshRate,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
84
|
+
sp: scheduler.featuresRefreshRate / 1000,
|
|
85
|
+
se: isClientSide ? undefined : scheduler.segmentsRefreshRate / 1000,
|
|
86
|
+
ms: isClientSide ? scheduler.segmentsRefreshRate / 1000 : undefined,
|
|
87
|
+
im: scheduler.impressionsRefreshRate / 1000,
|
|
88
|
+
ev: scheduler.eventsPushRate / 1000,
|
|
89
|
+
te: scheduler.telemetryRefreshRate / 1000,
|
|
76
90
|
},
|
|
77
91
|
uO: {
|
|
78
92
|
s: urls.sdk !== base.urls.sdk,
|
|
@@ -86,14 +100,13 @@ export function telemetryCacheConfigAdapter(telemetry, settings) {
|
|
|
86
100
|
iM: IMPRESSIONS_MODE_MAP[settings.sync.impressionsMode],
|
|
87
101
|
iL: settings.impressionListener ? true : false,
|
|
88
102
|
hP: false,
|
|
89
|
-
aF: getActiveFactories(),
|
|
90
|
-
rF: getRedundantActiveFactories(),
|
|
91
103
|
tR: telemetry.getTimeUntilReady(),
|
|
92
104
|
tC: telemetry.getTimeUntilReadyFromCache(),
|
|
93
105
|
nR: telemetry.getNonReadyUsage(),
|
|
94
106
|
t: telemetry.popTags(),
|
|
95
107
|
i: settings.integrations && settings.integrations.map(function (int) { return int.type; }),
|
|
96
|
-
|
|
108
|
+
uC: settings.userConsent ? USER_CONSENT_MAP[settings.userConsent] : 0
|
|
109
|
+
});
|
|
97
110
|
}
|
|
98
111
|
};
|
|
99
112
|
}
|
|
@@ -101,11 +114,11 @@ export function telemetryCacheConfigAdapter(telemetry, settings) {
|
|
|
101
114
|
* Submitter that periodically posts telemetry data
|
|
102
115
|
*/
|
|
103
116
|
export function telemetrySubmitterFactory(params) {
|
|
104
|
-
var _a = params.storage, splits = _a.splits, segments = _a.segments, telemetry = _a.telemetry;
|
|
105
|
-
if (!telemetry)
|
|
117
|
+
var _a = params.storage, splits = _a.splits, segments = _a.segments, telemetry = _a.telemetry, now = params.platform.now;
|
|
118
|
+
if (!telemetry || !now)
|
|
106
119
|
return; // No submitter created if telemetry cache is not defined
|
|
107
|
-
var settings = params.settings, _b = params.settings, log = _b.log, telemetryRefreshRate = _b.scheduler.telemetryRefreshRate, splitApi = params.splitApi,
|
|
108
|
-
var startTime = timer(now
|
|
120
|
+
var settings = params.settings, _b = params.settings, log = _b.log, telemetryRefreshRate = _b.scheduler.telemetryRefreshRate, splitApi = params.splitApi, readiness = params.readiness, sdkReadinessManager = params.sdkReadinessManager;
|
|
121
|
+
var startTime = timer(now);
|
|
109
122
|
var submitter = firstPushWindowDecorator(submitterFactory(log, splitApi.postMetricsUsage, telemetryCacheStatsAdapter(telemetry, splits, segments), telemetryRefreshRate, 'telemetry stats', undefined, 0, true), telemetryRefreshRate);
|
|
110
123
|
readiness.gate.once(SDK_READY_FROM_CACHE, function () {
|
|
111
124
|
telemetry.recordTimeUntilReadyFromCache(startTime());
|
|
@@ -26,7 +26,7 @@ export function telemetryTrackerFactory(telemetryCache, now) {
|
|
|
26
26
|
if (error && error.statusCode)
|
|
27
27
|
telemetryCache.recordHttpError(operation, error.statusCode);
|
|
28
28
|
else
|
|
29
|
-
telemetryCache.recordSuccessfulSync(operation, now());
|
|
29
|
+
telemetryCache.recordSuccessfulSync(operation, Date.now());
|
|
30
30
|
};
|
|
31
31
|
},
|
|
32
32
|
sessionLength: function () {
|
|
@@ -41,7 +41,7 @@ export function telemetryTrackerFactory(telemetryCache, now) {
|
|
|
41
41
|
telemetryCache.recordStreamingEvents({
|
|
42
42
|
e: e,
|
|
43
43
|
d: d,
|
|
44
|
-
t: now()
|
|
44
|
+
t: Date.now()
|
|
45
45
|
});
|
|
46
46
|
if (e === TOKEN_REFRESH)
|
|
47
47
|
telemetryCache.recordTokenRefreshes();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Can be used on any runtime, since it fallbacks to `Date.now` if `performance.now` is not available
|
|
2
2
|
function nowFactory() {
|
|
3
3
|
// eslint-disable-next-line
|
|
4
4
|
if (typeof performance === 'object' && typeof performance.now === 'function') {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
// @TODO migrate to Node SDK package eventually
|
|
2
1
|
export function now() {
|
|
3
2
|
// eslint-disable-next-line no-undef
|
|
4
3
|
var time = process.hrtime();
|
|
5
|
-
return time[0] * 1e3 + time[1] * 1e-6; // convert it to
|
|
4
|
+
return time[0] * 1e3 + time[1] * 1e-6; // convert it to millis
|
|
6
5
|
}
|
package/package.json
CHANGED
package/src/listeners/browser.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { objectAssign } from '../utils/lang/objectAssign';
|
|
|
12
12
|
import { CLEANUP_REGISTERING, CLEANUP_DEREGISTERING } from '../logger/constants';
|
|
13
13
|
import { ISyncManager } from '../sync/types';
|
|
14
14
|
import { isConsentGranted } from '../consent';
|
|
15
|
+
import { telemetryCacheStatsAdapter } from '../sync/submitters/telemetrySubmitter';
|
|
15
16
|
|
|
16
17
|
// 'unload' event is used instead of 'beforeunload', since 'unload' is not a cancelable event, so no other listeners can stop the event from occurring.
|
|
17
18
|
const UNLOAD_DOM_EVENT = 'unload';
|
|
@@ -77,7 +78,11 @@ export class BrowserSignalListener implements ISignalListener {
|
|
|
77
78
|
this._flushData(eventsUrl + '/testImpressions/beacon', this.storage.impressions, this.serviceApi.postTestImpressionsBulk, this.fromImpressionsCollector, extraMetadata);
|
|
78
79
|
this._flushData(eventsUrl + '/events/beacon', this.storage.events, this.serviceApi.postEventsBulk);
|
|
79
80
|
if (this.storage.impressionCounts) this._flushData(eventsUrl + '/testImpressions/count/beacon', this.storage.impressionCounts, this.serviceApi.postTestImpressionsCount, fromImpressionCountsCollector);
|
|
80
|
-
|
|
81
|
+
if (this.storage.telemetry) {
|
|
82
|
+
const telemetryUrl = this.settings.urls.telemetry;
|
|
83
|
+
const telemetryCacheAdapter = telemetryCacheStatsAdapter(this.storage.telemetry, this.storage.splits, this.storage.segments);
|
|
84
|
+
this._flushData(telemetryUrl + '/v1/metrics/usage/beacon', telemetryCacheAdapter, this.serviceApi.postMetricsUsage);
|
|
85
|
+
}
|
|
81
86
|
}
|
|
82
87
|
|
|
83
88
|
// Close streaming connection
|
package/src/sdkFactory/types.ts
CHANGED
|
@@ -11,13 +11,27 @@ import { SplitIO, ISettings, IEventEmitter } from '../types';
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Environment related dependencies.
|
|
14
|
-
* These getters are called a fixed number of times per factory instantiation.
|
|
15
14
|
*/
|
|
16
15
|
export interface IPlatform {
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* If provided, it is used to retrieve the Fetch API for HTTP requests. Otherwise, the global fetch is used.
|
|
18
|
+
*/
|
|
18
19
|
getFetch?: () => (IFetch | undefined)
|
|
20
|
+
/**
|
|
21
|
+
* If provided, it is used to pass additional options to fetch calls.
|
|
22
|
+
*/
|
|
23
|
+
getOptions?: () => object
|
|
24
|
+
/**
|
|
25
|
+
* If provided, it is used to retrieve the EventSource constructor for streaming support.
|
|
26
|
+
*/
|
|
19
27
|
getEventSource?: () => (IEventSourceConstructor | undefined)
|
|
28
|
+
/**
|
|
29
|
+
* EventEmitter constructor, like NodeJS.EventEmitter or a polyfill.
|
|
30
|
+
*/
|
|
20
31
|
EventEmitter: new () => IEventEmitter,
|
|
32
|
+
/**
|
|
33
|
+
* Function used to track latencies for telemetry.
|
|
34
|
+
*/
|
|
21
35
|
now?: () => number
|
|
22
36
|
}
|
|
23
37
|
|