@parca/profile 0.19.95 → 0.19.103
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 +32 -0
- package/dist/ProfileFlameGraph/FlameGraphArrow/FlameGraphNodes.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/FlameGraphArrow/FlameGraphNodes.js +65 -45
- package/dist/ProfileFlameGraph/FlameGraphArrow/index.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/FlameGraphArrow/index.js +16 -4
- package/dist/ProfileFlameGraph/FlameGraphArrow/useBatchedRendering.d.ts +11 -0
- package/dist/ProfileFlameGraph/FlameGraphArrow/useBatchedRendering.d.ts.map +1 -0
- package/dist/ProfileFlameGraph/FlameGraphArrow/useBatchedRendering.js +65 -0
- package/dist/ProfileFlameGraph/FlameGraphArrow/useScrollViewport.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/FlameGraphArrow/useScrollViewport.js +35 -5
- package/dist/ProfileFlameGraph/FlameGraphArrow/useVisibleNodes.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/FlameGraphArrow/useVisibleNodes.js +29 -3
- package/dist/ProfileSelector/MetricsGraphSection.d.ts +1 -1
- package/dist/ProfileSelector/MetricsGraphSection.d.ts.map +1 -1
- package/dist/ProfileSelector/MetricsGraphSection.js +1 -1
- package/dist/ProfileSelector/index.d.ts.map +1 -1
- package/dist/ProfileSelector/index.js +1 -2
- package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.d.ts.map +1 -1
- package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.js +8 -0
- package/dist/SimpleMatchers/Select.d.ts.map +1 -1
- package/dist/SimpleMatchers/Select.js +3 -3
- package/dist/hooks/useLabels.d.ts.map +1 -1
- package/dist/hooks/useLabels.js +7 -2
- package/dist/hooks/useQueryState.d.ts.map +1 -1
- package/dist/hooks/useQueryState.js +53 -23
- package/dist/hooks/useQueryState.test.js +32 -22
- package/dist/styles.css +1 -1
- package/dist/useSumBy.d.ts +10 -2
- package/dist/useSumBy.d.ts.map +1 -1
- package/dist/useSumBy.js +30 -7
- package/package.json +15 -10
- package/src/ProfileFlameGraph/FlameGraphArrow/FlameGraphNodes.tsx +89 -57
- package/src/ProfileFlameGraph/FlameGraphArrow/index.tsx +27 -2
- package/src/ProfileFlameGraph/FlameGraphArrow/useBatchedRendering.ts +84 -0
- package/src/ProfileFlameGraph/FlameGraphArrow/useScrollViewport.ts +40 -5
- package/src/ProfileFlameGraph/FlameGraphArrow/useVisibleNodes.ts +41 -5
- package/src/ProfileSelector/MetricsGraphSection.tsx +2 -2
- package/src/ProfileSelector/index.tsx +1 -5
- package/src/ProfileView/hooks/useResetStateOnProfileTypeChange.ts +8 -0
- package/src/SimpleMatchers/Select.tsx +3 -3
- package/src/hooks/useLabels.ts +8 -2
- package/src/hooks/useQueryState.test.tsx +41 -22
- package/src/hooks/useQueryState.ts +72 -31
- package/src/useSumBy.ts +58 -4
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,38 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [0.19.103](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.102...@parca/profile@0.19.103) (2025-12-17)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @parca/profile
|
|
9
|
+
|
|
10
|
+
## [0.19.102](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.101...@parca/profile@0.19.102) (2025-12-15)
|
|
11
|
+
|
|
12
|
+
**Note:** Version bump only for package @parca/profile
|
|
13
|
+
|
|
14
|
+
## [0.19.101](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.100...@parca/profile@0.19.101) (2025-12-15)
|
|
15
|
+
|
|
16
|
+
**Note:** Version bump only for package @parca/profile
|
|
17
|
+
|
|
18
|
+
## [0.19.100](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.99...@parca/profile@0.19.100) (2025-12-15)
|
|
19
|
+
|
|
20
|
+
**Note:** Version bump only for package @parca/profile
|
|
21
|
+
|
|
22
|
+
## [0.19.99](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.98...@parca/profile@0.19.99) (2025-12-12)
|
|
23
|
+
|
|
24
|
+
**Note:** Version bump only for package @parca/profile
|
|
25
|
+
|
|
26
|
+
## [0.19.98](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.97...@parca/profile@0.19.98) (2025-12-12)
|
|
27
|
+
|
|
28
|
+
**Note:** Version bump only for package @parca/profile
|
|
29
|
+
|
|
30
|
+
## [0.19.97](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.96...@parca/profile@0.19.97) (2025-12-12)
|
|
31
|
+
|
|
32
|
+
**Note:** Version bump only for package @parca/profile
|
|
33
|
+
|
|
34
|
+
## [0.19.96](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.95...@parca/profile@0.19.96) (2025-12-12)
|
|
35
|
+
|
|
36
|
+
**Note:** Version bump only for package @parca/profile
|
|
37
|
+
|
|
6
38
|
## [0.19.95](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.94...@parca/profile@0.19.95) (2025-12-09)
|
|
7
39
|
|
|
8
40
|
**Note:** Version bump only for package @parca/profile
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FlameGraphNodes.d.ts","sourceRoot":"","sources":["../../../src/ProfileFlameGraph/FlameGraphArrow/FlameGraphNodes.tsx"],"names":[],"mappings":"AAaA,OAAO,
|
|
1
|
+
{"version":3,"file":"FlameGraphNodes.d.ts","sourceRoot":"","sources":["../../../src/ProfileFlameGraph/FlameGraphArrow/FlameGraphNodes.tsx"],"names":[],"mappings":"AAaA,OAAO,KAA6B,MAAM,OAAO,CAAC;AAElD,OAAO,EAAC,KAAK,EAAC,MAAM,cAAc,CAAC;AAKnC,OAAO,yCAAyC,CAAC;AAEjD,OAAO,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAelD,eAAO,MAAM,SAAS,KAAK,CAAC;AAE5B,MAAM,WAAW,aAAa;IAC5B,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1D,oBAAoB,EAAE,MAAM,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,aAAa,EAAE,aAAa,CAAC;IAC7B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IAGnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;CACnD;AAED,eAAO,MAAM,eAAe;;;CAG3B,CAAC;AACF,eAAO,MAAM,oBAAoB;;;;CAIhC,CAAC;AAEF,eAAO,MAAM,SAAS,4CA6PrB,CAAC"}
|
|
@@ -11,7 +11,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
|
|
|
11
11
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
// See the License for the specific language governing permissions and
|
|
13
13
|
// limitations under the License.
|
|
14
|
-
import React, { useMemo } from 'react';
|
|
14
|
+
import React, { useCallback, useMemo } from 'react';
|
|
15
15
|
import cx from 'classnames';
|
|
16
16
|
import { selectBinaries, useAppSelector } from '@parca/store';
|
|
17
17
|
import 'react-contexify/dist/ReactContexify.css';
|
|
@@ -30,28 +30,38 @@ export const fadedFlameRectStyles = {
|
|
|
30
30
|
opacity: '0.5',
|
|
31
31
|
};
|
|
32
32
|
export const FlameNode = React.memo(function FlameNodeNoMemo({ table, row, colors, colorBy, height, totalWidth, darkMode, compareMode, colorForSimilarNodes, selectedRow, onClick, onContextMenu, hoveringRow, setHoveringRow, isFlameChart, profileSource, isRenderedAsFlamegraph = false, isInSandwichView = false, maxDepth = 0, effectiveDepth, tooltipId = 'default', }) {
|
|
33
|
-
//
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
33
|
+
// Memoize column references - only changes when table changes
|
|
34
|
+
const columns = useMemo(() => ({
|
|
35
|
+
mapping: table.getChild(FIELD_MAPPING_FILE),
|
|
36
|
+
functionName: table.getChild(FIELD_FUNCTION_NAME),
|
|
37
|
+
cumulative: table.getChild(FIELD_CUMULATIVE),
|
|
38
|
+
depth: table.getChild(FIELD_DEPTH),
|
|
39
|
+
diff: table.getChild(FIELD_DIFF),
|
|
40
|
+
filename: table.getChild(FIELD_FUNCTION_FILE_NAME),
|
|
41
|
+
valueOffset: table.getChild(FIELD_VALUE_OFFSET),
|
|
42
|
+
ts: table.getChild(FIELD_TIMESTAMP),
|
|
43
|
+
}), [table]);
|
|
42
44
|
// get the actual values from the columns
|
|
43
45
|
const binaries = useAppSelector(selectBinaries);
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
// Memoize row data extraction - only changes when table or row changes
|
|
47
|
+
const rowData = useMemo(() => {
|
|
48
|
+
const mappingFile = arrowToString(columns.mapping?.get(row));
|
|
49
|
+
const functionName = arrowToString(columns.functionName?.get(row));
|
|
50
|
+
const cumulative = columns.cumulative?.get(row) != null ? BigInt(columns.cumulative?.get(row)) : 0n;
|
|
51
|
+
const diff = columns.diff?.get(row) != null ? BigInt(columns.diff?.get(row)) : null;
|
|
52
|
+
const filename = arrowToString(columns.filename?.get(row));
|
|
53
|
+
const depth = columns.depth?.get(row) ?? 0;
|
|
54
|
+
const valueOffset = columns.valueOffset?.get(row) !== null && columns.valueOffset?.get(row) !== undefined
|
|
55
|
+
? BigInt(columns.valueOffset?.get(row))
|
|
56
|
+
: 0n;
|
|
57
|
+
return { mappingFile, functionName, cumulative, diff, filename, depth, valueOffset };
|
|
58
|
+
}, [columns, row]);
|
|
59
|
+
const { mappingFile, functionName, cumulative, diff, filename, depth, valueOffset } = rowData;
|
|
53
60
|
const colorAttribute = colorBy === 'filename' ? filename : colorBy === 'binary' ? mappingFile : null;
|
|
54
|
-
|
|
61
|
+
// Memoize hovering name lookup
|
|
62
|
+
const hoveringName = useMemo(() => {
|
|
63
|
+
return hoveringRow !== undefined ? arrowToString(columns.functionName?.get(hoveringRow)) : '';
|
|
64
|
+
}, [columns.functionName, hoveringRow]);
|
|
55
65
|
const shouldBeHighlighted = functionName != null && hoveringName != null && functionName === hoveringName;
|
|
56
66
|
const colorResult = useNodeColor({
|
|
57
67
|
isDarkMode: darkMode,
|
|
@@ -64,15 +74,43 @@ export const FlameNode = React.memo(function FlameNodeNoMemo({ table, row, color
|
|
|
64
74
|
const name = useMemo(() => {
|
|
65
75
|
return row === 0 ? 'root' : nodeLabel(table, row, binaries.length > 1);
|
|
66
76
|
}, [table, row, binaries]);
|
|
77
|
+
// Memoize selection data - only changes when selectedRow changes
|
|
78
|
+
const selectionData = useMemo(() => {
|
|
79
|
+
const selectionOffset = columns.valueOffset?.get(selectedRow) !== null &&
|
|
80
|
+
columns.valueOffset?.get(selectedRow) !== undefined
|
|
81
|
+
? BigInt(columns.valueOffset?.get(selectedRow))
|
|
82
|
+
: 0n;
|
|
83
|
+
const selectionCumulative = columns.cumulative?.get(selectedRow) !== null
|
|
84
|
+
? BigInt(columns.cumulative?.get(selectedRow))
|
|
85
|
+
: 0n;
|
|
86
|
+
const selectedDepth = columns.depth?.get(selectedRow);
|
|
87
|
+
const total = columns.cumulative?.get(selectedRow);
|
|
88
|
+
return { selectionOffset, selectionCumulative, selectedDepth, total };
|
|
89
|
+
}, [columns, selectedRow]);
|
|
90
|
+
const { selectionOffset, selectionCumulative, selectedDepth, total } = selectionData;
|
|
91
|
+
// Memoize tsBounds - only changes when profileSource changes
|
|
92
|
+
const tsBounds = useMemo(() => boundsFromProfileSource(profileSource), [profileSource]);
|
|
93
|
+
// Memoize event handlers
|
|
94
|
+
const onMouseEnter = useCallback(() => {
|
|
95
|
+
setHoveringRow(row);
|
|
96
|
+
window.dispatchEvent(new CustomEvent(`flame-tooltip-update-${tooltipId}`, {
|
|
97
|
+
detail: { row },
|
|
98
|
+
}));
|
|
99
|
+
}, [setHoveringRow, row, tooltipId]);
|
|
100
|
+
const onMouseLeave = useCallback(() => {
|
|
101
|
+
setHoveringRow(undefined);
|
|
102
|
+
window.dispatchEvent(new CustomEvent(`flame-tooltip-update-${tooltipId}`, {
|
|
103
|
+
detail: { row: null },
|
|
104
|
+
}));
|
|
105
|
+
}, [setHoveringRow, tooltipId]);
|
|
106
|
+
const handleContextMenu = useCallback((e) => {
|
|
107
|
+
onContextMenu(e, row);
|
|
108
|
+
}, [onContextMenu, row]);
|
|
109
|
+
// Early returns - all hooks must be called before this point
|
|
67
110
|
// Hide frames beyond effective depth limit
|
|
68
111
|
if (effectiveDepth !== undefined && depth > effectiveDepth) {
|
|
69
112
|
return _jsx(_Fragment, {});
|
|
70
113
|
}
|
|
71
|
-
const selectionOffset = valueOffsetColumn?.get(selectedRow) !== null &&
|
|
72
|
-
valueOffsetColumn?.get(selectedRow) !== undefined
|
|
73
|
-
? BigInt(valueOffsetColumn?.get(selectedRow))
|
|
74
|
-
: 0n;
|
|
75
|
-
const selectionCumulative = cumulativeColumn?.get(selectedRow) !== null ? BigInt(cumulativeColumn?.get(selectedRow)) : 0n;
|
|
76
114
|
if (valueOffset + cumulative <= selectionOffset ||
|
|
77
115
|
valueOffset >= selectionOffset + selectionCumulative) {
|
|
78
116
|
// If the end of the node is before the selection offset or the start of the node is after the selection offset + totalWidth, we don't render it.
|
|
@@ -83,8 +121,6 @@ export const FlameNode = React.memo(function FlameNodeNoMemo({ table, row, color
|
|
|
83
121
|
return _jsx(_Fragment, {});
|
|
84
122
|
}
|
|
85
123
|
// Cumulative can be larger than total when a selection is made. All parents of the selection are likely larger, but we want to only show them as 100% in the graph.
|
|
86
|
-
const tsBounds = boundsFromProfileSource(profileSource);
|
|
87
|
-
const total = cumulativeColumn?.get(selectedRow);
|
|
88
124
|
const totalRatio = cumulative > total ? 1 : Number(cumulative) / Number(total);
|
|
89
125
|
const width = isFlameChart
|
|
90
126
|
? (Number(cumulative) / (Number(tsBounds[1]) - Number(tsBounds[0]))) * totalWidth
|
|
@@ -92,25 +128,9 @@ export const FlameNode = React.memo(function FlameNodeNoMemo({ table, row, color
|
|
|
92
128
|
if (width <= 1) {
|
|
93
129
|
return _jsx(_Fragment, {});
|
|
94
130
|
}
|
|
95
|
-
const selectedDepth = depthColumn?.get(selectedRow);
|
|
96
131
|
const styles = selectedDepth !== undefined && selectedDepth > depth ? fadedFlameRectStyles : flameRectStyles;
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
window.dispatchEvent(new CustomEvent(`flame-tooltip-update-${tooltipId}`, {
|
|
100
|
-
detail: { row },
|
|
101
|
-
}));
|
|
102
|
-
};
|
|
103
|
-
const onMouseLeave = () => {
|
|
104
|
-
setHoveringRow(undefined);
|
|
105
|
-
window.dispatchEvent(new CustomEvent(`flame-tooltip-update-${tooltipId}`, {
|
|
106
|
-
detail: { row: null },
|
|
107
|
-
}));
|
|
108
|
-
};
|
|
109
|
-
const handleContextMenu = (e) => {
|
|
110
|
-
onContextMenu(e, row);
|
|
111
|
-
};
|
|
112
|
-
const ts = tsColumn !== null ? Number(tsColumn.get(row)) : 0;
|
|
113
|
-
const x = isFlameChart && tsColumn !== null
|
|
132
|
+
const ts = columns.ts !== null ? Number(columns.ts.get(row)) : 0;
|
|
133
|
+
const x = isFlameChart && columns.ts !== null
|
|
114
134
|
? ((ts - Number(tsBounds[0])) / (Number(tsBounds[1]) - Number(tsBounds[0]))) * totalWidth
|
|
115
135
|
: selectedDepth > depth
|
|
116
136
|
? 0
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ProfileFlameGraph/FlameGraphArrow/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAQN,MAAM,OAAO,CAAC;AAKf,OAAO,EAAC,eAAe,EAAC,MAAM,eAAe,CAAC;AAG9C,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAC;AAE1C,OAAO,EAAC,KAAK,WAAW,EAAC,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAIlD,OAAO,EAAuB,aAAa,EAAC,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ProfileFlameGraph/FlameGraphArrow/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAQN,MAAM,OAAO,CAAC;AAKf,OAAO,EAAC,eAAe,EAAC,MAAM,eAAe,CAAC;AAG9C,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAC;AAE1C,OAAO,EAAC,KAAK,WAAW,EAAC,MAAM,kBAAkB,CAAC;AAElD,OAAO,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAIlD,OAAO,EAAuB,aAAa,EAAC,MAAM,mBAAmB,CAAC;AAMtE,OAAO,EACL,gBAAgB,EAMjB,MAAM,SAAS,CAAC;AAEjB,eAAO,MAAM,iBAAiB,gBAAgB,CAAC;AAC/C,eAAO,MAAM,kBAAkB,iBAAiB,CAAC;AACjD,eAAO,MAAM,sBAAsB,qBAAqB,CAAC;AACzD,eAAO,MAAM,sBAAsB,qBAAqB,CAAC;AACzD,eAAO,MAAM,mBAAmB,kBAAkB,CAAC;AACnD,eAAO,MAAM,aAAa,YAAY,CAAC;AACvC,eAAO,MAAM,eAAe,cAAc,CAAC;AAC3C,eAAO,MAAM,cAAc,aAAa,CAAC;AACzC,eAAO,MAAM,sBAAsB,qBAAqB,CAAC;AACzD,eAAO,MAAM,mBAAmB,kBAAkB,CAAC;AACnD,eAAO,MAAM,0BAA0B,yBAAyB,CAAC;AACjE,eAAO,MAAM,wBAAwB,uBAAuB,CAAC;AAC7D,eAAO,MAAM,yBAAyB,uBAAuB,CAAC;AAC9D,eAAO,MAAM,cAAc,aAAa,CAAC;AACzC,eAAO,MAAM,YAAY,WAAW,CAAC;AACrC,eAAO,MAAM,gBAAgB,eAAe,CAAC;AAC7C,eAAO,MAAM,UAAU,SAAS,CAAC;AACjC,eAAO,MAAM,UAAU,SAAS,CAAC;AACjC,eAAO,MAAM,YAAY,WAAW,CAAC;AACrC,eAAO,MAAM,WAAW,UAAU,CAAC;AACnC,eAAO,MAAM,kBAAkB,iBAAiB,CAAC;AAEjD,UAAU,oBAAoB;IAC5B,KAAK,EAAE,eAAe,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,aAAa,EAAE,aAAa,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,UAAU,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;IAC/C,YAAY,EAAE,OAAO,CAAC;IACtB,wBAAwB,EAAE,MAAM,EAAE,CAAC;IACnC,yBAAyB,EAAE,MAAM,EAAE,CAAC;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,gBAAgB,GAC3B,cAAc,MAAM,EAAE,EACtB,YAAY,OAAO,EACnB,qBAAqB,WAAW,KAC/B,aAQF,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAC5B,eAAe,MAAM,EAAE,EACvB,YAAY,OAAO,EACnB,qBAAqB,WAAW,KAC/B,aAQF,CAAC;AAIF,eAAO,MAAM,eAAe,kDAkR1B,CAAC;AAEH,eAAe,eAAe,CAAC"}
|
|
@@ -14,7 +14,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
14
14
|
import { memo, useCallback, useDeferredValue, useEffect, useMemo, useRef, useState, } from 'react';
|
|
15
15
|
import { tableFromIPC } from 'apache-arrow';
|
|
16
16
|
import { useContextMenu } from 'react-contexify';
|
|
17
|
-
import { useParcaContext } from '@parca/components';
|
|
17
|
+
import { FlameGraphSkeleton, SandwichFlameGraphSkeleton, useParcaContext } from '@parca/components';
|
|
18
18
|
import { USER_PREFERENCES, useCurrentColorProfile, useUserPreference } from '@parca/hooks';
|
|
19
19
|
import { getColorForFeature, selectDarkMode, useAppSelector } from '@parca/store';
|
|
20
20
|
import { useProfileFilters } from '../../ProfileView/components/ProfileFilters/useProfileFilters';
|
|
@@ -23,6 +23,7 @@ import ContextMenuWrapper from './ContextMenuWrapper';
|
|
|
23
23
|
import { FlameNode, RowHeight } from './FlameGraphNodes';
|
|
24
24
|
import { MemoizedTooltip } from './MemoizedTooltip';
|
|
25
25
|
import { TooltipProvider } from './TooltipContext';
|
|
26
|
+
import { useBatchedRendering } from './useBatchedRendering';
|
|
26
27
|
import { useScrollViewport } from './useScrollViewport';
|
|
27
28
|
import { useVisibleNodes } from './useVisibleNodes';
|
|
28
29
|
import { extractFeature, extractFilenameFeature, getCurrentPathFrameData, getMaxDepth, isCurrentPathFrameMatch, } from './utils';
|
|
@@ -64,7 +65,7 @@ export const getFilenameColors = (filenamesList, isDarkMode, currentColorProfile
|
|
|
64
65
|
return colors;
|
|
65
66
|
};
|
|
66
67
|
const noop = () => { };
|
|
67
|
-
export const FlameGraphArrow = memo(function FlameGraphArrow({ arrow, total, filtered, width, setCurPath, curPath, profileType, profileSource, compareAbsolute, isFlameChart = false, isRenderedAsFlamegraph = false, isInSandwichView = false, tooltipId = 'default', maxFrameCount, isExpanded = false, mappingsListFromMetadata, filenamesListFromMetadata, colorBy, }) {
|
|
68
|
+
export const FlameGraphArrow = memo(function FlameGraphArrow({ arrow, total, filtered, width, setCurPath, curPath, profileType, profileSource, compareAbsolute, isFlameChart = false, isRenderedAsFlamegraph = false, isInSandwichView = false, isHalfScreen, tooltipId = 'default', maxFrameCount, isExpanded = false, mappingsListFromMetadata, filenamesListFromMetadata, colorBy, }) {
|
|
68
69
|
const [highlightSimilarStacksPreference] = useUserPreference(USER_PREFERENCES.HIGHLIGHT_SIMILAR_STACKS.key);
|
|
69
70
|
const [hoveringRow, setHoveringRow] = useState(undefined);
|
|
70
71
|
const [dockedMetainfo] = useUserPreference(USER_PREFERENCES.GRAPH_METAINFO_DOCKED.key);
|
|
@@ -80,6 +81,7 @@ export const FlameGraphArrow = memo(function FlameGraphArrow({ arrow, total, fil
|
|
|
80
81
|
const svg = useRef(null);
|
|
81
82
|
const containerRef = useRef(null);
|
|
82
83
|
const renderStartTime = useRef(0);
|
|
84
|
+
const hasInitialRenderCompleted = useRef(false);
|
|
83
85
|
const [svgElement, setSvgElement] = useState(null);
|
|
84
86
|
const { excludeBinary } = useProfileFilters();
|
|
85
87
|
const { compareMode } = useProfileViewContext();
|
|
@@ -179,6 +181,15 @@ export const FlameGraphArrow = memo(function FlameGraphArrow({ arrow, total, fil
|
|
|
179
181
|
selectedRow,
|
|
180
182
|
effectiveDepth: deferredEffectiveDepth,
|
|
181
183
|
});
|
|
184
|
+
// Add nodes in incremental batches to avoid blocking the UI
|
|
185
|
+
const { items: batchedNodes, isComplete: isBatchingComplete } = useBatchedRendering(visibleNodes, {
|
|
186
|
+
batchSize: 500,
|
|
187
|
+
});
|
|
188
|
+
if (isBatchingComplete) {
|
|
189
|
+
hasInitialRenderCompleted.current = true;
|
|
190
|
+
}
|
|
191
|
+
// Show skeleton only during initial load, not during scroll updates
|
|
192
|
+
const showSkeleton = !hasInitialRenderCompleted.current && batchedNodes.length !== visibleNodes.length;
|
|
182
193
|
useEffect(() => {
|
|
183
194
|
if (perf?.markInteraction != null) {
|
|
184
195
|
renderStartTime.current = performance.now();
|
|
@@ -187,9 +198,10 @@ export const FlameGraphArrow = memo(function FlameGraphArrow({ arrow, total, fil
|
|
|
187
198
|
useEffect(() => {
|
|
188
199
|
setSvgElement(svg.current);
|
|
189
200
|
}, [tooltipId]);
|
|
190
|
-
return (_jsx(TooltipProvider, { table: table, total: total, totalUnfiltered: total + filtered, profileType: profileType, unit: arrow.unit, compareAbsolute: compareAbsolute, tooltipId: tooltipId, children: _jsxs("div", { className: "relative", children: [_jsx(ContextMenuWrapper, { ref: contextMenuRef, menuId: MENU_ID, table: table, total: total, totalUnfiltered: total + filtered, compareAbsolute: compareAbsolute, resetPath: () => setCurPath([]), hideMenu: hideAll, hideBinary: hideBinary, unit: arrow.unit, profileType: profileType, isInSandwichView: isInSandwichView }), _jsx(MemoizedTooltip, { contextElement: svgElement, dockedMetainfo: dockedMetainfo }), _jsx("div", { ref: containerRef, className: "overflow-auto scrollbar-thin scrollbar-thumb-gray-400 scrollbar-track-gray-100 dark:scrollbar-thumb-gray-600 dark:scrollbar-track-gray-800 will-change-transform scroll-smooth webkit-overflow-scrolling-touch contain", style: {
|
|
201
|
+
return (_jsx(TooltipProvider, { table: table, total: total, totalUnfiltered: total + filtered, profileType: profileType, unit: arrow.unit, compareAbsolute: compareAbsolute, tooltipId: tooltipId, children: _jsxs("div", { className: "relative", children: [_jsx(ContextMenuWrapper, { ref: contextMenuRef, menuId: MENU_ID, table: table, total: total, totalUnfiltered: total + filtered, compareAbsolute: compareAbsolute, resetPath: () => setCurPath([]), hideMenu: hideAll, hideBinary: hideBinary, unit: arrow.unit, profileType: profileType, isInSandwichView: isInSandwichView }), _jsx(MemoizedTooltip, { contextElement: svgElement, dockedMetainfo: dockedMetainfo }), showSkeleton && (_jsx("div", { className: "absolute inset-0 z-10", children: isRenderedAsFlamegraph ? (_jsx(SandwichFlameGraphSkeleton, { isHalfScreen: isHalfScreen, isDarkMode: isDarkMode })) : (_jsx(FlameGraphSkeleton, { isHalfScreen: isHalfScreen, isDarkMode: isDarkMode })) })), _jsx("div", { ref: containerRef, className: "overflow-auto scrollbar-thin scrollbar-thumb-gray-400 scrollbar-track-gray-100 dark:scrollbar-thumb-gray-600 dark:scrollbar-track-gray-800 will-change-transform scroll-smooth webkit-overflow-scrolling-touch contain", style: {
|
|
191
202
|
width: width ?? '100%',
|
|
192
203
|
contain: 'layout style paint',
|
|
193
|
-
|
|
204
|
+
visibility: !showSkeleton ? 'visible' : 'hidden',
|
|
205
|
+
}, children: _jsx("svg", { className: "font-robotoMono", width: width ?? 0, height: totalHeight, preserveAspectRatio: "xMinYMid", ref: svg, children: batchedNodes.map(row => (_jsx(FlameNode, { table: table, row: row, colors: colorByColors, colorBy: colorByValue, totalWidth: width ?? 1, height: RowHeight, darkMode: isDarkMode, compareMode: compareMode, colorForSimilarNodes: colorForSimilarNodes, selectedRow: selectedRow, onClick: () => handleRowClick(row), onContextMenu: displayMenu, hoveringRow: highlightSimilarStacksPreference ? hoveringRow : undefined, setHoveringRow: highlightSimilarStacksPreference ? setHoveringRow : noop, isFlameChart: isFlameChart, profileSource: profileSource, isRenderedAsFlamegraph: isRenderedAsFlamegraph, isInSandwichView: isInSandwichView, maxDepth: maxDepth, effectiveDepth: deferredEffectiveDepth, tooltipId: tooltipId }, row))) }) })] }) }));
|
|
194
206
|
});
|
|
195
207
|
export default FlameGraphArrow;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface UseBatchedRenderingOptions {
|
|
2
|
+
batchSize?: number;
|
|
3
|
+
batchDelay?: number;
|
|
4
|
+
}
|
|
5
|
+
interface UseBatchedRenderingResult<T> {
|
|
6
|
+
items: T[];
|
|
7
|
+
isComplete: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare const useBatchedRendering: <T>(items: T[], options?: UseBatchedRenderingOptions) => UseBatchedRenderingResult<T>;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=useBatchedRendering.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useBatchedRendering.d.ts","sourceRoot":"","sources":["../../../src/ProfileFlameGraph/FlameGraphArrow/useBatchedRendering.ts"],"names":[],"mappings":"AAeA,UAAU,0BAA0B;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,yBAAyB,CAAC,CAAC;IACnC,KAAK,EAAE,CAAC,EAAE,CAAC;IACX,UAAU,EAAE,OAAO,CAAC;CACrB;AAGD,eAAO,MAAM,mBAAmB,GAAI,CAAC,EACnC,OAAO,CAAC,EAAE,EACV,UAAS,0BAA+B,KACvC,yBAAyB,CAAC,CAAC,CAqD7B,CAAC"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
import { useEffect, useRef, useState } from 'react';
|
|
14
|
+
// useBatchedRendering - Helps in incrementally rendering items in batches to avoid UI blocking.
|
|
15
|
+
export const useBatchedRendering = (items, options = {}) => {
|
|
16
|
+
const { batchSize = 500, batchDelay = 0 } = options;
|
|
17
|
+
const [renderedCount, setRenderedCount] = useState(0);
|
|
18
|
+
const itemsRef = useRef(items);
|
|
19
|
+
const rafRef = useRef(null);
|
|
20
|
+
const timeoutRef = useRef(null);
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (itemsRef.current !== items) {
|
|
23
|
+
itemsRef.current = items;
|
|
24
|
+
setRenderedCount(prev => {
|
|
25
|
+
if (items.length === 0)
|
|
26
|
+
return 0;
|
|
27
|
+
// If new items were added (scrolling down), keep current progress
|
|
28
|
+
if (items.length > prev)
|
|
29
|
+
return prev;
|
|
30
|
+
// If items reduced, cap to new length
|
|
31
|
+
return Math.min(prev, items.length);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}, [items]);
|
|
35
|
+
// Progressively render more items
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (renderedCount === items.length) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const scheduleNextBatch = () => {
|
|
41
|
+
const incrementState = () => {
|
|
42
|
+
setRenderedCount(prev => Math.min(prev + batchSize, items.length));
|
|
43
|
+
};
|
|
44
|
+
if (batchDelay > 0) {
|
|
45
|
+
timeoutRef.current = setTimeout(incrementState, batchDelay);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
rafRef.current = requestAnimationFrame(incrementState);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
scheduleNextBatch();
|
|
52
|
+
return () => {
|
|
53
|
+
if (rafRef.current !== null) {
|
|
54
|
+
cancelAnimationFrame(rafRef.current);
|
|
55
|
+
}
|
|
56
|
+
if (timeoutRef.current !== null) {
|
|
57
|
+
clearTimeout(timeoutRef.current);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}, [renderedCount, items.length, batchSize, batchDelay]);
|
|
61
|
+
return {
|
|
62
|
+
items: items.slice(0, renderedCount),
|
|
63
|
+
isComplete: renderedCount === items.length,
|
|
64
|
+
};
|
|
65
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useScrollViewport.d.ts","sourceRoot":"","sources":["../../../src/ProfileFlameGraph/FlameGraphArrow/useScrollViewport.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;CACxB;
|
|
1
|
+
{"version":3,"file":"useScrollViewport.d.ts","sourceRoot":"","sources":["../../../src/ProfileFlameGraph/FlameGraphArrow/useScrollViewport.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;CACxB;AAiBD,eAAO,MAAM,iBAAiB,GAAI,cAAc,KAAK,CAAC,SAAS,CAAC,cAAc,CAAC,KAAG,aAsFjF,CAAC"}
|
|
@@ -11,6 +11,21 @@
|
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
14
|
+
// Find the scrollable ancestor (the element with overflow: auto/scroll)
|
|
15
|
+
const findScrollableParent = (element) => {
|
|
16
|
+
if (element === null)
|
|
17
|
+
return undefined;
|
|
18
|
+
let current = element.parentElement;
|
|
19
|
+
while (current !== null) {
|
|
20
|
+
const style = window.getComputedStyle(current);
|
|
21
|
+
const overflowY = style.overflowY;
|
|
22
|
+
if (overflowY === 'auto' || overflowY === 'scroll') {
|
|
23
|
+
return current;
|
|
24
|
+
}
|
|
25
|
+
current = current.parentElement;
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
};
|
|
14
29
|
export const useScrollViewport = (containerRef) => {
|
|
15
30
|
const [viewport, setViewport] = useState({
|
|
16
31
|
scrollTop: 0,
|
|
@@ -22,10 +37,22 @@ export const useScrollViewport = (containerRef) => {
|
|
|
22
37
|
const updateViewport = useCallback(() => {
|
|
23
38
|
if (containerRef.current !== null) {
|
|
24
39
|
const container = containerRef.current;
|
|
40
|
+
const rect = container.getBoundingClientRect();
|
|
41
|
+
// Restrict container height to the visible portion on screen
|
|
42
|
+
// This handles cases where the container is partially off-screen
|
|
43
|
+
// We only want to consider the visible part for culling calculations
|
|
44
|
+
const containerTop = rect.top;
|
|
45
|
+
const containerBottom = rect.bottom;
|
|
46
|
+
const viewportTop = 0;
|
|
47
|
+
const viewportBottom = window.innerHeight;
|
|
48
|
+
const visibleTop = Math.max(containerTop, viewportTop);
|
|
49
|
+
const visibleBottom = Math.min(containerBottom, viewportBottom);
|
|
50
|
+
const visibleHeight = Math.max(0, visibleBottom - visibleTop);
|
|
51
|
+
const scrollOffset = Math.max(0, viewportTop - containerTop);
|
|
25
52
|
const newViewport = {
|
|
26
|
-
scrollTop:
|
|
53
|
+
scrollTop: scrollOffset,
|
|
27
54
|
scrollLeft: container.scrollLeft,
|
|
28
|
-
containerHeight:
|
|
55
|
+
containerHeight: visibleHeight, // Only the visible portion
|
|
29
56
|
containerWidth: container.clientWidth,
|
|
30
57
|
};
|
|
31
58
|
setViewport(newViewport);
|
|
@@ -44,22 +71,25 @@ export const useScrollViewport = (containerRef) => {
|
|
|
44
71
|
const container = containerRef.current;
|
|
45
72
|
if (container === null)
|
|
46
73
|
return;
|
|
74
|
+
const scrollableParent = findScrollableParent(container);
|
|
47
75
|
// ResizeObserver Strategy:
|
|
48
76
|
// Monitor container size changes (window resize, layout shifts)
|
|
49
77
|
// to update viewport dimensions for accurate culling calculations
|
|
50
78
|
const resizeObserver = new ResizeObserver(() => {
|
|
51
79
|
throttledUpdateViewport();
|
|
52
80
|
});
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
// Throttle with requestAnimationFrame to maintain 60fps target
|
|
81
|
+
// Listen to scroll on the actual scrollable parent
|
|
82
|
+
scrollableParent?.addEventListener('scroll', throttledUpdateViewport, { passive: true });
|
|
56
83
|
container.addEventListener('scroll', throttledUpdateViewport, { passive: true });
|
|
84
|
+
window.addEventListener('scroll', throttledUpdateViewport, { passive: true });
|
|
57
85
|
resizeObserver.observe(container);
|
|
58
86
|
// Initialize viewport state on mount
|
|
59
87
|
updateViewport();
|
|
60
88
|
return () => {
|
|
61
89
|
// Cleanup: Remove event listeners and cancel pending animations
|
|
90
|
+
scrollableParent?.removeEventListener('scroll', throttledUpdateViewport);
|
|
62
91
|
container.removeEventListener('scroll', throttledUpdateViewport);
|
|
92
|
+
window.removeEventListener('scroll', throttledUpdateViewport);
|
|
63
93
|
resizeObserver.disconnect();
|
|
64
94
|
if (throttleRef.current !== null) {
|
|
65
95
|
cancelAnimationFrame(throttleRef.current);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useVisibleNodes.d.ts","sourceRoot":"","sources":["../../../src/ProfileFlameGraph/FlameGraphArrow/useVisibleNodes.ts"],"names":[],"mappings":"AAeA,OAAO,EAAC,KAAK,EAAC,MAAM,cAAc,CAAC;AAInC,OAAO,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAiClD,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,QAAQ,EAAE,aAAa,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,GAAI,iEAO7B,qBAAqB,KAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"useVisibleNodes.d.ts","sourceRoot":"","sources":["../../../src/ProfileFlameGraph/FlameGraphArrow/useVisibleNodes.ts"],"names":[],"mappings":"AAeA,OAAO,EAAC,KAAK,EAAC,MAAM,cAAc,CAAC;AAInC,OAAO,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAiClD,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,QAAQ,EAAE,aAAa,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,eAAe,GAAI,iEAO7B,qBAAqB,KAAG,MAAM,EA4HhC,CAAC"}
|
|
@@ -53,22 +53,48 @@ const useDepthBuckets = (table) => {
|
|
|
53
53
|
export const useVisibleNodes = ({ table, viewport, total, width, selectedRow, effectiveDepth, }) => {
|
|
54
54
|
const depthBuckets = useDepthBuckets(table);
|
|
55
55
|
const lastResultRef = useRef({ key: '', result: [] });
|
|
56
|
+
const renderedRangeRef = useRef({
|
|
57
|
+
minDepth: Infinity,
|
|
58
|
+
maxDepth: -Infinity,
|
|
59
|
+
table: null,
|
|
60
|
+
});
|
|
56
61
|
return useMemo(() => {
|
|
62
|
+
// This happens when the continer is scrolled off screen, in this case we return all previously rendered nodes
|
|
63
|
+
// to avoid trimming the rendered nodes to zero which would cause jank when scrolling back into view
|
|
64
|
+
if (viewport.containerHeight === 0 && lastResultRef.current.result.length > 0) {
|
|
65
|
+
return lastResultRef.current.result;
|
|
66
|
+
}
|
|
57
67
|
// Create a stable key for memoization to prevent unnecessary recalculations
|
|
58
68
|
const memoKey = `${viewport.scrollTop}-${viewport.containerHeight}-${selectedRow}-${effectiveDepth}-${width}-${Number(total)}-${table.numRows}`;
|
|
59
69
|
// Return cached result if viewport hasn't meaningfully changed
|
|
60
70
|
if (lastResultRef.current.key === memoKey) {
|
|
61
71
|
return lastResultRef.current.result;
|
|
62
72
|
}
|
|
63
|
-
if (table === null
|
|
73
|
+
if (table === null)
|
|
64
74
|
return [];
|
|
65
75
|
const visibleRows = [];
|
|
66
76
|
const { scrollTop, containerHeight } = viewport;
|
|
67
77
|
// Viewport Culling Algorithm:
|
|
68
78
|
// 1. Calculate visible depth range based on scroll position and container height
|
|
69
79
|
// 2. Add 5-row buffer above/below for smooth scrolling experience
|
|
70
|
-
|
|
71
|
-
const
|
|
80
|
+
// Note: We never shrink the rendered range to avoid back and forth node removals (and in turn additions when scrolled down again) to the dom.
|
|
81
|
+
const BUFFER = 15; // Buffer for smoother scrolling
|
|
82
|
+
const visibleStartDepth = Math.max(0, Math.floor(scrollTop / RowHeight) - BUFFER);
|
|
83
|
+
const visibleDepths = Math.ceil(containerHeight / RowHeight);
|
|
84
|
+
const visibleEndDepth = Math.min(effectiveDepth, visibleStartDepth + visibleDepths + BUFFER);
|
|
85
|
+
// Reset range if table changed (new data loaded) as this is new data
|
|
86
|
+
if (renderedRangeRef.current.table !== table) {
|
|
87
|
+
renderedRangeRef.current = {
|
|
88
|
+
minDepth: Infinity,
|
|
89
|
+
maxDepth: -Infinity,
|
|
90
|
+
table: table,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
// Expand the rendered range (never shrink when scrolling up/down)
|
|
94
|
+
renderedRangeRef.current.minDepth = Math.min(renderedRangeRef.current.minDepth, visibleStartDepth);
|
|
95
|
+
renderedRangeRef.current.maxDepth = Math.max(renderedRangeRef.current.maxDepth, visibleEndDepth);
|
|
96
|
+
const startDepth = renderedRangeRef.current.minDepth;
|
|
97
|
+
const endDepth = renderedRangeRef.current.maxDepth;
|
|
72
98
|
const cumulativeColumn = table.getChild(FIELD_CUMULATIVE);
|
|
73
99
|
const valueOffsetColumn = table.getChild(FIELD_VALUE_OFFSET);
|
|
74
100
|
const selectionOffset = valueOffsetColumn?.get(selectedRow) !== null &&
|
|
@@ -10,7 +10,7 @@ interface MetricsGraphSectionProps {
|
|
|
10
10
|
querySelection: QuerySelection;
|
|
11
11
|
profileSelection: ProfileSelection | null;
|
|
12
12
|
comparing: boolean;
|
|
13
|
-
sumBy: string[] |
|
|
13
|
+
sumBy: string[] | undefined;
|
|
14
14
|
defaultSumByLoading: boolean;
|
|
15
15
|
queryClient: QueryServiceClient;
|
|
16
16
|
queryExpressionString: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MetricsGraphSection.d.ts","sourceRoot":"","sources":["../../src/ProfileSelector/MetricsGraphSection.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAQ,kBAAkB,EAAC,MAAM,eAAe,CAAC;AACxD,OAAO,EAAC,aAAa,EAAmB,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAC,KAAK,EAAC,MAAM,eAAe,CAAC;AAEpC,OAAO,EAAC,gBAAgB,EAAC,MAAM,IAAI,CAAC;AAGpC,OAAO,EAAC,cAAc,EAAC,MAAM,SAAS,CAAC;AAEvC,UAAU,wBAAwB;IAChC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,gCAAgC,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,cAAc,CAAC;IAC/B,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC1C,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,EAAE,GAAG,
|
|
1
|
+
{"version":3,"file":"MetricsGraphSection.d.ts","sourceRoot":"","sources":["../../src/ProfileSelector/MetricsGraphSection.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAQ,kBAAkB,EAAC,MAAM,eAAe,CAAC;AACxD,OAAO,EAAC,aAAa,EAAmB,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAC,KAAK,EAAC,MAAM,eAAe,CAAC;AAEpC,OAAO,EAAC,gBAAgB,EAAC,MAAM,IAAI,CAAC;AAGpC,OAAO,EAAC,cAAc,EAAC,MAAM,SAAS,CAAC;AAEvC,UAAU,wBAAwB;IAChC,gBAAgB,EAAE,OAAO,CAAC;IAC1B,gCAAgC,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,cAAc,CAAC;IAC/B,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC1C,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,EAAE,GAAG,SAAS,CAAC;IAC5B,mBAAmB,EAAE,OAAO,CAAC;IAC7B,WAAW,EAAE,kBAAkB,CAAC;IAChC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,qBAAqB,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IACtD,WAAW,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAC7C,mBAAmB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IAChF,KAAK,EAAE,KAAK,CAAC;IACb,qBAAqB,EAAE,CAAC,eAAe,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,kBAAkB,EAAE,CAAC,QAAQ,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IACjD,WAAW,EAAE,CACX,kBAAkB,CAAC,EAAE;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAC,EACtE,UAAU,CAAC,EAAE,MAAM,KAChB,IAAI,CAAC;CACX;AAED,wBAAgB,mBAAmB,CAAC,EAClC,gBAAgB,EAChB,gCAAgC,EAChC,WAAW,EACX,cAAc,EACd,gBAAgB,EAChB,SAAS,EACT,KAAK,EACL,mBAAmB,EACnB,WAAW,EACX,qBAAqB,EACrB,qBAAqB,EACrB,WAAW,EACX,mBAAmB,EACnB,KAAK,EACL,qBAAqB,EACrB,WAAW,GACZ,EAAE,wBAAwB,GAAG,GAAG,CAAC,OAAO,CA+HxC"}
|
|
@@ -84,5 +84,5 @@ export function MetricsGraphSection({ showMetricsGraph, setDisplayHideMetricsGra
|
|
|
84
84
|
};
|
|
85
85
|
return (_jsxs("div", { className: cx('relative', { 'py-4': !showMetricsGraph }), children: [setDisplayHideMetricsGraphButton != null ? (_jsxs("button", { onClick: () => setDisplayHideMetricsGraphButton(!showMetricsGraph), className: cx('hidden px-3 py-1 text-sm font-medium text-gray-700 dark:text-gray-200 bg-gray-100 rounded-md hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 dark:bg-gray-900 z-[5]', showMetricsGraph && 'absolute right-0 bottom-3 !flex', !showMetricsGraph && 'relative !flex ml-auto'), children: [showMetricsGraph ? 'Hide' : 'Show', " Metrics Graph"] })) : null, showMetricsGraph && (_jsx(_Fragment, { children: _jsx("div", { style: { height: heightStyle }, children: (querySelection.expression !== '' || defaultSumByLoading) &&
|
|
86
86
|
querySelection.from !== undefined &&
|
|
87
|
-
querySelection.to !== undefined ? (_jsx(_Fragment, { children: _jsx(ProfileMetricsGraph, { queryClient: queryClient, queryExpression: querySelection.expression, from: querySelection.from, to: querySelection.to, profile: profileSelection, comparing: comparing, sumBy:
|
|
87
|
+
querySelection.to !== undefined ? (_jsx(_Fragment, { children: _jsx(ProfileMetricsGraph, { queryClient: queryClient, queryExpression: querySelection.expression, from: querySelection.from, to: querySelection.to, profile: profileSelection, comparing: comparing, sumBy: sumBy ?? [], sumByLoading: defaultSumByLoading, setTimeRange: handleTimeRangeChange, addLabelMatcher: addLabelMatcher, onPointClick: handlePointClick }) })) : (profileSelection === null && (_jsx("div", { className: "p-2", children: _jsx(ProfileMetricsEmptyState, { message: "Please select a profile type and click 'Search' to begin." }) }))) }) }))] }));
|
|
88
88
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ProfileSelector/index.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAC,QAAQ,EAAE,cAAc,EAAoD,MAAM,OAAO,CAAC;AAElG,OAAO,EAAC,QAAQ,EAAC,MAAM,0BAA0B,CAAC;AAElD,OAAO,EAAsB,oBAAoB,EAAE,kBAAkB,EAAC,MAAM,eAAe,CAAC;AAY5F,OAAO,EAAyB,KAAK,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAY/E,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,uBAAuB;IAC/B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAED,UAAU,oBAAqB,SAAQ,uBAAuB;IAC5D,WAAW,EAAE,kBAAkB,CAAC;IAChC,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,gCAAgC,CAAC,EAAE,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACrE,MAAM,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,oBAAoB,CAAC;IAC5B,KAAK,CAAC,EAAE,QAAQ,CAAC;CAClB;AAED,eAAO,MAAM,eAAe,GAC1B,QAAQ,kBAAkB,EAC1B,QAAQ,MAAM,EACd,MAAM,MAAM,KACX,mBAsBF,CAAC;AAEF,QAAA,MAAM,eAAe,GAAI,kMAYtB,oBAAoB,KAAG,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ProfileSelector/index.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAC,QAAQ,EAAE,cAAc,EAAoD,MAAM,OAAO,CAAC;AAElG,OAAO,EAAC,QAAQ,EAAC,MAAM,0BAA0B,CAAC;AAElD,OAAO,EAAsB,oBAAoB,EAAE,kBAAkB,EAAC,MAAM,eAAe,CAAC;AAY5F,OAAO,EAAyB,KAAK,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAY/E,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,uBAAuB;IAC/B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAED,UAAU,oBAAqB,SAAQ,uBAAuB;IAC5D,WAAW,EAAE,kBAAkB,CAAC;IAChC,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,gCAAgC,CAAC,EAAE,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACrE,MAAM,CAAC,EAAE,IAAI,GAAG,IAAI,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;CAC3B;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,oBAAoB,CAAC;IAC5B,KAAK,CAAC,EAAE,QAAQ,CAAC;CAClB;AAED,eAAO,MAAM,eAAe,GAC1B,QAAQ,kBAAkB,EAC1B,QAAQ,MAAM,EACd,MAAM,MAAM,KACX,mBAsBF,CAAC;AAEF,QAAA,MAAM,eAAe,GAAI,kMAYtB,oBAAoB,KAAG,GAAG,CAAC,OAqO7B,CAAC;AAEF,eAAe,eAAe,CAAC"}
|
|
@@ -101,7 +101,6 @@ const ProfileSelector = ({ queryClient, closeProfile, enforcedProfileName, compa
|
|
|
101
101
|
const currentTo = timeRangeSelection.getToMs(true);
|
|
102
102
|
const currentRangeKey = timeRangeSelection.getRangeKey();
|
|
103
103
|
// Commit with refreshed time range
|
|
104
|
-
console.log('[draftExpression] setQueryExpression: committing with refreshed time range:', draftSelection.expression);
|
|
105
104
|
commitDraft({
|
|
106
105
|
from: currentFrom,
|
|
107
106
|
to: currentTo,
|
|
@@ -150,7 +149,7 @@ const ProfileSelector = ({ queryClient, closeProfile, enforcedProfileName, compa
|
|
|
150
149
|
queryExpressionString === '{}';
|
|
151
150
|
const queryBrowserRef = useRef(null);
|
|
152
151
|
const sumByRef = useRef(null);
|
|
153
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "mb-2 flex", children: [_jsx(LabelsQueryProvider, { setMatchersString: setMatchersString, runQuery: setQueryExpression, currentQuery: query, profileType: selectedProfileName ?? profileType.toString(), queryClient: queryClient, start: timeRangeSelection.getFromMs(), end: timeRangeSelection.getToMs(), suffix: suffix, children: _jsx(LabelsSource, { children: _jsx(QueryControls, { showProfileTypeSelector: showProfileTypeSelector, showSumBySelector: showSumBySelector, profileTypesData: profileTypesData, profileTypesLoading: profileTypesLoading, selectedProfileName: selectedProfileName, setProfileName: setProfileName, setMatchersString: setMatchersString, setQueryExpression: setQueryExpression, query: query, queryBrowserRef: queryBrowserRef, timeRangeSelection: timeRangeSelection, setTimeRangeSelection: handleTimeRangeChange, searchDisabled: searchDisabled, setQueryBrowserMode: setQueryBrowserMode, advancedModeForQueryBrowser: advancedModeForQueryBrowser, setAdvancedModeForQueryBrowser: setAdvancedModeForQueryBrowser, queryClient: queryClient, sumByRef: sumByRef, labels: labels, sumBySelection: draftSelection.sumBy ?? [], sumBySelectionLoading: sumByLoading, setUserSumBySelection: setDraftSumBy, profileType: profileType, profileTypesError: error, viewComponent: viewComponent, draftSelection: draftSelection, setDraftMatchers: setDraftMatchers, draftParsedQuery: draftParsedQuery, commitDraft: commitDraft }) }) }), comparing && (_jsx("div", { children: _jsx(IconButton, { onClick: () => closeProfile(), icon: _jsx(CloseIcon, {}), ...testId(TEST_IDS.COMPARE_CLOSE_BUTTON) }) }))] }), _jsx(MetricsGraphSection, { showMetricsGraph: showMetricsGraph, setDisplayHideMetricsGraphButton: setDisplayHideMetricsGraphButton, heightStyle: heightStyle, querySelection: querySelection, profileSelection: profileSelection, comparing: comparing, sumBy: querySelection.sumBy
|
|
152
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "mb-2 flex", children: [_jsx(LabelsQueryProvider, { setMatchersString: setMatchersString, runQuery: setQueryExpression, currentQuery: query, profileType: selectedProfileName ?? profileType.toString(), queryClient: queryClient, start: timeRangeSelection.getFromMs(), end: timeRangeSelection.getToMs(), suffix: suffix, children: _jsx(LabelsSource, { children: _jsx(QueryControls, { showProfileTypeSelector: showProfileTypeSelector, showSumBySelector: showSumBySelector, profileTypesData: profileTypesData, profileTypesLoading: profileTypesLoading, selectedProfileName: selectedProfileName, setProfileName: setProfileName, setMatchersString: setMatchersString, setQueryExpression: setQueryExpression, query: query, queryBrowserRef: queryBrowserRef, timeRangeSelection: timeRangeSelection, setTimeRangeSelection: handleTimeRangeChange, searchDisabled: searchDisabled, setQueryBrowserMode: setQueryBrowserMode, advancedModeForQueryBrowser: advancedModeForQueryBrowser, setAdvancedModeForQueryBrowser: setAdvancedModeForQueryBrowser, queryClient: queryClient, sumByRef: sumByRef, labels: labels, sumBySelection: draftSelection.sumBy ?? [], sumBySelectionLoading: sumByLoading, setUserSumBySelection: setDraftSumBy, profileType: profileType, profileTypesError: error, viewComponent: viewComponent, draftSelection: draftSelection, setDraftMatchers: setDraftMatchers, draftParsedQuery: draftParsedQuery, commitDraft: commitDraft }) }) }), comparing && (_jsx("div", { children: _jsx(IconButton, { onClick: () => closeProfile(), icon: _jsx(CloseIcon, {}), ...testId(TEST_IDS.COMPARE_CLOSE_BUTTON) }) }))] }), _jsx(MetricsGraphSection, { showMetricsGraph: showMetricsGraph, setDisplayHideMetricsGraphButton: setDisplayHideMetricsGraphButton, heightStyle: heightStyle, querySelection: querySelection, profileSelection: profileSelection, comparing: comparing, sumBy: querySelection.sumBy, defaultSumByLoading: sumByLoading, queryClient: queryClient, queryExpressionString: queryExpressionString, setTimeRangeSelection: handleTimeRangeChange, selectQuery: commitDraft, setProfileSelection: setProfileSelection, query: query, setQueryExpression: setQueryExpression, setNewQueryExpression: setDraftExpression, commitDraft: commitDraft })] }));
|
|
154
153
|
};
|
|
155
154
|
export default ProfileSelector;
|
|
156
155
|
const LabelsSource = ({ children }) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useResetStateOnProfileTypeChange.d.ts","sourceRoot":"","sources":["../../../src/ProfileView/hooks/useResetStateOnProfileTypeChange.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,gCAAgC,QAAO,CAAC,MAAM,IAAI,
|
|
1
|
+
{"version":3,"file":"useResetStateOnProfileTypeChange.d.ts","sourceRoot":"","sources":["../../../src/ProfileView/hooks/useResetStateOnProfileTypeChange.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,gCAAgC,QAAO,CAAC,MAAM,IAAI,CA+B9D,CAAC"}
|
|
@@ -15,6 +15,8 @@ import { useProfileFilters } from '../components/ProfileFilters/useProfileFilter
|
|
|
15
15
|
export const useResetStateOnProfileTypeChange = () => {
|
|
16
16
|
const [groupBy, setGroupBy] = useURLState('group_by');
|
|
17
17
|
const [curPath, setCurPath] = useURLState('cur_path');
|
|
18
|
+
const [sumByA, setSumByA] = useURLState('sum_by_a');
|
|
19
|
+
const [sumByB, setSumByB] = useURLState('sum_by_b');
|
|
18
20
|
const { resetFilters } = useProfileFilters();
|
|
19
21
|
const [sandwichFunctionName, setSandwichFunctionName] = useURLState('sandwich_function_name');
|
|
20
22
|
const batchUpdates = useURLStateBatch();
|
|
@@ -30,6 +32,12 @@ export const useResetStateOnProfileTypeChange = () => {
|
|
|
30
32
|
if (sandwichFunctionName !== undefined) {
|
|
31
33
|
setSandwichFunctionName(undefined);
|
|
32
34
|
}
|
|
35
|
+
if (sumByA !== undefined) {
|
|
36
|
+
setSumByA(undefined);
|
|
37
|
+
}
|
|
38
|
+
if (sumByB !== undefined) {
|
|
39
|
+
setSumByB(undefined);
|
|
40
|
+
}
|
|
33
41
|
resetFilters();
|
|
34
42
|
});
|
|
35
43
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../../src/SimpleMatchers/Select.tsx"],"names":[],"mappings":"AAaA,OAAO,
|
|
1
|
+
{"version":3,"file":"Select.d.ts","sourceRoot":"","sources":["../../src/SimpleMatchers/Select.tsx"],"names":[],"mappings":"AAaA,OAAO,KAA2D,MAAM,OAAO,CAAC;AAShF,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC;IACpB,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,aAAa,CAAC;CACxB;AAED,MAAM,WAAW,eAAgB,SAAQ,UAAU;IACjD,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB;AAED,UAAU,iBAAiB;IACzB,KAAK,EAAE,iBAAiB,EAAE,GAAG,UAAU,EAAE,CAAC;IAC1C,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC;IACnB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACpC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,QAAA,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAwUnE,CAAC;AAkDF,eAAe,YAAY,CAAC"}
|