@atlaskit/react-ufo 3.13.27 → 3.14.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @atlaskit/ufo-interaction-ignore
2
2
 
3
+ ## 3.14.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#176314](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/176314)
8
+ [`9c32e96190532`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/9c32e96190532) -
9
+ report memory usage via UFO
10
+
11
+ ## 3.13.28
12
+
13
+ ### Patch Changes
14
+
15
+ - [#173121](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/pull-requests/173121)
16
+ [`0d5a766d0f501`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/0d5a766d0f501) -
17
+ Track unknown interactions elements
18
+
3
19
  ## 3.13.27
4
20
 
5
21
  ### Patch Changes
@@ -709,7 +709,7 @@ function createInteractionMetricsPayload(_x3, _x4, _x5) {
709
709
  function _createInteractionMetricsPayload() {
710
710
  _createInteractionMetricsPayload = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee2(interaction, interactionId, experimental) {
711
711
  var _window$location, _config$additionalPay;
712
- var interactionPayloadStart, config, end, start, ufoName, knownSegments, rate, type, abortReason, routeName, featureFlags, previousInteractionName, isPreviousInteractionAborted, abortedByInteractionName, responsiveness, pageVisibilityAtTTI, pageVisibilityAtTTAI, segments, segmentTree, isDetailedPayload, isPageLoad, calculatePageVisibilityFromTheStartOfPageLoad, moreAccuratePageVisibilityAtTTI, moreAccuratePageVisibilityAtTTAI, labelStack, getInitialPageLoadSSRMetrics, pageLoadInteractionMetrics, getDetailedInteractionMetrics, getPageLoadDetailedInteractionMetrics, newUFOName, resourceTimings, _yield$Promise$all, _yield$Promise$all2, vcMetrics, experimentalMetrics, paintMetrics, payload;
712
+ var interactionPayloadStart, config, end, start, ufoName, knownSegments, rate, type, abortReason, routeName, featureFlags, previousInteractionName, isPreviousInteractionAborted, abortedByInteractionName, responsiveness, unknownElementName, unknownElementHierarchy, pageVisibilityAtTTI, pageVisibilityAtTTAI, segments, segmentTree, isDetailedPayload, isPageLoad, calculatePageVisibilityFromTheStartOfPageLoad, moreAccuratePageVisibilityAtTTI, moreAccuratePageVisibilityAtTTAI, labelStack, getInitialPageLoadSSRMetrics, pageLoadInteractionMetrics, getDetailedInteractionMetrics, getPageLoadDetailedInteractionMetrics, newUFOName, resourceTimings, _yield$Promise$all, _yield$Promise$all2, vcMetrics, experimentalMetrics, paintMetrics, payload;
713
713
  return _regenerator.default.wrap(function _callee2$(_context2) {
714
714
  while (1) switch (_context2.prev = _context2.next) {
715
715
  case 0:
@@ -721,7 +721,7 @@ function _createInteractionMetricsPayload() {
721
721
  }
722
722
  throw Error('UFO Configuration not provided');
723
723
  case 4:
724
- end = interaction.end, start = interaction.start, ufoName = interaction.ufoName, knownSegments = interaction.knownSegments, rate = interaction.rate, type = interaction.type, abortReason = interaction.abortReason, routeName = interaction.routeName, featureFlags = interaction.featureFlags, previousInteractionName = interaction.previousInteractionName, isPreviousInteractionAborted = interaction.isPreviousInteractionAborted, abortedByInteractionName = interaction.abortedByInteractionName, responsiveness = interaction.responsiveness;
724
+ end = interaction.end, start = interaction.start, ufoName = interaction.ufoName, knownSegments = interaction.knownSegments, rate = interaction.rate, type = interaction.type, abortReason = interaction.abortReason, routeName = interaction.routeName, featureFlags = interaction.featureFlags, previousInteractionName = interaction.previousInteractionName, isPreviousInteractionAborted = interaction.isPreviousInteractionAborted, abortedByInteractionName = interaction.abortedByInteractionName, responsiveness = interaction.responsiveness, unknownElementName = interaction.unknownElementName, unknownElementHierarchy = interaction.unknownElementHierarchy;
725
725
  pageVisibilityAtTTI = getPageVisibilityUpToTTI(interaction);
726
726
  pageVisibilityAtTTAI = (0, _getPageVisibilityUpToTtai.default)(interaction);
727
727
  segments = config.killswitchNestedSegments ? [] : knownSegments;
@@ -809,7 +809,7 @@ function _createInteractionMetricsPayload() {
809
809
  source: 'measured',
810
810
  tags: ['observability'],
811
811
  attributes: {
812
- properties: _objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread({
812
+ properties: _objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread({
813
813
  // basic
814
814
  'event:hostname': ((_window$location = window.location) === null || _window$location === void 0 ? void 0 : _window$location.hostname) || 'unknown',
815
815
  'event:product': config.product,
@@ -824,8 +824,10 @@ function _createInteractionMetricsPayload() {
824
824
  'experience:name': newUFOName
825
825
  }, (0, _platformFeatureFlags.fg)('platform_ufo_report_cpu_usage') ? {
826
826
  'event:cpu:usage': (0, _machineUtilisation.createPressureStateReport)(interaction.start, interaction.end)
827
+ } : {}), (0, _platformFeatureFlags.fg)('platform_ufo_report_memory_usage') ? {
828
+ 'event:memory:usage': (0, _machineUtilisation.createMemoryStateReport)(interaction.start, interaction.end)
827
829
  } : {}), getBrowserMetadata()), getSSRProperties(type)), getAssetsMetrics(interaction, pageLoadInteractionMetrics === null || pageLoadInteractionMetrics === void 0 ? void 0 : pageLoadInteractionMetrics.SSRDoneTime)), getPPSMetrics(interaction)), paintMetrics), getNavigationMetrics(type)), vcMetrics), experimentalMetrics), (_config$additionalPay = config.additionalPayloadData) === null || _config$additionalPay === void 0 ? void 0 : _config$additionalPay.call(config, interaction)), getTracingContextData(interaction)), getStylesheetMetrics()), getErrorCounts(interaction)), {}, {
828
- interactionMetrics: _objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread({
830
+ interactionMetrics: _objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread({
829
831
  namePrefix: config.namePrefix || '',
830
832
  segmentPrefix: config.segmentPrefix || '',
831
833
  interactionId: interactionId,
@@ -861,7 +863,11 @@ function _createInteractionMetricsPayload() {
861
863
  } : {}), labelStack), pageLoadInteractionMetrics), getDetailedInteractionMetrics(resourceTimings)), getPageLoadDetailedInteractionMetrics()), getBm3TrackerTimings(interaction)), {}, {
862
864
  'metric:ttai': experimental ? regularTTAI || expTTAI : undefined,
863
865
  'metric:experimental:ttai': expTTAI
864
- }),
866
+ }, unknownElementName ? {
867
+ unknownElementName: unknownElementName
868
+ } : {}), unknownElementHierarchy ? {
869
+ unknownElementHierarchy: unknownElementHierarchy
870
+ } : {}),
865
871
  'ufo:payloadTime': (0, _roundNumber.roundEpsilon)(performance.now() - interactionPayloadStart)
