Browse Source

feat(profiling) add a fallback renderer for UI frames (#60594)

Create a simple 2D canvas renderer for UI frames so that we can avoid
throwing once https://github.com/getsentry/sentry/pull/60591 is merged.
Jonas 1 year ago
parent
commit
33283e4283

+ 2 - 2
static/app/components/profiling/flamegraph/flamegraphUIFrames.tsx

@@ -14,7 +14,7 @@ import {
   getConfigViewTranslationBetweenVectors,
   getPhysicalSpacePositionFromOffset,
 } from 'sentry/utils/profiling/gl/utils';
-import {UIFramesRenderer} from 'sentry/utils/profiling/renderers/uiFramesRenderer';
+import {UIFramesRendererWebGL} from 'sentry/utils/profiling/renderers/uiFramesRendererWebGL';
 import {Rect} from 'sentry/utils/profiling/speedscope';
 import {UIFrameNode, UIFrames} from 'sentry/utils/profiling/uiFrames';
 import {useProfiles} from 'sentry/views/profiling/profilesProvider';
@@ -63,7 +63,7 @@ export function FlamegraphUIFrames({
       return null;
     }
 
-    return new UIFramesRenderer(uiFramesCanvasRef, uiFrames, flamegraphTheme);
+    return new UIFramesRendererWebGL(uiFramesCanvasRef, uiFrames, flamegraphTheme);
   }, [uiFramesCanvasRef, uiFrames, flamegraphTheme]);
 
   const hoveredNode: UIFrameNode[] | null = useMemo(() => {

+ 1 - 1
static/app/components/profiling/flamegraph/flamegraphUIFramesTooltip.tsx

@@ -7,7 +7,7 @@ import {t} from 'sentry/locale';
 import {CanvasView} from 'sentry/utils/profiling/canvasView';
 import {toRGBAString} from 'sentry/utils/profiling/colors/utils';
 import {FlamegraphCanvas} from 'sentry/utils/profiling/flamegraphCanvas';
-import {UIFramesRenderer} from 'sentry/utils/profiling/renderers/uiFramesRenderer';
+import {UIFramesRenderer} from 'sentry/utils/profiling/renderers/UIFramesRenderer';
 import {Rect} from 'sentry/utils/profiling/speedscope';
 import {UIFrames} from 'sentry/utils/profiling/uiFrames';
 

+ 37 - 0
static/app/utils/profiling/renderers/UIFramesRenderer.tsx

@@ -0,0 +1,37 @@
+import {mat3} from 'gl-matrix';
+
+import {FlamegraphTheme} from 'sentry/utils/profiling/flamegraph/flamegraphTheme';
+import {UIFrames} from 'sentry/utils/profiling/uiFrames';
+
+export abstract class UIFramesRenderer {
+  ctx: CanvasRenderingContext2D | WebGLRenderingContext | null = null;
+  canvas: HTMLCanvasElement | null;
+  uiFrames: UIFrames;
+  theme: FlamegraphTheme;
+  options: {
+    draw_border: boolean;
+  };
+
+  constructor(
+    canvas: HTMLCanvasElement,
+    uiFrames: UIFrames,
+    theme: FlamegraphTheme,
+    options: {draw_border: boolean} = {draw_border: false}
+  ) {
+    this.canvas = canvas;
+    this.uiFrames = uiFrames;
+    this.theme = theme;
+    this.options = options;
+  }
+
+  getColorForFrame(
+    type: UIFrames['frames'][0]['type']
+  ): [number, number, number, number] {
+    if (type === 'frozen') {
+      return this.theme.COLORS.UI_FRAME_COLOR_FROZEN;
+    }
+    return this.theme.COLORS.UI_FRAME_COLOR_SLOW;
+  }
+
+  abstract draw(configViewToPhysicalSpace: mat3): void;
+}

+ 61 - 0
static/app/utils/profiling/renderers/UIFramesRenderer2D.tsx

@@ -0,0 +1,61 @@
+import {mat3} from 'gl-matrix';
+
+import {colorComponentsToRGBA} from 'sentry/utils/profiling/colors/utils';
+import {FlamegraphTheme} from 'sentry/utils/profiling/flamegraph/flamegraphTheme';
+import {getContext, resizeCanvasToDisplaySize} from 'sentry/utils/profiling/gl/utils';
+import {
+  DEFAULT_FLAMEGRAPH_RENDERER_OPTIONS,
+  FlamegraphRendererOptions,
+} from 'sentry/utils/profiling/renderers/flamegraphRenderer';
+import {UIFramesRenderer} from 'sentry/utils/profiling/renderers/UIFramesRenderer';
+import {Rect} from 'sentry/utils/profiling/speedscope';
+import {UIFrames} from 'sentry/utils/profiling/uiFrames';
+
+export class UIFramesRenderer2D extends UIFramesRenderer {
+  ctx: CanvasRenderingContext2D | null = null;
+
+  constructor(
+    canvas: HTMLCanvasElement,
+    uiFrames: UIFrames,
+    theme: FlamegraphTheme,
+    options: FlamegraphRendererOptions = DEFAULT_FLAMEGRAPH_RENDERER_OPTIONS
+  ) {
+    super(canvas, uiFrames, theme, options);
+    this.initCanvasContext();
+  }
+
+  initCanvasContext(): void {
+    if (!this.canvas) {
+      throw new Error('Cannot initialize context from null canvas');
+    }
+    // Setup webgl canvas context
+    this.ctx = getContext(this.canvas, '2d');
+
+    if (!this.ctx) {
+      throw new Error('Could not get canvas 2d context');
+    }
+    resizeCanvasToDisplaySize(this.canvas);
+  }
+
+  draw(configViewToPhysicalSpace: mat3): void {
+    if (!this.canvas) {
+      throw new Error('No canvas to draw on');
+    }
+
+    if (!this.ctx) {
+      throw new Error('No canvas context to draw with');
+    }
+
+    const border = window.devicePixelRatio;
+    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
+
+    for (const frame of this.uiFrames.frames) {
+      const rect = new Rect(frame.start, 0, frame.end - frame.start, 1).transformRect(
+        configViewToPhysicalSpace
+      );
+
+      this.ctx.fillStyle = colorComponentsToRGBA(this.getColorForFrame(frame.type));
+      this.ctx.fillRect(rect.x + border, rect.y, rect.width - border, rect.height);
+    }
+  }
+}

+ 2 - 2
static/app/utils/profiling/renderers/uiFramesRenderer.spec.tsx → static/app/utils/profiling/renderers/uiFramesRendererWebGL.spec.tsx

@@ -2,7 +2,7 @@ import {vec2} from 'gl-matrix';
 
 import {makeCanvasMock, makeContextMock} from 'sentry-test/profiling/utils';
 
-import {UIFramesRenderer} from 'sentry/utils/profiling/renderers/uiFramesRenderer';
+import {UIFramesRendererWebGL} from 'sentry/utils/profiling/renderers/uiFramesRendererWebGL';
 import {Rect} from 'sentry/utils/profiling/speedscope';
 import {UIFrames} from 'sentry/utils/profiling/uiFrames';
 
@@ -48,7 +48,7 @@ describe('UIFramesRenderer', () => {
     {unit: 'nanoseconds'},
     new Rect(0, 0, 10, 1)
   );
-  const renderer = new UIFramesRenderer(canvas, uiFrames, LightFlamegraphTheme);
+  const renderer = new UIFramesRendererWebGL(canvas, uiFrames, LightFlamegraphTheme);
 
   it.each([
     [vec2.fromValues(-1, 0), null],

+ 7 - 19
static/app/utils/profiling/renderers/uiFramesRenderer.tsx → static/app/utils/profiling/renderers/uiFramesRendererWebGL.tsx

@@ -2,8 +2,6 @@ import * as Sentry from '@sentry/react';
 import {mat3, vec2} from 'gl-matrix';
 
 import {FlamegraphTheme} from 'sentry/utils/profiling/flamegraph/flamegraphTheme';
-import {Rect} from 'sentry/utils/profiling/speedscope';
-
 import {
   createAndBindBuffer,
   createProgram,
@@ -15,8 +13,10 @@ import {
   resizeCanvasToDisplaySize,
   safeGetContext,
   upperBound,
-} from '../gl/utils';
-import {UIFrameNode, UIFrames} from '../uiFrames';
+} from 'sentry/utils/profiling/gl/utils';
+import {UIFramesRenderer} from 'sentry/utils/profiling/renderers/UIFramesRenderer';
+import {Rect} from 'sentry/utils/profiling/speedscope';
+import {UIFrameNode, UIFrames} from 'sentry/utils/profiling/uiFrames';
 
 import {uiFramesFragment, uiFramesVertext} from './shaders';
 
@@ -25,15 +25,10 @@ const PHYSICAL_SPACE_PX = new Rect(0, 0, 1, 1);
 const CONFIG_TO_PHYSICAL_SPACE = mat3.create();
 const VERTICES_PER_FRAME = 6;
 
-class UIFramesRenderer {
-  canvas: HTMLCanvasElement | null;
-  uiFrames: UIFrames;
-
+class UIFramesRendererWebGL extends UIFramesRenderer {
   ctx: WebGLRenderingContext | null = null;
   program: WebGLProgram | null = null;
 
-  theme: FlamegraphTheme;
-
   // Vertex and color buffer
   positions: Float32Array = new Float32Array();
   frame_types: Float32Array = new Float32Array();
@@ -61,20 +56,13 @@ class UIFramesRenderer {
     u_projection: null,
   };
 
-  options: {
-    draw_border: boolean;
-  };
-
   constructor(
     canvas: HTMLCanvasElement,
     uiFrames: UIFrames,
     theme: FlamegraphTheme,
     options: {draw_border: boolean} = {draw_border: false}
   ) {
-    this.uiFrames = uiFrames;
-    this.canvas = canvas;
-    this.theme = theme;
-    this.options = options;
+    super(canvas, uiFrames, theme, options);
 
     const initialized = this.initCanvasContext();
     if (!initialized) {
@@ -335,4 +323,4 @@ class UIFramesRenderer {
   }
 }
 
-export {UIFramesRenderer};
+export {UIFramesRendererWebGL};