@kylincloud/flamegraph 0.35.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/CHANGELOG.md +1211 -0
  2. package/LICENSE +202 -0
  3. package/README.md +251 -0
  4. package/dist/FlameGraph/FlameGraphComponent/CheckIcon.d.ts +2 -0
  5. package/dist/FlameGraph/FlameGraphComponent/CheckIcon.d.ts.map +1 -0
  6. package/dist/FlameGraph/FlameGraphComponent/ContextMenu.d.ts +17 -0
  7. package/dist/FlameGraph/FlameGraphComponent/ContextMenu.d.ts.map +1 -0
  8. package/dist/FlameGraph/FlameGraphComponent/ContextMenuHighlight.d.ts +14 -0
  9. package/dist/FlameGraph/FlameGraphComponent/ContextMenuHighlight.d.ts.map +1 -0
  10. package/dist/FlameGraph/FlameGraphComponent/DiffLegend.d.ts +9 -0
  11. package/dist/FlameGraph/FlameGraphComponent/DiffLegend.d.ts.map +1 -0
  12. package/dist/FlameGraph/FlameGraphComponent/DiffLegendPaletteDropdown.d.ts +8 -0
  13. package/dist/FlameGraph/FlameGraphComponent/DiffLegendPaletteDropdown.d.ts.map +1 -0
  14. package/dist/FlameGraph/FlameGraphComponent/Flamegraph.d.ts +96 -0
  15. package/dist/FlameGraph/FlameGraphComponent/Flamegraph.d.ts.map +1 -0
  16. package/dist/FlameGraph/FlameGraphComponent/Flamegraph_render.d.ts +27 -0
  17. package/dist/FlameGraph/FlameGraphComponent/Flamegraph_render.d.ts.map +1 -0
  18. package/dist/FlameGraph/FlameGraphComponent/GraphVizPane.d.ts +7 -0
  19. package/dist/FlameGraph/FlameGraphComponent/GraphVizPane.d.ts.map +1 -0
  20. package/dist/FlameGraph/FlameGraphComponent/Header.d.ts +12 -0
  21. package/dist/FlameGraph/FlameGraphComponent/Header.d.ts.map +1 -0
  22. package/dist/FlameGraph/FlameGraphComponent/Highlight.d.ts +18 -0
  23. package/dist/FlameGraph/FlameGraphComponent/Highlight.d.ts.map +1 -0
  24. package/dist/FlameGraph/FlameGraphComponent/LogoLink.d.ts +2 -0
  25. package/dist/FlameGraph/FlameGraphComponent/LogoLink.d.ts.map +1 -0
  26. package/dist/FlameGraph/FlameGraphComponent/color.d.ts +20 -0
  27. package/dist/FlameGraph/FlameGraphComponent/color.d.ts.map +1 -0
  28. package/dist/FlameGraph/FlameGraphComponent/colorPalette.d.ts +11 -0
  29. package/dist/FlameGraph/FlameGraphComponent/colorPalette.d.ts.map +1 -0
  30. package/dist/FlameGraph/FlameGraphComponent/constants.d.ts +6 -0
  31. package/dist/FlameGraph/FlameGraphComponent/constants.d.ts.map +1 -0
  32. package/dist/FlameGraph/FlameGraphComponent/index.d.ts +37 -0
  33. package/dist/FlameGraph/FlameGraphComponent/index.d.ts.map +1 -0
  34. package/dist/FlameGraph/FlameGraphComponent/murmur3.d.ts +2 -0
  35. package/dist/FlameGraph/FlameGraphComponent/murmur3.d.ts.map +1 -0
  36. package/dist/FlameGraph/FlameGraphComponent/testData.d.ts +53 -0
  37. package/dist/FlameGraph/FlameGraphComponent/testData.d.ts.map +1 -0
  38. package/dist/FlameGraph/FlameGraphComponent/utils.d.ts +6 -0
  39. package/dist/FlameGraph/FlameGraphComponent/utils.d.ts.map +1 -0
  40. package/dist/FlameGraph/FlameGraphComponent/viewTypes.d.ts +2 -0
  41. package/dist/FlameGraph/FlameGraphComponent/viewTypes.d.ts.map +1 -0
  42. package/dist/FlameGraph/FlameGraphRenderer.d.ts +86 -0
  43. package/dist/FlameGraph/FlameGraphRenderer.d.ts.map +1 -0
  44. package/dist/FlameGraph/decode.d.ts +27 -0
  45. package/dist/FlameGraph/decode.d.ts.map +1 -0
  46. package/dist/FlameGraph/normalize.d.ts +6 -0
  47. package/dist/FlameGraph/normalize.d.ts.map +1 -0
  48. package/dist/FlameGraph/uniqueness.d.ts +3 -0
  49. package/dist/FlameGraph/uniqueness.d.ts.map +1 -0
  50. package/dist/FlamegraphRenderer.d.ts +19 -0
  51. package/dist/FlamegraphRenderer.d.ts.map +1 -0
  52. package/dist/Icons.d.ts +9 -0
  53. package/dist/Icons.d.ts.map +1 -0
  54. package/dist/ProfilerTable.d.ts +21 -0
  55. package/dist/ProfilerTable.d.ts.map +1 -0
  56. package/dist/SharedQueryInput.d.ts +10 -0
  57. package/dist/SharedQueryInput.d.ts.map +1 -0
  58. package/dist/Toolbar.d.ts +31 -0
  59. package/dist/Toolbar.d.ts.map +1 -0
  60. package/dist/Tooltip/FlamegraphTooltip.d.ts +59 -0
  61. package/dist/Tooltip/FlamegraphTooltip.d.ts.map +1 -0
  62. package/dist/Tooltip/LeftClickIcon.d.ts +2 -0
  63. package/dist/Tooltip/LeftClickIcon.d.ts.map +1 -0
  64. package/dist/Tooltip/RightClickIcon.d.ts +2 -0
  65. package/dist/Tooltip/RightClickIcon.d.ts.map +1 -0
  66. package/dist/Tooltip/TableTooltip.d.ts +12 -0
  67. package/dist/Tooltip/TableTooltip.d.ts.map +1 -0
  68. package/dist/Tooltip/Tooltip.d.ts +29 -0
  69. package/dist/Tooltip/Tooltip.d.ts.map +1 -0
  70. package/dist/convert/convertJaegerTraceToProfile.d.ts +3 -0
  71. package/dist/convert/convertJaegerTraceToProfile.d.ts.map +1 -0
  72. package/dist/convert/diffTwoProfiles.d.ts +3 -0
  73. package/dist/convert/diffTwoProfiles.d.ts.map +1 -0
  74. package/dist/convert/flamebearersToTree.d.ts +11 -0
  75. package/dist/convert/flamebearersToTree.d.ts.map +1 -0
  76. package/dist/convert/sandwichViewProfiles.d.ts +14 -0
  77. package/dist/convert/sandwichViewProfiles.d.ts.map +1 -0
  78. package/dist/convert/subtract.d.ts +3 -0
  79. package/dist/convert/subtract.d.ts.map +1 -0
  80. package/dist/convert/testData.d.ts +50 -0
  81. package/dist/convert/testData.d.ts.map +1 -0
  82. package/dist/convert/toGraphviz.d.ts +3 -0
  83. package/dist/convert/toGraphviz.d.ts.map +1 -0
  84. package/dist/fitMode/fitMode.d.ts +42 -0
  85. package/dist/fitMode/fitMode.d.ts.map +1 -0
  86. package/dist/format/format.d.ts +42 -0
  87. package/dist/format/format.d.ts.map +1 -0
  88. package/dist/i18n.d.ts +55 -0
  89. package/dist/i18n.d.ts.map +1 -0
  90. package/dist/index.cjs.css +792 -0
  91. package/dist/index.cjs.js +5087 -0
  92. package/dist/index.d.ts +4 -0
  93. package/dist/index.d.ts.map +1 -0
  94. package/dist/index.esm.css +792 -0
  95. package/dist/index.esm.js +5079 -0
  96. package/dist/index.node.d.ts +9 -0
  97. package/dist/index.node.d.ts.map +1 -0
  98. package/dist/logo-v3-small-T5VXIMRR.svg +32 -0
  99. package/dist/models/decode.d.ts +3 -0
  100. package/dist/models/decode.d.ts.map +1 -0
  101. package/dist/models/flamebearer.d.ts +63 -0
  102. package/dist/models/flamebearer.d.ts.map +1 -0
  103. package/dist/models/groups.d.ts +37 -0
  104. package/dist/models/groups.d.ts.map +1 -0
  105. package/dist/models/index.d.ts +8 -0
  106. package/dist/models/index.d.ts.map +1 -0
  107. package/dist/models/profile.d.ts +152 -0
  108. package/dist/models/profile.d.ts.map +1 -0
  109. package/dist/models/spyName.d.ts +8 -0
  110. package/dist/models/spyName.d.ts.map +1 -0
  111. package/dist/models/trace.d.ts +357 -0
  112. package/dist/models/trace.d.ts.map +1 -0
  113. package/dist/models/units.d.ts +6 -0
  114. package/dist/models/units.d.ts.map +1 -0
  115. package/dist/search.d.ts +2 -0
  116. package/dist/search.d.ts.map +1 -0
  117. package/dist/shims/Box.d.ts +38 -0
  118. package/dist/shims/Box.d.ts.map +1 -0
  119. package/dist/shims/Button.d.ts +26 -0
  120. package/dist/shims/Button.d.ts.map +1 -0
  121. package/dist/shims/Dropdown.d.ts +30 -0
  122. package/dist/shims/Dropdown.d.ts.map +1 -0
  123. package/dist/shims/Input.d.ts +19 -0
  124. package/dist/shims/Input.d.ts.map +1 -0
  125. package/dist/shims/LoadingSpinner.d.ts +7 -0
  126. package/dist/shims/LoadingSpinner.d.ts.map +1 -0
  127. package/dist/shims/Menu.d.ts +4 -0
  128. package/dist/shims/Menu.d.ts.map +1 -0
  129. package/dist/shims/NoData.d.ts +2 -0
  130. package/dist/shims/NoData.d.ts.map +1 -0
  131. package/dist/shims/Table.d.ts +52 -0
  132. package/dist/shims/Table.d.ts.map +1 -0
  133. package/dist/shims/Tooltip.d.ts +9 -0
  134. package/dist/shims/Tooltip.d.ts.map +1 -0
  135. package/package.json +84 -0
  136. package/src/FlameGraph/FlameGraphComponent/CheckIcon.tsx +27 -0
  137. package/src/FlameGraph/FlameGraphComponent/ContextMenu.module.scss +10 -0
  138. package/src/FlameGraph/FlameGraphComponent/ContextMenu.spec.tsx +84 -0
  139. package/src/FlameGraph/FlameGraphComponent/ContextMenu.tsx +86 -0
  140. package/src/FlameGraph/FlameGraphComponent/ContextMenuHighlight.module.css +8 -0
  141. package/src/FlameGraph/FlameGraphComponent/ContextMenuHighlight.tsx +47 -0
  142. package/src/FlameGraph/FlameGraphComponent/DiffLegend.module.css +21 -0
  143. package/src/FlameGraph/FlameGraphComponent/DiffLegend.tsx +52 -0
  144. package/src/FlameGraph/FlameGraphComponent/DiffLegendPaletteDropdown.module.css +40 -0
  145. package/src/FlameGraph/FlameGraphComponent/DiffLegendPaletteDropdown.tsx +129 -0
  146. package/src/FlameGraph/FlameGraphComponent/Flamegraph.spec.ts +552 -0
  147. package/src/FlameGraph/FlameGraphComponent/Flamegraph.ts +446 -0
  148. package/src/FlameGraph/FlameGraphComponent/Flamegraph_render.spec.tsx +233 -0
  149. package/src/FlameGraph/FlameGraphComponent/Flamegraph_render.ts +478 -0
  150. package/src/FlameGraph/FlameGraphComponent/GraphVizPane.tsx +56 -0
  151. package/src/FlameGraph/FlameGraphComponent/GraphVizPanel.module.scss +55 -0
  152. package/src/FlameGraph/FlameGraphComponent/Header.module.css +27 -0
  153. package/src/FlameGraph/FlameGraphComponent/Header.tsx +71 -0
  154. package/src/FlameGraph/FlameGraphComponent/Highlight.module.css +7 -0
  155. package/src/FlameGraph/FlameGraphComponent/Highlight.spec.tsx +53 -0
  156. package/src/FlameGraph/FlameGraphComponent/Highlight.tsx +94 -0
  157. package/src/FlameGraph/FlameGraphComponent/LogoLink.module.scss +10 -0
  158. package/src/FlameGraph/FlameGraphComponent/LogoLink.tsx +101 -0
  159. package/src/FlameGraph/FlameGraphComponent/__image_snapshots__/canvas-renderer-spec-tsx-canvas-renderer-group-snapshot-collapses-small-blocks-into-one-1-snap.png +0 -0
  160. package/src/FlameGraph/FlameGraphComponent/__image_snapshots__/canvas-renderer-spec-tsx-canvas-renderer-group-snapshot-works-with-diff-mode-1-snap.png +0 -0
  161. package/src/FlameGraph/FlameGraphComponent/__image_snapshots__/canvas-renderer-spec-tsx-canvas-renderer-group-snapshot-works-with-highlighted-flamegraph-1-snap.png +0 -0
  162. package/src/FlameGraph/FlameGraphComponent/__image_snapshots__/canvas-renderer-spec-tsx-canvas-renderer-group-snapshot-works-with-normal-flamegraph-1-snap.png +0 -0
  163. package/src/FlameGraph/FlameGraphComponent/__image_snapshots__/canvas-renderer-spec-tsx-canvas-renderer-group-snapshot-works-with-selected-node-1-snap.png +0 -0
  164. package/src/FlameGraph/FlameGraphComponent/__image_snapshots__/flamegraph-render-spec-tsx-render-group-snapshot-focused-also-zooms-1-snap.png +0 -0
  165. package/src/FlameGraph/FlameGraphComponent/__image_snapshots__/flamegraph-render-spec-tsx-render-group-snapshot-focused-renders-a-focused-node-in-the-beginning-1-snap.png +0 -0
  166. package/src/FlameGraph/FlameGraphComponent/__image_snapshots__/flamegraph-render-spec-tsx-render-group-snapshot-focused-renders-a-focused-node-when-node-is-not-in-the-beginning-1-snap.png +0 -0
  167. package/src/FlameGraph/FlameGraphComponent/__image_snapshots__/flamegraph-render-spec-tsx-render-group-snapshot-focused-renders-a-focused-node-zoom-top-level-1-snap.png +0 -0
  168. package/src/FlameGraph/FlameGraphComponent/__image_snapshots__/flamegraph-render-spec-tsx-render-group-snapshot-renders-a-complex-flamegraph-1-snap.png +0 -0
  169. package/src/FlameGraph/FlameGraphComponent/__image_snapshots__/flamegraph-render-spec-tsx-render-group-snapshot-renders-a-double-diff-flamegraph-1-snap.png +0 -0
  170. package/src/FlameGraph/FlameGraphComponent/__image_snapshots__/flamegraph-render-spec-tsx-render-group-snapshot-renders-a-highlighted-double-flamegraph-1-snap.png +0 -0
  171. package/src/FlameGraph/FlameGraphComponent/__image_snapshots__/flamegraph-render-spec-tsx-render-group-snapshot-renders-a-highlighted-flamegraph-1-snap.png +0 -0
  172. package/src/FlameGraph/FlameGraphComponent/__image_snapshots__/flamegraph-render-spec-tsx-render-group-snapshot-renders-a-simple-flamegraph-1-snap.png +0 -0
  173. package/src/FlameGraph/FlameGraphComponent/__image_snapshots__/flamegraph-render-spec-tsx-render-group-snapshot-renders-a-simple-tree-1-snap.png +0 -0
  174. package/src/FlameGraph/FlameGraphComponent/__image_snapshots__/flamegraph-render-spec-tsx-render-group-snapshot-renders-a-zoomed-flamegraph-1-snap.png +0 -0
  175. package/src/FlameGraph/FlameGraphComponent/__image_snapshots__/flamegraph-render-spec-tsx-render-group-snapshot-renders-a-zoomed-with-fit-mode-tail-1-snap.png +0 -0
  176. package/src/FlameGraph/FlameGraphComponent/canvas.module.css +6 -0
  177. package/src/FlameGraph/FlameGraphComponent/color.spec.ts +308 -0
  178. package/src/FlameGraph/FlameGraphComponent/color.ts +167 -0
  179. package/src/FlameGraph/FlameGraphComponent/colorPalette.ts +58 -0
  180. package/src/FlameGraph/FlameGraphComponent/constants.ts +5 -0
  181. package/src/FlameGraph/FlameGraphComponent/index.spec.tsx +291 -0
  182. package/src/FlameGraph/FlameGraphComponent/index.tsx +411 -0
  183. package/src/FlameGraph/FlameGraphComponent/murmur3.ts +97 -0
  184. package/src/FlameGraph/FlameGraphComponent/styles.module.scss +10 -0
  185. package/src/FlameGraph/FlameGraphComponent/testData.ts +427 -0
  186. package/src/FlameGraph/FlameGraphComponent/utils.ts +31 -0
  187. package/src/FlameGraph/FlameGraphComponent/viewTypes.ts +6 -0
  188. package/src/FlameGraph/FlameGraphRenderer.tsx +603 -0
  189. package/src/FlameGraph/FlamegraphRenderer.module.scss +93 -0
  190. package/src/FlameGraph/decode.ts +78 -0
  191. package/src/FlameGraph/normalize.spec.ts +76 -0
  192. package/src/FlameGraph/normalize.ts +60 -0
  193. package/src/FlameGraph/testData.json +423 -0
  194. package/src/FlameGraph/uniqueness.spec.ts +16 -0
  195. package/src/FlameGraph/uniqueness.ts +84 -0
  196. package/src/FlamegraphRenderer.tsx +61 -0
  197. package/src/Icons.tsx +74 -0
  198. package/src/ProfilerTable.tsx +527 -0
  199. package/src/SharedQueryInput.module.scss +82 -0
  200. package/src/SharedQueryInput.tsx +127 -0
  201. package/src/Toolbar.module.scss +117 -0
  202. package/src/Toolbar.spec.tsx +217 -0
  203. package/src/Toolbar.tsx +471 -0
  204. package/src/Tooltip/FlamegraphTooltip.spec.tsx +81 -0
  205. package/src/Tooltip/FlamegraphTooltip.tsx +257 -0
  206. package/src/Tooltip/LeftClickIcon.tsx +18 -0
  207. package/src/Tooltip/RightClickIcon.tsx +18 -0
  208. package/src/Tooltip/TableTooltip.spec.tsx +44 -0
  209. package/src/Tooltip/TableTooltip.tsx +145 -0
  210. package/src/Tooltip/Tooltip.module.scss +71 -0
  211. package/src/Tooltip/Tooltip.spec.tsx +395 -0
  212. package/src/Tooltip/Tooltip.tsx +336 -0
  213. package/src/__snapshots__/Toolbar.spec.tsx.snap +297 -0
  214. package/src/convert/convertJaegerTraceToProfile.ts +97 -0
  215. package/src/convert/diffTwoProfiles.ts +81 -0
  216. package/src/convert/flamebearersToTree.ts +78 -0
  217. package/src/convert/sandwichViewProfiles.spec.ts +65 -0
  218. package/src/convert/sandwichViewProfiles.ts +191 -0
  219. package/src/convert/subtract.ts +87 -0
  220. package/src/convert/testData.ts +145 -0
  221. package/src/convert/toGraphviz.ts +485 -0
  222. package/src/fitMode/fitMode.spec.ts +93 -0
  223. package/src/fitMode/fitMode.ts +122 -0
  224. package/src/format/format.spec.ts +291 -0
  225. package/src/format/format.ts +303 -0
  226. package/src/globals.d.ts +13 -0
  227. package/src/i18n.tsx +293 -0
  228. package/src/index.node.ts +19 -0
  229. package/src/index.spec.tsx +383 -0
  230. package/src/index.tsx +10 -0
  231. package/src/logo-v3-small.svg +32 -0
  232. package/src/models/decode.ts +45 -0
  233. package/src/models/flamebearer.ts +86 -0
  234. package/src/models/groups.ts +14 -0
  235. package/src/models/index.ts +7 -0
  236. package/src/models/profile.spec.ts +32 -0
  237. package/src/models/profile.ts +48 -0
  238. package/src/models/spyName.spec.ts +18 -0
  239. package/src/models/spyName.ts +32 -0
  240. package/src/models/trace.ts +45 -0
  241. package/src/models/units.spec.ts +21 -0
  242. package/src/models/units.ts +24 -0
  243. package/src/sass/_common.scss +206 -0
  244. package/src/sass/_css-variables.scss +201 -0
  245. package/src/sass/_mixins.scss +15 -0
  246. package/src/sass/_sanitize.scss +407 -0
  247. package/src/sass/_variables.scss +53 -0
  248. package/src/sass/flamegraph.scss +18 -0
  249. package/src/search.spec.ts +11 -0
  250. package/src/search.ts +4 -0
  251. package/src/shameful-any.d.ts +2 -0
  252. package/src/shims/Box.module.scss +57 -0
  253. package/src/shims/Box.tsx +105 -0
  254. package/src/shims/Button.module.scss +129 -0
  255. package/src/shims/Button.tsx +128 -0
  256. package/src/shims/Dropdown.module.scss +63 -0
  257. package/src/shims/Dropdown.tsx +96 -0
  258. package/src/shims/Input.module.scss +15 -0
  259. package/src/shims/Input.tsx +55 -0
  260. package/src/shims/LoadingSpinner.tsx +19 -0
  261. package/src/shims/Menu.tsx +9 -0
  262. package/src/shims/NoData.module.scss +6 -0
  263. package/src/shims/NoData.tsx +11 -0
  264. package/src/shims/Table.module.scss +82 -0
  265. package/src/shims/Table.spec.tsx +121 -0
  266. package/src/shims/Table.tsx +252 -0
  267. package/src/shims/Tooltip.tsx +51 -0
