flamegraphRenderer2D.tsx 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. import {mat3, vec2} from 'gl-matrix';
  2. import {Flamegraph} from 'sentry/utils/profiling/flamegraph';
  3. import {FlamegraphSearch} from 'sentry/utils/profiling/flamegraph/flamegraphStateProvider/flamegraphSearch';
  4. import {FlamegraphTheme} from 'sentry/utils/profiling/flamegraph/flamegraphTheme';
  5. import {FlamegraphFrame} from 'sentry/utils/profiling/flamegraphFrame';
  6. import {Rect} from 'sentry/utils/profiling/gl/utils';
  7. // Convert color component from 0-1 to 0-255 range
  8. function colorComponentsToRgba(color: number[]): string {
  9. return `rgba(${Math.floor(color[0] * 255)}, ${Math.floor(color[1] * 255)}, ${Math.floor(
  10. color[2] * 255
  11. )}, ${color[3] ?? 1})`;
  12. }
  13. export class FlamegraphRenderer2d {
  14. canvas: HTMLCanvasElement | null;
  15. flamegraph: Flamegraph;
  16. theme: FlamegraphTheme;
  17. options: {draw_border: boolean};
  18. frames: ReadonlyArray<FlamegraphFrame> = [];
  19. colorMap: Map<string | number, number[]> = new Map();
  20. constructor(
  21. canvas: HTMLCanvasElement,
  22. flamegraph: Flamegraph,
  23. theme: FlamegraphTheme,
  24. options: {draw_border: boolean} = {draw_border: false}
  25. ) {
  26. this.canvas = canvas;
  27. this.flamegraph = flamegraph;
  28. this.theme = theme;
  29. this.options = options;
  30. this.init();
  31. }
  32. init() {
  33. this.frames = [...this.flamegraph.frames];
  34. const {colorMap} = this.theme.COLORS.STACK_TO_COLOR(
  35. this.frames,
  36. this.theme.COLORS.COLOR_MAP,
  37. this.theme.COLORS.COLOR_BUCKET
  38. );
  39. this.colorMap = colorMap;
  40. }
  41. getColorForFrame(frame: FlamegraphFrame): number[] {
  42. return this.colorMap.get(frame.key) ?? this.theme.COLORS.FRAME_FALLBACK_COLOR;
  43. }
  44. // We dont really need this in node, it's just here for completeness and it makes
  45. // the flamegraph UI not throw errors when used in dev
  46. getHoveredNode(_configSpaceCursor: vec2): FlamegraphFrame | null {
  47. return null;
  48. }
  49. draw(configViewToPhysicalSpace: mat3, _searchResults: FlamegraphSearch['results']) {
  50. if (!this.canvas) {
  51. throw new Error('No canvas to draw on');
  52. }
  53. const queue: FlamegraphFrame[] = [...this.flamegraph.root.children];
  54. const context = this.canvas.getContext('2d');
  55. if (!context) {
  56. throw new Error('Could not get canvas 2d context');
  57. }
  58. context.clearRect(0, 0, this.canvas.width, this.canvas.height);
  59. const border = window.devicePixelRatio;
  60. while (queue.length > 0) {
  61. const frame = queue.pop()!;
  62. const rect = new Rect(
  63. frame.start,
  64. frame.depth,
  65. frame.end - frame.start,
  66. 1
  67. ).transformRect(configViewToPhysicalSpace);
  68. const colors =
  69. this.colorMap.get(frame.key) ?? this.theme.COLORS.FRAME_FALLBACK_COLOR;
  70. const color = colorComponentsToRgba(colors);
  71. context.fillStyle = color;
  72. context.fillRect(
  73. rect.x + border,
  74. rect.y + border,
  75. rect.width - border,
  76. rect.height - border
  77. );
  78. for (let i = 0; i < frame.children.length; i++) {
  79. queue.push(frame.children[i]);
  80. }
  81. }
  82. }
  83. }