flamegraphRenderer.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import {mat3, vec2} from 'gl-matrix';
  2. import {Flamegraph} from 'sentry/utils/profiling/flamegraph';
  3. import {FlamegraphColorCodings} from 'sentry/utils/profiling/flamegraph/flamegraphStateProvider/reducers/flamegraphPreferences';
  4. import {FlamegraphSearch} from 'sentry/utils/profiling/flamegraph/flamegraphStateProvider/reducers/flamegraphSearch';
  5. import {FlamegraphTheme} from 'sentry/utils/profiling/flamegraph/flamegraphTheme';
  6. import {FlamegraphFrame} from 'sentry/utils/profiling/flamegraphFrame';
  7. export type FlamegraphRendererOptions = {
  8. colorCoding: FlamegraphColorCodings[number];
  9. draw_border: boolean;
  10. };
  11. export const DEFAULT_FLAMEGRAPH_RENDERER_OPTIONS: FlamegraphRendererOptions = {
  12. colorCoding: 'by symbol name',
  13. draw_border: false,
  14. };
  15. export abstract class FlamegraphRenderer {
  16. canvas: HTMLCanvasElement;
  17. flamegraph: Flamegraph;
  18. theme: FlamegraphTheme;
  19. options: FlamegraphRendererOptions;
  20. frames: ReadonlyArray<FlamegraphFrame>;
  21. roots: ReadonlyArray<FlamegraphFrame>;
  22. colorBuffer: Array<number>;
  23. colorMap: Map<string | number, number[]>;
  24. constructor(
  25. canvas: HTMLCanvasElement,
  26. flamegraph: Flamegraph,
  27. theme: FlamegraphTheme,
  28. options: FlamegraphRendererOptions = DEFAULT_FLAMEGRAPH_RENDERER_OPTIONS
  29. ) {
  30. this.canvas = canvas;
  31. this.flamegraph = flamegraph;
  32. this.theme = theme;
  33. this.options = options;
  34. this.frames = this.flamegraph.frames;
  35. this.roots = this.flamegraph.root.children;
  36. const {colorBuffer, colorMap} = this.theme.COLORS.STACK_TO_COLOR(
  37. this.frames,
  38. this.theme.COLORS.COLOR_MAPS[this.options.colorCoding],
  39. this.theme.COLORS.COLOR_BUCKET,
  40. this.theme
  41. );
  42. this.colorBuffer = colorBuffer;
  43. this.colorMap = colorMap;
  44. }
  45. getColorForFrame(frame: FlamegraphFrame): number[] {
  46. return this.colorMap.get(frame.key) ?? this.theme.COLORS.FRAME_GRAYSCALE_COLOR;
  47. }
  48. findHoveredNode(configSpaceCursor: vec2): FlamegraphFrame | null {
  49. // ConfigSpace origin is at top of rectangle, so we need to offset bottom by 1
  50. // to account for size of renderered rectangle.
  51. if (configSpaceCursor[1] > this.flamegraph.configSpace.bottom + 1) {
  52. return null;
  53. }
  54. if (configSpaceCursor[0] < this.flamegraph.configSpace.left) {
  55. return null;
  56. }
  57. if (configSpaceCursor[0] > this.flamegraph.configSpace.right) {
  58. return null;
  59. }
  60. let hoveredNode: FlamegraphFrame | null = null;
  61. const queue = [...this.roots];
  62. while (queue.length && !hoveredNode) {
  63. const frame = queue.pop()!;
  64. // We treat entire flamegraph as a segment tree, this allows us to query in O(log n) time by
  65. // only looking at the nodes that are relevant to the current cursor position. We discard any values
  66. // on x axis that do not overlap the cursor, and descend until we find a node that overlaps at cursor y position
  67. if (configSpaceCursor[0] < frame.start || configSpaceCursor[0] > frame.end) {
  68. continue;
  69. }
  70. // If our frame depth overlaps cursor y position, we have found our node
  71. if (
  72. configSpaceCursor[1] >= frame.depth &&
  73. configSpaceCursor[1] <= frame.depth + 1
  74. ) {
  75. hoveredNode = frame;
  76. break;
  77. }
  78. // Descend into the rest of the children
  79. for (let i = 0; i < frame.children.length; i++) {
  80. queue.push(frame.children[i]);
  81. }
  82. }
  83. return hoveredNode;
  84. }
  85. abstract setSearchResults(
  86. _query: string,
  87. _searchResults: FlamegraphSearch['results']['frames']
  88. );
  89. abstract draw(_configViewToPhysicalSpace: mat3): void;
  90. }