@kylincloud/flamegraph 0.35.27 → 0.35.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/FlameGraph/FlameGraphComponent/DiffLegend.d.ts.map +1 -1
  3. package/dist/FlameGraph/FlameGraphComponent/DiffLegendPaletteDropdown.d.ts.map +1 -1
  4. package/dist/FlameGraph/FlameGraphComponent/Flamegraph.d.ts +16 -2
  5. package/dist/FlameGraph/FlameGraphComponent/Flamegraph.d.ts.map +1 -1
  6. package/dist/FlameGraph/FlameGraphComponent/Flamegraph_render.d.ts +15 -2
  7. package/dist/FlameGraph/FlameGraphComponent/Flamegraph_render.d.ts.map +1 -1
  8. package/dist/FlameGraph/FlameGraphComponent/Highlight.d.ts.map +1 -1
  9. package/dist/FlameGraph/FlameGraphComponent/index.d.ts.map +1 -1
  10. package/dist/FlameGraph/normalize.d.ts.map +1 -1
  11. package/dist/FlameGraph/uniqueness.d.ts.map +1 -1
  12. package/dist/Icons.d.ts.map +1 -1
  13. package/dist/ProfilerTable.d.ts.map +1 -1
  14. package/dist/SharedQueryInput.d.ts.map +1 -1
  15. package/dist/Toolbar.d.ts.map +1 -1
  16. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  17. package/dist/flamegraphRenderWorker.js +2 -0
  18. package/dist/flamegraphRenderWorker.js.map +1 -0
  19. package/dist/index.cjs.js +4 -4
  20. package/dist/index.cjs.js.map +1 -1
  21. package/dist/index.esm.js +4 -4
  22. package/dist/index.esm.js.map +1 -1
  23. package/dist/index.node.cjs.js +4 -4
  24. package/dist/index.node.cjs.js.map +1 -1
  25. package/dist/index.node.esm.js +4 -4
  26. package/dist/index.node.esm.js.map +1 -1
  27. package/dist/shims/Table.d.ts +15 -1
  28. package/dist/shims/Table.d.ts.map +1 -1
  29. package/dist/shims/Tooltip.d.ts.map +1 -1
  30. package/dist/workers/createFlamegraphRenderWorker.d.ts +2 -0
  31. package/dist/workers/createFlamegraphRenderWorker.d.ts.map +1 -0
  32. package/dist/workers/flamegraphRenderWorker.d.ts +2 -0
  33. package/dist/workers/flamegraphRenderWorker.d.ts.map +1 -0
  34. package/dist/workers/profilerTableWorker.d.ts +73 -0
  35. package/dist/workers/profilerTableWorker.d.ts.map +1 -0
  36. package/package.json +1 -1
  37. package/src/FlameGraph/FlameGraphComponent/DiffLegend.module.css +8 -2
  38. package/src/FlameGraph/FlameGraphComponent/DiffLegend.tsx +12 -1
  39. package/src/FlameGraph/FlameGraphComponent/DiffLegendPaletteDropdown.module.css +93 -10
  40. package/src/FlameGraph/FlameGraphComponent/DiffLegendPaletteDropdown.tsx +9 -4
  41. package/src/FlameGraph/FlameGraphComponent/Flamegraph.ts +33 -8
  42. package/src/FlameGraph/FlameGraphComponent/Flamegraph_render.ts +289 -85
  43. package/src/FlameGraph/FlameGraphComponent/Highlight.tsx +43 -17
  44. package/src/FlameGraph/FlameGraphComponent/index.tsx +208 -57
  45. package/src/FlameGraph/FlameGraphComponent/styles.module.scss +8 -0
  46. package/src/FlameGraph/normalize.ts +9 -7
  47. package/src/FlameGraph/uniqueness.ts +69 -59
  48. package/src/Icons.tsx +18 -9
  49. package/src/ProfilerTable.tsx +463 -33
  50. package/src/SharedQueryInput.module.scss +50 -0
  51. package/src/SharedQueryInput.tsx +18 -3
  52. package/src/Toolbar.module.scss +90 -0
  53. package/src/Toolbar.tsx +30 -16
  54. package/src/Tooltip/Tooltip.tsx +49 -16
  55. package/src/i18n.tsx +1 -1
  56. package/src/sass/_common.scss +22 -3
  57. package/src/sass/_css-variables.scss +5 -1
  58. package/src/sass/flamegraph.scss +26 -23
  59. package/src/shims/Table.module.scss +91 -13
  60. package/src/shims/Table.tsx +202 -7
  61. package/src/shims/Tooltip.module.scss +40 -0
  62. package/src/shims/Tooltip.tsx +31 -3
  63. package/src/workers/createFlamegraphRenderWorker.ts +7 -0
  64. package/src/workers/flamegraphRenderWorker.ts +198 -0
  65. package/src/workers/profilerTableWorker.ts +368 -0
