mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
134 lines
4.4 KiB
TypeScript
134 lines
4.4 KiB
TypeScript
import type {mat3, vec2} from 'gl-matrix';
|
|
|
|
import {DifferentialFlamegraph} from 'sentry/utils/profiling/differentialFlamegraph';
|
|
import type {Flamegraph} from 'sentry/utils/profiling/flamegraph';
|
|
import type {FlamegraphColorCodings} from 'sentry/utils/profiling/flamegraph/flamegraphStateProvider/reducers/flamegraphPreferences';
|
|
import type {FlamegraphSearch} from 'sentry/utils/profiling/flamegraph/flamegraphStateProvider/reducers/flamegraphSearch';
|
|
import type {FlamegraphTheme} from 'sentry/utils/profiling/flamegraph/flamegraphTheme';
|
|
import type {FlamegraphFrame} from 'sentry/utils/profiling/flamegraphFrame';
|
|
|
|
export type FlamegraphRendererOptions = {
|
|
colorCoding: FlamegraphColorCodings[number];
|
|
draw_border: boolean;
|
|
};
|
|
|
|
export const DEFAULT_FLAMEGRAPH_RENDERER_OPTIONS: FlamegraphRendererOptions = {
|
|
colorCoding: 'by symbol name',
|
|
draw_border: false,
|
|
};
|
|
|
|
export interface FlamegraphRendererConstructor {
|
|
new (
|
|
canvas: HTMLCanvasElement,
|
|
flamegraph: Flamegraph,
|
|
theme: FlamegraphTheme,
|
|
options?: FlamegraphRendererOptions
|
|
): FlamegraphRenderer;
|
|
}
|
|
|
|
export abstract class FlamegraphRenderer {
|
|
ctx: CanvasRenderingContext2D | WebGLRenderingContext | null = null;
|
|
canvas: HTMLCanvasElement;
|
|
flamegraph: Flamegraph | DifferentialFlamegraph;
|
|
theme: FlamegraphTheme;
|
|
options: FlamegraphRendererOptions;
|
|
|
|
frames: ReadonlyArray<FlamegraphFrame>;
|
|
roots: ReadonlyArray<FlamegraphFrame>;
|
|
|
|
colorBuffer: Array<number>;
|
|
colorMap: Map<string | number | FlamegraphFrame['node'], number[]>;
|
|
|
|
isDifferentialFlamegraph: boolean = false;
|
|
|
|
constructor(
|
|
canvas: HTMLCanvasElement,
|
|
flamegraph: Flamegraph | DifferentialFlamegraph,
|
|
theme: FlamegraphTheme,
|
|
options: FlamegraphRendererOptions = DEFAULT_FLAMEGRAPH_RENDERER_OPTIONS
|
|
) {
|
|
this.canvas = canvas;
|
|
this.flamegraph = flamegraph;
|
|
this.theme = theme;
|
|
this.options = options;
|
|
|
|
this.frames = this.flamegraph.frames;
|
|
this.roots = this.flamegraph.root.children;
|
|
|
|
if (flamegraph instanceof DifferentialFlamegraph) {
|
|
this.colorBuffer = flamegraph.colorBuffer;
|
|
this.colorMap = flamegraph.colors;
|
|
this.isDifferentialFlamegraph = true;
|
|
} else {
|
|
const {colorBuffer, colorMap} = this.theme.COLORS.STACK_TO_COLOR(
|
|
this.frames,
|
|
this.theme.COLORS.COLOR_MAPS[this.options.colorCoding],
|
|
this.theme.COLORS.COLOR_BUCKET,
|
|
this.theme
|
|
);
|
|
|
|
this.colorBuffer = colorBuffer;
|
|
this.colorMap = colorMap;
|
|
}
|
|
}
|
|
|
|
getColorForFrame(frame: FlamegraphFrame): number[] {
|
|
if (this.isDifferentialFlamegraph) {
|
|
return this.colorMap.get(frame.node) ?? this.theme.COLORS.FRAME_FALLBACK_COLOR;
|
|
}
|
|
return this.colorMap.get(frame.key) ?? this.theme.COLORS.FRAME_FALLBACK_COLOR;
|
|
}
|
|
|
|
findHoveredNode(configSpaceCursor: vec2): FlamegraphFrame | null {
|
|
// ConfigSpace origin is at top of rectangle, so we need to offset bottom by 1
|
|
// to account for size of renderered rectangle.
|
|
if (configSpaceCursor[1] > this.flamegraph.configSpace.bottom + 1) {
|
|
return null;
|
|
}
|
|
|
|
if (configSpaceCursor[0] < this.flamegraph.configSpace.left) {
|
|
return null;
|
|
}
|
|
|
|
if (configSpaceCursor[0] > this.flamegraph.configSpace.right) {
|
|
return null;
|
|
}
|
|
|
|
let hoveredNode: FlamegraphFrame | null = null;
|
|
const queue = [...this.roots];
|
|
|
|
while (queue.length && !hoveredNode) {
|
|
const frame = queue.pop()!;
|
|
|
|
// We treat entire flamegraph as a segment tree, this allows us to query in O(log n) time by
|
|
// only looking at the nodes that are relevant to the current cursor position. We discard any values
|
|
// on x axis that do not overlap the cursor, and descend until we find a node that overlaps at cursor y position
|
|
if (configSpaceCursor[0] < frame.start || configSpaceCursor[0] > frame.end) {
|
|
continue;
|
|
}
|
|
|
|
// If our frame depth overlaps cursor y position, we have found our node
|
|
if (
|
|
configSpaceCursor[1] >= frame.depth &&
|
|
configSpaceCursor[1] <= frame.depth + 1
|
|
) {
|
|
hoveredNode = frame;
|
|
break;
|
|
}
|
|
|
|
// Descend into the rest of the children
|
|
for (let i = 0; i < frame.children.length; i++) {
|
|
queue.push(frame.children[i]);
|
|
}
|
|
}
|
|
return hoveredNode;
|
|
}
|
|
|
|
abstract setSearchResults(
|
|
_query: string,
|
|
_searchResults: FlamegraphSearch['results']['frames']
|
|
);
|
|
|
|
abstract draw(_configViewToPhysicalSpace: mat3): void;
|
|
}
|