mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
324 lines
9.8 KiB
TypeScript
324 lines
9.8 KiB
TypeScript
import {vec2} from 'gl-matrix';
|
|
|
|
import {
|
|
makeCanvasMock,
|
|
makeContextMock,
|
|
makeFlamegraph,
|
|
} from 'sentry-test/profiling/utils';
|
|
|
|
import {CanvasView} from 'sentry/utils/profiling/canvasView';
|
|
import type {FlamegraphSearch} from 'sentry/utils/profiling/flamegraph/flamegraphStateProvider/reducers/flamegraphSearch';
|
|
import {
|
|
LightFlamegraphTheme,
|
|
LightFlamegraphTheme as theme,
|
|
} from 'sentry/utils/profiling/flamegraph/flamegraphTheme';
|
|
import {FlamegraphCanvas} from 'sentry/utils/profiling/flamegraphCanvas';
|
|
import {FlamegraphRendererWebGL} from 'sentry/utils/profiling/renderers/flamegraphRendererWebGL';
|
|
import {Rect} from 'sentry/utils/profiling/speedscope';
|
|
|
|
import type {Flamegraph} from '../flamegraph';
|
|
import {getFlamegraphFrameSearchId} from '../flamegraphFrame';
|
|
|
|
const originalDpr = window.devicePixelRatio;
|
|
|
|
describe('flamegraphRendererWebGL', () => {
|
|
beforeEach(() => {
|
|
// We simulate regular screens unless differently specified
|
|
window.devicePixelRatio = 1;
|
|
});
|
|
afterEach(() => {
|
|
window.devicePixelRatio = originalDpr;
|
|
});
|
|
describe('colors', () => {
|
|
it('generates new colors if none are set on the flamegraph', () => {
|
|
const canvas = makeCanvasMock({
|
|
getContext: jest.fn().mockReturnValue(makeContextMock()),
|
|
});
|
|
|
|
const flamegraph = makeFlamegraph();
|
|
|
|
const renderer = new FlamegraphRendererWebGL(
|
|
canvas as HTMLCanvasElement,
|
|
flamegraph,
|
|
{
|
|
...theme,
|
|
COLORS: {
|
|
...theme.COLORS,
|
|
// @ts-expect-error overridee the colors implementation
|
|
STACK_TO_COLOR: () => {
|
|
const colorMap = new Map<string, number[]>([['f0', [1, 0, 0, 1]]]);
|
|
return {
|
|
colorBuffer: [
|
|
// 2 triangles, each with 3 vertices
|
|
1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1,
|
|
],
|
|
colorMap,
|
|
};
|
|
},
|
|
},
|
|
},
|
|
vec2.fromValues(0, 0)
|
|
);
|
|
|
|
expect(JSON.stringify(renderer.colors)).toEqual(
|
|
JSON.stringify(
|
|
new Float32Array([
|
|
// 2 triangles, each with 3 vertices
|
|
1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1,
|
|
])
|
|
)
|
|
);
|
|
});
|
|
});
|
|
|
|
it('inits vertices', () => {
|
|
const canvas = makeCanvasMock({
|
|
getContext: jest.fn().mockReturnValue(makeContextMock()),
|
|
});
|
|
|
|
const flamegraph = makeFlamegraph();
|
|
|
|
const renderer = new FlamegraphRendererWebGL(
|
|
canvas as HTMLCanvasElement,
|
|
flamegraph,
|
|
theme
|
|
);
|
|
|
|
// Helper rect for the only frame in our flamegraph
|
|
const rect = new Rect(0, 0, 10, 1);
|
|
|
|
// To draw a rect, we need to draw 2 triangles, each with 3 vertices
|
|
// First triangle: top left -> top right -> bottom left
|
|
// Second triangle: bottom left -> top right -> bottom right
|
|
expect(JSON.stringify(renderer.positions.slice(0, 2))).toEqual(
|
|
JSON.stringify(new Float32Array([rect.left, rect.top]))
|
|
);
|
|
expect(JSON.stringify(renderer.positions.slice(2, 4))).toEqual(
|
|
JSON.stringify(new Float32Array([rect.right, rect.top]))
|
|
);
|
|
expect(JSON.stringify(renderer.positions.slice(4, 6))).toEqual(
|
|
JSON.stringify(new Float32Array([rect.left, rect.bottom]))
|
|
);
|
|
|
|
expect(JSON.stringify(renderer.positions.slice(6, 8))).toEqual(
|
|
JSON.stringify(new Float32Array([rect.left, rect.bottom]))
|
|
);
|
|
expect(JSON.stringify(renderer.positions.slice(8, 10))).toEqual(
|
|
JSON.stringify(new Float32Array([rect.right, rect.top]))
|
|
);
|
|
expect(JSON.stringify(renderer.positions.slice(10, 12))).toEqual(
|
|
JSON.stringify(new Float32Array([rect.right, rect.bottom]))
|
|
);
|
|
});
|
|
|
|
it('inits shaders', () => {
|
|
const VERTEX = `void main() { gl_Position = vec4(pos, 0.0, 1.0); }`;
|
|
const FRAGMENT = `void main() { gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); }`;
|
|
|
|
const context = makeContextMock({
|
|
createShader: jest.fn().mockReturnValueOnce(VERTEX).mockReturnValueOnce(FRAGMENT),
|
|
});
|
|
const canvas = makeCanvasMock({
|
|
getContext: jest.fn().mockReturnValue(context),
|
|
});
|
|
|
|
const flamegraph = makeFlamegraph();
|
|
|
|
// @ts-expect-error shaders are init from the constructor
|
|
const _renderer = new FlamegraphRendererWebGL(
|
|
canvas as HTMLCanvasElement,
|
|
flamegraph,
|
|
theme
|
|
);
|
|
|
|
expect(context.createShader).toHaveBeenCalledTimes(2);
|
|
expect(context.getShaderParameter).toHaveBeenCalledTimes(2);
|
|
|
|
// @ts-expect-error this is a mock
|
|
expect(context.getShaderParameter.mock.calls[0][0]).toEqual(VERTEX);
|
|
// @ts-expect-error this is a mock
|
|
expect(context.getShaderParameter.mock.calls[1][0]).toEqual(FRAGMENT);
|
|
});
|
|
|
|
it('getColorForFrame', () => {
|
|
const canvas = makeCanvasMock({
|
|
getContext: jest.fn().mockReturnValue(makeContextMock()),
|
|
});
|
|
|
|
const flamegraph = makeFlamegraph();
|
|
|
|
const renderer = new FlamegraphRendererWebGL(
|
|
canvas as HTMLCanvasElement,
|
|
flamegraph,
|
|
theme
|
|
);
|
|
|
|
expect(renderer.getColorForFrame(flamegraph.frames[0])).toEqual([
|
|
0.9625, 0.7125, 0.7125,
|
|
]);
|
|
expect(
|
|
renderer.getColorForFrame({
|
|
key: 20,
|
|
frame: flamegraph.frames[0].frame,
|
|
node: flamegraph.frames[0].node,
|
|
parent: null,
|
|
children: [],
|
|
depth: 0,
|
|
start: 0,
|
|
end: 0,
|
|
})
|
|
).toEqual(LightFlamegraphTheme.COLORS.FRAME_FALLBACK_COLOR);
|
|
});
|
|
|
|
it('getHoveredNode', () => {
|
|
const flamegraph = makeFlamegraph(
|
|
{
|
|
events: [
|
|
{type: 'O', at: 0, frame: 0},
|
|
{type: 'O', at: 1, frame: 1},
|
|
{type: 'C', at: 2, frame: 1},
|
|
{type: 'C', at: 3, frame: 0},
|
|
{type: 'O', at: 4, frame: 2},
|
|
{type: 'O', at: 5, frame: 3},
|
|
{type: 'C', at: 7, frame: 3},
|
|
{type: 'C', at: 8, frame: 2},
|
|
{type: 'O', at: 9, frame: 4},
|
|
{type: 'O', at: 10, frame: 5},
|
|
{type: 'C', at: 11, frame: 5},
|
|
{type: 'C', at: 12, frame: 4},
|
|
],
|
|
},
|
|
[{name: 'f0'}, {name: 'f1'}, {name: 'f2'}, {name: 'f3'}, {name: 'f4'}, {name: 'f5'}]
|
|
);
|
|
|
|
const renderer = new FlamegraphRendererWebGL(
|
|
makeCanvasMock() as HTMLCanvasElement,
|
|
flamegraph,
|
|
theme
|
|
);
|
|
|
|
expect(renderer.findHoveredNode(vec2.fromValues(-1, 0))).toBeNull();
|
|
expect(renderer.findHoveredNode(vec2.fromValues(-1, 0))).toBeNull();
|
|
expect(renderer.findHoveredNode(vec2.fromValues(0, 0))?.frame?.name).toBe('f0');
|
|
expect(renderer.findHoveredNode(vec2.fromValues(5, 2))?.frame?.name).toBe('f3');
|
|
});
|
|
|
|
describe('draw', () => {
|
|
it('sets search results buffer', () => {
|
|
const context = makeContextMock();
|
|
const canvas = makeCanvasMock({
|
|
getContext: jest.fn().mockReturnValue(context),
|
|
}) as HTMLCanvasElement;
|
|
|
|
const flamegraph = makeFlamegraph(
|
|
{
|
|
startValue: 0,
|
|
endValue: 100,
|
|
events: [
|
|
{
|
|
type: 'O',
|
|
frame: 0,
|
|
at: 0,
|
|
},
|
|
{
|
|
type: 'C',
|
|
frame: 0,
|
|
at: 1,
|
|
},
|
|
{
|
|
type: 'O',
|
|
frame: 1,
|
|
at: 1,
|
|
},
|
|
{
|
|
type: 'C',
|
|
frame: 1,
|
|
at: 2,
|
|
},
|
|
],
|
|
},
|
|
[{name: 'f0'}, {name: 'f1'}]
|
|
);
|
|
|
|
const results: FlamegraphSearch['results']['frames'] = new Map();
|
|
|
|
const flamegraphCanvas = new FlamegraphCanvas(canvas, vec2.fromValues(0, 0));
|
|
const flamegraphView = new CanvasView<Flamegraph>({
|
|
canvas: flamegraphCanvas,
|
|
model: flamegraph,
|
|
options: {
|
|
inverted: flamegraph.inverted,
|
|
minWidth: flamegraph.profile.minFrameDuration,
|
|
barHeight: theme.SIZES.BAR_HEIGHT,
|
|
depthOffset: theme.SIZES.FLAMEGRAPH_DEPTH_OFFSET,
|
|
},
|
|
});
|
|
const renderer = new FlamegraphRendererWebGL(canvas, flamegraph, theme);
|
|
|
|
// @ts-expect-error we only need a partial frame mock
|
|
results.set(getFlamegraphFrameSearchId(flamegraph.frames[0]), {});
|
|
renderer.setSearchResults('query', results);
|
|
|
|
expect(JSON.stringify(renderer.searchResults.slice(0, 6))).toEqual(
|
|
JSON.stringify(new Float32Array([1, 1, 1, 1, 1, 1]))
|
|
);
|
|
|
|
renderer.draw(flamegraphView.fromConfigView(flamegraphCanvas.physicalSpace));
|
|
expect(context.drawArrays).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('draws all frames', () => {
|
|
const context = makeContextMock();
|
|
const canvas = makeCanvasMock({
|
|
getContext: jest.fn().mockReturnValue(context),
|
|
}) as HTMLCanvasElement;
|
|
|
|
const flamegraph = makeFlamegraph(
|
|
{
|
|
startValue: 0,
|
|
endValue: 100,
|
|
events: [
|
|
{
|
|
type: 'O',
|
|
frame: 0,
|
|
at: 0,
|
|
},
|
|
{
|
|
type: 'C',
|
|
frame: 0,
|
|
at: 1,
|
|
},
|
|
{
|
|
type: 'O',
|
|
frame: 1,
|
|
at: 1,
|
|
},
|
|
{
|
|
type: 'C',
|
|
frame: 1,
|
|
at: 2,
|
|
},
|
|
],
|
|
},
|
|
[{name: 'f0'}, {name: 'f1'}]
|
|
);
|
|
|
|
const flamegraphCanvas = new FlamegraphCanvas(canvas, vec2.fromValues(0, 0));
|
|
const flamegraphView = new CanvasView<Flamegraph>({
|
|
canvas: flamegraphCanvas,
|
|
model: flamegraph,
|
|
options: {
|
|
inverted: flamegraph.inverted,
|
|
minWidth: flamegraph.profile.minFrameDuration,
|
|
barHeight: theme.SIZES.BAR_HEIGHT,
|
|
depthOffset: theme.SIZES.FLAMEGRAPH_DEPTH_OFFSET,
|
|
},
|
|
});
|
|
const renderer = new FlamegraphRendererWebGL(canvas, flamegraph, theme);
|
|
|
|
renderer.draw(flamegraphView.fromConfigView(flamegraphCanvas.physicalSpace));
|
|
expect(context.drawArrays).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
});
|