flamegraphRenderer.spec.tsx 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. import {vec2} from 'gl-matrix';
  2. import {
  3. makeCanvasMock,
  4. makeContextMock,
  5. makeFlamegraph,
  6. } from 'sentry-test/profiling/utils';
  7. import {FlamegraphSearch} from 'sentry/utils/profiling/flamegraph/flamegraphStateProvider/flamegraphSearch';
  8. import {
  9. LightFlamegraphTheme,
  10. LightFlamegraphTheme as theme,
  11. } from 'sentry/utils/profiling/flamegraph/flamegraphTheme';
  12. import {FlamegraphCanvas} from 'sentry/utils/profiling/flamegraphCanvas';
  13. import {FlamegraphView} from 'sentry/utils/profiling/flamegraphView';
  14. import {Rect} from 'sentry/utils/profiling/gl/utils';
  15. import {FlamegraphRenderer} from 'sentry/utils/profiling/renderers/flamegraphRenderer';
  16. const originalDpr = window.devicePixelRatio;
  17. describe('flamegraphRenderer', () => {
  18. beforeEach(() => {
  19. // We simulate regular screens unless differently specified
  20. window.devicePixelRatio = 1;
  21. });
  22. afterEach(() => {
  23. window.devicePixelRatio = originalDpr;
  24. });
  25. describe('colors', () => {
  26. it('generates new colors if none are set on the flamegraph', () => {
  27. const canvas = makeCanvasMock({
  28. getContext: jest.fn().mockReturnValue(makeContextMock()),
  29. });
  30. const flamegraph = makeFlamegraph();
  31. const renderer = new FlamegraphRenderer(
  32. canvas as HTMLCanvasElement,
  33. flamegraph,
  34. {
  35. ...theme,
  36. COLORS: {
  37. ...theme.COLORS,
  38. // @ts-ignore overridee the colors implementation
  39. STACK_TO_COLOR: () => {
  40. const colorMap = new Map<string, number[]>([['f0', [1, 0, 0, 1]]]);
  41. return {colorBuffer: [1, 0, 0, 1], colorMap};
  42. },
  43. },
  44. },
  45. vec2.fromValues(0, 0)
  46. );
  47. expect(renderer.colors).toEqual([1, 0, 0, 1]);
  48. });
  49. });
  50. it('inits vertices', () => {
  51. const canvas = makeCanvasMock({
  52. getContext: jest.fn().mockReturnValue(makeContextMock()),
  53. });
  54. const flamegraph = makeFlamegraph();
  55. const renderer = new FlamegraphRenderer(
  56. canvas as HTMLCanvasElement,
  57. flamegraph,
  58. theme
  59. );
  60. // Helper rect for the only frame in our flamegraph
  61. const rect = new Rect(0, 0, 10, 1);
  62. // To draw a rect, we need to draw 2 triangles, each with 3 vertices
  63. // First triangle: top left -> top right -> bottom left
  64. // Second triangle: bottom left -> top right -> bottom right
  65. expect(renderer.positions.slice(0, 2)).toEqual([rect.left, rect.top]);
  66. expect(renderer.positions.slice(2, 4)).toEqual([rect.right, rect.top]);
  67. expect(renderer.positions.slice(4, 6)).toEqual([rect.left, rect.bottom]);
  68. expect(renderer.positions.slice(6, 8)).toEqual([rect.left, rect.bottom]);
  69. expect(renderer.positions.slice(8, 10)).toEqual([rect.right, rect.top]);
  70. expect(renderer.positions.slice(10, 12)).toEqual([rect.right, rect.bottom]);
  71. });
  72. it('inits shaders', () => {
  73. const VERTEX = `void main() { gl_Position = vec4(pos, 0.0, 1.0); }`;
  74. const FRAGMENT = `void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }`;
  75. const context = makeContextMock({
  76. createShader: jest.fn().mockReturnValueOnce(VERTEX).mockReturnValueOnce(FRAGMENT),
  77. });
  78. const canvas = makeCanvasMock({
  79. getContext: jest.fn().mockReturnValue(context),
  80. });
  81. const flamegraph = makeFlamegraph();
  82. // @ts-ignore shaders are init from the constructor
  83. const _renderer = new FlamegraphRenderer(
  84. canvas as HTMLCanvasElement,
  85. flamegraph,
  86. theme
  87. );
  88. expect(context.createShader).toHaveBeenCalledTimes(2);
  89. expect(context.getShaderParameter).toHaveBeenCalledTimes(2);
  90. // @ts-ignore this is a mock
  91. expect(context.getShaderParameter.mock.calls[0][0]).toEqual(VERTEX);
  92. // @ts-ignore this is a mock
  93. expect(context.getShaderParameter.mock.calls[1][0]).toEqual(FRAGMENT);
  94. });
  95. it('getColorForFrame', () => {
  96. const canvas = makeCanvasMock({
  97. getContext: jest.fn().mockReturnValue(makeContextMock()),
  98. });
  99. const flamegraph = makeFlamegraph();
  100. const renderer = new FlamegraphRenderer(
  101. canvas as HTMLCanvasElement,
  102. flamegraph,
  103. theme
  104. );
  105. expect(renderer.getColorForFrame(flamegraph.frames[0])).toEqual([
  106. 0.9750000000000001, 0.7250000000000001, 0.7250000000000001,
  107. ]);
  108. expect(
  109. renderer.getColorForFrame({
  110. key: 20,
  111. frame: flamegraph.frames[0].frame,
  112. node: flamegraph.frames[0].node,
  113. parent: null,
  114. children: [],
  115. depth: 0,
  116. start: 0,
  117. end: 0,
  118. })
  119. ).toEqual(LightFlamegraphTheme.COLORS.FRAME_FALLBACK_COLOR);
  120. });
  121. it('getHoveredNode', () => {
  122. const flamegraph = makeFlamegraph(
  123. {
  124. events: [
  125. {type: 'O', at: 0, frame: 0},
  126. {type: 'O', at: 1, frame: 1},
  127. {type: 'C', at: 2, frame: 1},
  128. {type: 'C', at: 3, frame: 0},
  129. {type: 'O', at: 4, frame: 2},
  130. {type: 'O', at: 5, frame: 3},
  131. {type: 'C', at: 7, frame: 3},
  132. {type: 'C', at: 8, frame: 2},
  133. {type: 'O', at: 9, frame: 4},
  134. {type: 'O', at: 10, frame: 5},
  135. {type: 'C', at: 11, frame: 5},
  136. {type: 'C', at: 12, frame: 4},
  137. ],
  138. },
  139. [{name: 'f0'}, {name: 'f1'}, {name: 'f2'}, {name: 'f3'}, {name: 'f4'}, {name: 'f5'}]
  140. );
  141. const renderer = new FlamegraphRenderer(
  142. makeCanvasMock() as HTMLCanvasElement,
  143. flamegraph,
  144. theme
  145. );
  146. expect(renderer.getHoveredNode(vec2.fromValues(-1, 0))).toBeNull();
  147. expect(renderer.getHoveredNode(vec2.fromValues(-1, 0))).toBeNull();
  148. expect(renderer.getHoveredNode(vec2.fromValues(0, 0))?.frame?.name).toBe('f0');
  149. expect(renderer.getHoveredNode(vec2.fromValues(5, 2))?.frame?.name).toBe('f3');
  150. });
  151. describe('draw', () => {
  152. it('sets uniform1f for search results', () => {
  153. const context = makeContextMock();
  154. const canvas = makeCanvasMock({
  155. getContext: jest.fn().mockReturnValue(context),
  156. }) as HTMLCanvasElement;
  157. const flamegraph = makeFlamegraph(
  158. {
  159. startValue: 0,
  160. endValue: 100,
  161. events: [
  162. {
  163. type: 'O',
  164. frame: 0,
  165. at: 0,
  166. },
  167. {
  168. type: 'C',
  169. frame: 0,
  170. at: 1,
  171. },
  172. {
  173. type: 'O',
  174. frame: 1,
  175. at: 1,
  176. },
  177. {
  178. type: 'C',
  179. frame: 1,
  180. at: 2,
  181. },
  182. ],
  183. },
  184. [{name: 'f0'}, {name: 'f1'}]
  185. );
  186. const results: FlamegraphSearch['results'] = new Map();
  187. // @ts-ignore we just need a partial frame
  188. results.set('f00', {});
  189. const flamegraphCanvas = new FlamegraphCanvas(canvas, vec2.fromValues(0, 0));
  190. const flamegraphView = new FlamegraphView({
  191. canvas: flamegraphCanvas,
  192. flamegraph,
  193. theme,
  194. });
  195. const renderer = new FlamegraphRenderer(canvas, flamegraph, theme);
  196. renderer.draw(
  197. flamegraphView.fromConfigView(flamegraphCanvas.physicalSpace),
  198. results
  199. );
  200. expect(context.uniform1i).toHaveBeenCalledTimes(3);
  201. expect(context.drawArrays).toHaveBeenCalledTimes(2);
  202. });
  203. it('draws all frames', () => {
  204. const context = makeContextMock();
  205. const canvas = makeCanvasMock({
  206. getContext: jest.fn().mockReturnValue(context),
  207. }) as HTMLCanvasElement;
  208. const flamegraph = makeFlamegraph(
  209. {
  210. startValue: 0,
  211. endValue: 100,
  212. events: [
  213. {
  214. type: 'O',
  215. frame: 0,
  216. at: 0,
  217. },
  218. {
  219. type: 'C',
  220. frame: 0,
  221. at: 1,
  222. },
  223. {
  224. type: 'O',
  225. frame: 1,
  226. at: 1,
  227. },
  228. {
  229. type: 'C',
  230. frame: 1,
  231. at: 2,
  232. },
  233. ],
  234. },
  235. [{name: 'f0'}, {name: 'f1'}]
  236. );
  237. const flamegraphCanvas = new FlamegraphCanvas(canvas, vec2.fromValues(0, 0));
  238. const flamegraphView = new FlamegraphView({
  239. canvas: flamegraphCanvas,
  240. flamegraph,
  241. theme,
  242. });
  243. const renderer = new FlamegraphRenderer(canvas, flamegraph, theme);
  244. renderer.draw(
  245. flamegraphView.fromConfigView(flamegraphCanvas.physicalSpace),
  246. new Map()
  247. );
  248. expect(context.drawArrays).toHaveBeenCalledTimes(2);
  249. });
  250. });
  251. });