@@ -1,6 +1,6 @@
1
1
  // src/FlameGraph/FlameGraphComponent/index.tsx
2
2
  /* eslint-disable no-unused-expressions, import/no-extraneous-dependencies */
3
- import React, { useCallback, useRef, useMemo } from 'react';
3
+ import React, { useCallback, useRef, useMemo, useEffect } from 'react';
4
4
  import clsx from 'clsx';
5
5
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
6
6
  import { faRedo } from '@fortawesome/free-solid-svg-icons/faRedo';
@@ -28,6 +28,8 @@ import type { ViewTypes } from './viewTypes';
28
28
  import { FitModes, HeadMode, TailMode } from '../../fitMode/fitMode';
29
29
  import indexStyles from './styles.module.scss';
30
30
  import { useFlamegraphI18n } from '../../i18n';
31
+ import { createFlamegraphRenderWorker } from '../../workers/createFlamegraphRenderWorker';
32
+ import type { CanvasI18nMessages } from './Flamegraph_render';
31
33
 
32
34
  interface FlamegraphProps {
33
35
  flamebearer: Flamebearer;
@@ -78,11 +80,20 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
78
80
  const textCanvasRef = React.useRef<HTMLCanvasElement>(null);
79
81
  const flamegraph = useRef<Flamegraph>();
80
82
  const flamegraphText = useRef<Flamegraph>();
83
+ const renderSeq = useRef(0);
84
+ const renderWorkerRef = useRef<Worker | null>(null);
85
+ const rectOffscreenRef = useRef<OffscreenCanvas | null>(null);
86
+ const textOffscreenRef = useRef<OffscreenCanvas | null>(null);
87
+ const workerReadyRef = useRef({ rect: false, text: false });
81
88
  const i18n = useFlamegraphI18n();
82
89
  const resizeLogRef = useRef({
83
90
  lastWidth: 0,
84
91
  lastHeight: 0,
85
92
  });
93
+ const useRenderWorker =
94
+ typeof window !== 'undefined' &&
95
+ typeof OffscreenCanvas !== 'undefined' &&
96
+ 'transferControlToOffscreen' in HTMLCanvasElement.prototype;
86
97
 
87
98
  // ====== 新增:提取 canvas 渲染需要的 i18n messages ======
88
99
  const canvasMessages = useMemo(
@@ -94,6 +105,19 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
94
105
  [i18n.collapsedLevelsSingular, i18n.collapsedLevelsPlural, i18n.location]
95
106
  );
96
107
 
108
+ const serializePalette = useCallback(
109
+ (p: FlamegraphPalette) => ({
110
+ name: p.name,
111
+ goodColor: p.goodColor.rgb().array() as [number, number, number],
112
+ neutralColor: p.neutralColor.rgb().array() as [number, number, number],
113
+ badColor: p.badColor.rgb().array() as [number, number, number],
114
+ colors: p.colors.map(
115
+ (c) => c.rgb().array() as [number, number, number]
116
+ ),
117
+ }),
118
+ []
119
+ );
120
+
97
121
  const [rightClickedNode, setRightClickedNode] = React.useState<
98
122
  Maybe<{ top: number; left: number; width: number }>
99
123
  >(Maybe.nothing());
@@ -129,6 +153,29 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
129
153
  []
130
154
  );
131
155
 