866
872
  })
867
873
  }
@@ -87,6 +87,9 @@ function init(analyticsWebClientAsync, config) {
87
87
  if ((0, _platformFeatureFlags.fg)('platform_ufo_report_cpu_usage')) {
88
88
  (0, _machineUtilisation.initialisePressureObserver)();
89
89
  }
90
+ if ((0, _platformFeatureFlags.fg)('platform_ufo_report_memory_usage')) {
91
+ (0, _machineUtilisation.initialiseMemoryObserver)();
92
+ }
90
93
  (0, _config.setUFOConfig)(config);
91
94
  if ((_config$vc = config.vc) !== null && _config$vc !== void 0 && _config$vc.enabled) {
92
95
  var _config$experimentalI;
@@ -6,13 +6,67 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.setInteractionPerformanceEvent = exports.getPerformanceObserver = void 0;
8
8
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
9
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
9
10
  var _interactionMetrics = require("../interaction-metrics");
11
+ var _getUniqueElementName = _interopRequireDefault(require("../vc/vc-observer-new/get-unique-element-name"));
10
12
  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; }
11
13
  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; }
12
14
  function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
13
15
  function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
14
16
  function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
15
17
  var performanceEventObserver;
18
+ var selectorConfig = {
19
+ id: true,
20
+ testId: true,
21
+ role: true,
22
+ className: true
23
+ };
24
+ function getTestIdName(memoizedProps) {
25
+ if (memoizedProps['data-testid']) {
26
+ return "[data-testid=".concat(memoizedProps['data-testid'], "]");
27
+ } else if (memoizedProps['data-test-id']) {
28
+ return "[data-test-id=".concat(memoizedProps['data-testid'], "]");
29
+ }
30
+ return null;
31
+ }
32
+ function getReactComponentHierarchy(element) {
33
+ var componentHierarchy = [];
34
+ // Function to traverse up the fiber tree
35
+ function traverseFiber(fiber) {
36
+ var currentFiber = fiber;
37
+ while (currentFiber) {
38
+ if (currentFiber.type) {
39
+ // Check if there's a display name or a function name
40
+ var componentName = currentFiber.type.displayName || currentFiber.type.name;
41
+ // checking when component name is bigger than the minimized name produced by react
42
+ if (componentName && componentName.length > 2 && !componentName.includes('Listener') && !componentName.includes('Provider')) {
43
+ componentHierarchy.push(componentName);
44
+ }
45
+ if (componentName === 'UFOSegment') {
46
+ break;
47
+ }
48
+ }
49
+ if (currentFiber.memoizedProps) {
50
+ var dataIdInfo = getTestIdName(currentFiber.memoizedProps);
51
+ if (dataIdInfo) {
52
+ componentHierarchy.push(dataIdInfo);
53
+ currentFiber = null;
54
+ continue;
55
+ }
56
+ }
57
+ currentFiber = currentFiber.return;
58
+ }
59
+ }
60
+ // Access the reactFiber node from the HTML element
61
+ var reactFiberKey = Object.keys(element).find(function (key) {
62
+ return key.startsWith('__reactFiber$');
63
+ });
64
+ if (reactFiberKey) {
65
+ var fiber = element[reactFiberKey];
66
+ traverseFiber(fiber);
67
+ }
68
+ return componentHierarchy.reverse().join(' > ');
69
+ }
16
70
  var getPerformanceObserver = exports.getPerformanceObserver = function getPerformanceObserver() {
17
71
  performanceEventObserver = performanceEventObserver || new PerformanceObserver(function (entries) {
18
72
  var list = entries.getEntries();
@@ -47,5 +101,12 @@ var setInteractionPerformanceEvent = exports.setInteractionPerformanceEvent = fu
47
101
  // it means the interaction start time is not accurate, we assign
48
102
  // this value which will match the timestamp in the event
49
103
  interaction.start = Math.min(interaction.start, entry.startTime);
104
+ if (interaction.ufoName === 'unknown' && (0, _platformFeatureFlags.fg)('platform_ufo_enable_unknown_interactions_elements')) {
105
+ if (entry.target) {
106
+ var componentHierarchy = getReactComponentHierarchy(entry.target);
107
+ interaction.unknownElementHierarchy = componentHierarchy;
108
+ }
109
+ interaction.unknownElementName = (0, _getUniqueElementName.default)(selectorConfig, entry.target);
110
+ }
50
111
  }
51
112
  };
@@ -4,33 +4,49 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
+ exports.createMemoryStateReport = createMemoryStateReport;
7
8
  exports.createPressureStateReport = createPressureStateReport;
9
+ exports.disconnectMemoryObserver = disconnectMemoryObserver;
8
10
  exports.disconnectPressureObserver = disconnectPressureObserver;
11
+ exports.initialiseMemoryObserver = initialiseMemoryObserver;
9
12
  exports.initialisePressureObserver = initialisePressureObserver;
10
- exports.removeOldBufferRecords = removeOldBufferRecords;
13
+ exports.removeOldMemoryBufferRecords = removeOldMemoryBufferRecords;
14
+ exports.removeOldPressureBufferRecords = removeOldPressureBufferRecords;
15
+ exports.resetMemoryRecordBuffer = resetMemoryRecordBuffer;
11
16
  exports.resetPressureRecordBuffer = resetPressureRecordBuffer;
12
17
  var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
13
18
  var BUFFER_MAX_LENGTH = 1000; // ensure we don't blow up this buffer
14
19
  var pressureRecordBuffer = [];
15
20
  var pressureObserver = null;
21
+ var memoryRecordBuffer = [];
22
+ var memoryInterval;
16
23
  function resetPressureRecordBuffer() {
17
24
  pressureRecordBuffer.length = 0;
18
25
  }
19
- function removeOldBufferRecords(filter) {
26
+ function resetMemoryRecordBuffer() {
27
+ memoryRecordBuffer.length = 0;
28
+ }
29
+ function removeOldPressureBufferRecords(filter) {
20
30
  pressureRecordBuffer = pressureRecordBuffer.filter(function (_ref) {
21
31
  var time = _ref.time;
22
32
  return time > filter;
23
33
  });
24
34
  }
35
+ function removeOldMemoryBufferRecords(filter) {
36
+ memoryRecordBuffer = memoryRecordBuffer.filter(function (_ref2) {
37
+ var time = _ref2.time;
38
+ return time > filter;
39
+ });
40
+ }
25
41
  function createPressureStateReport(start, end) {
26
42
  try {
27
43
  // To differentiate between the API not available, vs no PressureRecords added
28
44
  if (!('PressureObserver' in globalThis)) {
29
45
  return null;
30
46
  }
31
- var pressureStateCount = pressureRecordBuffer.reduce(function (pressureReport, _ref2) {
32
- var time = _ref2.time,
33
- state = _ref2.state;
47
+ var pressureStateCount = pressureRecordBuffer.reduce(function (pressureReport, _ref3) {
48
+ var time = _ref3.time,
49
+ state = _ref3.state;
34
50
  if (time >= start && time <= end) {
35
51
  pressureReport[state] += 1;
36
52
  }
@@ -44,7 +60,7 @@ function createPressureStateReport(start, end) {
44
60
  var pressureStateTotal = Object.values(pressureStateCount).reduce(function (total, count) {
45
61
  return total + count;
46
62
  }) || 1;
47
- removeOldBufferRecords(end);
63
+ removeOldPressureBufferRecords(end);
48
64
  return {
49
65
  count: pressureStateCount,
50
66
  percentage: {
@@ -58,24 +74,81 @@ function createPressureStateReport(start, end) {
58
74
  return null;
59
75
  }
60
76
  }
77
+ function convertBytesToMegabytes(bytes) {
78
+ return Math.round(Math.round(bytes / (1024 * 1024) * 100) / 100);
79
+ }
80
+ function createMemoryStateReport(start, end) {
81
+ try {
82
+ if (!('memory' in performance)) {
83
+ return null;
84
+ }
85
+ var accumulatedMemoryUsage = memoryRecordBuffer.reduce(function (acc, snapshot) {
86
+ if (snapshot.time >= start && snapshot.time <= end) {
87
+ acc.totalJSHeapSize += snapshot.totalJSHeapSize;
88
+ acc.usedJSHeapSize += snapshot.usedJSHeapSize;
89
+ acc.snapshotCount += 1;
90
+ }
91
+ return acc;
92
+ }, {
93
+ totalJSHeapSize: 0,
94
+ usedJSHeapSize: 0,
95
+ snapshotCount: 0
96
+ });
97
+ var memoryStateReport = {
98
+ jsHeapSizeLimitInMB: convertBytesToMegabytes(memoryRecordBuffer[0].jsHeapSizeLimit),
99
+ // just use the first record, since this value always remains the same over time
100
+ avgTotalJSHeapSizeInMB: convertBytesToMegabytes(accumulatedMemoryUsage.totalJSHeapSize / accumulatedMemoryUsage.snapshotCount),
101
+ avgUsedJSHeapSizeInMB: convertBytesToMegabytes(accumulatedMemoryUsage.usedJSHeapSize / accumulatedMemoryUsage.snapshotCount)
102
+ };
103
+ removeOldMemoryBufferRecords(end);
104
+ return memoryStateReport;
105
+ } catch (_unused2) {
106
+ return null;
107
+ }
108
+ }
61
109
  function initialisePressureObserver() {
62
110
  try {
63
111
  if ('PressureObserver' in globalThis) {
64
- var _pressureObserver$obs;
65
112
  pressureObserver = new PressureObserver(function (records) {
66
113
  if (pressureRecordBuffer.length + records.length <= BUFFER_MAX_LENGTH) {
67
114
  var _pressureRecordBuffer;
68
115
  (_pressureRecordBuffer = pressureRecordBuffer).push.apply(_pressureRecordBuffer, (0, _toConsumableArray2.default)(records));
69
116
  }
70
117
  });
71
- (_pressureObserver$obs = pressureObserver.observe('cpu', {
118
+ pressureObserver.observe('cpu', {
72
119
  sampleInterval: 100
73
- })) === null || _pressureObserver$obs === void 0 || _pressureObserver$obs.catch();
120
+ }).catch();
74
121
  }
75
122
  } catch (err) {
76
123
  /* do nothing, this is a best efforts metric */
77
124
  }
78
125
  }
126
+ function initialiseMemoryObserver() {
127
+ try {
128
+ // only set up the interval if `performance.memory` is available in the browser
129
+ if ('memory' in performance) {
130
+ memoryInterval = setInterval(function () {
131
+ // another check of `performance.memory` availability to satisfy typescript
132
+ if ('memory' in performance) {
133
+ var memory = performance.memory;
134
+ if (memoryRecordBuffer.length <= BUFFER_MAX_LENGTH) {
135
+ memoryRecordBuffer.push({
136
+ time: performance.now(),
137
+ jsHeapSizeLimit: memory.jsHeapSizeLimit,
138
+ totalJSHeapSize: memory.totalJSHeapSize,
139
+ usedJSHeapSize: memory.usedJSHeapSize
140
+ });
141
+ }
142
+ }
143
+ }, 100);
144
+ }
145
+ } catch (_unused3) {
146
+ /* do nothing, this is a best efforts metric */
147
+ }
148
+ }
149
+ function disconnectMemoryObserver() {
150
+ clearInterval(memoryInterval);
151
+ }
79
152
  function disconnectPressureObserver() {
80
153
  var _pressureObserver;
81
154
  (_pressureObserver = pressureObserver) === null || _pressureObserver === void 0 || _pressureObserver.disconnect();
@@ -11,7 +11,7 @@ import { getGlobalErrorCount } from '../global-error-handler';
11
11
  import { getPageVisibilityState } from '../hidden-timing';
12
12
  import * as initialPageLoadExtraTiming from '../initial-page-load-extra-timing';
13
13
  import { interactionSpans as atlaskitInteractionSpans } from '../interaction-metrics';
14
- import { createPressureStateReport } from '../machine-utilisation';
14
+ import { createMemoryStateReport, createPressureStateReport } from '../machine-utilisation';
15
15
  import * as resourceTiming from '../resource-timing';
16
16
  import { filterResourceTimings } from '../resource-timing/common/utils/resource-timing-buffer';
17
17
  import { roundEpsilon } from '../round-number';
@@ -700,7 +700,9 @@ async function createInteractionMetricsPayload(interaction, interactionId, exper
700
700
  previousInteractionName,
701
701
  isPreviousInteractionAborted,
702
702
  abortedByInteractionName,
703
- responsiveness
703
+ responsiveness,
704
+ unknownElementName,
705
+ unknownElementHierarchy
704
706
  } = interaction;
705
707
  const pageVisibilityAtTTI = getPageVisibilityUpToTTI(interaction);
706
708
  const pageVisibilityAtTTAI = getPageVisibilityUpToTTAI(interaction);
@@ -801,6 +803,9 @@ async function createInteractionMetricsPayload(interaction, interactionId, exper
801
803
  ...(fg('platform_ufo_report_cpu_usage') ? {
802
804
  'event:cpu:usage': createPressureStateReport(interaction.start, interaction.end)
803
805
  } : {}),
806
+ ...(fg('platform_ufo_report_memory_usage') ? {
807
+ 'event:memory:usage': createMemoryStateReport(interaction.start, interaction.end)
808
+ } : {}),
804
809
  // root
805
810
  ...getBrowserMetadata(),
806
811
  ...getSSRProperties(type),
@@ -854,7 +859,13 @@ async function createInteractionMetricsPayload(interaction, interactionId, exper
854
859
  ...getPageLoadDetailedInteractionMetrics(),
855
860
  ...getBm3TrackerTimings(interaction),
856
861
  'metric:ttai': experimental ? regularTTAI || expTTAI : undefined,
857
- 'metric:experimental:ttai': expTTAI
862
+ 'metric:experimental:ttai': expTTAI,
863
+ ...(unknownElementName ? {
864
+ unknownElementName
865
+ } : {}),
866
+ ...(unknownElementHierarchy ? {
867
+ unknownElementHierarchy
868
+ } : {})
858
869
  },
859
870
  'ufo:payloadTime': roundEpsilon(performance.now() - interactionPayloadStart)
860
871
  }
@@ -5,7 +5,7 @@ import { experimentalVC, sinkExperimentalHandler } from '../create-experimental-
5
5
  import { setupHiddenTimingCapture } from '../hidden-timing';
6
6
  import { postInteractionLog, sinkInteractionHandler, sinkPostInteractionLogHandler } from '../interaction-metrics';
7
7
  import { getPerformanceObserver } from '../interactions-performance-observer';
8
- import { initialisePressureObserver } from '../machine-utilisation';
8
+ import { initialiseMemoryObserver, initialisePressureObserver } from '../machine-utilisation';
9
9
  import { getVCObserver } from '../vc';
10
10
  import scheduleIdleCallback from './schedule-idle-callback';
11
11
  let initialized = false;
@@ -77,6 +77,9 @@ export function init(analyticsWebClientAsync, config) {
77
77
  if (fg('platform_ufo_report_cpu_usage')) {
78
78
  initialisePressureObserver();
79
79
  }
80
+ if (fg('platform_ufo_report_memory_usage')) {
81
+ initialiseMemoryObserver();
82
+ }
80
83
  setUFOConfig(config);
81
84
  if ((_config$vc = config.vc) !== null && _config$vc !== void 0 && _config$vc.enabled) {
82
85
  var _config$experimentalI;
@@ -1,5 +1,57 @@
1
+ import { fg } from '@atlaskit/platform-feature-flags';
1
2
  import { getActiveInteraction } from '../interaction-metrics';
3
+ import getElementName from '../vc/vc-observer-new/get-unique-element-name';
2
4
  let performanceEventObserver;
5
+ const selectorConfig = {
6
+ id: true,
7
+ testId: true,
8
+ role: true,
9
+ className: true
10
+ };
11
+ function getTestIdName(memoizedProps) {
12
+ if (memoizedProps['data-testid']) {
13
+ return `[data-testid=${memoizedProps['data-testid']}]`;
14
+ } else if (memoizedProps['data-test-id']) {
15
+ return `[data-test-id=${memoizedProps['data-testid']}]`;
16
+ }
17
+ return null;
18
+ }
19
+ function getReactComponentHierarchy(element) {
20
+ const componentHierarchy = [];
21
+ // Function to traverse up the fiber tree
22
+ function traverseFiber(fiber) {
23
+ let currentFiber = fiber;
24
+ while (currentFiber) {
25
+ if (currentFiber.type) {
26
+ // Check if there's a display name or a function name
27
+ const componentName = currentFiber.type.displayName || currentFiber.type.name;
28
+ // checking when component name is bigger than the minimized name produced by react
29
+ if (componentName && componentName.length > 2 && !componentName.includes('Listener') && !componentName.includes('Provider')) {
30
+ componentHierarchy.push(componentName);
31
+ }
32
+ if (componentName === 'UFOSegment') {
33
+ break;
34
+ }
35
+ }
36
+ if (currentFiber.memoizedProps) {
37
+ const dataIdInfo = getTestIdName(currentFiber.memoizedProps);
38
+ if (dataIdInfo) {
39
+ componentHierarchy.push(dataIdInfo);
40
+ currentFiber = null;
41
+ continue;
42
+ }
43
+ }
44
+ currentFiber = currentFiber.return;
45
+ }
46
+ }
47
+ // Access the reactFiber node from the HTML element
48
+ const reactFiberKey = Object.keys(element).find(key => key.startsWith('__reactFiber$'));
49
+ if (reactFiberKey) {
50
+ const fiber = element[reactFiberKey];
51
+ traverseFiber(fiber);
52
+ }
53
+ return componentHierarchy.reverse().join(' > ');
54
+ }
3
55
  export const getPerformanceObserver = () => {
4
56
  performanceEventObserver = performanceEventObserver || new PerformanceObserver(entries => {
5
57
  const list = entries.getEntries();
@@ -26,5 +78,12 @@ export const setInteractionPerformanceEvent = entry => {
26
78
  // it means the interaction start time is not accurate, we assign
27
79
  // this value which will match the timestamp in the event
28
80
  interaction.start = Math.min(interaction.start, entry.startTime);
81
+ if (interaction.ufoName === 'unknown' && fg('platform_ufo_enable_unknown_interactions_elements')) {
82
+ if (entry.target) {
83
+ const componentHierarchy = getReactComponentHierarchy(entry.target);
84
+ interaction.unknownElementHierarchy = componentHierarchy;
85
+ }
86
+ interaction.unknownElementName = getElementName(selectorConfig, entry.target);
87
+ }
29
88
  }
30
89
  };
@@ -1,14 +1,24 @@
1
1
  const BUFFER_MAX_LENGTH = 1000; // ensure we don't blow up this buffer
2
2
  let pressureRecordBuffer = [];
3
3
  let pressureObserver = null;
4
+ let memoryRecordBuffer = [];
5
+ let memoryInterval;
4
6
  export function resetPressureRecordBuffer() {
5
7
  pressureRecordBuffer.length = 0;
6
8
  }
7
- export function removeOldBufferRecords(filter) {
9
+ export function resetMemoryRecordBuffer() {
10
+ memoryRecordBuffer.length = 0;
11
+ }
12
+ export function removeOldPressureBufferRecords(filter) {
8
13
  pressureRecordBuffer = pressureRecordBuffer.filter(({
9
14
  time
10
15
  }) => time > filter);
11
16
  }
17
+ export function removeOldMemoryBufferRecords(filter) {
18
+ memoryRecordBuffer = memoryRecordBuffer.filter(({
19
+ time
20
+ }) => time > filter);
21
+ }
12
22
  export function createPressureStateReport(start, end) {
13
23
  try {
14
24
  // To differentiate between the API not available, vs no PressureRecords added
@@ -30,7 +40,7 @@ export function createPressureStateReport(start, end) {
30
40
  critical: 0
31
41
  });
32
42
  const pressureStateTotal = Object.values(pressureStateCount).reduce((total, count) => total + count) || 1;
33
- removeOldBufferRecords(end);
43
+ removeOldPressureBufferRecords(end);
34
44
  return {
35
45
  count: pressureStateCount,
36
46
  percentage: {
@@ -44,23 +54,80 @@ export function createPressureStateReport(start, end) {
44
54
  return null;
45
55
  }
46
56
  }
57
+ function convertBytesToMegabytes(bytes) {
58
+ return Math.round(Math.round(bytes / (1024 * 1024) * 100) / 100);
59
+ }
60
+ export function createMemoryStateReport(start, end) {
61
+ try {
62
+ if (!('memory' in performance)) {
63
+ return null;
64
+ }
65
+ const accumulatedMemoryUsage = memoryRecordBuffer.reduce((acc, snapshot) => {
66
+ if (snapshot.time >= start && snapshot.time <= end) {
67
+ acc.totalJSHeapSize += snapshot.totalJSHeapSize;
68
+ acc.usedJSHeapSize += snapshot.usedJSHeapSize;
69
+ acc.snapshotCount += 1;
70
+ }
71
+ return acc;
72
+ }, {
73
+ totalJSHeapSize: 0,
74
+ usedJSHeapSize: 0,
75
+ snapshotCount: 0
76
+ });
77
+ const memoryStateReport = {
78
+ jsHeapSizeLimitInMB: convertBytesToMegabytes(memoryRecordBuffer[0].jsHeapSizeLimit),
79
+ // just use the first record, since this value always remains the same over time
80
+ avgTotalJSHeapSizeInMB: convertBytesToMegabytes(accumulatedMemoryUsage.totalJSHeapSize / accumulatedMemoryUsage.snapshotCount),
81
+ avgUsedJSHeapSizeInMB: convertBytesToMegabytes(accumulatedMemoryUsage.usedJSHeapSize / accumulatedMemoryUsage.snapshotCount)
82
+ };
83
+ removeOldMemoryBufferRecords(end);
84
+ return memoryStateReport;
85
+ } catch {
86
+ return null;
87
+ }
88
+ }
47
89
  export function initialisePressureObserver() {
48
90
  try {
49
91
  if ('PressureObserver' in globalThis) {
50
- var _pressureObserver$obs;
51
92
  pressureObserver = new PressureObserver(records => {
52
93
  if (pressureRecordBuffer.length + records.length <= BUFFER_MAX_LENGTH) {
53
94
  pressureRecordBuffer.push(...records);
54
95
  }
55
96
  });
56
- (_pressureObserver$obs = pressureObserver.observe('cpu', {
97
+ pressureObserver.observe('cpu', {
57
98
  sampleInterval: 100
58
- })) === null || _pressureObserver$obs === void 0 ? void 0 : _pressureObserver$obs.catch();
99
+ }).catch();
59
100
  }
60
101
  } catch (err) {
61
102
  /* do nothing, this is a best efforts metric */
62
103
  }
63
104
  }
105
+ export function initialiseMemoryObserver() {
106
+ try {
107
+ // only set up the interval if `performance.memory` is available in the browser
108
+ if ('memory' in performance) {
109
+ memoryInterval = setInterval(() => {
110
+ // another check of `performance.memory` availability to satisfy typescript
111
+ if ('memory' in performance) {
112
+ const memory = performance.memory;
113
+ if (memoryRecordBuffer.length <= BUFFER_MAX_LENGTH) {
114
+ memoryRecordBuffer.push({
115
+ time: performance.now(),
116
+ jsHeapSizeLimit: memory.jsHeapSizeLimit,
117
+ totalJSHeapSize: memory.totalJSHeapSize,
118
+ usedJSHeapSize: memory.usedJSHeapSize
119
+ });
120
+ }
121
+ }
122
+ }, 100);
123
+ }
124
+ } catch {
125
+ /* do nothing, this is a best efforts metric */
126
+ }
127
+ }
128
+ export function disconnectMemoryObserver() {
129
+ clearInterval(memoryInterval);
130
+ }
64
131
  export function disconnectPressureObserver() {
65
132
  var _pressureObserver;
66
133
  (_pressureObserver = pressureObserver) === null || _pressureObserver === void 0 ? void 0 : _pressureObserver.disconnect();
@@ -23,7 +23,7 @@ import { getGlobalErrorCount } from '../global-error-handler';
23
23
  import { getPageVisibilityState } from '../hidden-timing';
24
24
  import * as initialPageLoadExtraTiming from '../initial-page-load-extra-timing';
25
25
  import { interactionSpans as atlaskitInteractionSpans } from '../interaction-metrics';
26
- import { createPressureStateReport } from '../machine-utilisation';
26
+ import { createMemoryStateReport, createPressureStateReport } from '../machine-utilisation';
27
27
  import * as resourceTiming from '../resource-timing';
28
28
  import { filterResourceTimings } from '../resource-timing/common/utils/resource-timing-buffer';
29
29
  import { roundEpsilon } from '../round-number';
@@ -699,7 +699,7 @@ function createInteractionMetricsPayload(_x3, _x4, _x5) {
699
699
  function _createInteractionMetricsPayload() {
700
700
  _createInteractionMetricsPayload = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(interaction, interactionId, experimental) {
701
701
  var _window$location, _config$additionalPay;
702
- var interactionPayloadStart, config, end, start, ufoName, knownSegments, rate, type, abortReason, routeName, featureFlags, previousInteractionName, isPreviousInteractionAborted, abortedByInteractionName, responsiveness, pageVisibilityAtTTI, pageVisibilityAtTTAI, segments, segmentTree, isDetailedPayload, isPageLoad, calculatePageVisibilityFromTheStartOfPageLoad, moreAccuratePageVisibilityAtTTI, moreAccuratePageVisibilityAtTTAI, labelStack, getInitialPageLoadSSRMetrics, pageLoadInteractionMetrics, getDetailedInteractionMetrics, getPageLoadDetailedInteractionMetrics, newUFOName, resourceTimings, _yield$Promise$all, _yield$Promise$all2, vcMetrics, experimentalMetrics, paintMetrics, payload;
702
+ var interactionPayloadStart, config, end, start, ufoName, knownSegments, rate, type, abortReason, routeName, featureFlags, previousInteractionName, isPreviousInteractionAborted, abortedByInteractionName, responsiveness, unknownElementName, unknownElementHierarchy, pageVisibilityAtTTI, pageVisibilityAtTTAI, segments, segmentTree, isDetailedPayload, isPageLoad, calculatePageVisibilityFromTheStartOfPageLoad, moreAccuratePageVisibilityAtTTI, moreAccuratePageVisibilityAtTTAI, labelStack, getInitialPageLoadSSRMetrics, pageLoadInteractionMetrics, getDetailedInteractionMetrics, getPageLoadDetailedInteractionMetrics, newUFOName, resourceTimings, _yield$Promise$all, _yield$Promise$all2, vcMetrics, experimentalMetrics, paintMetrics, payload;
703
703
  return _regeneratorRuntime.wrap(function _callee2$(_context2) {
704
704
  while (1) switch (_context2.prev = _context2.next) {
705
705
  case 0:
@@ -711,7 +711,7 @@ function _createInteractionMetricsPayload() {
711
711
  }
712
712
  throw Error('UFO Configuration not provided');
713
713
  case 4:
714
- end = interaction.end, start = interaction.start, ufoName = interaction.ufoName, knownSegments = interaction.knownSegments, rate = interaction.rate, type = interaction.type, abortReason = interaction.abortReason, routeName = interaction.routeName, featureFlags = interaction.featureFlags, previousInteractionName = interaction.previousInteractionName, isPreviousInteractionAborted = interaction.isPreviousInteractionAborted, abortedByInteractionName = interaction.abortedByInteractionName, responsiveness = interaction.responsiveness;
714
+ end = interaction.end, start = interaction.start, ufoName = interaction.ufoName, knownSegments = interaction.knownSegments, rate = interaction.rate, type = interaction.type, abortReason = interaction.abortReason, routeName = interaction.routeName, featureFlags = interaction.featureFlags, previousInteractionName = interaction.previousInteractionName, isPreviousInteractionAborted = interaction.isPreviousInteractionAborted, abortedByInteractionName = interaction.abortedByInteractionName, responsiveness = interaction.responsiveness, unknownElementName = interaction.unknownElementName, unknownElementHierarchy = interaction.unknownElementHierarchy;
715
715
  pageVisibilityAtTTI = getPageVisibilityUpToTTI(interaction);
716
716
  pageVisibilityAtTTAI = getPageVisibilityUpToTTAI(interaction);
717
717
  segments = config.killswitchNestedSegments ? [] : knownSegments;
@@ -799,7 +799,7 @@ function _createInteractionMetricsPayload() {
799
799
  source: 'measured',
800
800
  tags: ['observability'],
801
801
  attributes: {
802
- properties: _objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread({
802
+ properties: _objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread({
803
803
  // basic
804
804
  'event:hostname': ((_window$location = window.location) === null || _window$location === void 0 ? void 0 : _window$location.hostname) || 'unknown',
805
805
  'event:product': config.product,
@@ -814,8 +814,10 @@ function _createInteractionMetricsPayload() {
814
814
  'experience:name': newUFOName
815
815
  }, fg('platform_ufo_report_cpu_usage') ? {
816
816
  'event:cpu:usage': createPressureStateReport(interaction.start, interaction.end)
817
+ } : {}), fg('platform_ufo_report_memory_usage') ? {
818
+ 'event:memory:usage': createMemoryStateReport(interaction.start, interaction.end)
817
819
  } : {}), getBrowserMetadata()), getSSRProperties(type)), getAssetsMetrics(interaction, pageLoadInteractionMetrics === null || pageLoadInteractionMetrics === void 0 ? void 0 : pageLoadInteractionMetrics.SSRDoneTime)), getPPSMetrics(interaction)), paintMetrics), getNavigationMetrics(type)), vcMetrics), experimentalMetrics), (_config$additionalPay = config.additionalPayloadData) === null || _config$additionalPay === void 0 ? void 0 : _config$additionalPay.call(config, interaction)), getTracingContextData(interaction)), getStylesheetMetrics()), getErrorCounts(interaction)), {}, {
818
- interactionMetrics: _objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread({
820
+ interactionMetrics: _objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread(_objectSpread({
819
821
  namePrefix: config.namePrefix || '',
820
822
  segmentPrefix: config.segmentPrefix || '',
821
823
  interactionId: interactionId,
@@ -851,7 +853,11 @@ function _createInteractionMetricsPayload() {
851
853
  } : {}), labelStack), pageLoadInteractionMetrics), getDetailedInteractionMetrics(resourceTimings)), getPageLoadDetailedInteractionMetrics()), getBm3TrackerTimings(interaction)), {}, {
852
854
  'metric:ttai': experimental ? regularTTAI || expTTAI : undefined,
853
855
  'metric:experimental:ttai': expTTAI
854
- }),
856
+ }, unknownElementName ? {
857
+ unknownElementName: unknownElementName
858
+ } : {}), unknownElementHierarchy ? {
859
+ unknownElementHierarchy: unknownElementHierarchy
860
+ } : {}),
855
861
  'ufo:payloadTime': roundEpsilon(performance.now() - interactionPayloadStart)
856
862
  })
857
863
  }
@@ -6,7 +6,7 @@ import { experimentalVC, sinkExperimentalHandler } from '../create-experimental-
6
6
  import { setupHiddenTimingCapture } from '../hidden-timing';
7
7
  import { postInteractionLog, sinkInteractionHandler, sinkPostInteractionLogHandler } from '../interaction-metrics';
8
8
  import { getPerformanceObserver } from '../interactions-performance-observer';
9
- import { initialisePressureObserver } from '../machine-utilisation';
9
+ import { initialiseMemoryObserver, initialisePressureObserver } from '../machine-utilisation';
10
10
  import { getVCObserver } from '../vc';
11
11
  import scheduleIdleCallback from './schedule-idle-callback';
12
12
  var initialized = false;
@@ -78,6 +78,9 @@ export function init(analyticsWebClientAsync, config) {
78
78
  if (fg('platform_ufo_report_cpu_usage')) {
79
79
  initialisePressureObserver();
80
80
  }
81
+ if (fg('platform_ufo_report_memory_usage')) {
82
+ initialiseMemoryObserver();
83
+ }
81
84
  setUFOConfig(config);
82
85
  if ((_config$vc = config.vc) !== null && _config$vc !== void 0 && _config$vc.enabled) {
83
86
  var _config$experimentalI;