@kylincloud/flamegraph 0.35.8 → 0.35.10

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 (38) hide show
  1. package/README.md +292 -50
  2. package/dist/FlameGraph/FlameGraphComponent/DiffLegendPaletteDropdown.d.ts.map +1 -1
  3. package/dist/FlameGraph/FlameGraphComponent/Header.d.ts +0 -3
  4. package/dist/FlameGraph/FlameGraphComponent/Header.d.ts.map +1 -1
  5. package/dist/FlameGraph/FlameGraphComponent/index.d.ts +2 -0
  6. package/dist/FlameGraph/FlameGraphComponent/index.d.ts.map +1 -1
  7. package/dist/FlameGraph/FlameGraphRenderer.d.ts +2 -0
  8. package/dist/FlameGraph/FlameGraphRenderer.d.ts.map +1 -1
  9. package/dist/FlamegraphRenderer.d.ts +23 -5
  10. package/dist/FlamegraphRenderer.d.ts.map +1 -1
  11. package/dist/ProfilerTable.d.ts.map +1 -1
  12. package/dist/Toolbar.d.ts +4 -1
  13. package/dist/Toolbar.d.ts.map +1 -1
  14. package/dist/i18n.d.ts +2 -0
  15. package/dist/i18n.d.ts.map +1 -1
  16. package/dist/index.cjs.js +4 -4
  17. package/dist/index.cjs.js.map +1 -1
  18. package/dist/index.esm.js +4 -4
  19. package/dist/index.esm.js.map +1 -1
  20. package/dist/index.node.cjs.js +6 -1
  21. package/dist/index.node.cjs.js.map +1 -1
  22. package/dist/index.node.esm.js +8 -3
  23. package/dist/index.node.esm.js.map +1 -1
  24. package/package.json +4 -1
  25. package/src/FlameGraph/FlameGraphComponent/DiffLegendPaletteDropdown.module.css +69 -21
  26. package/src/FlameGraph/FlameGraphComponent/DiffLegendPaletteDropdown.tsx +57 -86
  27. package/src/FlameGraph/FlameGraphComponent/Header.tsx +23 -36
  28. package/src/FlameGraph/FlameGraphComponent/index.tsx +13 -9
  29. package/src/FlameGraph/FlameGraphRenderer.tsx +14 -11
  30. package/src/FlamegraphRenderer.tsx +103 -20
  31. package/src/ProfilerTable.tsx +24 -11
  32. package/src/SharedQueryInput.module.scss +5 -2
  33. package/src/Toolbar.module.scss +13 -0
  34. package/src/Toolbar.tsx +29 -3
  35. package/src/i18n.tsx +11 -2
  36. package/src/sass/_css-variables.scss +15 -2
  37. package/src/sass/flamegraph.scss +59 -0
  38. package/src/shims/Table.module.scss +7 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kylincloud/flamegraph",
3
- "version": "0.35.8",
3
+ "version": "0.35.10",
4
4
  "description": "KylinCloud flamegraph renderer (Pyroscope-based)",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.node.cjs.js",
@@ -80,6 +80,9 @@
80
80
  "build:js": "rollup -c",
81
81
  "build:assets": "cp src/logo-v3-small.svg dist/logo-v3-small.svg || true",
82
82
  "type-check": "tsc -p tsconfig.build.json --noEmit",
83
+ "dev:types": "tsc -p tsconfig.build.json --emitDeclarationOnly --watch",
84
+ "dev:js": "rollup -c -w",
85
+ "dev": "pnpm run dev:js",
83
86
  "lint": "eslint ./ --cache --fix"
84
87
  }
85
88
  }
@@ -1,40 +1,88 @@
1
- .diffPaletteDropdown {
2
- max-width: 510px;
3
- min-width: 292px;
4
- width: inherit;
5
- padding-right: 20px !important;
6
- }
1
+ /* src/FlameGraph/FlameGraphComponent/DiffLegendPaletteDropdown.module.css */
7
2
 