@@ -0,0 +1,446 @@
1
+ import { DeepReadonly } from 'ts-essentials';
2
+ import { Maybe } from 'true-myth';
3
+ import {
4
+ createFF,
5
+ Flamebearer,
6
+ singleFF,
7
+ doubleFF,
8
+ SpyName,
9
+ } from '../../models';
10
+ import type { Units } from '../../models';
11
+ import { PX_PER_LEVEL, BAR_HEIGHT, COLLAPSE_THRESHOLD } from './constants';
12
+ import type { FlamegraphPalette } from './colorPalette';
13
+ // there's a dependency cycle here but it should be fine
14
+ /* eslint-disable-next-line import/no-cycle */
15
+ import RenderCanvas from './Flamegraph_render';
16
+
17
+ /* eslint-disable no-useless-constructor */
18
+
19
+ /*
20
+ * Branded Type to distinguish between x,y that were validated to be within bounds or not.
21
+ */
22
+ type XYWithinBounds = { x: number; y: number } & { __brand: 'XYWithinBounds' };
23
+
24
+ export default class Flamegraph {
25
+ private ff: ReturnType<typeof createFF>;
26
+
27
+ constructor(
28
+ private readonly flamebearer: Flamebearer,
29
+ private canvas: HTMLCanvasElement,
30
+ /**
31
+ * What node to be 'focused'
32
+ * ie what node to start the tree
33
+ */
34
+ private focusedNode: Maybe<DeepReadonly<{ i: number; j: number }>>,
35
+ /**
36
+ * What level has been "selected"
37
+ * All nodes above will be dimmed out
38
+ */
39
+ // private selectedLevel: number,
40
+ private readonly fitMode: 'HEAD' | 'TAIL',
41
+ /**
42
+ * The query used to match against the node name.
43
+ * For each node,
44
+ * if it matches it will be highlighted,
45
+ * otherwise it will be greyish.
46
+ */
47
+ private readonly highlightQuery: string,
48
+ private zoom: Maybe<DeepReadonly<{ i: number; j: number }>>,
49
+
50
+ private palette: FlamegraphPalette
51
+ ) {
52
+ // TODO
53
+ // these were only added because storybook is not setting
54
+ // the property to the component
55
+ this.zoom = zoom;
56
+ this.focusedNode = focusedNode;
57
+ this.flamebearer = flamebearer;
58
+ this.canvas = canvas;
59
+ this.highlightQuery = highlightQuery;
60
+ this.ff = createFF(flamebearer.format);
61
+ this.palette = palette;
62
+
63
+ // don't allow to have a zoom smaller than the focus
64
+ // since it does not make sense
65
+ if (focusedNode.isJust && zoom.isJust) {
66
+ if (zoom.value.i < focusedNode.value.i) {
67
+ throw new Error('Zoom i level should be bigger than Focus');
68
+ }
69
+ }
70
+ }
71
+
72
+ public render() {
73
+ const { rangeMin, rangeMax } = this.getRange();
74
+
75
+ const props = {
76
+ canvas: this.canvas,
77
+
78
+ format: this.flamebearer.format,
79
+ numTicks: this.flamebearer.numTicks,
80
+ sampleRate: this.flamebearer.sampleRate,
81
+ names: this.flamebearer.names,
82
+ levels: this.flamebearer.levels,
83
+ // keep type narrow https://stackoverflow.com/q/54333982
84
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
85
+ spyName: this.flamebearer.spyName as SpyName,
86
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
87
+ units: this.flamebearer.units as Units,
88
+ maxSelf: this.flamebearer.maxSelf,
89
+
90
+ rangeMin,
91
+ rangeMax,
92
+ fitMode: this.fitMode,
93
+ highlightQuery: this.highlightQuery,
94
+ zoom: this.zoom,
95
+ focusedNode: this.focusedNode,
96
+ pxPerTick: this.pxPerTick(),
97
+ tickToX: this.tickToX,
98
+ palette: this.palette,
99
+ };
100
+
101
+ const { format: viewType } = this.flamebearer;
102
+
103
+ switch (viewType) {
104
+ case 'single': {
105
+ RenderCanvas({ ...props, format: 'single' });
106
+ break;
107
+ }
108
+ case 'double': {
109
+ RenderCanvas({
110
+ ...props,
111
+ leftTicks: this.flamebearer.leftTicks,
112
+ rightTicks: this.flamebearer.rightTicks,
113
+ });
114
+ break;
115
+ }
116
+ default: {
117
+ throw new Error(`Invalid format: '${viewType}'`);
118
+ }
119
+ }
120
+ }
121
+
122
+ private pxPerTick() {
123
+ const { rangeMin, rangeMax } = this.getRange();
124
+ // const graphWidth = this.canvas.width;
125
+ const graphWidth = this.getCanvasWidth();
126
+
127
+ return graphWidth / this.flamebearer.numTicks / (rangeMax - rangeMin);
128
+ }
129
+
130
+ private tickToX = (i: number) => {
131
+ const { rangeMin } = this.getRange();
132
+ return (i - this.flamebearer.numTicks * rangeMin) * this.pxPerTick();
133
+ };
134
+
135
+ private getRange() {
136
+ const { ff } = this;
137
+
138
+ // delay calculation since they may not be set
139
+ const calculatedZoomRange = (zoom: { i: number; j: number }) => {
140
+ const level = this.flamebearer.levels[zoom.i];
141
+ if (!level) {
142
+ throw new Error(`Could not find level: '${zoom.i}'`);
143
+ }
144
+
145
+ const zoomMin =
146
+ ff.getBarOffset(level, zoom.j) / this.flamebearer.numTicks;
147
+ const zoomMax =
148
+ (ff.getBarOffset(level, zoom.j) + ff.getBarTotal(level, zoom.j)) /
149
+ this.flamebearer.numTicks;
150
+
151
+ return {
152
+ rangeMin: zoomMin,
153
+ rangeMax: zoomMax,
154
+ };
155
+ };
156
+
157
+ const calculatedFocusRange = (focusedNode: { i: number; j: number }) => {
158
+ const level = this.flamebearer.levels[focusedNode.i];
159
+
160
+ if (!level) {
161
+ throw new Error(`Could not find level: '${focusedNode.i}'`);
162
+ }
163
+ const focusMin =
164
+ ff.getBarOffset(level, focusedNode.j) / this.flamebearer.numTicks;
165
+
166
+ const focusMax =
167
+ (ff.getBarOffset(level, focusedNode.j) +
168
+ ff.getBarTotal(level, focusedNode.j)) /
169
+ this.flamebearer.numTicks;
170
+
171
+ return {
172
+ rangeMin: focusMin,
173
+ rangeMax: focusMax,
174
+ };
175
+ };
176
+
177
+ const { zoom, focusedNode } = this;
178
+
179
+ return zoom.match({
180
+ Just: (z) => {
181
+ return focusedNode.match({
182
+ // both are set
183
+ Just: (f) => {
184
+ const fRange = calculatedFocusRange(f);
185
+ const zRange = calculatedZoomRange(z);
186
+
187
+ // focus is smaller, let's use it
188
+ if (
189
+ fRange.rangeMax - fRange.rangeMin <
190
+ zRange.rangeMax - zRange.rangeMin
191
+ ) {
192
+ console.warn(
193
+ 'Focus is smaller than range, this shouldnt happen. Verify that the zoom is always bigger than the focus.'
194
+ );
195
+ return calculatedFocusRange(f);
196
+ }
197
+
198
+ return calculatedZoomRange(z);
199
+ },
200
+
201
+ // only zoom is set
202
+ Nothing: () => {
203
+ return calculatedZoomRange(z);
204
+ },
205
+ });
206
+ },
207
+
208
+ Nothing: () => {
209
+ return focusedNode.match({
210
+ Just: (f) => {
211
+ // only focus is set
212
+ return calculatedFocusRange(f);
213
+ },
214
+ Nothing: () => {
215
+ // neither are set
216
+ return {
217
+ rangeMin: 0,
218
+ rangeMax: 1,
219
+ };
220
+ },
221
+ });
222
+ },
223
+ });
224
+ }
225
+
226
+ private getCanvasWidth() {
227
+ // bit of a hack, but clientWidth is not available in node-canvas
228
+ return this.canvas.clientWidth || this.canvas.width;
229
+ }
230
+
231
+ private isFocused() {
232
+ return this.focusedNode.isJust;
233
+ }
234
+
235
+ // binary search of a block in a stack level
236
+ // TODO(eh-am): calculations seem wrong when x is 0 and y != 0,
237
+ // also on the border
238
+ private binarySearchLevel(x: number, level: number[]) {
239
+ const { ff } = this;
240
+
241
+ let i = 0;
242
+ let j = level.length - ff.jStep;
243
+
244
+ while (i <= j) {
245
+ /* eslint-disable-next-line no-bitwise */
246
+ const m = ff.jStep * ((i / ff.jStep + j / ff.jStep) >> 1);
247
+ const x0 = this.tickToX(ff.getBarOffset(level, m));
248
+ const x1 = this.tickToX(
249
+ ff.getBarOffset(level, m) + ff.getBarTotal(level, m)
250
+ );
251
+
252
+ if (x0 <= x && x1 >= x) {
253
+ return x1 - x0 > COLLAPSE_THRESHOLD ? m : -1;
254
+ }
255
+ if (x0 > x) {
256
+ j = m - ff.jStep;
257
+ } else {
258
+ i = m + ff.jStep;
259
+ }
260
+ }
261
+ return -1;
262
+ }
263
+
264
+ private xyToBarIndex(x: number, y: number) {
265
+ if (x < 0 || y < 0) {
266
+ throw new Error(`x and y must be bigger than 0. x = ${x}, y = ${y}`);
267
+ }
268
+
269
+ // clicked on the top bar and it's focused
270
+ if (this.isFocused() && y <= BAR_HEIGHT) {
271
+ return { i: 0, j: 0 };
272
+ }
273
+
274
+ // in focused mode there's a "fake" bar at the top
275
+ // so we must discount for it
276
+ const computedY = this.isFocused() ? y - BAR_HEIGHT : y;
277
+
278
+ const compensatedFocusedY = this.focusedNode.mapOrElse(
279
+ () => 0,
280
+ (node) => {
281
+ return node.i <= 0 ? 0 : node.i;
282
+ }
283
+ );
284
+
285
+ const compensation = this.zoom.match({
286
+ Just: () => {
287
+ return this.focusedNode.match({
288
+ Just: () => {
289
+ // both are set, prefer focus
290
+ return compensatedFocusedY;
291
+ },
292
+
293
+ Nothing: () => {
294
+ // only zoom is set
295
+ return 0;
296
+ },
297
+ });
298
+ },
299
+
300
+ Nothing: () => {
301
+ return this.focusedNode.match({
302
+ Just: () => {
303
+ // only focus is set
304
+ return compensatedFocusedY;
305
+ },
306
+
307
+ Nothing: () => {
308
+ // none of them are set
309
+ return 0;
310
+ },
311
+ });
312
+ },
313
+ });
314
+
315
+ const i = Math.floor(computedY / PX_PER_LEVEL) + compensation;
316
+
317
+ if (i >= 0 && i < this.flamebearer.levels.length) {
318
+ const level = this.flamebearer.levels[i];
319
+ if (!level) {
320
+ throw new Error(`Could not find level: '${i}'`);
321
+ }
322
+
323
+ const j = this.binarySearchLevel(x, level);
324
+
325
+ return { i, j };
326
+ }
327
+
328
+ return { i: 0, j: 0 };
329
+ }
330
+
331
+ private parseXY(x: number, y: number) {
332
+ const withinBounds = this.isWithinBounds(x, y);
333
+
334
+ const v = { x, y } as XYWithinBounds;
335
+
336
+ if (withinBounds) {
337
+ return Maybe.of(v);
338
+ }
339
+
340
+ return Maybe.nothing<typeof v>();
341
+ }
342
+
343
+ private xyToBarPosition = (xy: XYWithinBounds) => {
344
+ const { ff } = this;
345
+ const { i, j } = this.xyToBarIndex(xy.x, xy.y);
346
+
347
+ const topLevel = this.focusedNode.mapOrElse(
348
+ () => 0,
349
+ (node) => (node.i < 0 ? 0 : node.i - 1)
350
+ );
351
+
352
+ const level = this.flamebearer.levels[i];
353
+ if (!level) {
354
+ throw new Error(`Could not find level: '${i}'`);
355
+ }
356
+ const posX = Math.max(this.tickToX(ff.getBarOffset(level, j)), 0);
357
+
358
+ // lower bound is 0
359
+ const posY = Math.max((i - topLevel) * PX_PER_LEVEL, 0);
360
+
361
+ const sw = Math.min(
362
+ this.tickToX(ff.getBarOffset(level, j) + ff.getBarTotal(level, j)) - posX,
363
+ this.getCanvasWidth()
364
+ );
365
+
366
+ return {
367
+ x: posX,
368
+ y: posY,
369
+ width: sw,
370
+ };
371
+ };
372
+
373
+ private xyToBarData = (xy: XYWithinBounds) => {
374
+ const { i, j } = this.xyToBarIndex(xy.x, xy.y);
375
+ const level = this.flamebearer.levels[i];
376
+ if (!level) {
377
+ throw new Error(`Could not find level: '${i}'`);
378
+ }
379
+
380
+ switch (this.flamebearer.format) {
381
+ case 'single': {
382
+ const ff = singleFF;
383
+
384
+ return {
385
+ format: 'single' as const,
386
+ name: this.flamebearer.names[ff.getBarName(level, j)],
387
+ self: ff.getBarSelf(level, j),
388
+ offset: ff.getBarOffset(level, j),
389
+ total: ff.getBarTotal(level, j),
390
+ };
391
+ }
392
+ case 'double': {
393
+ const ff = doubleFF;
394
+
395
+ return {
396
+ format: 'double' as const,
397
+ barTotal: ff.getBarTotal(level, j),
398
+ totalLeft: ff.getBarTotalLeft(level, j),
399
+ totalRight: ff.getBarTotalRght(level, j),
400
+ totalDiff: ff.getBarTotalDiff(level, j),
401
+ name: this.flamebearer.names[ff.getBarName(level, j)],
402
+ };
403
+ }
404
+
405
+ default: {
406
+ throw new Error(`Unsupported type`);
407
+ }
408
+ }
409
+ };
410
+
411
+ public isWithinBounds = (x: number, y: number) => {
412
+ if (x < 0 || x > this.getCanvasWidth()) {
413
+ return false;
414
+ }
415
+
416
+ try {
417
+ const { i, j } = this.xyToBarIndex(x, y);
418
+ if (j === -1 || i === -1) {
419
+ return false;
420
+ }
421
+ } catch (e) {
422
+ return false;
423
+ }
424
+
425
+ return true;
426
+ };
427
+
428
+ /*
429
+ * Given x and y coordinates
430
+ * return all information about the bar under those coordinates
431
+ */
432
+ public xyToBar(x: number, y: number) {
433
+ return this.parseXY(x, y).map((xyWithinBounds) => {
434
+ const { i, j } = this.xyToBarIndex(x, y);
435
+ const position = this.xyToBarPosition(xyWithinBounds);
436
+ const data = this.xyToBarData(xyWithinBounds);
437
+
438
+ return {
439
+ i,
440
+ j,
441
+ ...position,
442
+ ...data,
443
+ };
444
+ });
445
+ }
446
+ }
@@ -0,0 +1,233 @@
1
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2
+ // @ts-ignore
3
+ import CanvasConverter from 'canvas-to-buffer';
4
+ import { createCanvas } from 'canvas';
5
+ import { Maybe } from 'true-myth';
6
+ import TestData from './testData';
7
+ import Flamegraph from './Flamegraph';
8
+ import { DefaultPalette } from './colorPalette';
9
+ import { configureToMatchImageSnapshot } from 'jest-image-snapshot';
10
+ import type { MatchImageSnapshotOptions } from 'jest-image-snapshot';
11
+
12
+ type focusedNodeType = ConstructorParameters<typeof Flamegraph>[2];
13
+ type zoomType = ConstructorParameters<typeof Flamegraph>[5];
14
+
15
+ expect.extend({
16
+ toMatchImageSnapshot(received: string, options: MatchImageSnapshotOptions) {
17
+ // If these checks pass, assume we're in a JSDOM environment with the 'canvas' package.
18
+ if (process.env.RUN_SNAPSHOTS) {
19
+ const toMatchImageSnapshot = configureToMatchImageSnapshot({
20
+ // Big enough threshold to account for different font rendering
21
+ // TODO: fix it
22
+ failureThreshold: 0.1,
23
+ failureThresholdType: 'percent',
24
+ }) as any;
25
+
26
+ // TODO
27
+ // for some reason it fails with
28
+ // Expected 1 arguments, but got 3.
29
+ // hence the any
30
+ return toMatchImageSnapshot.call(this, received, options);
31
+ }
32
+
33
+ return {
34
+ pass: true,
35
+ message: () =>
36
+ `Skipping 'toMatchImageSnapshot' assertion since env var 'RUN_SNAPSHOTS' is not set.`,
37
+ };
38
+ },
39
+ });
40
+ // All tests here refer strictly to the rendering bit of "Flamegraph"
41
+ describe("render group:snapshot'", () => {
42
+ // TODO i'm thinking here if we can simply reuse this?
43
+ const canvas = createCanvas(800, 0) as unknown as HTMLCanvasElement;
44
+ const fitMode = 'HEAD';
45
+ const highlightQuery = '';
46
+ const zoom: zoomType = Maybe.nothing();
47
+ const focusedNode: focusedNodeType = Maybe.nothing();
48
+
49
+ it('renders a simple flamegraph', () => {
50
+ const flame = new Flamegraph(
51
+ TestData.SimpleTree,
52
+ canvas,
53
+ focusedNode,
54
+ fitMode,
55
+ highlightQuery,
56
+ zoom,
57
+ DefaultPalette
58
+ );
59
+
60
+ flame.render();
61
+ expect(canvasToBuffer(canvas)).toMatchImageSnapshot();
62
+ });
63
+
64
+ // this test servers to validate funcionality like collapsing nodes
65
+ it('renders a complex flamegraph', () => {
66
+ const flame = new Flamegraph(
67
+ TestData.ComplexTree,
68
+ canvas,
69
+ focusedNode,
70
+ fitMode,
71
+ highlightQuery,
72
+ zoom,
73
+ DefaultPalette
74
+ );
75
+
76
+ flame.render();
77
+ expect(canvasToBuffer(canvas)).toMatchImageSnapshot();
78
+ });
79
+
80
+ it('renders a double(diff) flamegraph', () => {
81
+ const flame = new Flamegraph(
82
+ TestData.DiffTree,
83
+ canvas,
84
+ focusedNode,
85
+ fitMode,
86
+ highlightQuery,
87
+ zoom,
88
+ DefaultPalette
89
+ );
90
+
91
+ flame.render();
92
+ expect(canvasToBuffer(canvas)).toMatchImageSnapshot();
93
+ });
94
+
95
+ it('renders a highlighted flamegraph', () => {
96
+ const highlightQuery = 'main';
97
+ const focusedNode: focusedNodeType = Maybe.nothing();
98
+
99
+ const flame = new Flamegraph(
100
+ TestData.SimpleTree,
101
+ canvas,
102
+ focusedNode,
103
+ fitMode,
104
+ highlightQuery,
105
+ zoom,
106
+ DefaultPalette
107
+ );
108
+
109
+ flame.render();
110
+ expect(canvasToBuffer(canvas)).toMatchImageSnapshot();
111
+ });
112
+
113
+ it('renders a highlighted double flamegraph', () => {
114
+ const highlightQuery = 'main';
115
+ const focusedNode: focusedNodeType = Maybe.nothing();
116
+
117
+ const flame = new Flamegraph(
118
+ TestData.DiffTree,
119
+ canvas,
120
+ focusedNode,
121
+ fitMode,
122
+ highlightQuery,
123
+ zoom,
124
+ DefaultPalette
125
+ );
126
+
127
+ flame.render();
128
+ expect(canvasToBuffer(canvas)).toMatchImageSnapshot();
129
+ });
130
+
131
+ it('renders a zoomed flamegraph', () => {
132
+ const zoom = Maybe.just({ i: 2, j: 8 });
133
+ const focusedNode: focusedNodeType = Maybe.nothing();
134
+
135
+ const flame = new Flamegraph(
136
+ TestData.SimpleTree,
137
+ canvas,
138
+ focusedNode,
139
+ fitMode,
140
+ highlightQuery,
141
+ zoom,
142
+ DefaultPalette
143
+ );
144
+
145
+ flame.render();
146
+ expect(canvasToBuffer(canvas)).toMatchImageSnapshot();
147
+ });
148
+
149
+ it('renders a zoomed with fitMode="TAIL"', () => {
150
+ // we need a smaller canvas
151
+ // so that the function names don't fit
152
+ const canvas = createCanvas(300, 0) as unknown as HTMLCanvasElement;
153
+ const fitMode = 'TAIL';
154
+ const focusedNode: focusedNodeType = Maybe.nothing();
155
+
156
+ const flame = new Flamegraph(
157
+ TestData.SimpleTree,
158
+ canvas,
159
+ focusedNode,
160
+ fitMode,
161
+ highlightQuery,
162
+ zoom,
163
+ DefaultPalette
164
+ );
165
+
166
+ flame.render();
167
+ expect(canvasToBuffer(canvas)).toMatchImageSnapshot();
168
+ });
169
+
170
+ describe('focused', () => {
171
+ it('renders a focused node in the beginning', () => {
172
+ const zoom: zoomType = Maybe.nothing();
173
+ const focusedNode = Maybe.just({ i: 2, j: 0 });
174
+
175
+ const flame = new Flamegraph(
176
+ TestData.SimpleTree,
177
+ canvas,
178
+ focusedNode,
179
+ fitMode,
180
+ highlightQuery,
181
+ zoom,
182
+ DefaultPalette
183
+ );
184
+
185
+ flame.render();
186
+ expect(canvasToBuffer(canvas)).toMatchImageSnapshot();
187
+ });
188
+
189
+ it('renders a focused node (when node is not in the beginning)', () => {
190
+ const zoom: zoomType = Maybe.nothing();
191
+ const focusedNode = Maybe.just({ i: 2, j: 8 });
192
+
193
+ const flame = new Flamegraph(
194
+ TestData.SimpleTree,
195
+ canvas,
196
+ focusedNode,
197
+ fitMode,
198
+ highlightQuery,
199
+ zoom,
200
+ DefaultPalette
201
+ );
202
+
203
+ flame.render();
204
+ expect(canvasToBuffer(canvas)).toMatchImageSnapshot();
205
+ });
206
+
207
+ it('also zooms', () => {
208
+ const focusedNode = Maybe.just({ i: 1, j: 0 });
209
+ const zoom = Maybe.just({ i: 2, j: 0 }); // main.fastFunction
210
+
211
+ const flame = new Flamegraph(
212
+ TestData.SimpleTree,
213
+ canvas,
214
+ focusedNode,
215
+ fitMode,
216
+ highlightQuery,
217
+ zoom,
218
+ DefaultPalette
219
+ );
220
+
221
+ flame.render();
222
+ expect(canvasToBuffer(canvas)).toMatchImageSnapshot();
223
+ });
224
+ });
225
+ });
226
+
227
+ function canvasToBuffer(canvas: HTMLCanvasElement) {
228
+ const converter = new CanvasConverter(canvas, {
229
+ image: { types: ['png'] },
230
+ });
231
+
232
+ return converter.toBuffer();
233
+ }