traceTree.measurements.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import type {Measurement} from 'sentry/types/event';
  2. import {MobileVital, WebVital} from 'sentry/utils/fields';
  3. import {
  4. MOBILE_VITAL_DETAILS,
  5. WEB_VITAL_DETAILS,
  6. } from 'sentry/utils/performance/vitals/constants';
  7. import type {Vital} from 'sentry/utils/performance/vitals/types';
  8. import type {TraceTree} from './traceTree';
  9. import type {TraceTreeNode} from './traceTreeNode';
  10. // cls is not included as it is a cumulative layout shift and not a single point in time
  11. const RENDERABLE_MEASUREMENTS = [
  12. WebVital.TTFB,
  13. WebVital.FP,
  14. WebVital.FCP,
  15. WebVital.LCP,
  16. MobileVital.TIME_TO_FULL_DISPLAY,
  17. MobileVital.TIME_TO_INITIAL_DISPLAY,
  18. ]
  19. .map(n => n.replace('measurements.', ''))
  20. .reduce((acc, curr) => {
  21. acc[curr] = true;
  22. return acc;
  23. }, {});
  24. const WEB_VITALS = [
  25. WebVital.TTFB,
  26. WebVital.FP,
  27. WebVital.FCP,
  28. WebVital.LCP,
  29. WebVital.CLS,
  30. WebVital.FID,
  31. WebVital.INP,
  32. WebVital.REQUEST_TIME,
  33. ].map(n => n.replace('measurements.', ''));
  34. const MOBILE_VITALS = [
  35. MobileVital.APP_START_COLD,
  36. MobileVital.APP_START_WARM,
  37. MobileVital.TIME_TO_INITIAL_DISPLAY,
  38. MobileVital.TIME_TO_FULL_DISPLAY,
  39. MobileVital.FRAMES_TOTAL,
  40. MobileVital.FRAMES_SLOW,
  41. MobileVital.FRAMES_FROZEN,
  42. MobileVital.FRAMES_SLOW_RATE,
  43. MobileVital.FRAMES_FROZEN_RATE,
  44. MobileVital.STALL_COUNT,
  45. MobileVital.STALL_TOTAL_TIME,
  46. MobileVital.STALL_LONGEST_TIME,
  47. MobileVital.STALL_PERCENTAGE,
  48. ].map(n => n.replace('measurements.', ''));
  49. const WEB_VITALS_LOOKUP = new Set<string>(WEB_VITALS);
  50. const MOBILE_VITALS_LOOKUP = new Set<string>(MOBILE_VITALS);
  51. const COLLECTABLE_MEASUREMENTS = [...WEB_VITALS, ...MOBILE_VITALS].map(n =>
  52. n.replace('measurements.', '')
  53. );
  54. const MEASUREMENT_ACRONYM_MAPPING = {
  55. [MobileVital.TIME_TO_FULL_DISPLAY.replace('measurements.', '')]: 'TTFD',
  56. [MobileVital.TIME_TO_INITIAL_DISPLAY.replace('measurements.', '')]: 'TTID',
  57. };
  58. const MEASUREMENT_THRESHOLDS = {
  59. [WebVital.TTFB.replace('measurements.', '')]: 600,
  60. [WebVital.FP.replace('measurements.', '')]: 3000,
  61. [WebVital.FCP.replace('measurements.', '')]: 3000,
  62. [WebVital.LCP.replace('measurements.', '')]: 4000,
  63. [MobileVital.TIME_TO_INITIAL_DISPLAY.replace('measurements.', '')]: 2000,
  64. };
  65. export const TRACE_MEASUREMENT_LOOKUP: Record<string, Vital> = {};
  66. for (const key in {...MOBILE_VITAL_DETAILS, ...WEB_VITAL_DETAILS}) {
  67. TRACE_MEASUREMENT_LOOKUP[key.replace('measurements.', '')] = {
  68. ...MOBILE_VITAL_DETAILS[key],
  69. ...WEB_VITAL_DETAILS[key],
  70. };
  71. }
  72. function traceMeasurementToTimestamp(
  73. start_timestamp: number,
  74. measurement: number,
  75. unit: string
  76. ) {
  77. if (unit === 'second') {
  78. return (start_timestamp + measurement) * 1e3;
  79. }
  80. if (unit === 'millisecond') {
  81. return start_timestamp + measurement;
  82. }
  83. if (unit === 'nanosecond') {
  84. return (start_timestamp + measurement) * 1e-6;
  85. }
  86. throw new TypeError(`Unsupported measurement unit', ${unit}`);
  87. }
  88. // Collects measurements from a trace node and adds them to the indicators stored on trace tree
  89. export function collectTraceMeasurements(
  90. node: TraceTreeNode<TraceTree.NodeValue>,
  91. start_timestamp: number,
  92. measurements: Record<string, Measurement> | undefined,
  93. vitals: Map<TraceTreeNode<TraceTree.NodeValue>, TraceTree.CollectedVital[]>,
  94. vital_types: Set<'web' | 'mobile'>
  95. ): TraceTree.Indicator[] {
  96. const indicators: TraceTree.Indicator[] = [];
  97. if (!measurements) {
  98. return indicators;
  99. }
  100. for (const measurement of COLLECTABLE_MEASUREMENTS) {
  101. const value = measurements[measurement];
  102. if (!value || typeof value.value !== 'number') {
  103. continue;
  104. }
  105. if (!vitals.has(node)) {
  106. vitals.set(node, []);
  107. }
  108. WEB_VITALS_LOOKUP.has(measurement) && vital_types.add('web');
  109. MOBILE_VITALS_LOOKUP.has(measurement) && vital_types.add('mobile');
  110. const vital = vitals.get(node)!;
  111. vital.push({
  112. key: measurement,
  113. measurement: value,
  114. });
  115. if (!RENDERABLE_MEASUREMENTS[measurement]) {
  116. continue;
  117. }
  118. const timestamp = traceMeasurementToTimestamp(
  119. start_timestamp,
  120. value.value,
  121. value.unit ?? 'millisecond'
  122. );
  123. indicators.push({
  124. start: timestamp,
  125. duration: 0,
  126. measurement: value,
  127. poor: MEASUREMENT_THRESHOLDS[measurement]
  128. ? value.value > MEASUREMENT_THRESHOLDS[measurement]
  129. : false,
  130. type: measurement as TraceTree.Indicator['type'],
  131. label: (MEASUREMENT_ACRONYM_MAPPING[measurement] ?? measurement).toUpperCase(),
  132. });
  133. }
  134. return indicators;
  135. }