github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/packages/pyroscope-flamegraph/src/FlameGraph/FlameGraphComponent/Flamegraph.spec.ts (about) 1 import { Maybe } from 'true-myth'; 2 import Flamegraph from './Flamegraph'; 3 import { BAR_HEIGHT } from './constants'; 4 import { DefaultPalette } from './colorPalette'; 5 import TestData from './testData'; 6 7 jest.mock('./Flamegraph_render'); 8 const throwUnwrapErr = () => { 9 throw new Error('Failed to unwrap'); 10 }; 11 12 type focusedNodeType = ConstructorParameters<typeof Flamegraph>[2]; 13 type zoomType = ConstructorParameters<typeof Flamegraph>[5]; 14 15 describe('Flamegraph', () => { 16 let canvas: any; 17 let flame: Flamegraph; 18 const CANVAS_WIDTH = 600; 19 const CANVAS_HEIGHT = 300; 20 21 describe('isWithinBounds', () => { 22 beforeEach(() => { 23 canvas = document.createElement('canvas'); 24 canvas.width = CANVAS_WIDTH; 25 canvas.height = CANVAS_HEIGHT; 26 27 const fitMode = 'HEAD'; 28 const highlightQuery = ''; 29 const focusedNode: focusedNodeType = Maybe.nothing(); 30 const zoom = Maybe.of({ i: 2, j: 8 }); 31 32 flame = new Flamegraph( 33 TestData.ComplexTree, 34 canvas, 35 focusedNode, 36 fitMode, 37 highlightQuery, 38 zoom, 39 DefaultPalette 40 ); 41 42 flame.render(); 43 }); 44 45 it('handles within canvas', () => { 46 expect(flame.isWithinBounds(0, 0)).toBe(true); 47 expect(flame.isWithinBounds(CANVAS_WIDTH - 1, 0)).toBe(true); 48 expect(flame.isWithinBounds(-1, 0)).toBe(false); 49 expect(flame.isWithinBounds(0, -1)).toBe(false); 50 expect(flame.isWithinBounds(-1, -1)).toBe(false); 51 }); 52 53 it('handles within canvas but outside the flamegraph', () => { 54 // this test is a bit difficult to visually 55 // you just have to know that it has the format such as 56 // 57 // | | (level 3) 58 // |_| (level 4) 59 // (level 5) 60 expect(flame.isWithinBounds(0, BAR_HEIGHT * 3 + 1)).toBe(true); 61 expect(flame.isWithinBounds(0, BAR_HEIGHT * 4 + 1)).toBe(true); 62 expect(flame.isWithinBounds(0, BAR_HEIGHT * 5 + 1)).toBe(false); 63 }); 64 }); 65 66 describe('xyToBarData', () => { 67 describe('normal', () => { 68 beforeAll(() => { 69 canvas = document.createElement('canvas'); 70 canvas.width = CANVAS_WIDTH; 71 canvas.height = CANVAS_HEIGHT; 72 73 const fitMode = 'HEAD'; 74 const highlightQuery = ''; 75 const zoom: zoomType = Maybe.nothing(); 76 const focusedNode: focusedNodeType = Maybe.nothing(); 77 78 flame = new Flamegraph( 79 TestData.SimpleTree, 80 canvas, 81 focusedNode, 82 fitMode, 83 highlightQuery, 84 zoom, 85 DefaultPalette 86 ); 87 88 flame.render(); 89 }); 90 91 it('works with the first bar (total)', () => { 92 const got = flame.xyToBar(0, 0).unwrapOrElse(throwUnwrapErr); 93 94 expect(got.x).toBe(0); 95 expect(got.y).toBe(0); 96 expect(got.width).toBeCloseTo(CANVAS_WIDTH); 97 }); 98 99 it('works a full bar (runtime.main)', () => { 100 // 2nd line, 101 const got = flame 102 .xyToBar(0, BAR_HEIGHT + 1) 103 .unwrapOrElse(throwUnwrapErr); 104 105 expect(got.x).toBe(0); 106 expect(got.y).toBe(22); 107 expect(got.width).toBeCloseTo(CANVAS_WIDTH); 108 }); 109 110 it('works with (main.fastFunction)', () => { 111 // 3nd line, 'slowFunction' 112 const got = flame 113 .xyToBar(1, BAR_HEIGHT * 2 + 1) 114 .unwrapOrElse(throwUnwrapErr); 115 116 expect(got.x).toBe(0); 117 expect(got.y).toBe(44); 118 expect(got.width).toBeCloseTo(129.95951417004048); 119 }); 120 121 it('works with (main.slowFunction)', () => { 122 // 3nd line, 'slowFunction' 123 const got = flame 124 .xyToBar(CANVAS_WIDTH - 1, BAR_HEIGHT * 2 + 1) 125 .unwrapOrElse(throwUnwrapErr); 126 127 expect(got.x).toBeCloseTo(131.78); 128 expect(got.y).toBe(44); 129 expect(got.width).toBeCloseTo(468.218); 130 }); 131 132 describe('boundary testing', () => { 133 const cases = [ 134 [0, 0], 135 [CANVAS_WIDTH, 0], 136 [1, BAR_HEIGHT], 137 [CANVAS_WIDTH, BAR_HEIGHT], 138 [CANVAS_WIDTH / 2, BAR_HEIGHT / 2], 139 ]; 140 test.each(cases)( 141 'given %p and %p as arguments, returns the total bar', 142 (i: number, j: number) => { 143 const got = flame.xyToBar(i, j).unwrapOrElse(throwUnwrapErr); 144 expect(got).toMatchObject({ 145 i: 0, 146 j: 0, 147 x: 0, 148 y: 0, 149 }); 150 151 expect(got.width).toBeCloseTo(CANVAS_WIDTH); 152 } 153 ); 154 }); 155 }); 156 157 describe('focused', () => { 158 describe('on the first row (runtime.main)', () => { 159 beforeAll(() => { 160 canvas = document.createElement('canvas'); 161 canvas.width = CANVAS_WIDTH; 162 canvas.height = CANVAS_HEIGHT; 163 164 const fitMode = 'HEAD'; 165 const highlightQuery = ''; 166 const zoom: zoomType = Maybe.nothing(); 167 const focusedNode = Maybe.just({ i: 1, j: 0 }); 168 169 flame = new Flamegraph( 170 TestData.SimpleTree, 171 canvas, 172 focusedNode, 173 fitMode, 174 highlightQuery, 175 zoom, 176 DefaultPalette 177 ); 178 179 flame.render(); 180 }); 181 182 it('works with the first bar (total)', () => { 183 const got = flame.xyToBar(0, 0).unwrapOrElse(throwUnwrapErr); 184 185 expect(got.x).toBe(0); 186 expect(got.y).toBe(0); 187 expect(got.width).toBeCloseTo(CANVAS_WIDTH); 188 }); 189 190 it('works with a full bar (runtime.main)', () => { 191 // 2nd line, 192 const got = flame 193 .xyToBar(0, BAR_HEIGHT + 1) 194 .unwrapOrElse(throwUnwrapErr); 195 196 expect(got).toMatchObject({ 197 i: 1, 198 j: 0, 199 x: 0, 200 y: 22, 201 }); 202 203 expect(got.width).toBeCloseTo(CANVAS_WIDTH); 204 }); 205 // 206 // 207 it('works with (main.fastFunction)', () => { 208 // 3nd line, 'slowFunction' 209 const got = flame 210 .xyToBar(1, BAR_HEIGHT * 2 + 1) 211 .unwrapOrElse(throwUnwrapErr); 212 213 expect(got).toMatchObject({ 214 i: 2, 215 j: 0, 216 x: 0, 217 y: 44, 218 }); 219 220 expect(got.width).toBeCloseTo(129.95951417004048); 221 }); 222 // 223 it('works with (main.slowFunction)', () => { 224 // 3nd line, 'slowFunction' 225 const got = flame 226 .xyToBar(CANVAS_WIDTH - 1, BAR_HEIGHT * 2 + 1) 227 .unwrapOrElse(throwUnwrapErr); 228 229 expect(got).toMatchObject({ 230 i: 2, 231 j: 8, 232 }); 233 expect(got.x).toBeCloseTo(131.78); 234 expect(got.y).toBe(44); 235 expect(got.width).toBeCloseTo(468.218); 236 }); 237 }); 238 239 describe('on main.slowFunction', () => { 240 beforeAll(() => { 241 canvas = document.createElement('canvas'); 242 canvas.width = CANVAS_WIDTH; 243 canvas.height = CANVAS_HEIGHT; 244 245 const fitMode = 'HEAD'; 246 const highlightQuery = ''; 247 const zoom: zoomType = Maybe.nothing(); 248 const focusedNode = Maybe.just({ i: 2, j: 8 }); 249 250 flame = new Flamegraph( 251 TestData.SimpleTree, 252 canvas, 253 focusedNode, 254 fitMode, 255 highlightQuery, 256 zoom, 257 DefaultPalette 258 ); 259 260 flame.render(); 261 }); 262 263 it('works with the first row (total)', () => { 264 const got = flame.xyToBar(0, 0).unwrapOrElse(throwUnwrapErr); 265 expect(got.x).toBe(0); 266 expect(got.y).toBe(0); 267 expect(got.width).toBeCloseTo(CANVAS_WIDTH); 268 }); 269 270 it('works with itself as second row (main.slowFunction)', () => { 271 // 2nd line, 272 const got = flame 273 .xyToBar(1, BAR_HEIGHT + 1) 274 .unwrapOrElse(throwUnwrapErr); 275 276 expect(got).toMatchObject({ 277 i: 2, 278 j: 8, 279 x: 0, 280 y: 22, 281 }); 282 283 expect(got.width).toBeCloseTo(CANVAS_WIDTH); 284 }); 285 286 it('works with its child as third row (main.work)', () => { 287 // 2nd line, 288 const got = flame 289 .xyToBar(1, BAR_HEIGHT * 2 + 1) 290 .unwrapOrElse(throwUnwrapErr); 291 292 expect(got).toMatchObject({ 293 i: 3, 294 j: 8, 295 x: 0, 296 y: 44, 297 }); 298 299 expect(got.width).toBeCloseTo(CANVAS_WIDTH); 300 }); 301 }); 302 }); 303 304 describe('zoomed', () => { 305 describe('on the first row (runtime.main)', () => { 306 beforeAll(() => { 307 canvas = document.createElement('canvas'); 308 canvas.width = CANVAS_WIDTH; 309 canvas.height = CANVAS_HEIGHT; 310 311 const fitMode = 'HEAD'; 312 const highlightQuery = ''; 313 314 const zoom: zoomType = Maybe.of({ i: 1, j: 0 }); 315 const focusedNode: focusedNodeType = Maybe.nothing(); 316 317 flame = new Flamegraph( 318 TestData.SimpleTree, 319 canvas, 320 focusedNode, 321 fitMode, 322 highlightQuery, 323 zoom, 324 DefaultPalette 325 ); 326 327 flame.render(); 328 }); 329 330 it('works with the first bar (total)', () => { 331 const got = flame.xyToBar(0, 0).unwrapOrElse(throwUnwrapErr); 332 expect(got.x).toBe(0); 333 expect(got.y).toBe(0); 334 expect(got.width).toBeCloseTo(CANVAS_WIDTH); 335 }); 336 // 337 it('works with a full bar (runtime.main)', () => { 338 // 2nd line, 339 const got = flame 340 .xyToBar(0, BAR_HEIGHT + 1) 341 .unwrapOrElse(throwUnwrapErr); 342 343 expect(got).toMatchObject({ 344 i: 1, 345 j: 0, 346 x: 0, 347 y: 22, 348 }); 349 350 expect(got.width).toBeCloseTo(CANVAS_WIDTH); 351 }); 352 // 353 // 354 it('works with (main.fastFunction)', () => { 355 // 3nd line, 'slowFunction' 356 const got = flame 357 .xyToBar(1, BAR_HEIGHT * 2 + 1) 358 .unwrapOrElse(throwUnwrapErr); 359 360 expect(got).toMatchObject({ 361 i: 2, 362 j: 0, 363 x: 0, 364 y: 44, 365 }); 366 367 expect(got.width).toBeCloseTo(129.95951417004048); 368 }); 369 // 370 it('works with (main.slowFunction)', () => { 371 // 3nd line, 'slowFunction' 372 const got = flame 373 .xyToBar(CANVAS_WIDTH - 1, BAR_HEIGHT * 2 + 1) 374 .unwrapOrElse(throwUnwrapErr); 375 376 expect(got).toMatchObject({ 377 i: 2, 378 j: 8, 379 }); 380 expect(got.x).toBeCloseTo(131.78); 381 expect(got.y).toBe(44); 382 expect(got.width).toBeCloseTo(468.218); 383 }); 384 }); 385 386 describe('on main.slowFunction', () => { 387 beforeAll(() => { 388 canvas = document.createElement('canvas'); 389 canvas.width = CANVAS_WIDTH; 390 canvas.height = CANVAS_HEIGHT; 391 392 const fitMode = 'HEAD'; 393 const highlightQuery = ''; 394 const zoom = Maybe.of({ i: 2, j: 8 }); 395 const focusedNode: focusedNodeType = Maybe.nothing(); 396 397 flame = new Flamegraph( 398 TestData.SimpleTree, 399 canvas, 400 focusedNode, 401 fitMode, 402 highlightQuery, 403 zoom, 404 DefaultPalette 405 ); 406 407 flame.render(); 408 }); 409 410 it('works with the first bar (total)', () => { 411 const got = flame.xyToBar(0, 0).unwrapOrElse(throwUnwrapErr); 412 expect(got.x).toBe(0); 413 expect(got.y).toBe(0); 414 expect(got.width).toBeCloseTo(CANVAS_WIDTH); 415 }); 416 // 417 it('works with a full bar (runtime.main)', () => { 418 // 2nd line, 419 const got = flame 420 .xyToBar(0, BAR_HEIGHT + 1) 421 .unwrapOrElse(throwUnwrapErr); 422 423 expect(got).toMatchObject({ 424 i: 1, 425 j: 0, 426 x: 0, 427 y: 22, 428 }); 429 430 expect(got.width).toBeCloseTo(CANVAS_WIDTH); 431 }); 432 // 433 // 434 it('works with (main.slowFunction)', () => { 435 // 3nd line, 'slowFunction' 436 const got = flame 437 .xyToBar(1, BAR_HEIGHT * 2 + 1) 438 .unwrapOrElse(throwUnwrapErr); 439 440 expect(got).toMatchObject({ 441 i: 2, 442 j: 8, 443 x: 0, 444 y: 44, 445 }); 446 447 expect(got.width).toBeCloseTo(CANVAS_WIDTH); 448 }); 449 450 it('works with main.work (child of main.slowFunction)', () => { 451 // 4th line, 'main.work' 452 // TODO why 2?? 453 const got = flame 454 .xyToBar(1, BAR_HEIGHT * 3 + 2) 455 .unwrapOrElse(throwUnwrapErr); 456 457 expect(got).toMatchObject({ 458 i: 3, 459 j: 8, 460 x: 0, 461 y: 66, 462 }); 463 expect(got.width).toBeCloseTo(CANVAS_WIDTH); 464 }); 465 }); 466 }); 467 468 describe('focused+zoomed', () => { 469 describe('focused on the first row (runtime.main), zoomed on the third row (main.slowFunction)', () => { 470 beforeAll(() => { 471 canvas = document.createElement('canvas'); 472 canvas.width = CANVAS_WIDTH; 473 canvas.height = CANVAS_HEIGHT; 474 475 const fitMode = 'HEAD'; 476 const highlightQuery = ''; 477 const zoom = Maybe.of({ i: 2, j: 8 }); 478 const focusedNode = Maybe.of({ i: 1, j: 0 }); 479 480 flame = new Flamegraph( 481 TestData.SimpleTree, 482 canvas, 483 focusedNode, 484 fitMode, 485 highlightQuery, 486 zoom, 487 DefaultPalette 488 ); 489 490 flame.render(); 491 }); 492 493 it('works with the first bar (total)', () => { 494 const got = flame.xyToBar(0, 0).unwrapOrElse(throwUnwrapErr); 495 expect(got).toMatchObject({ 496 x: 0, 497 y: 0, 498 i: 0, 499 j: 0, 500 }); 501 expect(got.width).toBeCloseTo(CANVAS_WIDTH); 502 }); 503 504 it('works with a full bar (runtime.main)', () => { 505 // 2nd line, 506 const got = flame 507 .xyToBar(0, BAR_HEIGHT + 1) 508 .unwrapOrElse(throwUnwrapErr); 509 510 expect(got).toMatchObject({ 511 i: 1, 512 j: 0, 513 x: 0, 514 y: 22, 515 }); 516 517 expect(got.width).toBeCloseTo(CANVAS_WIDTH); 518 }); 519 520 it('works with (main.slowFunction)', () => { 521 // 3nd line, 'slowFunction' 522 const got = flame 523 .xyToBar(1, BAR_HEIGHT * 2 + 1) 524 .unwrapOrElse(throwUnwrapErr); 525 526 expect(got).toMatchObject({ 527 i: 2, 528 j: 8, 529 x: 0, 530 y: 44, 531 }); 532 533 expect(got.width).toBeCloseTo(CANVAS_WIDTH); 534 }); 535 it('works with (main.slowFunction)', () => { 536 // 3nd line, 'slowFunction' 537 const got = flame 538 .xyToBar(1, BAR_HEIGHT * 3 + 1) 539 .unwrapOrElse(throwUnwrapErr); 540 541 expect(got).toMatchObject({ 542 i: 3, 543 j: 8, 544 x: 0, 545 y: 66, 546 }); 547 expect(got.width).toBeCloseTo(CANVAS_WIDTH); 548 }); 549 }); 550 }); 551 }); 552 });