@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.
- package/CHANGELOG.md +22 -0
- package/dist/FlameGraph/FlameGraphComponent/Flamegraph.d.ts +6 -1
- package/dist/FlameGraph/FlameGraphComponent/Flamegraph.d.ts.map +1 -1
- package/dist/FlameGraph/FlameGraphComponent/FlamegraphBreadcrumb.d.ts +16 -0
- package/dist/FlameGraph/FlameGraphComponent/FlamegraphBreadcrumb.d.ts.map +1 -0
- package/dist/FlameGraph/FlameGraphComponent/Flamegraph_render.d.ts +8 -0
- package/dist/FlameGraph/FlameGraphComponent/Flamegraph_render.d.ts.map +1 -1
- package/dist/FlameGraph/FlameGraphComponent/index.d.ts +13 -0
- package/dist/FlameGraph/FlameGraphComponent/index.d.ts.map +1 -1
- package/dist/FlameGraph/FlameGraphRenderer.d.ts +13 -1
- package/dist/FlameGraph/FlameGraphRenderer.d.ts.map +1 -1
- package/dist/ProfilerTable.d.ts.map +1 -1
- package/dist/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/format/format.d.ts +14 -0
- package/dist/format/format.d.ts.map +1 -1
- package/dist/i18n.d.ts +9 -0
- package/dist/i18n.d.ts.map +1 -1
- package/dist/index.cjs.js +3 -3
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +4 -4
- package/dist/index.esm.js.map +1 -1
- package/dist/index.node.cjs.js +4 -4
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/index.node.esm.js +4 -4
- package/dist/index.node.esm.js.map +1 -1
- package/dist/shims/Table.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/FlameGraph/FlameGraphComponent/Flamegraph.ts +7 -2
- package/src/FlameGraph/FlameGraphComponent/FlamegraphBreadcrumb.module.scss +106 -0
- package/src/FlameGraph/FlameGraphComponent/FlamegraphBreadcrumb.tsx +98 -0
- package/src/FlameGraph/FlameGraphComponent/Flamegraph_render.ts +54 -23
- package/src/FlameGraph/FlameGraphComponent/index.tsx +46 -3
- package/src/FlameGraph/FlameGraphRenderer.tsx +211 -6
- package/src/ProfilerTable.module.scss +1 -2
- package/src/ProfilerTable.tsx +52 -46
- package/src/Tooltip/Tooltip.tsx +1 -75
- package/src/format/format.ts +98 -0
- package/src/i18n.tsx +39 -0
- package/src/shims/Table.module.scss +9 -2
- 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,
|
|
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
|
@@ -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}>></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
|
-
//
|
|
168
|
+
// 使用 i18n 翻译的 collapsed 文本
|
|
138
169
|
const shortName = focusedNode.mapOrElse(
|
|
139
170
|
() => 'total',
|
|
140
|
-
(f) =>
|
|
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
|
-
|
|
235
|
+
COLLAPSE_THRESHOLD &&
|
|
205
236
|
isHighlighted ===
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
354
|
-
const
|
|
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 && (
|