@kylincloud/flamegraph 0.35.22 → 0.35.24

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 (40) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/FlameGraph/FlameGraphComponent/Flamegraph.d.ts +6 -1
  3. package/dist/FlameGraph/FlameGraphComponent/Flamegraph.d.ts.map +1 -1
  4. package/dist/FlameGraph/FlameGraphComponent/FlamegraphBreadcrumb.d.ts +16 -0
  5. package/dist/FlameGraph/FlameGraphComponent/FlamegraphBreadcrumb.d.ts.map +1 -0
  6. package/dist/FlameGraph/FlameGraphComponent/Flamegraph_render.d.ts +8 -0
  7. package/dist/FlameGraph/FlameGraphComponent/Flamegraph_render.d.ts.map +1 -1
  8. package/dist/FlameGraph/FlameGraphComponent/index.d.ts +13 -0
  9. package/dist/FlameGraph/FlameGraphComponent/index.d.ts.map +1 -1
  10. package/dist/FlameGraph/FlameGraphRenderer.d.ts +13 -1
  11. package/dist/FlameGraph/FlameGraphRenderer.d.ts.map +1 -1
  12. package/dist/ProfilerTable.d.ts.map +1 -1
  13. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  14. package/dist/format/format.d.ts +14 -0
  15. package/dist/format/format.d.ts.map +1 -1
  16. package/dist/i18n.d.ts +9 -0
  17. package/dist/i18n.d.ts.map +1 -1
  18. package/dist/index.cjs.js +3 -3
  19. package/dist/index.cjs.js.map +1 -1
  20. package/dist/index.esm.js +4 -4
  21. package/dist/index.esm.js.map +1 -1
  22. package/dist/index.node.cjs.js +4 -4
  23. package/dist/index.node.cjs.js.map +1 -1
  24. package/dist/index.node.esm.js +4 -4
  25. package/dist/index.node.esm.js.map +1 -1
  26. package/dist/shims/Table.d.ts.map +1 -1
  27. package/package.json +1 -1
  28. package/src/FlameGraph/FlameGraphComponent/Flamegraph.ts +7 -2
  29. package/src/FlameGraph/FlameGraphComponent/FlamegraphBreadcrumb.module.scss +106 -0
  30. package/src/FlameGraph/FlameGraphComponent/FlamegraphBreadcrumb.tsx +98 -0
  31. package/src/FlameGraph/FlameGraphComponent/Flamegraph_render.ts +54 -23
  32. package/src/FlameGraph/FlameGraphComponent/index.tsx +46 -3
  33. package/src/FlameGraph/FlameGraphRenderer.tsx +211 -6
  34. package/src/ProfilerTable.module.scss +1 -2
  35. package/src/ProfilerTable.tsx +52 -46
  36. package/src/Tooltip/Tooltip.tsx +1 -75
  37. package/src/format/format.ts +98 -0
  38. package/src/i18n.tsx +39 -0
  39. package/src/shims/Table.module.scss +9 -2
  40. package/src/shims/Table.tsx +44 -40
