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