156
+ useEffect(() => {
157
+ if (!useRenderWorker) {
158
+ return () => {};
159
+ }
160
+ const worker = createFlamegraphRenderWorker();
161
+ renderWorkerRef.current = worker;
162
+ // eslint-disable-next-line no-console
163
+ console.debug('[flamegraph] render worker created');
164
+ setTimeout(() => {
165
+ renderRectCanvas();
166
+ renderTextCanvas();
167
+ }, 0);
168
+ return () => {
169
+ worker.terminate();
170
+ renderWorkerRef.current = null;
171
+ rectOffscreenRef.current = null;
172
+ textOffscreenRef.current = null;
173
+ workerReadyRef.current = { rect: false, text: false };
174
+ // eslint-disable-next-line no-console
175
+ console.debug('[flamegraph] render worker terminated');
176
+ };
177
+ }, [useRenderWorker]);
178
+
132
179
  useResizeObserver(canvasRef, () => {
133
180
  if (flamegraph) {
134
181
  if (canvasRef.current) {
@@ -328,6 +375,10 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
328
375
  );
329
376
 
330
377
  const constructCanvas = () => {
378
+ const seq = renderSeq.current + 1;
379
+ renderSeq.current = seq;
380
+ // eslint-disable-next-line no-console
381
+ console.time(`[flamegraph] constructCanvas #${seq}`);
331
382
  if (canvasRef.current) {
332
383
  const f = new Flamegraph(
333
384
  flamebearer,
@@ -355,8 +406,49 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
355
406
  );
356
407
  flamegraphText.current = f;
357
408
  }
409
+ // eslint-disable-next-line no-console
410
+ console.timeEnd(`[flamegraph] constructCanvas #${seq}`);
358
411
  };
359
412
 
413
+ const ensureRenderWorkerCanvases = useCallback(() => {
414
+ if (!useRenderWorker || !renderWorkerRef.current) {
415
+ return false;
416
+ }
417
+ const worker = renderWorkerRef.current;
418
+
419
+ if (canvasRef.current && !rectOffscreenRef.current) {
420
+ try {
421
+ const offscreen = canvasRef.current.transferControlToOffscreen();
422
+ rectOffscreenRef.current = offscreen;
423
+ worker.postMessage(
424
+ { type: 'init', payload: { kind: 'rect', canvas: offscreen } },
425
+ [offscreen]
426
+ );
427
+ workerReadyRef.current.rect = true;
428
+ } catch (err) {
429
+ // eslint-disable-next-line no-console
430
+ console.debug('[flamegraph] rect transfer failed', err);
431
+ }
432
+ }
433
+
434
+ if (textCanvasRef.current && !textOffscreenRef.current) {
435
+ try {
436
+ const offscreen = textCanvasRef.current.transferControlToOffscreen();
437
+ textOffscreenRef.current = offscreen;
438
+ worker.postMessage(
439
+ { type: 'init', payload: { kind: 'text', canvas: offscreen } },
440
+ [offscreen]
441
+ );
442
+ workerReadyRef.current.text = true;
443
+ } catch (err) {
444
+ // eslint-disable-next-line no-console
445
+ console.debug('[flamegraph] text transfer failed', err);
446
+ }
447
+ }
448
+
449
+ return true;
450
+ }, [useRenderWorker]);
451
+
360
452
  // ====== 修改:添加 canvasMessages 依赖 ======
361
453
  React.useEffect(() => {
362
454
  constructCanvas();
@@ -382,16 +474,73 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
382
474
  renderTextCanvas();
383
475
  }, [highlightQuery]);
384
476
 
477
+ const postWorkerRender = (
478
+ kind: 'rect' | 'text',
479
+ options: { renderRects?: boolean; renderText?: boolean }
480
+ ) => {
481
+ if (!useRenderWorker) {
482
+ return false;
483
+ }
484
+ if (!renderWorkerRef.current) {
485
+ // eslint-disable-next-line no-console
486
+ console.debug('[flamegraph] worker not ready yet');
487
+ return true;
488
+ }
489
+ if (!ensureRenderWorkerCanvases()) {
490
+ return true;
491
+ }
492
+ const canvasEl =
493
+ kind === 'rect' ? canvasRef.current : textCanvasRef.current;
494
+ const width = canvasEl?.clientWidth || canvasEl?.width || 0;
495
+ const payload = {
496
+ kind,
497
+ flamebearer,
498
+ focusedNode: focusedNode.isJust ? focusedNode.value : null,
499
+ fitMode,
500
+ highlightQuery,
501
+ zoom: zoom.isJust ? zoom.value : null,
502
+ palette: serializePalette(palette),
503
+ messages: canvasMessages as CanvasI18nMessages,
504
+ renderRects: options.renderRects,
505
+ renderText: options.renderText,
506
+ width,
507
+ devicePixelRatio:
508
+ typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1,
509
+ };
510
+ renderWorkerRef.current.postMessage({ type: 'render', payload });
511
+ return true;
512
+ };
513
+
385
514
  const renderRectCanvas = () => {
515
+ const seq = renderSeq.current;
516
+ if (postWorkerRender('rect', { renderText: false })) {
517
+ // eslint-disable-next-line no-console
518
+ console.debug(`[flamegraph] renderRects via worker #${seq}`);
519
+ return;
520
+ }
521
+ // eslint-disable-next-line no-console
522
+ console.time(`[flamegraph] renderRects #${seq}`);
386
523
  canvasRef?.current?.setAttribute('data-state', 'rendering');
387
524
  flamegraph?.current?.render({ renderText: false });
388
525
  canvasRef?.current?.setAttribute('data-state', 'rendered');
526
+ // eslint-disable-next-line no-console
527
+ console.timeEnd(`[flamegraph] renderRects #${seq}`);
389
528
  };
390
529
 
391
530
  const renderTextCanvas = () => {
531
+ const seq = renderSeq.current;
532
+ if (postWorkerRender('text', { renderRects: false })) {
533
+ // eslint-disable-next-line no-console
534
+ console.debug(`[flamegraph] renderText via worker #${seq}`);
535
+ return;
536
+ }
537
+ // eslint-disable-next-line no-console
538
+ console.time(`[flamegraph] renderText #${seq}`);
392
539
  textCanvasRef?.current?.setAttribute('data-state', 'rendering');
393
540
  flamegraphText?.current?.render({ renderRects: false });
394
541
  textCanvasRef?.current?.setAttribute('data-state', 'rendered');
542
+ // eslint-disable-next-line no-console
543
+ console.timeEnd(`[flamegraph] renderText #${seq}`);
395
544
  };
396
545
 
397
546
  const dataUnavailable =
@@ -418,67 +567,69 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
418
567
  'vertical-orientation': flamebearer.format === 'double',
419
568
  })}
