jsSelfProfile.spec.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  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. stackId: 0,
  13. },
  14. {
  15. timestamp: 1000,
  16. stackId: 0,
  17. },
  18. ],
  19. stacks: [
  20. {
  21. frameId: 0,
  22. },
  23. ],
  24. };
  25. const profile = JSSelfProfile.FromProfile(
  26. trace,
  27. createFrameIndex('javascript', trace.frames, trace),
  28. {type: 'flamechart'}
  29. );
  30. expect(profile.duration).toBe(1000);
  31. expect(profile.startedAt).toBe(0);
  32. expect(profile.endedAt).toBe(1000);
  33. expect(profile.callTree.children[0].frame.name).toBe('ReactDOM.render');
  34. expect(profile.callTree.children[0].frame.resource).toBe('app.js');
  35. });
  36. it('tracks discarded samples', () => {
  37. const trace: JSSelfProfiling.Trace = {
  38. resources: ['app.js', 'vendor.js'],
  39. frames: [{name: 'ReactDOM.render', line: 1, column: 1, resourceId: 0}],
  40. samples: [
  41. {
  42. timestamp: 0,
  43. stackId: 0,
  44. },
  45. ],
  46. stacks: [
  47. {
  48. frameId: 0,
  49. },
  50. ],
  51. };
  52. const profile = JSSelfProfile.FromProfile(
  53. trace,
  54. createFrameIndex('javascript', [{name: 'f0'}]),
  55. {type: 'flamechart'}
  56. );
  57. expect(profile.stats.discardedSamplesCount).toBe(1);
  58. });
  59. it('tracks negative samples', () => {
  60. const trace: JSSelfProfiling.Trace = {
  61. resources: ['app.js', 'vendor.js'],
  62. frames: [{name: 'ReactDOM.render', line: 1, column: 1, resourceId: 0}],
  63. samples: [
  64. {
  65. timestamp: 0,
  66. stackId: 0,
  67. },
  68. {
  69. timestamp: -1,
  70. stackId: 0,
  71. },
  72. ],
  73. stacks: [
  74. {
  75. frameId: 0,
  76. },
  77. ],
  78. };
  79. const profile = JSSelfProfile.FromProfile(
  80. trace,
  81. createFrameIndex('javascript', [{name: 'f0'}]),
  82. {type: 'flamechart'}
  83. );
  84. expect(profile.stats.negativeSamplesCount).toBe(1);
  85. });
  86. it('tracks raw weights', () => {
  87. const trace: JSSelfProfiling.Trace = {
  88. resources: ['app.js', 'vendor.js'],
  89. frames: [{name: 'ReactDOM.render', line: 1, column: 1, resourceId: 0}],
  90. samples: [
  91. {
  92. timestamp: 5,
  93. stackId: 0,
  94. },
  95. {
  96. timestamp: 10,
  97. stackId: 0,
  98. },
  99. {
  100. timestamp: 15,
  101. stackId: 0,
  102. },
  103. ],
  104. stacks: [
  105. {
  106. frameId: 0,
  107. },
  108. ],
  109. };
  110. const profile = JSSelfProfile.FromProfile(
  111. trace,
  112. createFrameIndex('javascript', [{name: 'f0'}]),
  113. {type: 'flamechart'}
  114. );
  115. // For JsSelfProfile, first sample is appended with 0 weight because it
  116. // contains the stack sample of when startProfile was called
  117. expect(profile.rawWeights.length).toBe(2);
  118. });
  119. it('handles the first stack sample differently', () => {
  120. const trace: JSSelfProfiling.Trace = {
  121. resources: ['app.js'],
  122. frames: [
  123. {name: 'main', line: 1, column: 1, resourceId: 0},
  124. {name: 'new Profiler', line: 1, column: 1, resourceId: 0},
  125. {name: 'afterProfiler.init', line: 1, column: 1, resourceId: 0},
  126. ],
  127. samples: [
  128. {
  129. stackId: 1,
  130. timestamp: 500,
  131. },
  132. {
  133. stackId: 2,
  134. timestamp: 1500,
  135. },
  136. ],
  137. stacks: [
  138. {frameId: 0, parentId: undefined},
  139. {frameId: 1, parentId: 0},
  140. {frameId: 2, parentId: 0},
  141. ],
  142. };
  143. const {open, close, openSpy, closeSpy, timings} = makeTestingBoilerplate();
  144. const profile = JSSelfProfile.FromProfile(
  145. trace,
  146. createFrameIndex('javascript', trace.frames, trace),
  147. {type: 'flamechart'}
  148. );
  149. profile.forEach(open, close);
  150. expect(timings).toEqual([
  151. ['main', 'open'],
  152. ['new Profiler', 'open'],
  153. ['new Profiler', 'close'],
  154. ['afterProfiler.init', 'open'],
  155. ['afterProfiler.init', 'close'],
  156. ['main', 'close'],
  157. ]);
  158. expect(openSpy).toHaveBeenCalledTimes(3);
  159. expect(closeSpy).toHaveBeenCalledTimes(3);
  160. const root = firstCallee(profile.callTree);
  161. if (!root) {
  162. throw new Error('root is null');
  163. }
  164. expect(root.totalWeight).toEqual(1000);
  165. expect(root.selfWeight).toEqual(0);
  166. expect(nthCallee(root, 0).selfWeight).toEqual(0);
  167. expect(nthCallee(root, 0).totalWeight).toEqual(0);
  168. expect(nthCallee(root, 1).selfWeight).toEqual(1000);
  169. expect(nthCallee(root, 1).totalWeight).toEqual(1000);
  170. });
  171. it('rebuilds the stack', () => {
  172. const trace: JSSelfProfiling.Trace = {
  173. resources: ['app.js'],
  174. frames: [
  175. {name: 'f0', line: 1, column: 1, resourceId: 0},
  176. {name: 'f1', line: 1, column: 1, resourceId: 0},
  177. ],
  178. samples: [
  179. {
  180. stackId: 0,
  181. timestamp: 0,
  182. },
  183. {
  184. timestamp: 1000,
  185. stackId: 0,
  186. },
  187. ],
  188. stacks: [{frameId: 1, parentId: 1}, {frameId: 0}],
  189. };
  190. const {open, close, openSpy, closeSpy, timings} = makeTestingBoilerplate();
  191. const profile = JSSelfProfile.FromProfile(
  192. trace,
  193. createFrameIndex('javascript', trace.frames, trace),
  194. {type: 'flamechart'}
  195. );
  196. profile.forEach(open, close);
  197. expect(timings).toEqual([
  198. ['f0', 'open'],
  199. ['f1', 'open'],
  200. ['f1', 'close'],
  201. ['f0', 'close'],
  202. ]);
  203. expect(openSpy).toHaveBeenCalledTimes(2);
  204. expect(closeSpy).toHaveBeenCalledTimes(2);
  205. const root = firstCallee(profile.callTree);
  206. expect(root.totalWeight).toEqual(1000);
  207. expect(firstCallee(root).totalWeight).toEqual(1000);
  208. expect(root.selfWeight).toEqual(0);
  209. expect(firstCallee(root).selfWeight).toEqual(1000);
  210. });
  211. it('marks direct recursion', () => {
  212. const trace: JSSelfProfiling.Trace = {
  213. resources: ['app.js'],
  214. frames: [{name: 'f0', line: 1, column: 1, resourceId: 0}],
  215. samples: [
  216. {
  217. stackId: 0,
  218. timestamp: 0,
  219. },
  220. {
  221. stackId: 0,
  222. timestamp: 0,
  223. },
  224. ],
  225. stacks: [{frameId: 0, parentId: 1}, {frameId: 0}],
  226. };
  227. const profile = JSSelfProfile.FromProfile(
  228. trace,
  229. createFrameIndex('javascript', trace.frames, trace),
  230. {type: 'flamechart'}
  231. );
  232. expect(!!firstCallee(firstCallee(profile.callTree)).recursive).toBe(true);
  233. });
  234. it('marks indirect recursion', () => {
  235. const trace: JSSelfProfiling.Trace = {
  236. resources: ['app.js'],
  237. frames: [
  238. {name: 'f0', line: 1, column: 1, resourceId: 0},
  239. {name: 'f1', line: 1, column: 1, resourceId: 0},
  240. ],
  241. samples: [
  242. {
  243. stackId: 0,
  244. timestamp: 0,
  245. },
  246. {
  247. stackId: 2,
  248. timestamp: 100,
  249. },
  250. ],
  251. stacks: [
  252. {frameId: 0, parentId: undefined},
  253. {frameId: 1, parentId: 0},
  254. {frameId: 0, parentId: 1},
  255. ],
  256. };
  257. const profile = JSSelfProfile.FromProfile(
  258. trace,
  259. createFrameIndex('javascript', trace.frames, trace),
  260. {type: 'flamechart'}
  261. );
  262. expect(!!firstCallee(firstCallee(firstCallee(profile.callTree))).recursive).toBe(
  263. true
  264. );
  265. });
  266. it('tracks minFrameDuration', () => {
  267. const trace: JSSelfProfiling.Trace = {
  268. resources: ['app.js'],
  269. frames: [
  270. {name: 'f0', line: 1, column: 1, resourceId: 0},
  271. {name: 'f1', line: 1, column: 1, resourceId: 0},
  272. {name: 'f2', line: 1, column: 1, resourceId: 0},
  273. ],
  274. samples: [
  275. {
  276. stackId: 0,
  277. timestamp: 0,
  278. },
  279. {
  280. stackId: 2,
  281. timestamp: 10,
  282. },
  283. {
  284. stackId: 3,
  285. timestamp: 100,
  286. },
  287. ],
  288. stacks: [
  289. {frameId: 0, parentId: undefined},
  290. {frameId: 1, parentId: 0},
  291. {frameId: 0, parentId: 1},
  292. {frameId: 2},
  293. ],
  294. };
  295. const profile = JSSelfProfile.FromProfile(
  296. trace,
  297. createFrameIndex('javascript', trace.frames, trace),
  298. {type: 'flamechart'}
  299. );
  300. expect(profile.minFrameDuration).toBe(10);
  301. });
  302. it('appends gc to previous stack', () => {
  303. const trace: JSSelfProfiling.Trace = {
  304. resources: ['app.js'],
  305. frames: [
  306. {name: 'f1', line: 1, column: 1, resourceId: 0},
  307. {name: 'f0', line: 1, column: 1, resourceId: 0},
  308. ],
  309. samples: [
  310. {
  311. stackId: 0,
  312. timestamp: 0,
  313. },
  314. {
  315. timestamp: 10,
  316. marker: 'gc',
  317. stackId: 0,
  318. },
  319. ],
  320. stacks: [
  321. {frameId: 0, parentId: 1},
  322. {frameId: 1, parentId: undefined},
  323. ],
  324. };
  325. const profile = JSSelfProfile.FromProfile(
  326. trace,
  327. createFrameIndex('javascript', trace.frames, trace),
  328. {type: 'flamechart'}
  329. );
  330. const {open, close, openSpy, closeSpy, timings} = makeTestingBoilerplate();
  331. profile.forEach(open, close);
  332. expect(openSpy).toHaveBeenCalledTimes(3);
  333. expect(closeSpy).toHaveBeenCalledTimes(3);
  334. expect(timings).toEqual([
  335. ['f0', 'open'],
  336. ['f1', 'open'],
  337. ['Garbage Collection', 'open'],
  338. ['Garbage Collection', 'close'],
  339. ['f1', 'close'],
  340. ['f0', 'close'],
  341. ]);
  342. });
  343. it('flamegraph tracks node occurences', () => {
  344. const trace: JSSelfProfiling.Trace = {
  345. resources: ['app.js'],
  346. frames: [
  347. {name: 'f1', line: 1, column: 1, resourceId: 0},
  348. {name: 'f0', line: 1, column: 1, resourceId: 0},
  349. ],
  350. samples: [
  351. {
  352. stackId: 0,
  353. timestamp: 0,
  354. },
  355. {
  356. timestamp: 10,
  357. stackId: 1,
  358. },
  359. {
  360. timestamp: 20,
  361. stackId: 0,
  362. },
  363. ],
  364. stacks: [
  365. {frameId: 0, parentId: undefined},
  366. {frameId: 1, parentId: 0},
  367. ],
  368. };
  369. const profile = JSSelfProfile.FromProfile(
  370. trace,
  371. createFrameIndex('javascript', trace.frames, trace),
  372. {type: 'flamechart'}
  373. );
  374. expect(profile.callTree.children[0].count).toBe(3);
  375. expect(profile.callTree.children[0].children[0].count).toBe(1);
  376. });
  377. });