flamegraph.spec.tsx 8.5 KB

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