jsSelfProfile.spec.tsx 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. import {JSSelfProfile} from 'sentry/utils/profiling/profile/jsSelfProfile';
  2. import {createFrameIndex} from 'sentry/utils/profiling/profile/utils';
  3. import {firstCallee, makeTestingBoilerplate, nthCallee} from './profile.spec';
  4. describe('jsSelfProfile', () => {
  5. it('imports the base properties', () => {
  6. const trace: JSSelfProfiling.Trace = {
  7. resources: ['app.js', 'vendor.js'],
  8. frames: [{name: 'ReactDOM.render', line: 1, column: 1, resourceId: 0}],
  9. samples: [
  10. {
  11. timestamp: 0,
  12. },
  13. {
  14. timestamp: 1000,
  15. stackId: 0,
  16. },
  17. ],
  18. stacks: [
  19. {
  20. frameId: 0,
  21. },
  22. ],
  23. };
  24. const profile = JSSelfProfile.FromProfile(
  25. trace,
  26. createFrameIndex('web', trace.frames, trace)
  27. );
  28. expect(profile.duration).toBe(1000);
  29. expect(profile.startedAt).toBe(0);
  30. expect(profile.endedAt).toBe(1000);
  31. expect(profile.appendOrderTree.children[0].frame.name).toBe('ReactDOM.render');
  32. expect(profile.appendOrderTree.children[0].frame.resource).toBe('app.js');
  33. });
  34. it('tracks discarded samples', () => {
  35. const trace: JSSelfProfiling.Trace = {
  36. resources: ['app.js', 'vendor.js'],
  37. frames: [{name: 'ReactDOM.render', line: 1, column: 1, resourceId: 0}],
  38. samples: [
  39. {
  40. timestamp: 0,
  41. },
  42. ],
  43. stacks: [
  44. {
  45. frameId: 0,
  46. },
  47. ],
  48. };
  49. const profile = JSSelfProfile.FromProfile(
  50. trace,
  51. createFrameIndex('web', [{name: 'f0'}])
  52. );
  53. expect(profile.stats.discardedSamplesCount).toBe(1);
  54. });
  55. it('tracks negative samples', () => {
  56. const trace: JSSelfProfiling.Trace = {
  57. resources: ['app.js', 'vendor.js'],
  58. frames: [{name: 'ReactDOM.render', line: 1, column: 1, resourceId: 0}],
  59. samples: [
  60. {
  61. timestamp: 0,
  62. },
  63. {
  64. timestamp: -1,
  65. },
  66. ],
  67. stacks: [
  68. {
  69. frameId: 0,
  70. },
  71. ],
  72. };
  73. const profile = JSSelfProfile.FromProfile(
  74. trace,
  75. createFrameIndex('web', [{name: 'f0'}])
  76. );
  77. expect(profile.stats.negativeSamplesCount).toBe(1);
  78. });
  79. it('handles the first stack sample differently', () => {
  80. const trace: JSSelfProfiling.Trace = {
  81. resources: ['app.js'],
  82. frames: [
  83. {name: 'main', line: 1, column: 1, resourceId: 0},
  84. {name: 'new Profiler', line: 1, column: 1, resourceId: 0},
  85. {name: 'afterProfiler.init', line: 1, column: 1, resourceId: 0},
  86. ],
  87. samples: [
  88. {
  89. stackId: 1,
  90. timestamp: 500,
  91. },
  92. {
  93. stackId: 2,
  94. timestamp: 1500,
  95. },
  96. ],
  97. stacks: [
  98. {frameId: 0, parentId: undefined},
  99. {frameId: 1, parentId: 0},
  100. {frameId: 2, parentId: 0},
  101. ],
  102. };
  103. const {open, close, openSpy, closeSpy, timings} = makeTestingBoilerplate();
  104. const profile = JSSelfProfile.FromProfile(
  105. trace,
  106. createFrameIndex('web', trace.frames, trace)
  107. );
  108. profile.forEach(open, close);
  109. expect(timings).toEqual([
  110. ['main', 'open'],
  111. ['new Profiler', 'open'],
  112. ['new Profiler', 'close'],
  113. ['afterProfiler.init', 'open'],
  114. ['afterProfiler.init', 'close'],
  115. ['main', 'close'],
  116. ]);
  117. expect(openSpy).toHaveBeenCalledTimes(3);
  118. expect(closeSpy).toHaveBeenCalledTimes(3);
  119. const root = firstCallee(profile.appendOrderTree);
  120. if (!root) {
  121. throw new Error('root is null');
  122. }
  123. expect(root.totalWeight).toEqual(1000);
  124. expect(root.selfWeight).toEqual(0);
  125. expect(nthCallee(root, 0).selfWeight).toEqual(0);
  126. expect(nthCallee(root, 0).totalWeight).toEqual(0);
  127. expect(nthCallee(root, 1).selfWeight).toEqual(1000);
  128. expect(nthCallee(root, 1).totalWeight).toEqual(1000);
  129. });
  130. it('rebuilds the stack', () => {
  131. const trace: JSSelfProfiling.Trace = {
  132. resources: ['app.js'],
  133. frames: [
  134. {name: 'f0', line: 1, column: 1, resourceId: 0},
  135. {name: 'f1', line: 1, column: 1, resourceId: 0},
  136. ],
  137. samples: [
  138. {
  139. stackId: 0,
  140. timestamp: 0,
  141. },
  142. {
  143. timestamp: 1000,
  144. stackId: 0,
  145. },
  146. ],
  147. stacks: [{frameId: 1, parentId: 1}, {frameId: 0}],
  148. };
  149. const {open, close, openSpy, closeSpy, timings} = makeTestingBoilerplate();
  150. const profile = JSSelfProfile.FromProfile(
  151. trace,
  152. createFrameIndex('web', trace.frames, trace)
  153. );
  154. profile.forEach(open, close);
  155. expect(timings).toEqual([
  156. ['f0', 'open'],
  157. ['f1', 'open'],
  158. ['f1', 'close'],
  159. ['f0', 'close'],
  160. ]);
  161. expect(openSpy).toHaveBeenCalledTimes(2);
  162. expect(closeSpy).toHaveBeenCalledTimes(2);
  163. const root = firstCallee(profile.appendOrderTree);
  164. expect(root.totalWeight).toEqual(1000);
  165. expect(firstCallee(root).totalWeight).toEqual(1000);
  166. expect(root.selfWeight).toEqual(0);
  167. expect(firstCallee(root).selfWeight).toEqual(1000);
  168. });
  169. it('marks direct recursion', () => {
  170. const trace: JSSelfProfiling.Trace = {
  171. resources: ['app.js'],
  172. frames: [{name: 'f0', line: 1, column: 1, resourceId: 0}],
  173. samples: [
  174. {
  175. stackId: 0,
  176. timestamp: 0,
  177. },
  178. {
  179. stackId: 0,
  180. timestamp: 0,
  181. },
  182. ],
  183. stacks: [{frameId: 0, parentId: 1}, {frameId: 0}],
  184. };
  185. const profile = JSSelfProfile.FromProfile(
  186. trace,
  187. createFrameIndex('web', trace.frames, trace)
  188. );
  189. expect(firstCallee(firstCallee(profile.appendOrderTree)).isRecursive()).toBe(true);
  190. });
  191. it('marks indirect recursion', () => {
  192. const trace: JSSelfProfiling.Trace = {
  193. resources: ['app.js'],
  194. frames: [
  195. {name: 'f0', line: 1, column: 1, resourceId: 0},
  196. {name: 'f1', line: 1, column: 1, resourceId: 0},
  197. ],
  198. samples: [
  199. {
  200. stackId: 0,
  201. timestamp: 0,
  202. },
  203. {
  204. stackId: 2,
  205. timestamp: 100,
  206. },
  207. ],
  208. stacks: [
  209. {frameId: 0, parentId: undefined},
  210. {frameId: 1, parentId: 0},
  211. {frameId: 0, parentId: 1},
  212. ],
  213. };
  214. const profile = JSSelfProfile.FromProfile(
  215. trace,
  216. createFrameIndex('web', trace.frames, trace)
  217. );
  218. expect(
  219. firstCallee(firstCallee(firstCallee(profile.appendOrderTree))).isRecursive()
  220. ).toBe(true);
  221. });
  222. it('tracks minFrameDuration', () => {
  223. const trace: JSSelfProfiling.Trace = {
  224. resources: ['app.js'],
  225. frames: [
  226. {name: 'f0', line: 1, column: 1, resourceId: 0},
  227. {name: 'f1', line: 1, column: 1, resourceId: 0},
  228. {name: 'f2', line: 1, column: 1, resourceId: 0},
  229. ],
  230. samples: [
  231. {
  232. stackId: 0,
  233. timestamp: 0,
  234. },
  235. {
  236. stackId: 2,
  237. timestamp: 10,
  238. },
  239. {
  240. stackId: 3,
  241. timestamp: 100,
  242. },
  243. ],
  244. stacks: [
  245. {frameId: 0, parentId: undefined},
  246. {frameId: 1, parentId: 0},
  247. {frameId: 0, parentId: 1},
  248. {frameId: 2},
  249. ],
  250. };
  251. const profile = JSSelfProfile.FromProfile(
  252. trace,
  253. createFrameIndex('web', trace.frames, trace)
  254. );
  255. expect(profile.minFrameDuration).toBe(10);
  256. });
  257. it('appends gc to previous stack', () => {
  258. const trace: JSSelfProfiling.Trace = {
  259. resources: ['app.js'],
  260. frames: [
  261. {name: 'f1', line: 1, column: 1, resourceId: 0},
  262. {name: 'f0', line: 1, column: 1, resourceId: 0},
  263. ],
  264. samples: [
  265. {
  266. stackId: 0,
  267. timestamp: 0,
  268. },
  269. {
  270. timestamp: 10,
  271. marker: 'gc',
  272. },
  273. ],
  274. stacks: [
  275. {frameId: 0, parentId: 1},
  276. {frameId: 1, parentId: undefined},
  277. ],
  278. };
  279. const profile = JSSelfProfile.FromProfile(
  280. trace,
  281. createFrameIndex('web', trace.frames, trace)
  282. );
  283. const {open, close, openSpy, closeSpy, timings} = makeTestingBoilerplate();
  284. profile.forEach(open, close);
  285. expect(openSpy).toHaveBeenCalledTimes(3);
  286. expect(closeSpy).toHaveBeenCalledTimes(3);
  287. expect(timings).toEqual([
  288. ['f0', 'open'],
  289. ['f1', 'open'],
  290. ['Garbage Collection', 'open'],
  291. ['Garbage Collection', 'close'],
  292. ['f1', 'close'],
  293. ['f0', 'close'],
  294. ]);
  295. });
  296. });