utils.tsx 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. import * as Sentry from '@sentry/react';
  2. import type {Span} from '@sentry/types';
  3. import {defined} from 'sentry/utils';
  4. import type {FlamegraphFrame} from 'sentry/utils/profiling/flamegraphFrame';
  5. import {Frame} from 'sentry/utils/profiling/frame';
  6. import type {CallTreeNode} from '../callTreeNode';
  7. type FrameIndex = Record<string | number, Frame>;
  8. export function createSentrySampleProfileFrameIndex(
  9. frames: Profiling.SentrySampledProfile['profile']['frames'],
  10. platform: 'mobile' | 'node' | 'javascript' | string
  11. ): FrameIndex {
  12. const index: FrameIndex = {};
  13. const insertionCache: Record<string, Frame> = {};
  14. let idx = -1;
  15. for (let i = 0; i < frames.length; i++) {
  16. const frame = frames[i];
  17. const frameKey = `${frame.filename ?? ''}:${frame.function ?? 'unknown'}:${
  18. String(frame.lineno) ?? ''
  19. }:${frame.instruction_addr ?? ''}`;
  20. const existing = insertionCache[frameKey];
  21. if (existing) {
  22. index[++idx] = existing;
  23. continue;
  24. }
  25. const f = new Frame(
  26. {
  27. key: i,
  28. is_application: frame.in_app,
  29. file: frame.filename,
  30. path: frame.abs_path,
  31. module: frame.module,
  32. package: frame.package,
  33. name: frame.function ?? 'unknown',
  34. line: frame.lineno,
  35. column: frame.colno ?? frame?.col ?? frame?.column,
  36. instructionAddr: frame.instruction_addr,
  37. symbol: frame.symbol,
  38. symbolAddr: frame.sym_addr,
  39. symbolicatorStatus: frame.status,
  40. },
  41. platform
  42. );
  43. index[++idx] = f;
  44. insertionCache[frameKey] = f;
  45. }
  46. return index;
  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. column: frame.colno ?? frame?.col ?? frame?.column,
  83. ...frame,
  84. },
  85. type
  86. );
  87. return acc;
  88. }, {});
  89. }
  90. type Cache<Arguments extends ReadonlyArray<any> | any, Value> = {
  91. args: Arguments;
  92. value: Value;
  93. };
  94. export function memoizeByReference<Arguments, Value>(
  95. fn: (args: Arguments) => Value
  96. ): (t: Arguments) => Value {
  97. let cache: Cache<Arguments, Value> | null = null;
  98. return function memoizeByReferenceCallback(args: Arguments) {
  99. // If this is the first run then eval the fn and cache the result
  100. if (!cache) {
  101. cache = {args, value: fn(args)};
  102. return cache.value;
  103. }
  104. // If args match by reference, then return cached value
  105. if (cache.args === args && cache.args !== undefined && args !== undefined) {
  106. return cache.value;
  107. }
  108. // Else eval the fn and store the new value
  109. cache.args = args;
  110. cache.value = fn(args);
  111. return cache.value;
  112. };
  113. }
  114. type Arguments<F extends Function> = F extends (...args: infer A) => any ? A : never;
  115. export function memoizeVariadicByReference<T extends (...args) => V, V = ReturnType<T>>(
  116. fn: T
  117. ): (...t: Arguments<T>) => V {
  118. let cache: Cache<Arguments<T>, V> | null = null;
  119. return function memoizeByReferenceCallback(...args: Arguments<T>): V {
  120. // If this is the first run then eval the fn and cache the result
  121. if (!cache) {
  122. cache = {args, value: fn(...args)};
  123. return cache.value;
  124. }
  125. // If args match by reference, then return cached value
  126. if (
  127. cache.args.length === args.length &&
  128. cache.args.length !== 0 &&
  129. args.length !== 0 &&
  130. args.every((arg, i) => arg === cache?.args[i])
  131. ) {
  132. return cache.value;
  133. }
  134. // Else eval the fn and store the new value
  135. cache.args = args;
  136. cache.value = fn(...args);
  137. return cache.value;
  138. };
  139. }
  140. export function wrapWithSpan<T>(parentSpan: Span | undefined, fn: () => T, options): T {
  141. if (!defined(parentSpan)) {
  142. return fn();
  143. }
  144. return Sentry.withActiveSpan(parentSpan, () => {
  145. return Sentry.startSpan(options, () => {
  146. return fn();
  147. });
  148. });
  149. }
  150. export const isSystemCall = (node: CallTreeNode): boolean => {
  151. return !node.frame.is_application;
  152. };
  153. export const isApplicationCall = (node: CallTreeNode): boolean => {
  154. return !!node.frame.is_application;
  155. };
  156. function indexNodeToParents(
  157. roots: Readonly<FlamegraphFrame[]>,
  158. map: Record<string, FlamegraphFrame[]>,
  159. leafs: FlamegraphFrame[]
  160. ) {
  161. // Index each child node to its parent
  162. function indexNode(node: FlamegraphFrame, parent: FlamegraphFrame) {
  163. if (!map[node.key]) {
  164. map[node.key] = [];
  165. }
  166. map[node.key]!.push(parent); // we initialize this above
  167. if (!node.children.length) {
  168. leafs.push(node);
  169. return;
  170. }
  171. for (let i = 0; i < node.children.length; i++) {
  172. indexNode(node.children[i]!, node); // iterating over non empty array
  173. }
  174. }
  175. // Begin in each root node
  176. for (let i = 0; i < roots.length; i++) {
  177. // If the root is a leaf node, push it to the leafs array
  178. if (!roots[i].children?.length) {
  179. leafs.push(roots[i]);
  180. }
  181. // Init the map for the root in case we havent yet
  182. if (!map[roots[i].key]) {
  183. map[roots[i].key] = [];
  184. }
  185. // descend down to each child and index them
  186. for (let j = 0; j < roots[i].children.length; j++) {
  187. indexNode(roots[i].children[j], roots[i]);
  188. }
  189. }
  190. }
  191. function reverseTrail(
  192. nodes: Readonly<FlamegraphFrame[]>,
  193. parentMap: Record<string, FlamegraphFrame[]>
  194. ): FlamegraphFrame[] {
  195. const splits: FlamegraphFrame[] = [];
  196. for (const n of nodes) {
  197. const nc = {
  198. ...n,
  199. parent: null as FlamegraphFrame | null,
  200. children: [] as FlamegraphFrame[],
  201. };
  202. const parents = parentMap[n.key];
  203. if (!parents) {
  204. continue;
  205. }
  206. for (const parent of parents) {
  207. nc.children.push(...reverseTrail([parent], parentMap));
  208. }
  209. splits.push(nc);
  210. }
  211. return splits;
  212. }
  213. export const invertCallTree = (roots: Readonly<FlamegraphFrame[]>): FlamegraphFrame[] => {
  214. const nodeToParentIndex: Record<string, FlamegraphFrame[]> = {};
  215. const leafNodes: FlamegraphFrame[] = [];
  216. indexNodeToParents(roots, nodeToParentIndex, leafNodes);
  217. const reversed = reverseTrail(leafNodes, nodeToParentIndex);
  218. return reversed;
  219. };
  220. export function resolveFlamegraphSamplesProfileIds(
  221. samplesProfiles: Readonly<number[][]>,
  222. profileIds: Readonly<string[]>
  223. ): string[][] {
  224. return samplesProfiles.map(profileIdIndices => {
  225. return profileIdIndices.map(i => profileIds[i]);
  226. });
  227. }