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  });