utils.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  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' | 'node' | 'web',
  24. frames: Readonly<Profiling.Schema['shared']['frames']>
  25. ): FrameIndex;
  26. export function createFrameIndex(
  27. type: 'mobile' | 'node' | 'web',
  28. frames: Readonly<JSSelfProfiling.Frame[]>,
  29. trace: Readonly<JSSelfProfiling.Trace>
  30. ): FrameIndex;
  31. export function createFrameIndex(
  32. type: 'mobile' | 'node' | 'web',
  33. frames: Readonly<Profiling.Schema['shared']['frames'] | JSSelfProfiling.Frame[]>,
  34. trace?: Readonly<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. type Arguments<F extends Function> = F extends (...args: infer A) => any ? A : never;
  88. export function memoizeVariadicByReference<T extends (...args) => V, V = ReturnType<T>>(
  89. fn: T
  90. ): (...t: Arguments<T>) => V {
  91. let cache: Cache<Arguments<T>, V> | null = null;
  92. return function memoizeByReferenceCallback(...args: Arguments<T>): V {
  93. // If this is the first run then eval the fn and cache the result
  94. if (!cache) {
  95. cache = {args, value: fn(...args)};
  96. return cache.value;
  97. }
  98. // If args match by reference, then return cached value
  99. if (
  100. cache.args.length === args.length &&
  101. cache.args.length !== 0 &&
  102. args.length !== 0 &&
  103. args.every((arg, i) => arg === cache?.args[i])
  104. ) {
  105. return cache.value;
  106. }
  107. // Else eval the fn and store the new value
  108. cache.args = args;
  109. cache.value = fn(...args);
  110. return cache.value;
  111. };
  112. }
  113. export function wrapWithSpan<T>(parentSpan: Span | undefined, fn: () => T, options): T {
  114. if (!defined(parentSpan)) {
  115. return fn();
  116. }
  117. const sentrySpan = parentSpan.startChild(options);
  118. try {
  119. return fn();
  120. } catch (error) {
  121. sentrySpan.setStatus('internal_error');
  122. throw error;
  123. } finally {
  124. sentrySpan.finish();
  125. }
  126. }
  127. export const isSystemCall = (node: CallTreeNode): boolean => {
  128. return !node.frame.is_application;
  129. };
  130. export const isApplicationCall = (node: CallTreeNode): boolean => {
  131. return !!node.frame.is_application;
  132. };
  133. function indexNodeToParents(
  134. roots: Readonly<FlamegraphFrame[]>,
  135. map: Record<string, FlamegraphFrame[]>,
  136. leafs: FlamegraphFrame[]
  137. ) {
  138. // Index each child node to its parent
  139. function indexNode(node: FlamegraphFrame, parent: FlamegraphFrame) {
  140. if (!map[node.key]) {
  141. map[node.key] = [];
  142. }
  143. map[node.key]!.push(parent); // we initialize this above
  144. if (!node.children.length) {
  145. leafs.push(node);
  146. return;
  147. }
  148. for (let i = 0; i < node.children.length; i++) {
  149. indexNode(node.children[i]!, node); // iterating over non empty array
  150. }
  151. }
  152. // Begin in each root node
  153. for (let i = 0; i < roots.length; i++) {
  154. // If the root is a leaf node, push it to the leafs array
  155. if (!roots[i].children?.length) {
  156. leafs.push(roots[i]);
  157. }
  158. // Init the map for the root in case we havent yet
  159. if (!map[roots[i].key]) {
  160. map[roots[i].key] = [];
  161. }
  162. // descend down to each child and index them
  163. for (let j = 0; j < roots[i].children.length; j++) {
  164. indexNode(roots[i].children[j], roots[i]);
  165. }
  166. }
  167. }
  168. function reverseTrail(
  169. nodes: Readonly<FlamegraphFrame[]>,
  170. parentMap: Record<string, FlamegraphFrame[]>
  171. ): FlamegraphFrame[] {
  172. const splits: FlamegraphFrame[] = [];
  173. for (const n of nodes) {
  174. const nc = {
  175. ...n,
  176. parent: null as FlamegraphFrame | null,
  177. children: [] as FlamegraphFrame[],
  178. };
  179. const parents = parentMap[n.key];
  180. if (!parents) {
  181. continue;
  182. }
  183. for (const parent of parents) {
  184. nc.children.push(...reverseTrail([parent], parentMap));
  185. }
  186. splits.push(nc);
  187. }
  188. return splits;
  189. }
  190. export const invertCallTree = (roots: Readonly<FlamegraphFrame[]>): FlamegraphFrame[] => {
  191. const nodeToParentIndex: Record<string, FlamegraphFrame[]> = {};
  192. const leafNodes: FlamegraphFrame[] = [];
  193. indexNodeToParents(roots, nodeToParentIndex, leafNodes);
  194. const reversed = reverseTrail(leafNodes, nodeToParentIndex);
  195. return reversed;
  196. };