flamegraph.spec.tsx 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. import {Flamegraph} from 'sentry/utils/profiling/flamegraph';
  2. import {EventedProfile} from 'sentry/utils/profiling/profile/eventedProfile';
  3. import {createFrameIndex} from 'sentry/utils/profiling/profile/utils';
  4. import {Rect} from 'sentry/utils/profiling/speedscope';
  5. const makeEmptyEventedTrace = (type?: 'flamegraph' | 'flamechart'): EventedProfile => {
  6. return EventedProfile.FromProfile(
  7. {
  8. name: 'profile',
  9. startValue: 0,
  10. endValue: 0,
  11. unit: 'microseconds',
  12. type: 'evented',
  13. threadID: 0,
  14. events: [],
  15. },
  16. createFrameIndex('mobile', []),
  17. {type: type ?? 'flamechart'}
  18. );
  19. };
  20. describe('flamegraph', () => {
  21. it('throws if we are trying to construct call order flamegraph', () => {
  22. expect(() => {
  23. return new Flamegraph(makeEmptyEventedTrace('flamegraph'), 0, {
  24. inverted: false,
  25. sort: 'call order',
  26. });
  27. }).toThrow();
  28. });
  29. it('throws if we are trying to construct alphabetic order flamechart', () => {
  30. expect(() => {
  31. return new Flamegraph(makeEmptyEventedTrace('flamechart'), 0, {
  32. inverted: false,
  33. sort: 'alphabetical',
  34. });
  35. }).toThrow();
  36. });
  37. it('sets default timeline for empty flamegraph', () => {
  38. const flamegraph = new Flamegraph(makeEmptyEventedTrace(), 0, {
  39. inverted: false,
  40. sort: 'call order',
  41. });
  42. expect(flamegraph.configSpace.equals(new Rect(0, 0, 1_000_000, 0))).toBe(true);
  43. expect(flamegraph.inverted).toBe(false);
  44. expect(flamegraph.sort).toBe('call order');
  45. });
  46. it('initializes formatter', () => {
  47. const trace: Profiling.EventedProfile = {
  48. name: 'profile',
  49. startValue: 0,
  50. endValue: 1000,
  51. unit: 'milliseconds',
  52. threadID: 0,
  53. type: 'evented',
  54. events: [
  55. {type: 'O', at: 0, frame: 0},
  56. {type: 'O', at: 500, frame: 1},
  57. {type: 'C', at: 600, frame: 1},
  58. {type: 'C', at: 1000, frame: 0},
  59. ],
  60. };
  61. const flamegraph = new Flamegraph(
  62. EventedProfile.FromProfile(
  63. trace,
  64. createFrameIndex('mobile', [{name: 'f0'}, {name: 'f1'}]),
  65. {type: 'flamechart'}
  66. ),
  67. 10,
  68. {
  69. inverted: true,
  70. sort: 'left heavy',
  71. }
  72. );
  73. expect(flamegraph.formatter(1000)).toBe('1.00s');
  74. expect(flamegraph.formatter(500)).toBe('500.00ms');
  75. });
  76. it('stores profile properties', () => {
  77. const trace: Profiling.EventedProfile = {
  78. name: 'profile',
  79. startValue: 0,
  80. endValue: 1000,
  81. unit: 'milliseconds',
  82. threadID: 0,
  83. type: 'evented',
  84. events: [
  85. {type: 'O', at: 0, frame: 0},
  86. {type: 'O', at: 1, frame: 1},
  87. {type: 'C', at: 2, frame: 1},
  88. {type: 'C', at: 3, frame: 0},
  89. ],
  90. };
  91. const flamegraph = new Flamegraph(
  92. EventedProfile.FromProfile(
  93. trace,
  94. createFrameIndex('mobile', [{name: 'f0'}, {name: 'f1'}]),
  95. {type: 'flamechart'}
  96. ),
  97. 10,
  98. {
  99. inverted: true,
  100. sort: 'left heavy',
  101. }
  102. );
  103. expect(flamegraph.inverted).toBe(true);
  104. expect(flamegraph.sort).toBe('left heavy');
  105. expect(flamegraph.profileIndex).toBe(10);
  106. });
  107. it('creates a call order graph', () => {
  108. const trace: Profiling.EventedProfile = {
  109. name: 'profile',
  110. startValue: 0,
  111. endValue: 1000,
  112. unit: 'milliseconds',
  113. threadID: 0,
  114. type: 'evented',
  115. events: [
  116. {type: 'O', at: 0, frame: 0},
  117. {type: 'O', at: 1, frame: 1},
  118. {type: 'O', at: 2, frame: 2},
  119. {type: 'C', at: 3, frame: 2},
  120. {type: 'C', at: 4, frame: 1},
  121. {type: 'C', at: 5, frame: 0},
  122. ],
  123. };
  124. const flamegraph = new Flamegraph(
  125. EventedProfile.FromProfile(
  126. trace,
  127. createFrameIndex('mobile', [{name: 'f0'}, {name: 'f1'}, {name: 'f2'}]),
  128. {type: 'flamechart'}
  129. ),
  130. 10,
  131. {
  132. inverted: false,
  133. sort: 'call order',
  134. }
  135. );
  136. const order = ['f0', 'f1', 'f2'].reverse();
  137. for (let i = 0; i < order.length; i++) {
  138. expect(flamegraph.frames[i].frame.name).toBe(order[i]);
  139. expect(flamegraph.frames[i].depth).toBe(order.length - i - 1);
  140. }
  141. });
  142. it('omits 0 width frames', () => {
  143. const trace: Profiling.EventedProfile = {
  144. name: 'profile',
  145. startValue: 0,
  146. endValue: 1000,
  147. unit: 'milliseconds',
  148. threadID: 0,
  149. type: 'evented',
  150. events: [
  151. {type: 'O', at: 0, frame: 0},
  152. {type: 'O', at: 1, frame: 1},
  153. {type: 'C', at: 1, frame: 1},
  154. {type: 'C', at: 3, frame: 0},
  155. ],
  156. };
  157. const flamegraph = new Flamegraph(
  158. EventedProfile.FromProfile(
  159. trace,
  160. createFrameIndex('mobile', [{name: 'f0'}, {name: 'f1'}]),
  161. {type: 'flamechart'}
  162. ),
  163. 10,
  164. {
  165. inverted: false,
  166. sort: 'call order',
  167. }
  168. );
  169. expect(flamegraph.frames.length).toBe(1);
  170. expect(flamegraph.frames.every(f => f.frame.name !== 'f1')).toBe(true);
  171. });
  172. it('tracks max stack depth', () => {
  173. const trace: Profiling.EventedProfile = {
  174. name: 'profile',
  175. startValue: 0,
  176. endValue: 1000,
  177. unit: 'milliseconds',
  178. threadID: 0,
  179. type: 'evented',
  180. events: [
  181. {type: 'O', at: 0, frame: 0},
  182. {type: 'O', at: 1, frame: 1},
  183. {type: 'O', at: 2, frame: 1},
  184. {type: 'C', at: 3, frame: 1},
  185. {type: 'C', at: 4, frame: 1},
  186. {type: 'C', at: 5, frame: 0},
  187. ],
  188. };
  189. const flamegraph = new Flamegraph(
  190. EventedProfile.FromProfile(
  191. trace,
  192. createFrameIndex('mobile', [{name: 'f0'}, {name: 'f1'}]),
  193. {type: 'flamechart'}
  194. ),
  195. 10,
  196. {
  197. inverted: false,
  198. sort: 'call order',
  199. }
  200. );
  201. expect(flamegraph.depth).toBe(2);
  202. });
  203. it('throws on unbalanced stack', () => {
  204. const trace: Profiling.EventedProfile = {
  205. name: 'profile',
  206. startValue: 0,
  207. endValue: 1000,
  208. unit: 'milliseconds',
  209. threadID: 0,
  210. type: 'evented',
  211. events: [
  212. {type: 'O', at: 0, frame: 0},
  213. {type: 'O', at: 1, frame: 1},
  214. {type: 'C', at: 1, frame: 1},
  215. ],
  216. };
  217. expect(
  218. () =>
  219. new Flamegraph(
  220. EventedProfile.FromProfile(
  221. trace,
  222. createFrameIndex('mobile', [{name: 'f0'}, {name: 'f1'}]),
  223. {type: 'flamechart'}
  224. ),
  225. 10,
  226. {
  227. inverted: false,
  228. sort: 'call order',
  229. }
  230. )
  231. ).toThrow('Unbalanced append order stack');
  232. });
  233. it('creates left heavy graph', () => {
  234. const trace: Profiling.EventedProfile = {
  235. name: 'profile',
  236. startValue: 0,
  237. endValue: 1000,
  238. unit: 'milliseconds',
  239. threadID: 0,
  240. type: 'evented',
  241. events: [
  242. {type: 'O', at: 0, frame: 0},
  243. {type: 'C', at: 1, frame: 0},
  244. {type: 'O', at: 2, frame: 1},
  245. {type: 'C', at: 4, frame: 1},
  246. ],
  247. };
  248. const flamegraph = new Flamegraph(
  249. EventedProfile.FromProfile(
  250. trace,
  251. createFrameIndex('mobile', [{name: 'f0'}, {name: 'f1'}]),
  252. {type: 'flamechart'}
  253. ),
  254. 10,
  255. {
  256. inverted: false,
  257. sort: 'left heavy',
  258. }
  259. );
  260. expect(flamegraph.frames[1].frame.name).toBe('f0');
  261. expect(flamegraph.frames[1].frame.totalWeight).toBe(1);
  262. expect(flamegraph.frames[1].start).toBe(2);
  263. expect(flamegraph.frames[1].end).toBe(3);
  264. expect(flamegraph.frames[0].frame.name).toBe('f1');
  265. expect(flamegraph.frames[0].frame.totalWeight).toBe(2);
  266. expect(flamegraph.frames[0].start).toBe(0);
  267. expect(flamegraph.frames[0].end).toBe(2);
  268. });
  269. it('updates startTime and endTime of left heavy children graph', () => {
  270. const trace: Profiling.EventedProfile = {
  271. name: 'profile',
  272. startValue: 0,
  273. endValue: 1000,
  274. unit: 'milliseconds',
  275. threadID: 0,
  276. type: 'evented',
  277. events: [
  278. {type: 'O', at: 0, frame: 0},
  279. {type: 'O', at: 1, frame: 1},
  280. {type: 'C', at: 2, frame: 1},
  281. {type: 'O', at: 2, frame: 2},
  282. {type: 'C', at: 4, frame: 2},
  283. {type: 'C', at: 6, frame: 0},
  284. ],
  285. };
  286. const flamegraph = new Flamegraph(
  287. EventedProfile.FromProfile(
  288. trace,
  289. createFrameIndex('mobile', [{name: 'f0'}, {name: 'f1'}, {name: 'f2'}]),
  290. {type: 'flamechart'}
  291. ),
  292. 10,
  293. {
  294. inverted: false,
  295. sort: 'left heavy',
  296. }
  297. );
  298. expect(flamegraph.frames[2].frame.name).toBe('f0');
  299. });
  300. it('From', () => {
  301. const trace: Profiling.EventedProfile = {
  302. name: 'profile',
  303. startValue: 0,
  304. endValue: 1000,
  305. unit: 'milliseconds',
  306. threadID: 0,
  307. type: 'evented',
  308. events: [
  309. {type: 'O', at: 0, frame: 0},
  310. {type: 'O', at: 1, frame: 1},
  311. {type: 'C', at: 2, frame: 1},
  312. {type: 'O', at: 2, frame: 2},
  313. {type: 'C', at: 4, frame: 2},
  314. {type: 'C', at: 6, frame: 0},
  315. ],
  316. };
  317. const flamegraph = new Flamegraph(
  318. EventedProfile.FromProfile(
  319. trace,
  320. createFrameIndex('mobile', [{name: 'f0'}, {name: 'f1'}, {name: 'f2'}]),
  321. {type: 'flamechart'}
  322. ),
  323. 10,
  324. {
  325. inverted: false,
  326. sort: 'left heavy',
  327. }
  328. );
  329. expect(
  330. Flamegraph.From(flamegraph, {
  331. inverted: false,
  332. sort: 'call order',
  333. }).configSpace.equals(flamegraph.configSpace)
  334. ).toBe(true);
  335. });
  336. it('Empty', () => {
  337. expect(Flamegraph.Empty().configSpace.equals(new Rect(0, 0, 1_000, 0))).toBe(true);
  338. });
  339. });