utils.tsx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  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. platform: 'mobile' | 'node' | 'javascript' | string
  10. ): FrameIndex {
  11. const index: FrameIndex = {};
  12. const insertionCache: Record<string, Frame> = {};
  13. let idx = -1;
  14. for (let i = 0; i < frames.length; i++) {
  15. const frame = frames[i];
  16. const frameKey = `${frame.filename ?? ''}:${frame.function ?? 'unknown'}:${
  17. String(frame.lineno) ?? ''
  18. }:${frame.instruction_addr ?? ''}`;
  19. const existing = insertionCache[frameKey];
  20. if (existing) {
  21. index[++idx] = existing;
  22. continue;
  23. }
  24. const f = new Frame(
  25. {
  26. key: i,
  27. is_application: frame.in_app,
  28. file: frame.filename,
  29. path: frame.abs_path,
  30. module: frame.module,
  31. package: frame.package,
  32. name: frame.function ?? 'unknown',
  33. line: frame.lineno,
  34. column: frame.colno,
  35. instructionAddr: frame.instruction_addr,
  36. symbol: frame.symbol,
  37. symbolAddr: frame.sym_addr,
  38. symbolicatorStatus: frame.status,
  39. },
  40. platform
  41. );
  42. index[++idx] = f;
  43. insertionCache[frameKey] = f;
  44. }
  45. return index;
  46. }
  47. export function createFrameIndex(
  48. type: 'mobile' | 'node' | 'javascript',
  49. frames: Readonly<Profiling.Schema['shared']['frames']>
  50. ): FrameIndex;
  51. export function createFrameIndex(
  52. type: 'mobile' | 'node' | 'javascript',
  53. frames: Readonly<JSSelfProfiling.Frame[]>,
  54. trace: Readonly<JSSelfProfiling.Trace>
  55. ): FrameIndex;
  56. export function createFrameIndex(
  57. type: 'mobile' | 'node' | 'javascript',
  58. frames: Readonly<Profiling.Schema['shared']['frames'] | JSSelfProfiling.Frame[]>,
  59. trace?: Readonly<JSSelfProfiling.Trace>
  60. ): FrameIndex {
  61. if (trace) {
  62. return (frames as JSSelfProfiling.Frame[]).reduce((acc, frame, index) => {
  63. acc[index] = new Frame(
  64. {
  65. key: index,
  66. resource:
  67. frame.resourceId !== undefined
  68. ? trace.resources[frame.resourceId]
  69. : undefined,
  70. ...frame,
  71. },
  72. 'javascript'
  73. );
  74. return acc;
  75. }, {});
  76. }
  77. return (frames as Profiling.Schema['shared']['frames']).reduce((acc, frame, index) => {
  78. acc[index] = new Frame(
  79. {
  80. key: index,
  81. ...frame,
  82. },
  83. type
  84. );
  85. return acc;
  86. }, {});
  87. }
  88. type Cache<Arguments extends ReadonlyArray<any> | any, Value> = {
  89. args: Arguments;
  90. value: Value;
  91. };
  92. export function memoizeByReference<Arguments, Value>(
  93. fn: (args: Arguments) => Value
  94. ): (t: Arguments) => Value {
  95. let cache: Cache<Arguments, Value> | null = null;
  96. return function memoizeByReferenceCallback(args: Arguments) {
  97. // If this is the first run then eval the fn and cache the result
  98. if (!cache) {
  99. cache = {args, value: fn(args)};
  100. return cache.value;
  101. }
  102. // If args match by reference, then return cached value
  103. if (cache.args === args && cache.args !== undefined && args !== undefined) {
  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. type Arguments<F extends Function> = F extends (...args: infer A) => any ? A : never;
  113. export function memoizeVariadicByReference<T extends (...args) => V, V = ReturnType<T>>(
  114. fn: T
  115. ): (...t: Arguments<T>) => V {
  116. let cache: Cache<Arguments<T>, V> | null = null;
  117. return function memoizeByReferenceCallback(...args: Arguments<T>): V {
  118. // If this is the first run then eval the fn and cache the result
  119. if (!cache) {
  120. cache = {args, value: fn(...args)};
  121. return cache.value;
  122. }
  123. // If args match by reference, then return cached value
  124. if (
  125. cache.args.length === args.length &&
  126. cache.args.length !== 0 &&
  127. args.length !== 0 &&
  128. args.every((arg, i) => arg === cache?.args[i])
  129. ) {
  130. return cache.value;
  131. }
  132. // Else eval the fn and store the new value
  133. cache.args = args;
  134. cache.value = fn(...args);
  135. return cache.value;
  136. };
  137. }
  138. export function wrapWithSpan<T>(parentSpan: Span | undefined, fn: () => T, options): T {
  139. if (!defined(parentSpan)) {
  140. return fn();
  141. }
  142. const sentrySpan = parentSpan.startChild(options);
  143. try {
  144. return fn();
  145. } catch (error) {
  146. sentrySpan.setStatus('internal_error');
  147. throw error;
  148. } finally {
  149. sentrySpan.finish();
  150. }
  151. }
  152. export const isSystemCall = (node: CallTreeNode): boolean => {
  153. return !node.frame.is_application;
  154. };
  155. export const isApplicationCall = (node: CallTreeNode): boolean => {
  156. return !!node.frame.is_application;
  157. };
  158. function indexNodeToParents(
  159. roots: Readonly<FlamegraphFrame[]>,
  160. map: Record<string, FlamegraphFrame[]>,
  161. leafs: FlamegraphFrame[]
  162. ) {
  163. // Index each child node to its parent
  164. function indexNode(node: FlamegraphFrame, parent: FlamegraphFrame) {
  165. if (!map[node.key]) {
  166. map[node.key] = [];
  167. }
  168. map[node.key]!.push(parent); // we initialize this above
  169. if (!node.children.length) {
  170. leafs.push(node);
  171. return;
  172. }
  173. for (let i = 0; i < node.children.length; i++) {
  174. indexNode(node.children[i]!, node); // iterating over non empty array
  175. }
  176. }
  177. // Begin in each root node
  178. for (let i = 0; i < roots.length; i++) {
  179. // If the root is a leaf node, push it to the leafs array
  180. if (!roots[i].children?.length) {
  181. leafs.push(roots[i]);
  182. }
  183. // Init the map for the root in case we havent yet
  184. if (!map[roots[i].key]) {
  185. map[roots[i].key] = [];
  186. }
  187. // descend down to each child and index them
  188. for (let j = 0; j < roots[i].children.length; j++) {
  189. indexNode(roots[i].children[j], roots[i]);
  190. }
  191. }
  192. }
  193. function reverseTrail(
  194. nodes: Readonly<FlamegraphFrame[]>,
  195. parentMap: Record<string, FlamegraphFrame[]>
  196. ): FlamegraphFrame[] {
  197. const splits: FlamegraphFrame[] = [];
  198. for (const n of nodes) {
  199. const nc = {
  200. ...n,
  201. parent: null as FlamegraphFrame | null,
  202. children: [] as FlamegraphFrame[],
  203. };
  204. const parents = parentMap[n.key];
  205. if (!parents) {
  206. continue;
  207. }
  208. for (const parent of parents) {
  209. nc.children.push(...reverseTrail([parent], parentMap));
  210. }
  211. splits.push(nc);
  212. }
  213. return splits;
  214. }
  215. export const invertCallTree = (roots: Readonly<FlamegraphFrame[]>): FlamegraphFrame[] => {
  216. const nodeToParentIndex: Record<string, FlamegraphFrame[]> = {};
  217. const leafNodes: FlamegraphFrame[] = [];
  218. indexNodeToParents(roots, nodeToParentIndex, leafNodes);
  219. const reversed = reverseTrail(leafNodes, nodeToParentIndex);
  220. return reversed;
  221. };
  222. export function resolveFlamegraphSamplesProfileIds(
  223. samplesProfiles: Readonly<number[][]>,
  224. profileIds: Readonly<string[]>
  225. ): string[][] {
  226. return samplesProfiles.map(profileIdIndices => {
  227. return profileIdIndices.map(i => profileIds[i]);
  228. });
  229. }