8
- .dropdownWrapper {
3
+ /* 容器 */
4
+ .container {
9
5
  display: flex;
10
6
  align-items: center;
11
- flex-direction: column;
12
7
  }
13
8
 
14
- .diffPaletteDropdown > div {
15
- margin-bottom: 0px;
9
+ /* 触发按钮:色带 + 下拉箭头 */
10
+ .triggerButton {
16
11
  display: flex;
12
+ align-items: center;
13
+ gap: 6px;
14
+ padding: 4px 8px;
15
+ border: 1px solid var(--ps-ui-border);
16
+ border-radius: 4px;
17
+ background-color: var(--ps-immutable-off-white);
18
+ cursor: pointer;
19
+ height: 37px;
17
20
  }
18
21
 
19
- .diffPaletteDropdown::after {
20
- top: 0px;
22
+ .triggerButton:hover {
23
+ background-color: var(--ps-ui-element-bg-highlight);
21
24
  }
22
25
 
23
- .dropdownItem {
24
- width: 100%;
26
+ .dropdownArrow {
27
+ font-size: 8px;
28
+ color: var(--ps-text-secondary);
29
+ margin-left: 4px;
30
+ }
31
+
32
+ /* 菜单项样式 */
33
+ .menuItem {
25
34
  display: flex;
35
+ align-items: center;
36
+ justify-content: space-between;
37
+ gap: 12px;
38
+ min-width: 200px;
39
+ padding: 4px 0;
26
40
  }
27
41
 
28
- .dropdownItem svg {
29
- margin-left: 1em;
30
- fill: var(--ps-neutral-2);
42
+ .menuItemText {
43
+ color: var(--ps-text-primary);
44
+ font-size: 13px;
31
45
  }
32
46
 
47
+ /* ========== 以下为旧样式,保留兼容性 ========== */
48
+
33
49
  .row {
34
50
  display: flex;
51
+ flex-direction: row;
35
52
  justify-content: space-between;
36
- margin-left: auto;
37
- margin-right: auto;
38
- width: 300px;
39
- height: 45px;
53
+ padding-bottom: 2px;
54
+ }
55
+
56
+ .row p {
57
+ font-size: 13px;
58
+ margin: 0;
59
+ }
60
+
61
+ .dropdownWrapper {
62
+ display: flex;
63
+ }
64
+
65
+ .diffPaletteDropdown {
66
+ cursor: pointer;
67
+ border: 1px solid var(--ps-ui-border);
68
+ border-radius: 4px;
69
+ padding: 4px 8px;
70
+ background-color: var(--ps-immutable-off-white);
71
+ }
72
+
73
+ .diffPaletteDropdown:hover {
74
+ background-color: var(--ps-ui-element-bg-highlight);
75
+ }
76
+
77
+ .dropdownItem {
78
+ display: flex;
79
+ align-items: center;
80
+ gap: 8px;
81
+ margin-top: 4px;
82
+ }
83
+
84
+ .dropdownItem svg {
85
+ width: 16px;
86
+ height: 16px;
87
+ flex-shrink: 0;
40
88
  }
@@ -1,7 +1,7 @@
1
1
  // src/FlameGraph/FlameGraphComponent/DiffLegendPaletteDropdown.tsx
2
+ // 差分火焰图图例区域的「调色板选择下拉框」组件,用于切换色带样式
2
3
  import React from 'react';
3
4
  import cx from 'classnames';
4
- import useResizeObserver from '@react-hook/resize-observer';
5
5
  import {
6
6
  ColorBlindPalette,
7
7
  DefaultPalette,
@@ -9,7 +9,7 @@ import {
9
9
  } from './colorPalette';
10
10
  import DiffLegend from './DiffLegend';
11
11
  import CheckIcon from './CheckIcon';
12
- // Until we migrate ui to its own package this should do it
12
+ // 在把通用 UI 抽成独立包之前,临时复用 shims 目录下的 Dropdown 封装
13
13
  // eslint-disable-next-line
14
14
  import Dropdown, { MenuItem, MenuButton } from '../../shims/Dropdown';
15
15
  // eslint-disable-next-line
@@ -18,10 +18,13 @@ import dropdownStyles from '../../shims/Dropdown.module.scss';
18
18
  import styles from './DiffLegendPaletteDropdown.module.css';
19
19
  import { useFlamegraphI18n } from '../../i18n';
20
20
 
21
+ // 当前支持的调色板列表(默认 + 色盲友好)
21
22
  const paletteList = [DefaultPalette, ColorBlindPalette];
22
23
 
23
24
  interface DiffLegendPaletteDropdownProps {
25
+ // 当前选中的调色板
24
26
  palette: FlamegraphPalette;
27
+ // 调色板变化回调
25
28
  onChange: (p: FlamegraphPalette) => void;
26
29
  }
27
30
 
@@ -29,101 +32,69 @@ export const DiffLegendPaletteDropdown = (
29
32
  props: DiffLegendPaletteDropdownProps
30
33
  ) => {
31
34
  const { palette = DefaultPalette, onChange } = props;
32
- const legendRef = React.useRef<HTMLDivElement>(null);
33
- const showMode = useSizeMode(legendRef);
34
-
35
35
  const messages = useFlamegraphI18n();
36
36
 
37
- const renderPaletteLabel = (name: string) => {
38
- if (name === 'Default') {
37
+ // 获取调色板的显示名称(优先走 i18n 文案)
38
+ const getPaletteName = (p: FlamegraphPalette) => {
39
+ if (p.name === 'Default') {
39
40
  return messages.paletteDefaultName;
40
41
  }
41
- if (name === 'Color Blind') {
42
+ if (p.name === 'Color Blind') {
42
43
  return messages.paletteColorBlindName;
43
44
  }
44
- // 兜底:未知名字直接原样展示
45
- return name;
45
+ return p.name;
46
46
  };
47
47
 
48
- return (
49
- <>
50
- <div className={styles.row} role="heading" aria-level={2}>
51
- <p style={{ color: palette.goodColor.rgb().string() }}>
52
- {messages.diffLegendRemovedLabel}
53
- </p>
54
- <p style={{ color: palette.badColor.rgb().string() }}>
55
- {messages.diffLegendAddedLabel}
56
- </p>
57
- </div>
58
-
59
- <div ref={legendRef} className={styles.dropdownWrapper}>
60
- <Dropdown
61
- label={messages.diffLegendSelectPalette}
62
- align="end"
63
- menuButton={
64
- <MenuButton
65
- className={cx(
66
- // eslint-disable-next-line
67
- dropdownStyles.dropdownMenuButton,
68
- styles.diffPaletteDropdown
69
- )}
70
- >
71
- <DiffLegend palette={palette} showMode={showMode} />
72
- </MenuButton>
73
- }
74
- onItemClick={(e) => onChange(e.value)}
75
- >
76
- {paletteList.map((p) => (
77
- <MenuItem key={p.name} value={p}>
78
- <div>
79
- <label>{renderPaletteLabel(p.name)}</label>
80
- <div className={styles.dropdownItem}>
81
- <DiffLegend palette={p} showMode={showMode} />
82
-
83
- {p === palette ? <CheckIcon /> : null}
84
- </div>
85
- </div>
86
- </MenuItem>
87
- ))}
88
- </Dropdown>
89
- </div>
90
- </>
91
- );
92
- };
93
-
94
- /**
95
- * TODO: unify this and toolbar's
96
- * Custom hook that returns the size ('large' | 'small')
97
- * that should be displayed
98
- * based on the toolbar width
99
- */
100
- // arbitrary value
101
- // as a simple heuristic, try to run the comparison view
102
- // and see when the buttons start to overlap
103
- const WIDTH_THRESHOLD = 13 * 37;
104
- const useSizeMode = (target: React.RefObject<HTMLDivElement>) => {
105
- const [size, setSize] = React.useState<'large' | 'small'>('large');
106
-
107
- const calcMode = (width: number) => {
108
- if (width < WIDTH_THRESHOLD) {
109
- return 'small';
48
+ // 获取调色板的颜色描述(用于下拉菜单中的辅助说明,同样走 i18n)
49
+ const getPaletteColorDesc = (p: FlamegraphPalette) => {
50
+ if (p.name === 'Default') {
51
+ return messages.paletteDefaultDesc;
110
52
  }
111
- return 'large';
112
- };
113
-
114
- React.useLayoutEffect(() => {
115
- if (target.current) {
116
- const { width } = target.current.getBoundingClientRect();
117
-
118
- setSize(calcMode(width));
53
+ if (p.name === 'Color Blind') {
54
+ return messages.paletteColorBlindDesc;
119
55
  }
120
- }, [target.current]);
121
-
122
- useResizeObserver(target, (entry: ResizeObserverEntry) => {
123
- setSize(calcMode(entry.contentRect.width));
124
- });
56
+ return '';
57
+ };
125
58
 
126
- return size;
59
+ return (
60
+ <div className={styles.container}>
61
+ <Dropdown
62
+ // 下拉菜单的无障碍 label 文案(i18n)
63
+ label={messages.diffLegendSelectPalette}
64
+ align="end"
65
+ menuButton={
66
+ <MenuButton
67
+ className={cx(
68
+ dropdownStyles.dropdownMenuButton,
69
+ styles.triggerButton
70
+ )}
71
+ >
72
+ {/* 触发按钮:当前色带预览 + 下拉箭头 */}
73
+ <DiffLegend palette={palette} showMode="small" />
74
+ <span className={styles.dropdownArrow}>▼</span>
75
+ </MenuButton>
76
+ }
77
+ onItemClick={(e) => {
78
+ // e.value 是可选值,点击菜单项时带出的 palette 对象
79
+ if (e.value) {
80
+ onChange(e.value as FlamegraphPalette);
81
+ }
82
+ }}
83
+ >
84
+ {paletteList.map((p) => (
85
+ <MenuItem key={p.name} value={p}>
86
+ <div className={styles.menuItem}>
87
+ <span className={styles.menuItemText}>
88
+ {getPaletteName(p)} {getPaletteColorDesc(p)}
89
+ </span>
90
+ {/* 当前选中的调色板右侧展示对勾图标 */}
91
+ {p.name === palette.name && <CheckIcon />}
92
+ </div>
93
+ </MenuItem>
94
+ ))}
95
+ </Dropdown>
96
+ </div>
97
+ );
127
98
  };
128
99
 
129
100
  export default DiffLegendPaletteDropdown;
@@ -1,19 +1,16 @@
1
+ // src/FlameGraph/FlameGraphComponent/Header.tsx
1
2
  import React from 'react';
2
3
  import { Flamebearer } from '../../models';
3
4
  import styles from './Header.module.css';
4
- import { FlamegraphPalette } from './colorPalette';
5
- import { DiffLegendPaletteDropdown } from './DiffLegendPaletteDropdown';
6
5
 
7
6
  interface HeaderProps {
8
7
  format: Flamebearer['format'];
9
8
  units: Flamebearer['units'];
10
-
11
- palette: FlamegraphPalette;
12
- setPalette: (p: FlamegraphPalette) => void;
13
9
  toolbarVisible?: boolean;
14
10
  }
11
+
15
12
  export default function Header(props: HeaderProps) {
16
- const { format, units, palette, setPalette, toolbarVisible } = props;
13
+ const { format, units, toolbarVisible } = props;
17
14
 
18
15
  const unitsToFlamegraphTitle = {
19
16
  objects: 'number of objects in RAM per function',
@@ -28,41 +25,31 @@ export default function Header(props: HeaderProps) {
28
25
  };
29
26
 
30
27
  const getTitle = () => {
31
- switch (format) {
32
- case 'single': {
33
- return (
34
- <div>
35
- <div
36
- className={`${styles.row} ${styles.flamegraphTitle}`}
37
- role="heading"
38
- aria-level={2}
39
- >
40
- {unitsToFlamegraphTitle[units] && (
41
- <>Frame width represents {unitsToFlamegraphTitle[units]}</>
42
- )}
43
- </div>
44
- </div>
45
- );
46
- }
47
-
48
- case 'double': {
49
- return (
50
- <>
51
- <DiffLegendPaletteDropdown
52
- palette={palette}
53
- onChange={setPalette}
54
- />
55
- </>
56
- );
57
- }
58
-
59
- default:
60
- throw new Error(`unexpected format ${format}`);
28
+ // 只有 single 模式才显示标题
29
+ // double 模式的 DiffLegend 已移到 Toolbar
30
+ if (format === 'single') {
31
+ return (
32
+ <div
33
+ className={`${styles.row} ${styles.flamegraphTitle}`}
34
+ role="heading"
35
+ aria-level={2}
36
+ >
37
+ {unitsToFlamegraphTitle[units] && (
38
+ <>Frame width represents {unitsToFlamegraphTitle[units]}</>
39
+ )}
40
+ </div>
41
+ );
61
42
  }
43
+ return null;
62
44
  };
63
45
 
64
46
  const title = toolbarVisible ? getTitle() : null;
65
47
 
48
+ // 如果没有内容要显示,返回 null 避免占用空间
49
+ if (!title) {
50
+ return null;
51
+ }
52
+
66
53
  return (
67
54
  <div className={styles.flamegraphHeader}>
68
55
  <div>{title}</div>
@@ -53,6 +53,9 @@ interface FlamegraphProps {
53
53
  headerVisible?: boolean;
54
54
  disableClick?: boolean;
55
55
  showSingleLevel?: boolean;
56
+
57
+ /** 是否显示右键菜单中的「打开 Sandwich 视图」项,默认 true */
58
+ enableSandwichView?: boolean;
56
59
  }
57
60
 
58
61
  export default function FlameGraphComponent(props: FlamegraphProps) {
@@ -79,6 +82,7 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
79
82
  setActiveItem,
80
83
  selectedItem,
81
84
  updateView,
85
+ enableSandwichView = true,
82
86
  } = props;
83
87
 
84
88
  const { onZoom, onReset, isDirty, onFocusOnNode } = props;
@@ -105,7 +109,7 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
105
109
  );
106
110
 
107
111
  opt.match({
108
- Nothing: () => {},
112
+ Nothing: () => { },
109
113
  Just: (bar) => {
110
114
  zoom.match({
111
115
  Nothing: () => {
@@ -160,7 +164,7 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
160
164
  );
161
165
 
162
166
  const onClick = bar.mapOrElse(
163
- () => () => {},
167
+ () => () => { },
164
168
  (f) => onFocusOnNode.bind(null, f.i, f.j)
165
169
  );
166
170
 
@@ -210,15 +214,14 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
210
214
  };
211
215
 
212
216
  const OpenInSandwichViewItem = () => {
213
- if (!updateView) {
217
+ // 如果没有 updateView,或者不开启三明治视图菜单,直接不渲染
218
+ if (!updateView || !enableSandwichView) {
214
219
  return null;
215
220
  }
216
221
 
217
222
  const handleClick = () => {
218
- if (updateView) {
219
- updateView('sandwich');
220
- setActiveItem({ name: barName });
221
- }
223
+ updateView('sandwich');
224
+ setActiveItem({ name: barName });
222
225
  };
223
226
 
224
227
  return (
@@ -275,6 +278,7 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
275
278
  setActiveItem,
276
279
  updateView,
277
280
  i18n,
281
+ enableSandwichView,
278
282
  ]
279
283
  );
280
284
 
@@ -341,12 +345,12 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
341
345
  'vertical-orientation': flamebearer.format === 'double',
342
346
  })}
343
347
  >
348
+ {/* Header 已简化,仅在 single 模式下显示标题 */}
349
+ {/* DiffLegend 已移到 Toolbar */}
344
350
  {headerVisible && (
345
351
  <Header
346
352
  format={flamebearer.format}
347
353
  units={flamebearer.units}
348
- palette={palette}
349
- setPalette={setPalette}
350
354
  toolbarVisible={toolbarVisible}
351
355
  />
352
356
  )}
@@ -1,3 +1,4 @@
1
+ // src/FlameGraph/FlameGraphRenderer.tsx
1
2
  /* eslint-disable react/no-unused-state */
2
3
  /* eslint-disable no-bitwise */
3
4
  /* eslint-disable react/no-access-state-in-setstate */
@@ -60,6 +61,7 @@ export interface FlamegraphRendererProps {
60
61
  };
61
62
 
62
63
  children?: ReactNode;
64
+ enableSandwichView?: boolean;
63
65
  }
64
66
 
65
67
  interface FlamegraphRendererState {
@@ -101,7 +103,7 @@ class FlameGraphRenderer extends Component<
101
103
 
102
104
  // eslint-disable-next-line react/static-property-placement
103
105
  static defaultProps = {
104
- showCredit: true,
106
+ showCredit: false,
105
107
  };
106
108
 
107
109
  constructor(props: FlamegraphRendererProps) {
@@ -344,6 +346,11 @@ class FlameGraphRenderer extends Component<
344
346
  return this.props.showToolbar !== undefined ? this.props.showToolbar : true;
345
347
  }
346
348
 
349
+ // 统一的 setPalette 方法
350
+ handleSetPalette = (p: typeof DefaultPalette) => {
351
+ this.setState({ palette: p });
352
+ };
353
+
347
354
  render = () => {
348
355
  // This is necessary because the order switches depending on single vs comparison view
349
356
  const tablePane = (
@@ -390,11 +397,8 @@ class FlameGraphRenderer extends Component<
390
397
  isDirty={this.isDirty}
391
398
  palette={this.state.palette}
392
399
  toolbarVisible={toolbarVisible}
393
- setPalette={(p) =>
394
- this.setState({
395
- palette: p,
396
- })
397
- }
400
+ setPalette={this.handleSetPalette}
401
+ enableSandwichView={this.props.enableSandwichView}
398
402
  />
399
403
  );
400
404
 
@@ -446,11 +450,7 @@ class FlameGraphRenderer extends Component<
446
450
  isDirty={this.isDirty}
447
451
  palette={this.state.palette}
448
452
  toolbarVisible={toolbarVisible}
449
- setPalette={(p) =>
450
- this.setState({
451
- palette: p,
452
- })
453
- }
453
+ setPalette={this.handleSetPalette}
454
454
  {...myCustomParams}
455
455
  />
456
456
  );
@@ -535,6 +535,9 @@ class FlameGraphRenderer extends Component<
535
535
  highlightQuery={this.state.searchQuery}
536
536
  onFocusOnSubtree={this.onFocusOnNode}
537
537
  ExportData={this.props.ExportData}
538
+ // 添加 palette 和 setPalette props
539
+ palette={this.state.palette}
540
+ setPalette={this.handleSetPalette}
538
541
  />
539
542
  )}
540
543
  {this.props.children}