utils.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. import {Span} from '@sentry/types';
  2. import {defined} from 'sentry/utils';
  3. import {FlamegraphFrame} from 'sentry/utils/profiling/flamegraphFrame';
  4. import {Frame} from 'sentry/utils/profiling/frame';
  5. import {CallTreeNode} from '../callTreeNode';
  6. type FrameIndex = Record<string | number, Frame>;
  7. export function createSentrySampleProfileFrameIndex(
  8. frames: Profiling.SentrySampledProfile['profile']['frames']
  9. ): FrameIndex {
  10. const frameIndex: FrameIndex = {};
  11. for (let i = 0; i < frames.length; i++) {
  12. const frame = frames[i];
  13. frameIndex[i] = new Frame({
  14. key: i,
  15. name: frame.function ?? 'unknown',
  16. line: frame.lineno,
  17. column: frame.colno,
  18. });
  19. }
  20. return frameIndex;
  21. }
  22. export function createFrameIndex(
  23. type: 'mobile' | 'web',
  24. frames: Profiling.Schema['shared']['frames']
  25. ): FrameIndex;
  26. export function createFrameIndex(
  27. type: 'mobile' | 'web',
  28. frames: JSSelfProfiling.Frame[],
  29. trace: JSSelfProfiling.Trace
  30. ): FrameIndex;
  31. export function createFrameIndex(
  32. type: 'mobile' | 'web',
  33. frames: Profiling.Schema['shared']['frames'] | JSSelfProfiling.Frame[],
  34. trace?: JSSelfProfiling.Trace
  35. ): FrameIndex {
  36. if (trace) {
  37. return (frames as JSSelfProfiling.Frame[]).reduce((acc, frame, index) => {
  38. acc[index] = new Frame(
  39. {
  40. key: index,
  41. resource:
  42. frame.resourceId !== undefined
  43. ? trace.resources[frame.resourceId]
  44. : undefined,
  45. ...frame,
  46. },
  47. 'web'
  48. );
  49. return acc;
  50. }, {});
  51. }
  52. return (frames as Profiling.Schema['shared']['frames']).reduce((acc, frame, index) => {
  53. acc[index] = new Frame(
  54. {
  55. key: index,
  56. ...frame,
  57. },
  58. type
  59. );
  60. return acc;
  61. }, {});
  62. }
  63. type Cache<Arguments extends ReadonlyArray<any> | any, Value> = {
  64. args: Arguments;
  65. value: Value;
  66. };
  67. export function memoizeByReference<Arguments, Value>(
  68. fn: (args: Arguments) => Value
  69. ): (t: Arguments) => Value {
  70. let cache: Cache<Arguments, Value> | null = null;
  71. return function memoizeByReferenceCallback(args: Arguments) {
  72. // If this is the first run then eval the fn and cache the result
  73. if (!cache) {
  74. cache = {args, value: fn(args)};
  75. return cache.value;
  76. }
  77. // If args match by reference, then return cached value
  78. if (cache.args === args && cache.args !== undefined && args !== undefined) {
  79. return cache.value;
  80. }
  81. // Else eval the fn and store the new value
  82. cache.args = args;
  83. cache.value = fn(args);
  84. return cache.value;
  85. };
  86. }
  87. export function memoizeVariadicByReference<Arguments, Value>(
  88. fn: (...args: ReadonlyArray<Arguments>) => Value
  89. ): (...t: ReadonlyArray<Arguments>) => Value {
  90. let cache: Cache<ReadonlyArray<Arguments>, Value> | null = null;
  91. return function memoizeByReferenceCallback(...args: ReadonlyArray<Arguments>) {
  92. // If this is the first run then eval the fn and cache the result
  93. if (!cache) {
  94. cache = {args, value: fn(...args)};
  95. return cache.value;
  96. }
  97. // If args match by reference, then return cached value
  98. if (
  99. cache.args.length === args.length &&
  100. cache.args.length !== 0 &&
  101. args.length !== 0 &&
  102. args.every((arg, i) => arg === cache?.args[i])
  103. ) {
  104. return cache.value;
  105. }
  106. // Else eval the fn and store the new value
  107. cache.args = args;
  108. cache.value = fn(...args);
  109. return cache.value;
  110. };
  111. }
  112. export function wrapWithSpan<T>(parentSpan: Span | undefined, fn: () => T, options): T {
  113. if (!defined(parentSpan)) {
  114. return fn();
  115. }
  116. const sentrySpan = parentSpan.startChild(options);
  117. try {
  118. return fn();
  119. } catch (error) {
  120. sentrySpan.setStatus('internal_error');
  121. throw error;
  122. } finally {
  123. sentrySpan.finish();
  124. }
  125. }
  126. export const isSystemCall = (node: CallTreeNode): boolean => {
  127. return !node.frame.is_application;
  128. };
  129. export const isApplicationCall = (node: CallTreeNode): boolean => {
  130. return !!node.frame.is_application;
  131. };
  132. function indexNodeToParents(
  133. roots: FlamegraphFrame[],
  134. map: Record<string, FlamegraphFrame[]>,
  135. leafs: FlamegraphFrame[]
  136. ) {
  137. // Index each child node to its parent
  138. function indexNode(node: FlamegraphFrame, parent: FlamegraphFrame) {
  139. if (!map[node.key]) {
  140. map[node.key] = [];
  141. }
  142. map[node.key]!.push(parent); // we initialize this above
  143. if (!node.children.length) {
  144. leafs.push(node);
  145. return;
  146. }
  147. for (let i = 0; i < node.children.length; i++) {
  148. indexNode(node.children[i]!, node); // iterating over non empty array
  149. }
  150. }
  151. // Begin in each root node
  152. for (let i = 0; i < roots.length; i++) {
  153. // If the root is a leaf node, push it to the leafs array
  154. if (!roots[i].children?.length) {
  155. leafs.push(roots[i]);
  156. }
  157. // Init the map for the root in case we havent yet
  158. if (!map[roots[i].key]) {
  159. map[roots[i].key] = [];
  160. }
  161. // descend down to each child and index them
  162. for (let j = 0; j < roots[i].children.length; j++) {
  163. indexNode(roots[i].children[j], roots[i]);
  164. }
  165. }
  166. }
  167. function reverseTrail(
  168. nodes: FlamegraphFrame[],
  169. parentMap: Record<string, FlamegraphFrame[]>
  170. ): FlamegraphFrame[] {
  171. const splits: FlamegraphFrame[] = [];
  172. for (const n of nodes) {
  173. const nc = {
  174. ...n,
  175. parent: null as FlamegraphFrame | null,
  176. children: [] as FlamegraphFrame[],
  177. };
  178. const parents = parentMap[n.key];
  179. if (!parents) {
  180. continue;
  181. }
  182. for (const parent of parents) {
  183. nc.children.push(...reverseTrail([parent], parentMap));
  184. }
  185. splits.push(nc);
  186. }
  187. return splits;
  188. }
  189. export const invertCallTree = (roots: FlamegraphFrame[]): FlamegraphFrame[] => {
  190. const nodeToParentIndex: Record<string, FlamegraphFrame[]> = {};
  191. const leafNodes: FlamegraphFrame[] = [];
  192. indexNodeToParents(roots, nodeToParentIndex, leafNodes);
  193. const reversed = reverseTrail(leafNodes, nodeToParentIndex);
  194. return reversed;
  195. };