@kylincloud/flamegraph 0.35.21 → 0.35.23

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.
@@ -1,6 +1,6 @@
1
- import { ReactNode, CSSProperties, RefObject } from 'react';
1
+ import React, { ReactNode, CSSProperties, RefObject } from 'react';
2
2
  interface CustomProp {
3
- [k: string]: string | CSSProperties | ReactNode | number | undefined;
3
+ [k: string]: string | CSSProperties | ReactNode | number | undefined | ((e: React.MouseEvent) => void) | (() => void);
4
4
  }
5
5
  export interface Cell extends CustomProp {
6
6
  value: ReactNode | string;
@@ -18,6 +18,8 @@ export interface BodyRow {
18
18
  isRowDisabled?: boolean;
19
19
  cells: Cell[];
20
20
  onClick?: () => void;
21
+ onDoubleClick?: () => void;
22
+ onContextMenu?: (e: React.MouseEvent) => void;
21
23
  className?: string;
22
24
  }
23
25
  export type TableBodyType = {
@@ -1 +1 @@
1
- {"version":3,"file":"Table.d.ts","sourceRoot":"","sources":["../../src/shims/Table.tsx"],"names":[],"mappings":"AACA,OAAc,EAAY,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAU7E,UAAU,UAAU;IAClB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;CACtE;AAED,MAAM,WAAW,IAAK,SAAQ,UAAU;IACtC,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AAED,UAAU,QAAS,SAAQ,UAAU;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,OAAO;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,aAAa,GACrB;IACE,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GACD;IACE,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB,CAAC;AAEN,KAAK,KAAK,GAAG,aAAa,GAAG;IAC3B,OAAO,EAAE,QAAQ,EAAE,CAAC;CACrB,CAAC;AAEF,UAAU,cAAc;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,eAAe,EAAE,MAAM,GAAG,KAAK,CAAC;CACjC;AAED,eAAO,MAAM,YAAY,YAAa,QAAQ,EAAE,KAAG,cAsBlD,CAAC;AAEF,UAAU,UAAU;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,KAAK,EAAE,KAAK,CAAC;IACb,YAAY,CAAC,EAAE,SAAS,CAAC,uBAAuB,CAAC,CAAC;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,iBAAS,KAAK,CAAC,EACb,eAAe,EACf,MAAM,EACN,gBAAgB,EAChB,KAAK,EACL,YAAY,EACZ,SAAS,EACT,SAAS,EACT,YAAY,GACb,EAAE,UAAU,2CA0FZ;AA2DD,eAAe,KAAK,CAAC"}
1
+ {"version":3,"file":"Table.d.ts","sourceRoot":"","sources":["../../src/shims/Table.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAoB,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAUrF,UAAU,UAAU;IAClB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;CACvH;AAED,MAAM,WAAW,IAAK,SAAQ,UAAU;IACtC,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AAED,UAAU,QAAS,SAAQ,UAAU;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,OAAO;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,aAAa,GACrB;IACA,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GACC;IACA,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB,CAAC;AAEJ,KAAK,KAAK,GAAG,aAAa,GAAG;IAC3B,OAAO,EAAE,QAAQ,EAAE,CAAC;CACrB,CAAC;AAEF,UAAU,cAAc;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,eAAe,EAAE,MAAM,GAAG,KAAK,CAAC;CACjC;AAED,eAAO,MAAM,YAAY,YAAa,QAAQ,EAAE,KAAG,cAsBlD,CAAC;AAEF,UAAU,UAAU;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,KAAK,EAAE,KAAK,CAAC;IACb,YAAY,CAAC,EAAE,SAAS,CAAC,uBAAuB,CAAC,CAAC;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA0CD,iBAAS,KAAK,CAAC,EACb,eAAe,EACf,MAAM,EACN,gBAAgB,EAChB,KAAK,EACL,YAAY,EACZ,SAAS,EACT,SAAS,EACT,YAAY,GACb,EAAE,UAAU,2CAkEZ;AA2DD,eAAe,KAAK,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kylincloud/flamegraph",
3
- "version": "0.35.21",
3
+ "version": "0.35.23",
4
4
  "description": "KylinCloud flamegraph renderer (Pyroscope-based)",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.node.cjs.js",
@@ -12,7 +12,7 @@ import { PX_PER_LEVEL, BAR_HEIGHT, COLLAPSE_THRESHOLD } from './constants';
12
12
  import type { FlamegraphPalette } from './colorPalette';
13
13
  // there's a dependency cycle here but it should be fine
14
14
  /* eslint-disable-next-line import/no-cycle */
15
- import RenderCanvas from './Flamegraph_render';
15
+ import RenderCanvas, { CanvasI18nMessages } from './Flamegraph_render';
16
16
 
17
17
  /* eslint-disable no-useless-constructor */
18
18
 
@@ -47,7 +47,10 @@ export default class Flamegraph {
47
47
  private readonly highlightQuery: string,
48
48
  private zoom: Maybe<DeepReadonly<{ i: number; j: number }>>,
49
49
 
50
- private palette: FlamegraphPalette
50
+ private palette: FlamegraphPalette,
51
+
52
+ /** i18n messages for canvas rendering */
53
+ private messages?: CanvasI18nMessages
51
54
  ) {
52
55
  // TODO
53
56
  // these were only added because storybook is not setting
@@ -59,6 +62,7 @@ export default class Flamegraph {
59
62
  this.highlightQuery = highlightQuery;
60
63
  this.ff = createFF(flamebearer.format);
61
64
  this.palette = palette;
65
+ this.messages = messages;
62
66
 
63
67
  // don't allow to have a zoom smaller than the focus
64
68
  // since it does not make sense
@@ -96,6 +100,7 @@ export default class Flamegraph {
96
100
  pxPerTick: this.pxPerTick(),
97
101
  tickToX: this.tickToX,
98
102
  palette: this.palette,
103
+ messages: this.messages,
99
104
  };
100
105
 
101
106
  const { format: viewType } = this.flamebearer;
@@ -49,6 +49,18 @@ import { isMatch } from '../../search';
49
49
  /* eslint-disable-next-line import/no-cycle */
50
50
  import Flamegraph from './Flamegraph';
51
51
 
52
+ /** i18n messages needed for canvas rendering */
53
+ export interface CanvasI18nMessages {
54
+ collapsedLevelsSingular: string;
55
+ collapsedLevelsPlural: string;
56
+ }
57
+
58
+ /** Default English messages (fallback) */
59
+ const defaultCanvasMessages: CanvasI18nMessages = {
60
+ collapsedLevelsSingular: 'total (1 level collapsed)',
61
+ collapsedLevelsPlural: 'total ({n} levels collapsed)',
62
+ };
63
+
52
64
  type CanvasRendererConfig = Flamebearer & {
53
65
  canvas: HTMLCanvasElement;
54
66
  focusedNode: ConstructorParameters<typeof Flamegraph>[2];
@@ -73,13 +85,30 @@ type CanvasRendererConfig = Flamebearer & {
73
85
 
74
86
  palette: FlamegraphPalette;
75
87
  maxSelf?: number;
88
+
89
+ /** i18n messages for collapsed text */
90
+ messages?: CanvasI18nMessages;
76
91
  };
77
92
 
93
+ /**
94
+ * 生成 collapsed 层级的显示文本
95
+ */
96
+ function getCollapsedText(
97
+ levelCount: number,
98
+ messages: CanvasI18nMessages
99
+ ): string {
100
+ if (levelCount === 1) {
101
+ return messages.collapsedLevelsSingular;
102
+ }
103
+ return messages.collapsedLevelsPlural.replace('{n}', String(levelCount));
104
+ }
105
+
78
106
  export default function RenderCanvas(props: CanvasRendererConfig) {
79
107
  const { canvas, fitMode, units, tickToX, levels, palette } = props;
80
108
  const { numTicks, sampleRate, pxPerTick } = props;
81
109
  const { rangeMin, rangeMax } = props;
82
110
  const { focusedNode, zoom } = props;
111
+ const messages = props.messages || defaultCanvasMessages;
83
112
 
84
113
  const graphWidth = getCanvasWidth(canvas);
85
114
  // TODO: why is this needed? otherwise height is all messed up
@@ -134,10 +163,10 @@ export default function RenderCanvas(props: CanvasRendererConfig) {
134
163
  ctx.fillStyle = colorGreyscale(200, 1).rgb().string();
135
164
  ctx.fill();
136
165
 
137
- // TODO show the samples too?
166
+ // 使用 i18n 翻译的 collapsed 文本
138
167
  const shortName = focusedNode.mapOrElse(
139
168
  () => 'total',
140
- (f) => `total (${f.i - 1} level(s) collapsed)`
169
+ (f) => getCollapsedText(f.i - 1, messages)
141
170
  );
142
171
 
143
172
  // Set the font syle
@@ -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 } from 'react';
3
+ import React, { useCallback, useRef, useMemo } 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';
@@ -63,6 +63,15 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
63
63
  const flamegraph = useRef<Flamegraph>();
64
64
  const i18n = useFlamegraphI18n();
65
65
 
66
+ // ====== 新增:提取 canvas 渲染需要的 i18n messages ======
67
+ const canvasMessages = useMemo(
68
+ () => ({
69
+ collapsedLevelsSingular: i18n.collapsedLevelsSingular,
70
+ collapsedLevelsPlural: i18n.collapsedLevelsPlural,
71
+ }),
72
+ [i18n.collapsedLevelsSingular, i18n.collapsedLevelsPlural]
73
+ );
74
+
66
75
  const [rightClickedNode, setRightClickedNode] = React.useState<
67
76
  Maybe<{ top: number; left: number; width: number }>
68
77
  >(Maybe.nothing());
@@ -291,17 +300,19 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
291
300
  fitMode,
292
301
  highlightQuery,
293
302
  zoom,
294
- palette
303
+ palette,
304
+ canvasMessages // ====== 新增:传递 i18n messages ======
295
305
  );
296
306
 
297
307
  flamegraph.current = f;
298
308
  }
299
309
  };
300
310
 
311
+ // ====== 修改:添加 canvasMessages 依赖 ======
301
312
  React.useEffect(() => {
302
313
  constructCanvas();
303
314
  renderCanvas();
304
- }, [palette]);
315
+ }, [palette, canvasMessages]);
305
316
 
306
317
  React.useEffect(() => {
307
318
  constructCanvas();
@@ -275,6 +275,55 @@ class FlameGraphRenderer extends Component<
275
275
  });
276
276
  };
277
277
 
278
+ /**
279
+ * 检查指定名称的函数是否在当前聚焦节点的子树中
280
+ */
281
+ isNameInFocusedSubtree = (name: string): boolean => {
282
+ const { focusedNode } = this.state.flamegraphConfigs;
283
+
284
+ // 如果没有聚焦节点,任何 name 都算"在子树中"
285
+ if (focusedNode.isNothing) {
286
+ return true;
287
+ }
288
+
289
+ const { flamebearer } = this.state;
290
+ if (!flamebearer || !flamebearer.levels) {
291
+ return true;
292
+ }
293
+
294
+ const { names, levels, format } = flamebearer;
295
+ const ff = createFF(format);
296
+ const focusedI = (focusedNode as any).value.i;
297
+ const focusedJ = (focusedNode as any).value.j;
298
+
299
+ // 获取聚焦节点的范围
300
+ const focusedLevel = levels[focusedI];
301
+ if (!focusedLevel) return true;
302
+
303
+ const focusedOffset = ff.getBarOffset(focusedLevel, focusedJ);
304
+ const focusedTotal = ff.getBarTotal(focusedLevel, focusedJ);
305
+ const focusedEnd = focusedOffset + focusedTotal;
306
+
307
+ // 遍历聚焦节点及其下面的所有层级,检查是否有匹配的函数名
308
+ for (let i = focusedI; i < levels.length; i++) {
309
+ const level = levels[i];
310
+ for (let j = 0; j < level.length; j += ff.jStep) {
311
+ const offset = ff.getBarOffset(level, j);
312
+ const total = ff.getBarTotal(level, j);
313
+
314
+ // 检查这个节点是否在聚焦范围内
315
+ if (offset >= focusedOffset && offset + total <= focusedEnd) {
316
+ const nameIndex = ff.getBarName(level, j);
317
+ if (names[nameIndex] === name) {
318
+ return true;
319
+ }
320
+ }
321
+ }
322
+ }
323
+
324
+ return false;
325
+ };
326
+
278
327
  setActiveItem = (item: { name: string }) => {
279
328
  const { name } = item;
280
329
 
@@ -288,10 +337,25 @@ class FlameGraphRenderer extends Component<
288
337
  }
289
338
  }
290
339
 
291
- // clicking for the first time
292
- this.setState({
293
- selectedItem: Maybe.just(name),
294
- });
340
+ // 检查选中的 item 是否在当前聚焦的子树中
341
+ // 如果不在,需要重置聚焦状态,以便在火焰图中正确显示高亮
342
+ const isInSubtree = this.isNameInFocusedSubtree(name);
343
+
344
+ if (isInSubtree) {
345
+ // 在子树中,只更新选中项
346
+ this.setState({
347
+ selectedItem: Maybe.just(name),
348
+ });
349
+ } else {
350
+ // 不在子树中,重置聚焦并更新选中项
351
+ this.setState({
352
+ selectedItem: Maybe.just(name),
353
+ flamegraphConfigs: {
354
+ ...this.state.flamegraphConfigs,
355
+ focusedNode: this.resetFlamegraphState.focusedNode,
356
+ },
357
+ });
358
+ }
295
359
  };