@@ -1 +1 @@
1
- {"version":3,"file":"Table.d.ts","sourceRoot":"","sources":["../../src/shims/Table.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,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,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;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,2CAsGZ;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.22",
3
+ "version": "0.35.24",
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;
@@ -0,0 +1,106 @@
1
+ .bar {
2
+ display: flex;
3
+ align-items: center;
4
+ justify-content: center;
5
+ width: 100%;
6
+ flex-wrap: wrap;
7
+ gap: 8px;
8
+ margin: 6px 0;
9
+ }
10
+
11
+ .chip {
12
+ display: inline-flex;
13
+ align-items: center;
14
+ gap: 6px;
15
+ padding: 4px 10px;
16
+ border-radius: 999px;
17
+ font-size: 12px;
18
+ line-height: 1.2;
19
+ background-color: var(--ps-ui-element-bg-primary);
20
+ border: 1px solid var(--ps-ui-border);
21
+ color: var(--ps-ui-foreground-text);
22
+ }
23
+
24
+ .focusTooltip {
25
+ position: relative;
26
+ }
27
+
28
+ .focusTooltip[data-tooltip]:hover::after,
29
+ .focusTooltip[data-tooltip]:hover::before {
30
+ opacity: 1;
31
+ transform: translate(-50%, -6px);
32
+ }
33
+
34
+ .focusTooltip[data-tooltip]::after {
35
+ content: attr(data-tooltip);
36
+ position: absolute;
37
+ left: 50%;
38
+ bottom: 100%;
39
+ transform: translate(-50%, -2px);
40
+ padding: 6px 8px;
41
+ border-radius: 6px;
42
+ background: var(--ps-ui-foreground);
43
+ color: var(--ps-ui-foreground-text);
44
+ border: 1px solid var(--ps-ui-border);
45
+ font-size: 12px;
46
+ white-space: nowrap;
47
+ pointer-events: none;
48
+ opacity: 0;
49
+ transition: opacity 120ms ease, transform 120ms ease;
50
+ z-index: 3;
51
+ }
52
+
53
+ .focusTooltip[data-tooltip]::before {
54
+ content: '';
55
+ position: absolute;
56
+ left: 50%;
57
+ bottom: 100%;
58
+ transform: translate(-50%, -2px);
59
+ border-width: 6px 6px 0 6px;
60
+ border-style: solid;
61
+ border-color: var(--ps-ui-border) transparent transparent transparent;
62
+ pointer-events: none;
63
+ opacity: 0;
64
+ transition: opacity 120ms ease, transform 120ms ease;
65
+ z-index: 2;
66
+ }
67
+
68
+ .separator {
69
+ opacity: 0.6;
70
+ }
71
+
72
+ .chipSeparator {
73
+ opacity: 0.6;
74
+ color: var(--ps-ui-foreground-text);
75
+ }
76
+
77
+ .unitLabel {
78
+ opacity: 0.8;
79
+ }
80
+
81
+ .focusIcon {
82
+ font-size: 12px;
83
+ display: inline-flex;
84
+ align-items: center;
85
+ color: currentColor;
86
+ }
87
+
88
+ .ofTotal {
89
+ opacity: 0.8;
90
+ }
91
+
92
+ .clearBtn {
93
+ margin-left: 4px;
94
+ padding: 0;
95
+ border: none;
96
+ background: transparent;
97
+ color: var(--ps-toolbar-icon-color);
98
+ cursor: pointer;
99
+ font-size: 14px;
100
+ line-height: 1;
101
+ }
102
+
103
+ .clearBtn:focus-visible {
104
+ outline: 1px solid var(--ps-ui-border);
105
+ border-radius: 4px;
106
+ }
@@ -0,0 +1,98 @@
1
+ import React from 'react';
2
+ import styles from './FlamegraphBreadcrumb.module.scss';
3
+
4
+ type FlamegraphBreadcrumbProps = {
5
+ totalValueText: string;
6
+ totalSamplesText: string;
7
+ samplesLabel: string;
8
+ unitLabel: string;
9
+ hasFocus: boolean;
10
+ focusPercent?: number;
11
+ focusLabel?: string;
12
+ ofTotalPlacement?: 'before' | 'after';
13
+ ofTotalLabel: string;
14
+ clearFocusLabel: string;
15
+ onClearFocus: () => void;
16
+ };
17
+
18
+ export default function FlamegraphBreadcrumb(
19
+ props: FlamegraphBreadcrumbProps
20
+ ) {
21
+ const {
22
+ totalValueText,
23
+ totalSamplesText,
24
+ samplesLabel,
25
+ unitLabel,
26
+ hasFocus,
27
+ focusPercent,
28
+ focusLabel,
29
+ ofTotalPlacement = 'after',
30
+ ofTotalLabel,
31
+ clearFocusLabel,
32
+ onClearFocus,
33
+ } = props;
34
+
35
+ const focusTooltip =
36
+ focusLabel && focusLabel.trim().length > 0 ? focusLabel : undefined;
37
+
38
+ return (
39
+ <div className={styles.bar} aria-live="polite">
40
+ <span className={styles.chip}>
41
+ <span>{totalValueText}</span>
42
+ <span className={styles.separator}>|</span>
43
+ <span>
44
+ {totalSamplesText} {samplesLabel}
45
+ </span>
46
+ <span className={styles.unitLabel}>({unitLabel})</span>
47
+ </span>
48
+ {hasFocus && focusPercent !== undefined && (
49
+ <>
50
+ <span className={styles.chipSeparator}>&gt;</span>
51
+ <span
52
+ className={`${styles.chip} ${styles.focusTooltip}`}
53
+ data-tooltip={focusTooltip}
54
+ >
55
+ <span className={styles.focusIcon} aria-hidden="true">
56
+ <svg
57
+ viewBox="0 0 24 24"
58
+ width="14"
59
+ height="14"
60
+ fill="none"
61
+ stroke="currentColor"
62
+ strokeWidth="1.6"
63
+ strokeLinecap="round"
64
+ strokeLinejoin="round"
65
+ >
66
+ <path d="M2 12s3.5-6 10-6 10 6 10 6-3.5 6-10 6-10-6-10-6Z" />
67
+ <circle cx="12" cy="12" r="3.2" />
68
+ </svg>
69
+ </span>
70
+ {ofTotalPlacement === 'before' ? (
71
+ <>
72
+ <span className={styles.ofTotal}>{ofTotalLabel}</span>
73
+ <span>{focusPercent.toFixed(2)}%</span>
74
+ </>
75
+ ) : (
76
+ <>
77
+ <span>{focusPercent.toFixed(2)}%</span>
78
+ <span className={styles.ofTotal}>{ofTotalLabel}</span>
79
+ </>
80
+ )}
81
+ <button
82
+ type="button"
83
+ className={styles.clearBtn}
84
+ aria-label={clearFocusLabel}
85
+ onClick={(event) => {
86
+ event.preventDefault();
87
+ event.stopPropagation();
88
+ onClearFocus();
89
+ }}
90
+ >
91
+ ×
92
+ </button>
93
+ </span>
94
+ </>
95
+ )}
96
+ </div>
97
+ );
98
+ }
@@ -24,9 +24,9 @@ THIS SOFTWARE.
24
24
  /* eslint-disable no-continue */
25
25
  import { createFF, Flamebearer, SpyName } from '../../models';
26
26
  import {
27
- formatPercent,
28
27
  getFormatter,
29
28
  ratioToPercent,
29
+ localizeDurationString,
30
30
  } from '../../format/format';
31
31
  import { fitToCanvasRect } from '../../fitMode/fitMode';
32
32
  import { getRatios } from './utils';
@@ -49,6 +49,20 @@ 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
+ isZh?: boolean;
57
+ }
58
+
59
+ /** Default English messages (fallback) */
60
+ const defaultCanvasMessages: CanvasI18nMessages = {
61
+ collapsedLevelsSingular: 'total (1 level collapsed)',
62
+ collapsedLevelsPlural: 'total ({n} levels collapsed)',
63
+ isZh: false,
64
+ };
65
+
52
66
  type CanvasRendererConfig = Flamebearer & {
53
67
  canvas: HTMLCanvasElement;
54
68
  focusedNode: ConstructorParameters<typeof Flamegraph>[2];
@@ -73,13 +87,30 @@ type CanvasRendererConfig = Flamebearer & {
73
87
 
74
88
  palette: FlamegraphPalette;
75
89
  maxSelf?: number;
90
+
91
+ /** i18n messages for collapsed text */
92
+ messages?: CanvasI18nMessages;
76
93
  };
77
94
 
95
+ /**
96
+ * 生成 collapsed 层级的显示文本
97
+ */
98
+ function getCollapsedText(
99
+ levelCount: number,
100
+ messages: CanvasI18nMessages
101
+ ): string {
102
+ if (levelCount === 1) {
103
+ return messages.collapsedLevelsSingular;
104
+ }
105
+ return messages.collapsedLevelsPlural.replace('{n}', String(levelCount));
106
+ }
107
+
78
108
  export default function RenderCanvas(props: CanvasRendererConfig) {
79
109
  const { canvas, fitMode, units, tickToX, levels, palette } = props;
80
110
  const { numTicks, sampleRate, pxPerTick } = props;
81
111
  const { rangeMin, rangeMax } = props;
82
112
  const { focusedNode, zoom } = props;
113
+ const messages = props.messages || defaultCanvasMessages;
83
114
 
84
115
  const graphWidth = getCanvasWidth(canvas);
85
116
  // TODO: why is this needed? otherwise height is all messed up
@@ -134,10 +165,10 @@ export default function RenderCanvas(props: CanvasRendererConfig) {
134
165
  ctx.fillStyle = colorGreyscale(200, 1).rgb().string();
135
166
  ctx.fill();
136
167
 
137
- // TODO show the samples too?
168
+ // 使用 i18n 翻译的 collapsed 文本
138
169
  const shortName = focusedNode.mapOrElse(
139
170
  () => 'total',
140
- (f) => `total (${f.i - 1} level(s) collapsed)`
171
+ (f) => getCollapsedText(f.i - 1, messages)
141
172
  );
142
173
 
143
174
  // Set the font syle
@@ -201,16 +232,16 @@ export default function RenderCanvas(props: CanvasRendererConfig) {
201
232
  j < level.length - ff.jStep &&
202
233
  barIndex + numBarTicks === ff.getBarOffset(level, j + ff.jStep) &&
203
234
  ff.getBarTotal(level, j + ff.jStep) * pxPerTick <=
204
- COLLAPSE_THRESHOLD &&
235
+ COLLAPSE_THRESHOLD &&
205
236
  isHighlighted ===
206
- ((props.highlightQuery &&
207
- nodeIsInQuery(
208
- j + ff.jStep + ff.jName,
209
- level,
210
- names,
211
- props.highlightQuery
212
- )) ||
213
- false)
237
+ ((props.highlightQuery &&
238
+ nodeIsInQuery(
239
+ j + ff.jStep + ff.jName,
240
+ level,
241
+ names,
242
+ props.highlightQuery
243
+ )) ||
244
+ false)
214
245
  ) {
215
246
  j += ff.jStep;
216
247
  numBarTicks += ff.getBarTotal(level, j);
@@ -286,9 +317,9 @@ export default function RenderCanvas(props: CanvasRendererConfig) {
286
317
  const longName = getLongName(
287
318
  shortName,
288
319
  numBarTicks,
289
- numTicks,
290
320
  sampleRate,
291
- formatter
321
+ formatter,
322
+ messages
292
323
  );
293
324
 
294
325
  // Set the font syle
@@ -343,20 +374,20 @@ function getFunctionName(
343
374
  return shortName;
344
375
  }
345
376
 
377
+ /**
378
+ * 生成条形文本的长名称
379
+ * 格式: functionName (value) - 对齐 Grafana 风格,不显示百分比
380
+ */
346
381
  function getLongName(
347
382
  shortName: string,
348
383
  numBarTicks: number,
349
- numTicks: number,
350
384
  sampleRate: number,
351
- formatter: ReturnType<typeof getFormatter>
385
+ formatter: ReturnType<typeof getFormatter>,
386
+ messages: CanvasI18nMessages
352
387
  ) {
353
- const ratio = numBarTicks / numTicks;
354
- const percent = formatPercent(ratio);
355
-
356
- const longName = `${shortName} (${percent}, ${formatter.format(
357
- numBarTicks,
358
- sampleRate
359
- )})`;
388
+ const formatted = formatter.format(numBarTicks, sampleRate);
389
+ const localized = localizeDurationString(formatted, !!messages.isZh);
390
+ const longName = `${shortName} (${localized || formatted})`;
360
391
 
361
392
  return longName;
362
393
  }
@@ -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';
@@ -23,6 +23,7 @@ import { SandwichIcon, HeadFirstIcon, TailFirstIcon } from '../../Icons';
23
23
  import { PX_PER_LEVEL } from './constants';
24
24
  import Header from './Header';
25
25
  import { FlamegraphPalette } from './colorPalette';
26
+ import FlamegraphBreadcrumb from './FlamegraphBreadcrumb';
26
27
  import type { ViewTypes } from './viewTypes';
27
28
  import { FitModes, HeadMode, TailMode } from '../../fitMode/fitMode';
28
29
  import indexStyles from './styles.module.scss';
@@ -56,6 +57,20 @@ interface FlamegraphProps {
56
57
 
57
58
  /** 是否显示右键菜单中的「打开 Sandwich 视图」项,默认 true */
58
59
  enableSandwichView?: boolean;
60
+
61
+ breadcrumb?: {
62
+ totalValueText: string;
63
+ totalSamplesText: string;
64
+ samplesLabel: string;
65
+ unitLabel: string;
66
+ hasFocus: boolean;
67
+ focusPercent?: number;
68
+ focusLabel?: string;
69
+ ofTotalPlacement?: 'before' | 'after';
70
+ ofTotalLabel: string;
71
+ clearFocusLabel: string;
72
+ onClearFocus: () => void;
73
+ };
59
74
  }
60
75
 
61
76
  export default function FlameGraphComponent(props: FlamegraphProps) {
@@ -63,6 +78,16 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
63
78
  const flamegraph = useRef<Flamegraph>();
64
79
  const i18n = useFlamegraphI18n();
65
80
 
81
+ // ====== 新增:提取 canvas 渲染需要的 i18n messages ======
82
+ const canvasMessages = useMemo(
83
+ () => ({
84
+ collapsedLevelsSingular: i18n.collapsedLevelsSingular,
85
+ collapsedLevelsPlural: i18n.collapsedLevelsPlural,
86
+ isZh: i18n.location !== 'Location',
87
+ }),
88
+ [i18n.collapsedLevelsSingular, i18n.collapsedLevelsPlural, i18n.location]
89
+ );
90
+
66
91
  const [rightClickedNode, setRightClickedNode] = React.useState<
67
92
  Maybe<{ top: number; left: number; width: number }>
68
93
  >(Maybe.nothing());
@@ -83,6 +108,7 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
83
108
  selectedItem,
84
109
  updateView,
85
110
  enableSandwichView = true,
111
+ breadcrumb,
86
112
  } = props;
87
113
 
88
114
  const { onZoom, onReset, isDirty, onFocusOnNode } = props;
@@ -291,17 +317,19 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
291
317
  fitMode,
292
318
  highlightQuery,
293
319
  zoom,
294
- palette
320
+ palette,
321
+ canvasMessages // ====== 新增:传递 i18n messages ======
295
322
  );
296
323
 
297
324
  flamegraph.current = f;
298
325
  }
299
326
  };
300
327
 
328
+ // ====== 修改:添加 canvasMessages 依赖 ======
301
329
  React.useEffect(() => {
302
330
  constructCanvas();
303
331
  renderCanvas();
304
- }, [palette]);
332
+ }, [palette, canvasMessages]);
305
333
 
306
334
  React.useEffect(() => {
307
335
  constructCanvas();
@@ -345,6 +373,21 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
345
373
  'vertical-orientation': flamebearer.format === 'double',
346
374
  })}
347
375
  >
376
+ {breadcrumb && (
377
+ <FlamegraphBreadcrumb
378
+ totalValueText={breadcrumb.totalValueText}
379
+ totalSamplesText={breadcrumb.totalSamplesText}
380
+ samplesLabel={breadcrumb.samplesLabel}
381
+ unitLabel={breadcrumb.unitLabel}
382
+ hasFocus={breadcrumb.hasFocus}
383
+ focusPercent={breadcrumb.focusPercent}
384
+ focusLabel={breadcrumb.focusLabel}
385
+ ofTotalPlacement={breadcrumb.ofTotalPlacement}
386
+ ofTotalLabel={breadcrumb.ofTotalLabel}
387
+ clearFocusLabel={breadcrumb.clearFocusLabel}
388
+ onClearFocus={breadcrumb.onClearFocus}
389
+ />
390
+ )}
348
391
  {/* Header 已简化,仅在 single 模式下显示标题 */}
349
392
  {/* DiffLegend 已移到 Toolbar */}
350
393
  {headerVisible && (