@parca/profile 0.19.26 → 0.19.28
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 +8 -0
- package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenu.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenu.js +4 -1
- package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenuWrapper.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/FlameGraphArrow/ContextMenuWrapper.js +10 -5
- package/dist/ProfileFlameGraph/FlameGraphArrow/index.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/FlameGraphArrow/index.js +7 -9
- package/dist/ProfileView/components/ProfileFilters/filterPresets.d.ts +1 -1
- package/dist/ProfileView/components/ProfileFilters/filterPresets.d.ts.map +1 -1
- package/dist/ProfileView/components/ProfileFilters/filterPresets.js +2 -1
- package/dist/ProfileView/components/ProfileFilters/useProfileFilters.d.ts +7 -4
- package/dist/ProfileView/components/ProfileFilters/useProfileFilters.d.ts.map +1 -1
- package/dist/ProfileView/components/ProfileFilters/useProfileFilters.js +39 -50
- package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.d.ts +1 -1
- package/dist/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.d.ts.map +1 -1
- package/dist/ProfileView/components/Toolbars/index.js +1 -1
- package/dist/ProfileView/components/ViewSelector/index.js +1 -1
- package/package.json +5 -5
- package/src/ProfileFlameGraph/FlameGraphArrow/ContextMenu.tsx +5 -0
- package/src/ProfileFlameGraph/FlameGraphArrow/ContextMenuWrapper.tsx +11 -5
- package/src/ProfileFlameGraph/FlameGraphArrow/index.tsx +22 -21
- package/src/ProfileView/components/ProfileFilters/filterPresets.ts +4 -2
- package/src/ProfileView/components/ProfileFilters/useProfileFilters.ts +57 -76
- package/src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.ts +1 -1
- package/src/ProfileView/components/Toolbars/index.tsx +1 -1
- package/src/ProfileView/components/ViewSelector/index.tsx +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
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.28](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.27...@parca/profile@0.19.28) (2025-07-22)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @parca/profile
|
|
9
|
+
|
|
10
|
+
## [0.19.27](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.26...@parca/profile@0.19.27) (2025-07-21)
|
|
11
|
+
|
|
12
|
+
**Note:** Version bump only for package @parca/profile
|
|
13
|
+
|
|
6
14
|
## [0.19.26](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.25...@parca/profile@0.19.26) (2025-07-21)
|
|
7
15
|
|
|
8
16
|
**Note:** Version bump only for package @parca/profile
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContextMenu.d.ts","sourceRoot":"","sources":["../../../src/ProfileFlameGraph/FlameGraphArrow/ContextMenu.tsx"],"names":[],"mappings":"AAcA,OAAO,EAAC,KAAK,EAAC,MAAM,cAAc,CAAC;AAOnC,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAC;AAO1C,UAAU,gBAAgB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,EAAE,OAAO,CAAC;IACzB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,QAAA,MAAM,WAAW,GAAI,kIAalB,gBAAgB,KAAG,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"ContextMenu.d.ts","sourceRoot":"","sources":["../../../src/ProfileFlameGraph/FlameGraphArrow/ContextMenu.tsx"],"names":[],"mappings":"AAcA,OAAO,EAAC,KAAK,EAAC,MAAM,cAAc,CAAC;AAOnC,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAC;AAO1C,UAAU,gBAAgB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,EAAE,OAAO,CAAC;IACzB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,QAAA,MAAM,WAAW,GAAI,kIAalB,gBAAgB,KAAG,GAAG,CAAC,OAsOzB,CAAC;AAEF,eAAe,WAAW,CAAC"}
|
|
@@ -93,13 +93,16 @@ const ContextMenu = ({ menuId, table, total, totalUnfiltered, row, compareAbsolu
|
|
|
93
93
|
setDashboardItems([...dashboardItems, 'table']);
|
|
94
94
|
}
|
|
95
95
|
}, children: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "ph:table" }), _jsx("div", { children: "Show in table" })] }) }), enableSandwichView === true && (_jsx(Item, { id: "show-in-sandwich", onClick: () => {
|
|
96
|
+
if (functionName === '' || functionName == null) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
96
99
|
if (dashboardItems.includes('sandwich')) {
|
|
97
100
|
setSandwichFunctionName(functionName);
|
|
98
101
|
return;
|
|
99
102
|
}
|
|
100
103
|
setSandwichFunctionName(functionName);
|
|
101
104
|
setDashboardItems([...dashboardItems, 'sandwich']);
|
|
102
|
-
}, children: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "tdesign:sandwich-filled" }), _jsxs("div", { className: "relative", children: [dashboardItems.includes('sandwich')
|
|
105
|
+
}, disabled: functionName === '' || functionName == null, children: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "tdesign:sandwich-filled" }), _jsxs("div", { className: "relative", children: [dashboardItems.includes('sandwich')
|
|
103
106
|
? 'Focus sandwich on this frame.'
|
|
104
107
|
: 'Show in sandwich', _jsx("span", { className: "absolute top-[-2px] text-xs lowercase text-red-500", children: "\u00A0alpha" })] })] }) })), _jsx(Item, { id: "reset-view", onClick: handleResetView, children: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "system-uicons:reset" }), _jsx("div", { children: "Reset graph" })] }) }), _jsxs(Item, { id: "hide-binary", onClick: () => hideBinary(getLastItem(mappingFile)), disabled: mappingFile === null || mappingFile === '', children: [_jsx("div", { "data-tooltip-id": "hide-binary-help", "data-tooltip-content": "Hide all frames for this binary", children: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "bx:bxs-hide" }), _jsxs("div", { children: ["Hide binary ", mappingFile !== null && `(${getLastItem(mappingFile)})`] })] }) }), _jsx(Tooltip, { place: "left", id: "hide-binary-help" })] }), _jsx(Submenu, { label: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "ph:copy" }), _jsx("div", { children: "Copy" })] }), children: _jsx("div", { className: "max-h-[300px] overflow-scroll", children: nonEmptyValuesToCopy.map(({ id, value }) => (_jsx(Item, { id: id, onClick: () => handleCopyItem(value), className: "dark:bg-gray-800", children: _jsxs("div", { className: "flex flex-col dark:text-gray-300 hover:dark:text-gray-100", children: [_jsx("div", { className: "text-sm", children: id }), _jsx("div", { className: "text-xs", children: truncateString(value, 30) })] }) }, id))) }) }), checkDebuginfoStatusHandler !== undefined ? (_jsx(Item, { id: "check-debuginfo-status", onClick: () => checkDebuginfoStatusHandler(mappingBuildID), disabled: !isMappingBuildIDAvailable, children: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "bx:bx-info-circle" }), _jsx("div", { className: "relative pr-4", children: "Check debuginfo status" })] }) })) : null, _jsx(Separator, {}), _jsx(Item, { id: "dock-tooltip", onClick: handleDockTooltip, children: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "bx:dock-bottom" }), isGraphTooltipDocked ? 'Undock tooltip' : 'Dock tooltip'] }) })] }));
|
|
105
108
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContextMenuWrapper.d.ts","sourceRoot":"","sources":["../../../src/ProfileFlameGraph/FlameGraphArrow/ContextMenuWrapper.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAC,KAAK,EAAC,MAAM,cAAc,CAAC;AAEnC,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAC;AAI1C,UAAU,uBAAuB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;CACtD;AAED,QAAA,MAAM,kBAAkB,
|
|
1
|
+
{"version":3,"file":"ContextMenuWrapper.d.ts","sourceRoot":"","sources":["../../../src/ProfileFlameGraph/FlameGraphArrow/ContextMenuWrapper.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAC,KAAK,EAAC,MAAM,cAAc,CAAC;AAEnC,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAC;AAI1C,UAAU,uBAAuB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,UAAU,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,IAAI,KAAK,IAAI,CAAC;CACtD;AAED,QAAA,MAAM,kBAAkB,2HAwBvB,CAAC;AAIF,eAAe,kBAAkB,CAAC"}
|
|
@@ -14,19 +14,24 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
14
14
|
import { forwardRef, useImperativeHandle, useState } from 'react';
|
|
15
15
|
import ContextMenu from './ContextMenu';
|
|
16
16
|
const ContextMenuWrapper = forwardRef((props, ref) => {
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
const [row, setRow] = useState(0);
|
|
17
|
+
// Initialize with null to prevent rendering with invalid data
|
|
18
|
+
const [row, setRow] = useState(null);
|
|
20
19
|
useImperativeHandle(ref, () => ({
|
|
21
20
|
setRow: (newRow, callback) => {
|
|
22
21
|
setRow(newRow);
|
|
23
22
|
// Execute callback after state update using requestAnimationFrame
|
|
24
23
|
if (callback != null) {
|
|
25
|
-
requestAnimationFrame(
|
|
24
|
+
requestAnimationFrame(() => {
|
|
25
|
+
requestAnimationFrame(callback);
|
|
26
|
+
});
|
|
26
27
|
}
|
|
27
28
|
},
|
|
28
29
|
}));
|
|
29
|
-
|
|
30
|
+
// Only render ContextMenu when we have a valid row
|
|
31
|
+
if (row === null) {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return _jsx(ContextMenu, { ...props, row: row, isSandwich: props.isInSandwichView });
|
|
30
35
|
});
|
|
31
36
|
ContextMenuWrapper.displayName = 'ContextMenuWrapper';
|
|
32
37
|
export default ContextMenuWrapper;
|
|
@@ -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;AAE/D,OAAO,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAIlD,OAAO,EAAuB,aAAa,EAAC,MAAM,mBAAmB,CAAC;AAMtE,OAAO,EACL,gBAAgB,EAOjB,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,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,
|
|
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;AAE/D,OAAO,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAIlD,OAAO,EAAuB,aAAa,EAAC,MAAM,mBAAmB,CAAC;AAMtE,OAAO,EACL,gBAAgB,EAOjB,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,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,kDAuR1B,CAAC;AAEH,eAAe,eAAe,CAAC"}
|
|
@@ -148,7 +148,11 @@ export const FlameGraphArrow = memo(function FlameGraphArrow({ arrow, total, fil
|
|
|
148
148
|
// Add a new frame filter to hide this binary using the new ProfileFilters system
|
|
149
149
|
excludeBinary(binaryToRemove);
|
|
150
150
|
};
|
|
151
|
-
const handleRowClick = (row) => {
|
|
151
|
+
const handleRowClick = useCallback((row) => {
|
|
152
|
+
if (isFlameChart) {
|
|
153
|
+
// In flame charts, we don't want to expand the node, so we return early.
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
152
156
|
// Walk down the stack starting at row until we reach the root (row 0).
|
|
153
157
|
const path = [];
|
|
154
158
|
let currentRow = row;
|
|
@@ -160,7 +164,7 @@ export const FlameGraphArrow = memo(function FlameGraphArrow({ arrow, total, fil
|
|
|
160
164
|
// Reverse the path so that the root is first.
|
|
161
165
|
path.reverse();
|
|
162
166
|
setCurPath(path);
|
|
163
|
-
};
|
|
167
|
+
}, [table, setCurPath, isFlameChart]);
|
|
164
168
|
const depthColumn = table.getChild(FIELD_DEPTH);
|
|
165
169
|
const maxDepth = getMaxDepth(depthColumn);
|
|
166
170
|
// Apply frame limit if maxFrameCount is provided and not expanded
|
|
@@ -215,12 +219,6 @@ export const FlameGraphArrow = memo(function FlameGraphArrow({ arrow, total, fil
|
|
|
215
219
|
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: {
|
|
216
220
|
width: width ?? '100%',
|
|
217
221
|
contain: 'layout style paint',
|
|
218
|
-
}, children: _jsx("svg", { className: "font-robotoMono", width: width ?? 0, height: totalHeight, preserveAspectRatio: "xMinYMid", ref: svg, children: visibleNodes.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: () =>
|
|
219
|
-
if (isFlameChart) {
|
|
220
|
-
// We don't want to expand in flame charts.
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
handleRowClick(row);
|
|
224
|
-
}, 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))) }) })] }) }));
|
|
222
|
+
}, children: _jsx("svg", { className: "font-robotoMono", width: width ?? 0, height: totalHeight, preserveAspectRatio: "xMinYMid", ref: svg, children: visibleNodes.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))) }) })] }) }));
|
|
225
223
|
});
|
|
226
224
|
export default FlameGraphArrow;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filterPresets.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/filterPresets.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"filterPresets.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/filterPresets.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAEvD,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC,CAAC;CAC3C;AAED,eAAO,MAAM,aAAa,EAAE,YAAY,EA6CvC,CAAC;AAIF,eAAO,MAAM,WAAW,GAAI,KAAK,MAAM,KAAG,OAEzC,CAAC;AAEF,eAAO,MAAM,cAAc,GAAI,KAAK,MAAM,KAAG,YAAY,GAAG,SAE3D,CAAC"}
|
|
@@ -56,8 +56,9 @@ export const filterPresets = [
|
|
|
56
56
|
],
|
|
57
57
|
},
|
|
58
58
|
];
|
|
59
|
+
const presetKeys = new Set(filterPresets.map(preset => preset.key));
|
|
59
60
|
export const isPresetKey = (key) => {
|
|
60
|
-
return
|
|
61
|
+
return presetKeys.has(key);
|
|
61
62
|
};
|
|
62
63
|
export const getPresetByKey = (key) => {
|
|
63
64
|
return filterPresets.find(preset => preset.key === key);
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { type Filter } from '@parca/client';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
export interface ProfileFilter {
|
|
3
|
+
id: string;
|
|
4
|
+
type?: 'stack' | 'frame' | string;
|
|
5
|
+
field?: 'function_name' | 'binary' | 'system_name' | 'filename' | 'address' | 'line_number';
|
|
6
|
+
matchType?: 'equal' | 'not_equal' | 'contains' | 'not_contains';
|
|
7
|
+
value: string;
|
|
8
|
+
}
|
|
5
9
|
export declare const convertToProtoFilters: (profileFilters: ProfileFilter[]) => Filter[];
|
|
6
10
|
export declare const useProfileFilters: () => {
|
|
7
11
|
localFilters: ProfileFilter[];
|
|
@@ -15,6 +19,5 @@ export declare const useProfileFilters: () => {
|
|
|
15
19
|
removeFilter: (id: string) => void;
|
|
16
20
|
updateFilter: (id: string, updates: Partial<ProfileFilter>) => void;
|
|
17
21
|
resetFilters: () => void;
|
|
18
|
-
applyPreset: (preset: FilterPreset) => void;
|
|
19
22
|
};
|
|
20
23
|
//# sourceMappingURL=useProfileFilters.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useProfileFilters.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/useProfileFilters.ts"],"names":[],"mappings":"AAeA,OAAO,EAAC,KAAK,MAAM,EAAC,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"useProfileFilters.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/useProfileFilters.ts"],"names":[],"mappings":"AAeA,OAAO,EAAC,KAAK,MAAM,EAAC,MAAM,eAAe,CAAC;AAK1C,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,OAAO,GAAG,OAAO,GAAG,MAAM,CAAC;IAClC,KAAK,CAAC,EAAE,eAAe,GAAG,QAAQ,GAAG,aAAa,GAAG,UAAU,GAAG,SAAS,GAAG,aAAa,CAAC;IAC5F,SAAS,CAAC,EAAE,OAAO,GAAG,WAAW,GAAG,UAAU,GAAG,cAAc,CAAC;IAChE,KAAK,EAAE,MAAM,CAAC;CACf;AAGD,eAAO,MAAM,qBAAqB,GAAI,gBAAgB,aAAa,EAAE,KAAG,MAAM,EAqG7E,CAAC;AAEF,eAAO,MAAM,iBAAiB,QAAO;IACnC,YAAY,EAAE,aAAa,EAAE,CAAC;IAC9B,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,aAAa,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,mBAAmB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IAClD,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACnC,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,CAAC,KAAK,IAAI,CAAC;IACpE,YAAY,EAAE,MAAM,IAAI,CAAC;CAsL1B,CAAC"}
|
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
|
-
import { useCallback, useEffect, useMemo } from 'react';
|
|
14
|
-
import { selectLocalFilters, setLocalFilters, useAppDispatch, useAppSelector, } from '@parca/store';
|
|
13
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
15
14
|
import { getPresetByKey, isPresetKey } from './filterPresets';
|
|
16
15
|
import { useProfileFiltersUrlState } from './useProfileFiltersUrlState';
|
|
17
16
|
// Convert ProfileFilter[] to protobuf Filter[] matching the expected structure
|
|
@@ -27,6 +26,7 @@ export const convertToProtoFilters = (profileFilters) => {
|
|
|
27
26
|
expandedFilters.push({
|
|
28
27
|
...presetFilter,
|
|
29
28
|
id: `${filter.id}-expanded-${index}`,
|
|
29
|
+
value: presetFilter.value,
|
|
30
30
|
});
|
|
31
31
|
});
|
|
32
32
|
}
|
|
@@ -114,29 +114,29 @@ export const convertToProtoFilters = (profileFilters) => {
|
|
|
114
114
|
};
|
|
115
115
|
export const useProfileFilters = () => {
|
|
116
116
|
const { appliedFilters, setAppliedFilters } = useProfileFiltersUrlState();
|
|
117
|
-
const
|
|
118
|
-
const
|
|
117
|
+
const [localFilters, setLocalFilters] = useState([]);
|
|
118
|
+
const lastAppliedFiltersRef = useRef([]);
|
|
119
|
+
const localFiltersRef = useRef(localFilters);
|
|
120
|
+
localFiltersRef.current = localFilters;
|
|
119
121
|
useEffect(() => {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
else if (appliedFilters != null && appliedFilters.length === 0 && localFilters.length > 0) {
|
|
136
|
-
dispatch(setLocalFilters([]));
|
|
122
|
+
const currentApplied = appliedFilters ?? [];
|
|
123
|
+
const lastApplied = lastAppliedFiltersRef.current;
|
|
124
|
+
// Check if appliedFilters actually changed (avoid circular updates)
|
|
125
|
+
const appliedChanged = currentApplied.length !== lastApplied.length ||
|
|
126
|
+
currentApplied.some((applied, index) => {
|
|
127
|
+
const last = lastApplied[index];
|
|
128
|
+
return (last == null ||
|
|
129
|
+
applied.type !== last.type ||
|
|
130
|
+
applied.field !== last.field ||
|
|
131
|
+
applied.matchType !== last.matchType ||
|
|
132
|
+
applied.value !== last.value);
|
|
133
|
+
});
|
|
134
|
+
if (!appliedChanged) {
|
|
135
|
+
return;
|
|
137
136
|
}
|
|
138
|
-
|
|
139
|
-
|
|
137
|
+
lastAppliedFiltersRef.current = currentApplied;
|
|
138
|
+
setLocalFilters(currentApplied);
|
|
139
|
+
}, [appliedFilters]);
|
|
140
140
|
const hasUnsavedChanges = useMemo(() => {
|
|
141
141
|
const localWithValues = localFilters.filter(f => {
|
|
142
142
|
// For preset filters, only need type and value
|
|
@@ -169,8 +169,8 @@ export const useProfileFilters = () => {
|
|
|
169
169
|
id: `filter-${Date.now()}-${Math.random()}`,
|
|
170
170
|
value: '',
|
|
171
171
|
};
|
|
172
|
-
|
|
173
|
-
}, [
|
|
172
|
+
setLocalFilters([...localFiltersRef.current, newFilter]);
|
|
173
|
+
}, []);
|
|
174
174
|
const excludeBinary = useCallback((binaryName) => {
|
|
175
175
|
// Check if this binary is already being filtered with not_contains
|
|
176
176
|
const existingFilter = (appliedFilters ?? []).find(f => f.type === 'frame' &&
|
|
@@ -187,11 +187,13 @@ export const useProfileFilters = () => {
|
|
|
187
187
|
matchType: 'not_contains',
|
|
188
188
|
value: binaryName,
|
|
189
189
|
};
|
|
190
|
-
|
|
190
|
+
setLocalFilters([...localFiltersRef.current, newFilter]);
|
|
191
191
|
// Auto-apply the filter since it has a value
|
|
192
192
|
const filtersToApply = [...(appliedFilters ?? []), newFilter];
|
|
193
193
|
setAppliedFilters(filtersToApply);
|
|
194
|
-
},
|
|
194
|
+
},
|
|
195
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
196
|
+
[setAppliedFilters]);
|
|
195
197
|
const removeExcludeBinary = useCallback((binaryName) => {
|
|
196
198
|
// Search for the exclude filter (not_contains) for this binary
|
|
197
199
|
const filterToRemove = (appliedFilters ?? []).find(f => f.type === 'frame' &&
|
|
@@ -203,22 +205,21 @@ export const useProfileFilters = () => {
|
|
|
203
205
|
const updatedAppliedFilters = (appliedFilters ?? []).filter(f => f.id !== filterToRemove.id);
|
|
204
206
|
setAppliedFilters(updatedAppliedFilters);
|
|
205
207
|
// Also remove from local filters
|
|
206
|
-
|
|
207
|
-
dispatch(setLocalFilters(updatedLocalFilters));
|
|
208
|
+
setLocalFilters(localFiltersRef.current.filter(f => f.id !== filterToRemove.id));
|
|
208
209
|
}
|
|
209
|
-
}, [appliedFilters, setAppliedFilters
|
|
210
|
+
}, [appliedFilters, setAppliedFilters]);
|
|
210
211
|
const removeFilter = useCallback((id) => {
|
|
211
|
-
|
|
212
|
-
}, [
|
|
212
|
+
setLocalFilters(localFiltersRef.current.filter(f => f.id !== id));
|
|
213
|
+
}, []);
|
|
213
214
|
const updateFilter = useCallback((id, updates) => {
|
|
214
|
-
|
|
215
|
-
}, [
|
|
215
|
+
setLocalFilters(localFiltersRef.current.map(f => (f.id === id ? { ...f, ...updates } : f)));
|
|
216
|
+
}, []);
|
|
216
217
|
const resetFilters = useCallback(() => {
|
|
217
|
-
|
|
218
|
+
setLocalFilters([]);
|
|
218
219
|
setAppliedFilters([]);
|
|
219
|
-
}, [
|
|
220
|
+
}, [setAppliedFilters]);
|
|
220
221
|
const onApplyFilters = useCallback(() => {
|
|
221
|
-
const validFilters =
|
|
222
|
+
const validFilters = localFiltersRef.current.filter(f => {
|
|
222
223
|
// For preset filters, only need type and value
|
|
223
224
|
if (f.type != null && isPresetKey(f.type)) {
|
|
224
225
|
return f.value !== '' && f.type != null;
|
|
@@ -231,21 +232,10 @@ export const useProfileFilters = () => {
|
|
|
231
232
|
id: `filter-${Date.now()}-${index}`,
|
|
232
233
|
}));
|
|
233
234
|
setAppliedFilters(filtersToApply);
|
|
234
|
-
}, [
|
|
235
|
+
}, [setAppliedFilters]);
|
|
235
236
|
const protoFilters = useMemo(() => {
|
|
236
237
|
return convertToProtoFilters(appliedFilters ?? []);
|
|
237
238
|
}, [appliedFilters]);
|
|
238
|
-
const applyPreset = useCallback((preset) => {
|
|
239
|
-
const presetFilter = {
|
|
240
|
-
id: `filter-preset-${Date.now()}`,
|
|
241
|
-
type: preset.key,
|
|
242
|
-
value: preset.name,
|
|
243
|
-
};
|
|
244
|
-
// Add preset filter to existing filters
|
|
245
|
-
const updatedFilters = [...localFilters, presetFilter];
|
|
246
|
-
dispatch(setLocalFilters(updatedFilters));
|
|
247
|
-
setAppliedFilters(updatedFilters);
|
|
248
|
-
}, [dispatch, setAppliedFilters, localFilters]);
|
|
249
239
|
return {
|
|
250
240
|
localFilters,
|
|
251
241
|
appliedFilters,
|
|
@@ -258,6 +248,5 @@ export const useProfileFilters = () => {
|
|
|
258
248
|
removeFilter,
|
|
259
249
|
updateFilter,
|
|
260
250
|
resetFilters,
|
|
261
|
-
applyPreset,
|
|
262
251
|
};
|
|
263
252
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ParamValueSetterCustom } from '@parca/components';
|
|
2
|
-
import { type ProfileFilter } from '
|
|
2
|
+
import { type ProfileFilter } from './useProfileFilters';
|
|
3
3
|
export declare const decodeProfileFilters: (encoded: string) => ProfileFilter[];
|
|
4
4
|
export declare const useProfileFiltersUrlState: () => {
|
|
5
5
|
appliedFilters: ProfileFilter[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useProfileFiltersUrlState.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.ts"],"names":[],"mappings":"AAaA,OAAO,EAAoB,KAAK,sBAAsB,EAAC,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"useProfileFiltersUrlState.d.ts","sourceRoot":"","sources":["../../../../src/ProfileView/components/ProfileFilters/useProfileFiltersUrlState.ts"],"names":[],"mappings":"AAaA,OAAO,EAAoB,KAAK,sBAAsB,EAAC,MAAM,mBAAmB,CAAC;AAGjF,OAAO,EAAC,KAAK,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAsDvD,eAAO,MAAM,oBAAoB,GAAI,SAAS,MAAM,KAAG,aAAa,EAkCnE,CAAC;AAEF,eAAO,MAAM,yBAAyB,QAAO;IAC3C,cAAc,EAAE,aAAa,EAAE,CAAC;IAChC,iBAAiB,EAAE,sBAAsB,CAAC,aAAa,EAAE,CAAC,CAAC;CAmB5D,CAAC"}
|
|
@@ -31,5 +31,5 @@ export const VisualisationToolbar = ({ groupBy, toggleGroupBy, groupByLabels, se
|
|
|
31
31
|
fields: groupBy ?? [],
|
|
32
32
|
};
|
|
33
33
|
}
|
|
34
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex w-full justify-between items-end", children: [_jsxs("div", { className: "flex gap-2 items-end", children: [isGraphViz && (_jsxs(_Fragment, { children: [_jsx(GroupByDropdown, { groupBy: groupBy, labels: groupByLabels, setGroupByLabels: setGroupByLabels }), _jsx(InvertCallStack, {})] })), _jsx(ProfileFilters, {}), profileViewExternalSubActions != null ? profileViewExternalSubActions : null] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(MultiLevelDropdown, { groupBy: groupBy, toggleGroupBy: toggleGroupBy, profileType: profileType, onSelect: () => { }, isTableVizOnly: isTableVizOnly }), _jsx(ShareButton, { profileSource: profileSource, queryClient: queryClient, queryRequest: req, onDownloadPProf: onDownloadPProf, pprofdownloading: pprofdownloading ?? false, profileViewExternalSubActions: profileViewExternalSubActions }), showVisualizationSelector ? _jsx(ViewSelector, { profileSource: profileSource }) : null] })] }), isGraphVizOnly && (_jsxs(_Fragment, { children: [_jsx(Divider, {}), _jsx(FlameGraphToolbar, { curPath: curPath, setNewCurPath: setNewCurPath })] })), isTableVizOnly && (_jsxs(_Fragment, { children: [_jsx(Divider, {}), _jsx(TableToolbar, { profileType: profileType, total: total, filtered: filtered })] }))] }));
|
|
34
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex w-full justify-between items-end gap-2", children: [_jsxs("div", { className: "flex gap-2 items-end", children: [isGraphViz && (_jsxs(_Fragment, { children: [_jsx(GroupByDropdown, { groupBy: groupBy, labels: groupByLabels, setGroupByLabels: setGroupByLabels }), _jsx(InvertCallStack, {})] })), _jsx(ProfileFilters, {}), profileViewExternalSubActions != null ? profileViewExternalSubActions : null] }), _jsxs("div", { className: "flex gap-2", children: [_jsx(MultiLevelDropdown, { groupBy: groupBy, toggleGroupBy: toggleGroupBy, profileType: profileType, onSelect: () => { }, isTableVizOnly: isTableVizOnly }), _jsx(ShareButton, { profileSource: profileSource, queryClient: queryClient, queryRequest: req, onDownloadPProf: onDownloadPProf, pprofdownloading: pprofdownloading ?? false, profileViewExternalSubActions: profileViewExternalSubActions }), showVisualizationSelector ? _jsx(ViewSelector, { profileSource: profileSource }) : null] })] }), isGraphVizOnly && (_jsxs(_Fragment, { children: [_jsx(Divider, {}), _jsx(FlameGraphToolbar, { curPath: curPath, setNewCurPath: setNewCurPath })] })), isTableVizOnly && (_jsxs(_Fragment, { children: [_jsx(Divider, {}), _jsx(TableToolbar, { profileType: profileType, total: total, filtered: filtered })] }))] }));
|
|
35
35
|
};
|
|
@@ -34,7 +34,7 @@ const ViewSelector = ({ profileSource }) => {
|
|
|
34
34
|
allItems.push({ key: 'source', label: 'Source', canBeSelected: false });
|
|
35
35
|
}
|
|
36
36
|
const getOption = ({ label, supportingText, }) => {
|
|
37
|
-
const title = (_jsx("span", { className: "capitalize", children: typeof label === 'string' ? label.replaceAll('-', ' ') : label }));
|
|
37
|
+
const title = (_jsx("span", { className: "capitalize whitespace-nowrap", children: typeof label === 'string' ? label.replaceAll('-', ' ') : label }));
|
|
38
38
|
return {
|
|
39
39
|
active: title,
|
|
40
40
|
expanded: (_jsxs(_Fragment, { children: [title, supportingText !== null && _jsx("span", { className: "text-xs", children: supportingText })] })),
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parca/profile",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.28",
|
|
4
4
|
"description": "Profile viewing libraries",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@floating-ui/react": "^0.27.12",
|
|
7
7
|
"@headlessui/react": "^1.7.19",
|
|
8
8
|
"@iconify/react": "^4.0.0",
|
|
9
9
|
"@parca/client": "0.17.3",
|
|
10
|
-
"@parca/components": "0.16.
|
|
10
|
+
"@parca/components": "0.16.353",
|
|
11
11
|
"@parca/dynamicsize": "0.16.65",
|
|
12
|
-
"@parca/hooks": "0.0.
|
|
12
|
+
"@parca/hooks": "0.0.98",
|
|
13
13
|
"@parca/icons": "0.16.72",
|
|
14
14
|
"@parca/parser": "0.16.79",
|
|
15
|
-
"@parca/store": "0.16.
|
|
15
|
+
"@parca/store": "0.16.182",
|
|
16
16
|
"@parca/utilities": "0.0.105",
|
|
17
17
|
"@popperjs/core": "^2.11.8",
|
|
18
18
|
"@protobuf-ts/runtime-rpc": "^2.5.0",
|
|
@@ -78,5 +78,5 @@
|
|
|
78
78
|
"access": "public",
|
|
79
79
|
"registry": "https://registry.npmjs.org/"
|
|
80
80
|
},
|
|
81
|
-
"gitHead": "
|
|
81
|
+
"gitHead": "fcd8ab30d83c0bc8f23e4c244da5e2b06aacf74a"
|
|
82
82
|
}
|
|
@@ -188,6 +188,10 @@ const ContextMenu = ({
|
|
|
188
188
|
<Item
|
|
189
189
|
id="show-in-sandwich"
|
|
190
190
|
onClick={() => {
|
|
191
|
+
if (functionName === '' || functionName == null) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
191
195
|
if (dashboardItems.includes('sandwich')) {
|
|
192
196
|
setSandwichFunctionName(functionName);
|
|
193
197
|
return;
|
|
@@ -196,6 +200,7 @@ const ContextMenu = ({
|
|
|
196
200
|
setSandwichFunctionName(functionName);
|
|
197
201
|
setDashboardItems([...dashboardItems, 'sandwich']);
|
|
198
202
|
}}
|
|
203
|
+
disabled={functionName === '' || functionName == null}
|
|
199
204
|
>
|
|
200
205
|
<div className="flex w-full items-center gap-2">
|
|
201
206
|
<Icon icon="tdesign:sandwich-filled" />
|
|
@@ -39,21 +39,27 @@ export interface ContextMenuWrapperRef {
|
|
|
39
39
|
|
|
40
40
|
const ContextMenuWrapper = forwardRef<ContextMenuWrapperRef, ContextMenuWrapperProps>(
|
|
41
41
|
(props, ref) => {
|
|
42
|
-
//
|
|
43
|
-
|
|
44
|
-
const [row, setRow] = useState(0);
|
|
42
|
+
// Initialize with null to prevent rendering with invalid data
|
|
43
|
+
const [row, setRow] = useState<number | null>(null);
|
|
45
44
|
|
|
46
45
|
useImperativeHandle(ref, () => ({
|
|
47
46
|
setRow: (newRow: number, callback?: () => void) => {
|
|
48
47
|
setRow(newRow);
|
|
49
48
|
// Execute callback after state update using requestAnimationFrame
|
|
50
49
|
if (callback != null) {
|
|
51
|
-
requestAnimationFrame(
|
|
50
|
+
requestAnimationFrame(() => {
|
|
51
|
+
requestAnimationFrame(callback);
|
|
52
|
+
});
|
|
52
53
|
}
|
|
53
54
|
},
|
|
54
55
|
}));
|
|
55
56
|
|
|
56
|
-
|
|
57
|
+
// Only render ContextMenu when we have a valid row
|
|
58
|
+
if (row === null) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return <ContextMenu {...props} row={row} isSandwich={props.isInSandwichView} />;
|
|
57
63
|
}
|
|
58
64
|
);
|
|
59
65
|
|
|
@@ -247,20 +247,27 @@ export const FlameGraphArrow = memo(function FlameGraphArrow({
|
|
|
247
247
|
excludeBinary(binaryToRemove);
|
|
248
248
|
};
|
|
249
249
|
|
|
250
|
-
const handleRowClick = (
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
250
|
+
const handleRowClick = useCallback(
|
|
251
|
+
(row: number): void => {
|
|
252
|
+
if (isFlameChart) {
|
|
253
|
+
// In flame charts, we don't want to expand the node, so we return early.
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
// Walk down the stack starting at row until we reach the root (row 0).
|
|
257
|
+
const path: CurrentPathFrame[] = [];
|
|
258
|
+
let currentRow = row;
|
|
259
|
+
while (currentRow > 0) {
|
|
260
|
+
const frame = getCurrentPathFrameData(table, currentRow);
|
|
261
|
+
path.push(frame);
|
|
262
|
+
currentRow = table.getChild(FIELD_PARENT)?.get(currentRow) ?? 0;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Reverse the path so that the root is first.
|
|
266
|
+
path.reverse();
|
|
267
|
+
setCurPath(path);
|
|
268
|
+
},
|
|
269
|
+
[table, setCurPath, isFlameChart]
|
|
270
|
+
);
|
|
264
271
|
|
|
265
272
|
const depthColumn = table.getChild(FIELD_DEPTH);
|
|
266
273
|
const maxDepth = getMaxDepth(depthColumn);
|
|
@@ -377,13 +384,7 @@ export const FlameGraphArrow = memo(function FlameGraphArrow({
|
|
|
377
384
|
compareMode={compareMode}
|
|
378
385
|
colorForSimilarNodes={colorForSimilarNodes}
|
|
379
386
|
selectedRow={selectedRow}
|
|
380
|
-
onClick={() =>
|
|
381
|
-
if (isFlameChart) {
|
|
382
|
-
// We don't want to expand in flame charts.
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
handleRowClick(row);
|
|
386
|
-
}}
|
|
387
|
+
onClick={() => handleRowClick(row)}
|
|
387
388
|
onContextMenu={displayMenu}
|
|
388
389
|
hoveringRow={highlightSimilarStacksPreference ? hoveringRow : undefined}
|
|
389
390
|
setHoveringRow={highlightSimilarStacksPreference ? setHoveringRow : noop}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
|
|
14
|
-
import type {ProfileFilter} from '
|
|
14
|
+
import type {ProfileFilter} from './useProfileFilters';
|
|
15
15
|
|
|
16
16
|
export interface FilterPreset {
|
|
17
17
|
key: string;
|
|
@@ -67,8 +67,10 @@ export const filterPresets: FilterPreset[] = [
|
|
|
67
67
|
},
|
|
68
68
|
];
|
|
69
69
|
|
|
70
|
+
const presetKeys = new Set(filterPresets.map(preset => preset.key));
|
|
71
|
+
|
|
70
72
|
export const isPresetKey = (key: string): boolean => {
|
|
71
|
-
return
|
|
73
|
+
return presetKeys.has(key);
|
|
72
74
|
};
|
|
73
75
|
|
|
74
76
|
export const getPresetByKey = (key: string): FilterPreset | undefined => {
|
|
@@ -11,21 +11,20 @@
|
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
|
|
14
|
-
import {useCallback, useEffect, useMemo} from 'react';
|
|
14
|
+
import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
|
15
15
|
|
|
16
16
|
import {type Filter} from '@parca/client';
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
setLocalFilters,
|
|
20
|
-
useAppDispatch,
|
|
21
|
-
useAppSelector,
|
|
22
|
-
type ProfileFilter,
|
|
23
|
-
} from '@parca/store';
|
|
24
|
-
|
|
25
|
-
import {getPresetByKey, isPresetKey, type FilterPreset} from './filterPresets';
|
|
17
|
+
|
|
18
|
+
import {getPresetByKey, isPresetKey} from './filterPresets';
|
|
26
19
|
import {useProfileFiltersUrlState} from './useProfileFiltersUrlState';
|
|
27
20
|
|
|
28
|
-
export
|
|
21
|
+
export interface ProfileFilter {
|
|
22
|
+
id: string;
|
|
23
|
+
type?: 'stack' | 'frame' | string; // string allows preset keys
|
|
24
|
+
field?: 'function_name' | 'binary' | 'system_name' | 'filename' | 'address' | 'line_number';
|
|
25
|
+
matchType?: 'equal' | 'not_equal' | 'contains' | 'not_contains';
|
|
26
|
+
value: string;
|
|
27
|
+
}
|
|
29
28
|
|
|
30
29
|
// Convert ProfileFilter[] to protobuf Filter[] matching the expected structure
|
|
31
30
|
export const convertToProtoFilters = (profileFilters: ProfileFilter[]): Filter[] => {
|
|
@@ -41,6 +40,7 @@ export const convertToProtoFilters = (profileFilters: ProfileFilter[]): Filter[]
|
|
|
41
40
|
expandedFilters.push({
|
|
42
41
|
...presetFilter,
|
|
43
42
|
id: `${filter.id}-expanded-${index}`,
|
|
43
|
+
value: presetFilter.value,
|
|
44
44
|
});
|
|
45
45
|
});
|
|
46
46
|
}
|
|
@@ -142,36 +142,41 @@ export const useProfileFilters = (): {
|
|
|
142
142
|
removeFilter: (id: string) => void;
|
|
143
143
|
updateFilter: (id: string, updates: Partial<ProfileFilter>) => void;
|
|
144
144
|
resetFilters: () => void;
|
|
145
|
-
applyPreset: (preset: FilterPreset) => void;
|
|
146
145
|
} => {
|
|
147
146
|
const {appliedFilters, setAppliedFilters} = useProfileFiltersUrlState();
|
|
148
|
-
|
|
149
|
-
const localFilters =
|
|
147
|
+
|
|
148
|
+
const [localFilters, setLocalFilters] = useState<ProfileFilter[]>([]);
|
|
149
|
+
|
|
150
|
+
const lastAppliedFiltersRef = useRef<ProfileFilter[]>([]);
|
|
151
|
+
|
|
152
|
+
const localFiltersRef = useRef<ProfileFilter[]>(localFilters);
|
|
153
|
+
localFiltersRef.current = localFilters;
|
|
150
154
|
|
|
151
155
|
useEffect(() => {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
156
|
+
const currentApplied = appliedFilters ?? [];
|
|
157
|
+
const lastApplied = lastAppliedFiltersRef.current;
|
|
158
|
+
|
|
159
|
+
// Check if appliedFilters actually changed (avoid circular updates)
|
|
160
|
+
const appliedChanged =
|
|
161
|
+
currentApplied.length !== lastApplied.length ||
|
|
162
|
+
currentApplied.some((applied, index) => {
|
|
163
|
+
const last = lastApplied[index];
|
|
164
|
+
return (
|
|
165
|
+
last == null ||
|
|
166
|
+
applied.type !== last.type ||
|
|
167
|
+
applied.field !== last.field ||
|
|
168
|
+
applied.matchType !== last.matchType ||
|
|
169
|
+
applied.value !== last.value
|
|
170
|
+
);
|
|
171
|
+
});
|
|
166
172
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
} else if (appliedFilters != null && appliedFilters.length === 0 && localFilters.length > 0) {
|
|
171
|
-
dispatch(setLocalFilters([]));
|
|
173
|
+
if (!appliedChanged) {
|
|
174
|
+
return;
|
|
172
175
|
}
|
|
173
|
-
|
|
174
|
-
|
|
176
|
+
|
|
177
|
+
lastAppliedFiltersRef.current = currentApplied;
|
|
178
|
+
setLocalFilters(currentApplied);
|
|
179
|
+
}, [appliedFilters]);
|
|
175
180
|
|
|
176
181
|
const hasUnsavedChanges = useMemo(() => {
|
|
177
182
|
const localWithValues = localFilters.filter(f => {
|
|
@@ -210,8 +215,8 @@ export const useProfileFilters = (): {
|
|
|
210
215
|
id: `filter-${Date.now()}-${Math.random()}`,
|
|
211
216
|
value: '',
|
|
212
217
|
};
|
|
213
|
-
|
|
214
|
-
}, [
|
|
218
|
+
setLocalFilters([...localFiltersRef.current, newFilter]);
|
|
219
|
+
}, []);
|
|
215
220
|
|
|
216
221
|
const excludeBinary = useCallback(
|
|
217
222
|
(binaryName: string) => {
|
|
@@ -235,13 +240,14 @@ export const useProfileFilters = (): {
|
|
|
235
240
|
matchType: 'not_contains',
|
|
236
241
|
value: binaryName,
|
|
237
242
|
};
|
|
238
|
-
|
|
243
|
+
setLocalFilters([...localFiltersRef.current, newFilter]);
|
|
239
244
|
|
|
240
245
|
// Auto-apply the filter since it has a value
|
|
241
246
|
const filtersToApply = [...(appliedFilters ?? []), newFilter];
|
|
242
247
|
setAppliedFilters(filtersToApply);
|
|
243
248
|
},
|
|
244
|
-
|
|
249
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
250
|
+
[setAppliedFilters]
|
|
245
251
|
);
|
|
246
252
|
|
|
247
253
|
const removeExcludeBinary = useCallback(
|
|
@@ -263,34 +269,27 @@ export const useProfileFilters = (): {
|
|
|
263
269
|
setAppliedFilters(updatedAppliedFilters);
|
|
264
270
|
|
|
265
271
|
// Also remove from local filters
|
|
266
|
-
|
|
267
|
-
dispatch(setLocalFilters(updatedLocalFilters));
|
|
272
|
+
setLocalFilters(localFiltersRef.current.filter(f => f.id !== filterToRemove.id));
|
|
268
273
|
}
|
|
269
274
|
},
|
|
270
|
-
[appliedFilters, setAppliedFilters
|
|
275
|
+
[appliedFilters, setAppliedFilters]
|
|
271
276
|
);
|
|
272
277
|
|
|
273
|
-
const removeFilter = useCallback(
|
|
274
|
-
(id
|
|
275
|
-
|
|
276
|
-
},
|
|
277
|
-
[dispatch, localFilters]
|
|
278
|
-
);
|
|
278
|
+
const removeFilter = useCallback((id: string) => {
|
|
279
|
+
setLocalFilters(localFiltersRef.current.filter(f => f.id !== id));
|
|
280
|
+
}, []);
|
|
279
281
|
|
|
280
|
-
const updateFilter = useCallback(
|
|
281
|
-
(id
|
|
282
|
-
|
|
283
|
-
},
|
|
284
|
-
[dispatch, localFilters]
|
|
285
|
-
);
|
|
282
|
+
const updateFilter = useCallback((id: string, updates: Partial<ProfileFilter>) => {
|
|
283
|
+
setLocalFilters(localFiltersRef.current.map(f => (f.id === id ? {...f, ...updates} : f)));
|
|
284
|
+
}, []);
|
|
286
285
|
|
|
287
286
|
const resetFilters = useCallback(() => {
|
|
288
|
-
|
|
287
|
+
setLocalFilters([]);
|
|
289
288
|
setAppliedFilters([]);
|
|
290
|
-
}, [
|
|
289
|
+
}, [setAppliedFilters]);
|
|
291
290
|
|
|
292
291
|
const onApplyFilters = useCallback((): void => {
|
|
293
|
-
const validFilters =
|
|
292
|
+
const validFilters = localFiltersRef.current.filter(f => {
|
|
294
293
|
// For preset filters, only need type and value
|
|
295
294
|
if (f.type != null && isPresetKey(f.type)) {
|
|
296
295
|
return f.value !== '' && f.type != null;
|
|
@@ -305,29 +304,12 @@ export const useProfileFilters = (): {
|
|
|
305
304
|
}));
|
|
306
305
|
|
|
307
306
|
setAppliedFilters(filtersToApply);
|
|
308
|
-
}, [
|
|
307
|
+
}, [setAppliedFilters]);
|
|
309
308
|
|
|
310
309
|
const protoFilters = useMemo(() => {
|
|
311
310
|
return convertToProtoFilters(appliedFilters ?? []);
|
|
312
311
|
}, [appliedFilters]);
|
|
313
312
|
|
|
314
|
-
const applyPreset = useCallback(
|
|
315
|
-
(preset: FilterPreset) => {
|
|
316
|
-
const presetFilter: ProfileFilter = {
|
|
317
|
-
id: `filter-preset-${Date.now()}`,
|
|
318
|
-
type: preset.key,
|
|
319
|
-
value: preset.name,
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
// Add preset filter to existing filters
|
|
323
|
-
const updatedFilters = [...localFilters, presetFilter];
|
|
324
|
-
dispatch(setLocalFilters(updatedFilters));
|
|
325
|
-
|
|
326
|
-
setAppliedFilters(updatedFilters);
|
|
327
|
-
},
|
|
328
|
-
[dispatch, setAppliedFilters, localFilters]
|
|
329
|
-
);
|
|
330
|
-
|
|
331
313
|
return {
|
|
332
314
|
localFilters,
|
|
333
315
|
appliedFilters,
|
|
@@ -340,6 +322,5 @@ export const useProfileFilters = (): {
|
|
|
340
322
|
removeFilter,
|
|
341
323
|
updateFilter,
|
|
342
324
|
resetFilters,
|
|
343
|
-
applyPreset,
|
|
344
325
|
};
|
|
345
326
|
};
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
|
|
14
14
|
import {useURLStateCustom, type ParamValueSetterCustom} from '@parca/components';
|
|
15
|
-
import {type ProfileFilter} from '@parca/store';
|
|
16
15
|
|
|
17
16
|
import {isPresetKey} from './filterPresets';
|
|
17
|
+
import {type ProfileFilter} from './useProfileFilters';
|
|
18
18
|
|
|
19
19
|
// Compact encoding mappings
|
|
20
20
|
const TYPE_MAP: Record<string, string> = {
|
|
@@ -154,7 +154,7 @@ export const VisualisationToolbar: FC<VisualisationToolbarProps> = ({
|
|
|
154
154
|
|
|
155
155
|
return (
|
|
156
156
|
<>
|
|
157
|
-
<div className="flex w-full justify-between items-end">
|
|
157
|
+
<div className="flex w-full justify-between items-end gap-2">
|
|
158
158
|
<div className="flex gap-2 items-end">
|
|
159
159
|
{isGraphViz && (
|
|
160
160
|
<>
|
|
@@ -88,7 +88,7 @@ const ViewSelector = ({profileSource}: Props): JSX.Element => {
|
|
|
88
88
|
supportingText?: string;
|
|
89
89
|
}): DropdownElement => {
|
|
90
90
|
const title = (
|
|
91
|
-
<span className="capitalize">
|
|
91
|
+
<span className="capitalize whitespace-nowrap">
|
|
92
92
|
{typeof label === 'string' ? label.replaceAll('-', ' ') : label}
|
|
93
93
|
</span>
|
|
94
94
|
);
|