296
360
 
297
361
  getHighlightQuery = () => {
@@ -337,7 +401,7 @@ class FlameGraphRenderer extends Component<
337
401
  return (
338
402
  this.state.selectedItem.isJust ||
339
403
  JSON.stringify(this.initialFlamegraphState) !==
340
- JSON.stringify(this.state.flamegraphConfigs)
404
+ JSON.stringify(this.state.flamegraphConfigs)
341
405
  );
342
406
  };
343
407
 
@@ -371,6 +435,10 @@ class FlameGraphRenderer extends Component<
371
435
  selectedItem={this.state.selectedItem}
372
436
  handleTableItemClick={this.setActiveItem}
373
437
  palette={this.state.palette}
438
+ // 新增:传递聚焦和视图切换能力给表格
439
+ onFocusOnNode={this.onFocusOnNode}
440
+ updateView={this.props.onlyDisplay ? undefined : this.updateView}
441
+ enableSandwichView={this.props.enableSandwichView}
374
442
  />
375
443
  </div>
376
444
  );
@@ -603,4 +671,4 @@ function decidePanesOrder(
603
671
  }
604
672
  }
605
673
 
606
- export default FlameGraphRenderer;
674
+ export default FlameGraphRenderer;
@@ -0,0 +1,42 @@
1
+ // src/ProfilerTable.module.scss
2
+
3
+ .tableContextMenu {
4
+ // 继承 react-menu 的基础样式
5
+ :global(.szh-menu) {
6
+ background-color: var(--ps-ui-background, #fff);
7
+ border: 1px solid var(--ps-ui-border, #ccc);
8
+ border-radius: 4px;
9
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
10
+ padding: 4px 0;
11
+ min-width: 180px;
12
+ z-index: 1000;
13
+ }
14
+
15
+ :global(.szh-menu__item) {
16
+ display: flex;
17
+ align-items: center;
18
+ gap: 8px;
19
+ padding: 8px 12px;
20
+ cursor: pointer;
21
+ color: var(--ps-ui-foreground-text, #333);
22
+ font-size: 13px;
23
+ transition: background-color 0.15s;
24
+
25
+ &:hover {
26
+ background-color: var(--ps-ui-element-bg-highlight, #f0f0f0);
27
+ }
28
+
29
+ svg {
30
+ width: 14px;
31
+ height: 14px;
32
+ flex-shrink: 0;
33
+ }
34
+ }
35
+ }
36
+
37
+ .sandwichItem {
38
+ svg {
39
+ width: 16px;
40
+ height: 16px;
41
+ }
42
+ }