Browse Source

fix(cpu): optimize draw (#54102)

Be smarter about draw calls and use binary search to find 1st and last
visible point in the series.

The max number of draw calls here is core count * profile max duration /
10hz report interval. Eg for 8 core draw at max profile duration of 30s
that is about ~2.4k draw calls for series alone.
Jonas 1 year ago
parent
commit
569c26d1d6

+ 42 - 9
static/app/utils/profiling/gl/utils.ts

@@ -254,7 +254,7 @@ function getContext(canvas: HTMLCanvasElement, context: string): RenderingContex
 // Exported separately as writing export function for each overload as
 // breaks the line width rules and makes it harder to read.
 export {getContext};
-
+export const ELLIPSIS = '\u2026';
 export function measureText(string: string, ctx?: CanvasRenderingContext2D): Rect {
   if (!string) {
     return Rect.Empty();
@@ -279,9 +279,19 @@ export function measureText(string: string, ctx?: CanvasRenderingContext2D): Rec
  * @param values {Array<T> | ReadonlyArray<T>}
  * @returns number
  */
-export function upperBound<T extends {end: number; start: number; x: number}>(
+export function upperBound<T extends {end: number; start: number}>(
+  target: number,
+  values: Array<T> | ReadonlyArray<T>
+): number;
+export function upperBound<T>(
+  target: number,
+  values: Array<T> | ReadonlyArray<T>,
+  getValue: (value: T) => number
+): number;
+export function upperBound<T extends {end: number; start: number} | {x: number}>(
   target: number,
-  values: Array<T> | ReadonlyArray<T> | Record<any, any>
+  values: Array<T> | ReadonlyArray<T> | Record<any, any>,
+  getValue?: (value: T) => number
 ) {
   let low = 0;
   let high = values.length;
@@ -291,13 +301,20 @@ export function upperBound<T extends {end: number; start: number; x: number}>(
   }
 
   if (high === 1) {
-    return values[0].start < target ? 1 : 0;
+    return getValue
+      ? getValue(values[0]) < target
+        ? 1
+        : 0
+      : values[0].start < target
+      ? 1
+      : 0;
   }
 
   while (low !== high) {
     const mid = low + Math.floor((high - low) / 2);
+    const value = getValue ? getValue(values[mid]) : values[mid].start;
 
-    if (values[mid].start < target) {
+    if (value < target) {
       low = mid + 1;
     } else {
       high = mid;
@@ -317,7 +334,17 @@ export function upperBound<T extends {end: number; start: number; x: number}>(
 export function lowerBound<T extends {end: number; start: number}>(
   target: number,
   values: Array<T> | ReadonlyArray<T>
-) {
+): number;
+export function lowerBound<T>(
+  target: number,
+  values: Array<T> | ReadonlyArray<T>,
+  getValue: (value: T) => number
+): number;
+export function lowerBound<T extends {end: number; start: number}>(
+  target: number,
+  values: Array<T> | ReadonlyArray<T>,
+  getValue?: (value: T) => number
+): number {
   let low = 0;
   let high = values.length;
 
@@ -326,13 +353,20 @@ export function lowerBound<T extends {end: number; start: number}>(
   }
 
   if (high === 1) {
-    return values[0].end < target ? 1 : 0;
+    return getValue
+      ? getValue(values[0]) < target
+        ? 1
+        : 0
+      : values[0].end < target
+      ? 1
+      : 0;
   }
 
   while (low !== high) {
     const mid = low + Math.floor((high - low) / 2);
+    const value = getValue ? getValue(values[mid]) : values[mid].end;
 
-    if (values[mid].end < target) {
+    if (value < target) {
       low = mid + 1;
     } else {
       high = mid;
@@ -375,7 +409,6 @@ export function formatColorForFrame(
   return `rgba(${color.map(n => n * 255).join(',')}, 1.0)`;
 }
 
-export const ELLIPSIS = '\u2026';
 export interface TrimTextCenter {
   end: number;
   length: number;

+ 20 - 2
static/app/utils/profiling/renderers/chartRenderer.tsx

@@ -2,7 +2,12 @@ import {mat3, vec2, vec3} from 'gl-matrix';
 
 import {FlamegraphTheme} from 'sentry/utils/profiling/flamegraph/flamegraphTheme';
 import {FlamegraphChart} from 'sentry/utils/profiling/flamegraphChart';
-import {getContext, resizeCanvasToDisplaySize} from 'sentry/utils/profiling/gl/utils';
+import {
+  getContext,
+  lowerBound,
+  resizeCanvasToDisplaySize,
+  upperBound,
+} from 'sentry/utils/profiling/gl/utils';
 import {Rect} from 'sentry/utils/profiling/speedscope';
 
 function findYIntervals(
@@ -110,7 +115,20 @@ export class FlamegraphChartRenderer {
       const origin = vec3.fromValues(0, 0, 1);
       vec3.transformMat3(origin, origin, configViewToPhysicalSpace);
 
-      for (let j = 0; j < serie.points.length; j++) {
+      let start = lowerBound(configView.left, serie.points, a => a.x);
+      let end = upperBound(configView.right, serie.points, a => a.x);
+
+      // Bounds are inclusive, so we adjust start and end by 1. This ensures we
+      // draw the previous/next line that goes outside of bounds.
+      // If we dont do this, the chart looks like | -- | instead of |----|
+      if (start > 0) {
+        start = start - 1;
+      }
+      if (end < serie.points.length) {
+        end = end + 1;
+      }
+
+      for (let j = start; j < end; j++) {
         const point = serie.points[j];
 
         const r = vec3.fromValues(point.x, point.y, 1);