420
569
  >
421
- {breadcrumb && (
422
- <FlamegraphBreadcrumb
423
- totalValueText={breadcrumb.totalValueText}
424
- totalSamplesText={breadcrumb.totalSamplesText}
425
- samplesLabel={breadcrumb.samplesLabel}
426
- unitLabel={breadcrumb.unitLabel}
427
- hasFocus={breadcrumb.hasFocus}
428
- focusPercent={breadcrumb.focusPercent}
429
- focusLabel={breadcrumb.focusLabel}
430
- ofTotalPlacement={breadcrumb.ofTotalPlacement}
431
- ofTotalLabel={breadcrumb.ofTotalLabel}
432
- clearFocusLabel={breadcrumb.clearFocusLabel}
433
- onClearFocus={breadcrumb.onClearFocus}
434
- />
435
- )}
436
- {/* Header 已简化,仅在 single 模式下显示标题 */}
437
- {/* DiffLegend 已移到 Toolbar */}
438
- {headerVisible && (
439
- <Header
440
- format={flamebearer.format}
441
- units={flamebearer.units}
442
- toolbarVisible={toolbarVisible}
443
- />
444
- )}
445
- <div
446
- data-testid={dataTestId}
447
- style={{
448
- position: 'relative',
449
- opacity: dataUnavailable && !showSingleLevel ? 0 : 1,
450
- }}
451
- >
452
- <canvas
453
- height="0"
454
- data-testid="flamegraph-canvas"
455
- data-highlightquery={highlightQuery}
456
- className={clsx('flamegraph-canvas', styles.canvas)}
457
- ref={canvasRef}
458
- onClick={!disableClick ? onClick : undefined}
459
- />
460
- <canvas
461
- height="0"
462
- data-testid="flamegraph-text-canvas"
463
- className={clsx('flamegraph-text-canvas', styles.textCanvas)}
464
- ref={textCanvasRef}
465
- />
466
- {flamegraph && canvasRef && (
467
- <Highlight
468
- barHeight={PX_PER_LEVEL}
469
- canvasRef={canvasRef}
470
- zoom={zoom}
471
- xyToHighlightData={xyToHighlightData}
570
+ <div className={indexStyles.flamegraphFrame}>
571
+ {breadcrumb && (
572
+ <FlamegraphBreadcrumb
573
+ totalValueText={breadcrumb.totalValueText}
574
+ totalSamplesText={breadcrumb.totalSamplesText}
575
+ samplesLabel={breadcrumb.samplesLabel}
576
+ unitLabel={breadcrumb.unitLabel}
577
+ hasFocus={breadcrumb.hasFocus}
578
+ focusPercent={breadcrumb.focusPercent}
579
+ focusLabel={breadcrumb.focusLabel}
580
+ ofTotalPlacement={breadcrumb.ofTotalPlacement}
581
+ ofTotalLabel={breadcrumb.ofTotalLabel}
582
+ clearFocusLabel={breadcrumb.clearFocusLabel}
583
+ onClearFocus={breadcrumb.onClearFocus}
472
584
  />
473
585
  )}
474
- {flamegraph && (
475
- <ContextMenuHighlight
476
- barHeight={PX_PER_LEVEL}
477
- node={rightClickedNode}
586
+ {/* Header 已简化,仅在 single 模式下显示标题 */}
587
+ {/* DiffLegend 已移到 Toolbar */}
588
+ {headerVisible && (
589
+ <Header
590
+ format={flamebearer.format}
591
+ units={flamebearer.units}
592
+ toolbarVisible={toolbarVisible}
478
593
  />
479
594
  )}
595
+ <div
596
+ data-testid={dataTestId}
597
+ style={{
598
+ position: 'relative',
599
+ opacity: dataUnavailable && !showSingleLevel ? 0 : 1,
600
+ }}
601
+ >
602
+ <canvas
603
+ height="0"
604
+ data-testid="flamegraph-canvas"
605
+ data-highlightquery={highlightQuery}
606
+ className={clsx('flamegraph-canvas', styles.canvas)}
607
+ ref={canvasRef}
608
+ onClick={!disableClick ? onClick : undefined}
609
+ />
610
+ <canvas
611
+ height="0"
612
+ data-testid="flamegraph-text-canvas"
613
+ className={clsx('flamegraph-text-canvas', styles.textCanvas)}
614
+ ref={textCanvasRef}
615
+ />
616
+ {flamegraph && canvasRef && (
617
+ <Highlight
618
+ barHeight={PX_PER_LEVEL}
619
+ canvasRef={canvasRef}
620
+ zoom={zoom}
621
+ xyToHighlightData={xyToHighlightData}
622
+ />
623
+ )}
624
+ {flamegraph && (
625
+ <ContextMenuHighlight
626
+ barHeight={PX_PER_LEVEL}
627
+ node={rightClickedNode}
628
+ />
629
+ )}
630
+ </div>
631
+ {showCredit ? <LogoLink /> : ''}
480
632
  </div>
481
- {showCredit ? <LogoLink /> : ''}
482
633
  {flamegraph && (
483
634
  <FlamegraphTooltip
484
635
  format={flamebearer.format}
@@ -8,3 +8,11 @@
8
8
  margin-right: 10px;
9
9
  }
10
10
  }
11
+
12
+ .flamegraphFrame {
13
+ border: 1px solid var(--ps-ui-border);
14
+ border-radius: 6px;
15
+ padding: 6px 8px 8px;
16
+ background-color: transparent;
17
+ min-height: var(--kylin-flamegraph-pane-min-height, 320px);
18
+ }
@@ -14,16 +14,18 @@ export function normalize(p: {
14
14
  }
15
15
 
16
16
  if (p.profile) {
17
+ const profile = p.profile as Profile & { __kylinDecoded?: boolean };
17
18
  const copy = {
18
- ...p.profile,
19
- flamebearer: { ...p.profile.flamebearer },
19
+ ...profile,
20
+ flamebearer: { ...profile.flamebearer },
20
21
  };
21
22
 
22
- // TODO: copy levels, since that's modified by decode
23
- copy.flamebearer.levels = JSON.parse(
24
- JSON.stringify(copy.flamebearer.levels)
25
- );
26
- decodeFlamebearer(copy);
23
+ // Avoid deep-cloning huge levels arrays; decode once in-place.
24
+ if (!profile.__kylinDecoded) {
25
+ decodeFlamebearer(copy);
26
+ profile.__kylinDecoded = true;
27
+ (copy as Profile & { __kylinDecoded?: boolean }).__kylinDecoded = true;
28
+ }
27
29
 
28
30
  const p2 = {
29
31
  ...copy,
@@ -4,81 +4,91 @@ export function isSameFlamebearer(
4
4
  prevFlame: Flamebearer,
5
5
  currFlame: Flamebearer
6
6
  ) {
7
- // We first compare simple fields, since they are faster
8
- if (prevFlame.format !== currFlame.format) {
9
- return false;
10
- }
7
+ const prevSig = getFlamebearerSignature(prevFlame);
8
+ const currSig = getFlamebearerSignature(currFlame);
9
+ return prevSig === currSig;
10
+ }
11
11
 
12
- if (prevFlame.numTicks !== currFlame.numTicks) {
13
- return false;
12
+ function hashNumbersSample(levels: Flamebearer['levels']) {
13
+ let hash = 2166136261;
14
+ const totalLevels = levels.length;
15
+ if (totalLevels === 0) {
16
+ return hash >>> 0;
14
17
  }
15
18
 
16
- if (prevFlame.sampleRate !== currFlame.sampleRate) {
17
- return false;
18
- }
19
+ const sampleLevels = [0, Math.floor(totalLevels / 2), totalLevels - 1];
20
+ for (let s = 0; s < sampleLevels.length; s += 1) {
21
+ const idx = sampleLevels[s];
22
+ const level = levels[idx];
23
+ if (!level || level.length === 0) {
24
+ continue;
25
+ }
19
26
 
20
- if (prevFlame.units !== currFlame.units) {
21
- return false;
22
- }
27
+ const take = Math.min(8, level.length);
28
+ for (let i = 0; i < take; i += 1) {
29
+ hash ^= level[i] | 0;
30
+ hash = Math.imul(hash, 16777619);
31
+ }
23
32
 
24
- if (prevFlame.names?.length !== currFlame?.names.length) {
25
- return false;
33
+ const tailStart = Math.max(0, level.length - take);
34
+ for (let i = tailStart; i < level.length; i += 1) {
35
+ hash ^= level[i] | 0;
36
+ hash = Math.imul(hash, 16777619);
37
+ }
26
38
  }
27
39
 
28
- if (prevFlame.levels.length !== currFlame.levels.length) {
29
- return false;
30
- }
40
+ return hash >>> 0;
41
+ }
31
42
 
32
- // Most likely names is smaller, so let's start with it
33
- // Are all names the same?
34
- if (
35
- !prevFlame.names.every((a, i) => {
36
- return a === currFlame.names[i];
37
- })
38
- ) {
39
- return false;
43
+ function hashStringsSample(names: string[]) {
44
+ let hash = 2166136261;
45
+ const count = names.length;
46
+ if (count === 0) {
47
+ return hash >>> 0;
40
48
  }
41
49
 
42
- if (!areLevelsTheSame(prevFlame.levels, currFlame.levels)) {
43
- return false;
50
+ const take = Math.min(32, count);
51
+ const sampleIndices = [
52
+ ...Array.from({ length: take }, (_, i) => i),
53
+ ...Array.from({ length: take }, (_, i) => count - take + i),
54
+ ];
55
+
56
+ for (let i = 0; i < sampleIndices.length; i += 1) {
57
+ const name = names[sampleIndices[i]] || '';
58
+ for (let j = 0; j < name.length; j += 1) {
59
+ hash ^= name.charCodeAt(j);
60
+ hash = Math.imul(hash, 16777619);
61
+ }
44
62
  }
45
63
 
46
- // Fallback in case new fields are added
47
- return (
48
- JSON.stringify({
49
- ...prevFlame,
50
- levels: undefined,
51
- names: undefined,
52
- }) ===
53
- JSON.stringify({
54
- ...currFlame,
55
- levels: undefined,
56
- names: undefined,
57
- })
58
- );
64
+ return hash >>> 0;
59
65
  }
60
66
 
61
- function areLevelsTheSame(
62
- l1: Flamebearer['levels'],
63
- l2: Flamebearer['levels']
64
- ) {
65
- if (l1.length !== l2.length) {
66
- return false;
67
+ function getFlamebearerSignature(flamebearer: Flamebearer) {
68
+ const cached = (flamebearer as any).__kylinSignature;
69
+ if (cached) {
70
+ return cached;
67
71
  }
68
72
 
69
- // eslint-disable-next-line no-plusplus
70
- for (let i = 0; i < l1.length; i++) {
71
- if (l1[i].length !== l2[i].length) {
72
- return false;
73
- }
73
+ const extra = flamebearer as Flamebearer & {
74
+ leftTicks?: number;
75
+ rightTicks?: number;
76
+ };
74
77
 
75
- // eslint-disable-next-line no-plusplus
76
- for (let j = 0; j < l1[i].length; j++) {
77
- if (l1[i][j] !== l2[i][j]) {
78
- return false;
79
- }
80
- }
81
- }
78
+ const signature = [
79
+ flamebearer.format,
80
+ flamebearer.numTicks,
81
+ flamebearer.sampleRate,
82
+ flamebearer.units,
83
+ flamebearer.maxSelf,
84
+ flamebearer.names?.length ?? 0,
85
+ flamebearer.levels?.length ?? 0,
86
+ extra.leftTicks ?? 'na',
87
+ extra.rightTicks ?? 'na',
88
+ hashNumbersSample(flamebearer.levels),
89
+ hashStringsSample(flamebearer.names || []),
90
+ ].join('|');
82
91
 
83
- return true;
92
+ (flamebearer as any).__kylinSignature = signature;
93
+ return signature;
84
94
  }
package/src/Icons.tsx CHANGED
@@ -2,27 +2,36 @@ import React from 'react';
2
2
 
3
3
  export const TableIcon = () => {
4
4
  return (
5
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
6
- <g id="z9nPJj.tif">
7
- <path d="M62,13.33V49.68c-.02,.05-.04,.11-.05,.16-.3,2.66-2.66,5.16-6.08,5.15-15.92,0-31.83,0-47.75-.01-.54,0-1.1-.06-1.62-.19-2.73-.69-4.52-3.09-4.52-5.88,0-12,.01-23.56,.01-34.9,0-.29,.01-.57,.05-.86,.35-2.3,1.62-3.91,3.84-4.75,.6-.23,1.27-.28,1.9-.41,16.13,0,32.27,0,48.4,0,.09,.01,.17,.04,.26,.04,2.66,.21,4.8,2.01,5.4,4.57,.06,.24,.1,.48,.14,.72ZM28.99,31.81c0-3.92-.01-7.91-.01-11.81H8c0,3.91,.01,7.9,.01,11.81H28.99Zm27,0c0-3.92-.01-7.92-.01-11.81h-20.97c0,3.92,.01,7.91,.01,11.81h20.97Zm-47.98,5.19c0,3.7,0,8,0,11.69v.31H29v-12m6.01,.04c0,3.93-.01,8.05-.01,11.96h20.99c0-.12,0-.22,0-.31,0-3.7,0-7.99,0-11.69" />
8
- </g>
5
+ <svg
6
+ xmlns="http://www.w3.org/2000/svg"
7
+ viewBox="64 64 896 896"
8
+ fill="currentColor"
9
+ >
10
+ <path d="M928 160H96c-17.7 0-32 14.3-32 32v640c0 17.7 14.3 32 32 32h832c17.7 0 32-14.3 32-32V192c0-17.7-14.3-32-32-32zm-40 208H676V232h212v136zm0 224H676V432h212v160zM412 432h200v160H412V432zm200-64H412V232h200v136zm-476 64h212v160H136V432zm0-200h212v136H136V232zm0 424h212v136H136V656zm276 0h200v136H412V656zm476 136H676V656h212v136z" />
9
11
  </svg>
10
12
  );
11
13
  };
12
14
 
13
15
  export const TablePlusFlamegraphIcon = () => {
14
16
  return (
15
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
16
- <path d="M31,8.04H7.62c-.61,.13-1.26,.18-1.84,.41-2.14,.84-3.37,2.45-3.71,4.75-.04,.28-.05,.57-.05,.86,0,11.32-.02,22.88-.01,34.87,0,2.79,1.73,5.18,4.37,5.88,.51,.13,1.04,.19,1.57,.19,7.69,0,15.38,0,23.07,0V8.04Zm-2.92,11.99c0,3.9,.01,7.89,.01,11.8H7.82c0-3.9-.01-7.89-.01-11.8H28.08ZM7.82,49.01v-.31c0-3.69,0-7.99,0-11.68H28.1v11.99H7.82Z" />
17
- <path d="M58.67,7.9h-25.67V55h2.9c1.84,0,2.9-1.55,2.9-3.45V27.14h11.6v6.37c0,1.91,1.49,3.45,3.33,3.45h4.94c1.84,0,3.33-1.55,3.33-3.45V11.35c0-1.91-1.49-3.45-3.33-3.45Z" />
17
+ <svg
18
+ xmlns="http://www.w3.org/2000/svg"
19
+ viewBox="64 64 896 896"
20
+ fill="currentColor"
21
+ >
22
+ <path d="M464 144H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H212V212h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H612V212h200v200zM464 544H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H212V612h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H612V612h200v200z" />
18
23
  </svg>
19
24
  );
20
25
  };
21
26
 
22
27
  export const FlamegraphIcon = () => {
23
28
  return (
24
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
25
- <path d="M5.44,8H58.56c1.9,0,3.44,1.54,3.44,3.44v6.16s0,9.6,0,9.6v6.36c0,1.9-1.54,3.44-3.44,3.44h-5.11c-1.9,0-3.44-1.54-3.44-3.44v-6.36h-12s0,9.8,0,9.8v9s0,5.56,0,5.56c0,1.9-1.1,3.44-3,3.44h-6c-1.9,0-3-1.54-3-3.44v-5.56h-9c-1.99,0-3-1.52-3-3.4v-5.6H5.44c-1.9,0-3.44-1.74-3.44-3.64v-6.16s0-9.6,0-9.6v-6.16c0-1.9,1.54-3.44,3.44-3.44Z" />
29
+ <svg
30
+ xmlns="http://www.w3.org/2000/svg"
31
+ viewBox="64 64 896 896"
32
+ fill="currentColor"
33
+ >
34
+ <path d="M834.1 469.2A347.49 347.49 0 00751.2 354l-29.1-26.7a8.09 8.09 0 00-13 3.3l-13 37.3c-8.1 23.4-23 47.3-44.1 70.8-1.4 1.5-3 1.9-4.1 2-1.1.1-2.8-.1-4.3-1.5-1.4-1.2-2.1-3-2-4.8 3.7-60.2-14.3-128.1-53.7-202C555.3 171 510 123.1 453.4 89.7l-41.3-24.3c-5.4-3.2-12.3 1-12 7.3l2.2 48c1.5 32.8-2.3 61.8-11.3 85.9-11 29.5-26.8 56.9-47 81.5a295.64 295.64 0 01-47.5 46.1 352.6 352.6 0 00-100.3 121.5A347.75 347.75 0 00160 610c0 47.2 9.3 92.9 27.7 136a349.4 349.4 0 0075.5 110.9c32.4 32 70 57.2 111.9 74.7C418.5 949.8 464.5 959 512 959s93.5-9.2 136.9-27.3A348.6 348.6 0 00760.8 857c32.4-32 57.8-69.4 75.5-110.9a344.2 344.2 0 0027.7-136c0-48.8-10-96.2-29.9-140.9zM713 808.5c-53.7 53.2-125 82.4-201 82.4s-147.3-29.2-201-82.4c-53.5-53.1-83-123.5-83-198.4 0-43.5 9.8-85.2 29.1-124 18.8-37.9 46.8-71.8 80.8-97.9a349.6 349.6 0 0058.6-56.8c25-30.5 44.6-64.5 58.2-101a240 240 0 0012.1-46.5c24.1 22.2 44.3 49 61.2 80.4 33.4 62.6 48.8 118.3 45.8 165.7a74.01 74.01 0 0024.4 59.8 73.36 73.36 0 0053.4 18.8c19.7-1 37.8-9.7 51-24.4 13.3-14.9 24.8-30.1 34.4-45.6 14 17.9 25.7 37.4 35 58.4 15.9 35.8 24 73.9 24 113.1 0 74.9-29.5 145.4-83 198.4z" />
26
35
  </svg>
27
36
  );
28
37
  };